🚀 Добавлен мастер-скрипт развертывания с полной автоматизацией
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
✨ Новые возможности: - Мастер-развертывание с автоматической настройкой всех компонентов - Генерация безопасных .env файлов с криптографически стойкими ключами - Полная изоляция и защита PostgreSQL - Автоматическая настройка Let's Encrypt SSL - Система backup и мониторинга - Comprehensive security audit для БД 🔧 Новые команды: - make deploy - мастер-развертывание - make pre-deploy-check - проверка готовности системы - make security-audit - аудит безопасности PostgreSQL - make ssl-setup - интерактивная настройка SSL - make update-production-security - безопасное обновление в продакшене 📁 Новые файлы: - scripts/master-deploy.sh - основной скрипт развертывания - scripts/pre-deploy-check.sh - проверка системы - scripts/ssl-manager.sh - управление SSL сертификатами - scripts/audit-db-security.sh - аудит безопасности БД - DEPLOYMENT.md - полное руководство по развертыванию - COMMANDS.md - справочник команд - SECURITY.md - документация по безопасности 🔒 Улучшения безопасности: - Изоляция PostgreSQL в Docker сети (без внешних портов) - SCRAM-SHA-256 аутентификация - TLSv1.3 для БД соединений - Удаление прав суперпользователя у приложения - Детальное логирование всех операций БД - Security headers в nginx - Автообновление SSL сертификатов
This commit is contained in:
423
scripts/ssl-manager.sh
Executable file
423
scripts/ssl-manager.sh
Executable file
@@ -0,0 +1,423 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SSL Certificate Management для CatLink
|
||||
# Автоматическая настройка SSL с Let's Encrypt и управление сертификатами
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}✅ $1${NC}"; }
|
||||
warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
|
||||
error() { echo -e "${RED}❌ $1${NC}"; exit 1; }
|
||||
|
||||
# Проверка переменных окружения
|
||||
check_env() {
|
||||
if [ ! -f ".env" ]; then
|
||||
error ".env файл не найден. Запустите make deploy для его создания."
|
||||
fi
|
||||
|
||||
source .env
|
||||
|
||||
if [ -z "$MAIN_DOMAIN" ]; then
|
||||
error "MAIN_DOMAIN не задан в .env файле"
|
||||
fi
|
||||
|
||||
if [ -z "$LETSENCRYPT_EMAIL" ]; then
|
||||
error "LETSENCRYPT_EMAIL не задан в .env файле"
|
||||
fi
|
||||
}
|
||||
|
||||
# Установка certbot если не установлен
|
||||
install_certbot() {
|
||||
if ! command -v certbot &> /dev/null; then
|
||||
log "Установка certbot..."
|
||||
sudo apt update
|
||||
sudo apt install -y certbot python3-certbot-nginx
|
||||
success "certbot установлен"
|
||||
else
|
||||
success "certbot уже установлен"
|
||||
fi
|
||||
}
|
||||
|
||||
# Проверка DNS записей
|
||||
check_dns() {
|
||||
log "Проверка DNS записей для $MAIN_DOMAIN..."
|
||||
|
||||
# Получаем внешний IP сервера
|
||||
SERVER_IP=$(curl -s https://ipinfo.io/ip || curl -s https://icanhazip.com || echo "unknown")
|
||||
log "IP сервера: $SERVER_IP"
|
||||
|
||||
# Проверяем A запись
|
||||
DOMAIN_IP=$(nslookup $MAIN_DOMAIN | grep -A 1 "Name:" | tail -n1 | awk '{print $2}' || echo "unknown")
|
||||
log "IP домена $MAIN_DOMAIN: $DOMAIN_IP"
|
||||
|
||||
if [ "$SERVER_IP" = "$DOMAIN_IP" ]; then
|
||||
success "DNS запись настроена корректно"
|
||||
return 0
|
||||
else
|
||||
warning "DNS запись может быть настроена неверно"
|
||||
warning "Сервер: $SERVER_IP, Домен: $DOMAIN_IP"
|
||||
read -p "Продолжить? (yes/no): " CONTINUE
|
||||
if [ "$CONTINUE" != "yes" ]; then
|
||||
error "Настройте DNS запись и попробуйте снова"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Получение Let's Encrypt сертификата
|
||||
obtain_letsencrypt() {
|
||||
log "Получение Let's Encrypt сертификата для $MAIN_DOMAIN..."
|
||||
|
||||
# Формируем список доменов
|
||||
CERT_DOMAINS="-d $MAIN_DOMAIN"
|
||||
if [ -n "$ADDITIONAL_DOMAINS" ]; then
|
||||
IFS=',' read -ra DOMAINS <<< "$ADDITIONAL_DOMAINS"
|
||||
for domain in "${DOMAINS[@]}"; do
|
||||
domain=$(echo $domain | xargs) # trim whitespace
|
||||
if [ -n "$domain" ]; then
|
||||
CERT_DOMAINS="$CERT_DOMAINS -d $domain"
|
||||
log "Добавлен домен: $domain"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
log "Домены для сертификата: $CERT_DOMAINS"
|
||||
|
||||
# Временно останавливаем nginx для standalone режима
|
||||
if systemctl is-active --quiet nginx; then
|
||||
log "Остановка nginx для получения сертификата..."
|
||||
sudo systemctl stop nginx
|
||||
NGINX_WAS_RUNNING=true
|
||||
else
|
||||
NGINX_WAS_RUNNING=false
|
||||
fi
|
||||
|
||||
# Получаем сертификат в standalone режиме
|
||||
if sudo certbot certonly --standalone \
|
||||
$CERT_DOMAINS \
|
||||
--email "$LETSENCRYPT_EMAIL" \
|
||||
--agree-tos \
|
||||
--non-interactive \
|
||||
--expand; then
|
||||
|
||||
success "Сертификат получен успешно"
|
||||
|
||||
# Запускаем nginx обратно
|
||||
if [ "$NGINX_WAS_RUNNING" = true ]; then
|
||||
sudo systemctl start nginx
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
error "Не удалось получить сертификат"
|
||||
fi
|
||||
}
|
||||
|
||||
# Создание самоподписанного сертификата
|
||||
create_selfsigned() {
|
||||
log "Создание самоподписанного сертификата для $MAIN_DOMAIN..."
|
||||
|
||||
# Создаем директории
|
||||
sudo mkdir -p /etc/ssl/private /etc/ssl/certs
|
||||
|
||||
# Генерируем ключ и сертификат
|
||||
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout "/etc/ssl/private/${MAIN_DOMAIN}.key" \
|
||||
-out "/etc/ssl/certs/${MAIN_DOMAIN}.crt" \
|
||||
-subj "/C=US/ST=State/L=City/O=CatLink/CN=${MAIN_DOMAIN}" \
|
||||
-extensions v3_req \
|
||||
-config <(cat <<EOF
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
CN = ${MAIN_DOMAIN}
|
||||
|
||||
[v3_req]
|
||||
keyUsage = keyEncipherment, dataEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = ${MAIN_DOMAIN}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Устанавливаем права
|
||||
sudo chmod 600 "/etc/ssl/private/${MAIN_DOMAIN}.key"
|
||||
sudo chmod 644 "/etc/ssl/certs/${MAIN_DOMAIN}.crt"
|
||||
|
||||
success "Самоподписанный сертификат создан"
|
||||
}
|
||||
|
||||
# Обновление nginx конфигурации для SSL
|
||||
update_nginx_ssl() {
|
||||
local ssl_type=$1
|
||||
|
||||
log "Обновление nginx конфигурации для SSL ($ssl_type)..."
|
||||
|
||||
if [ "$ssl_type" = "letsencrypt" ]; then
|
||||
SSL_CERT="/etc/letsencrypt/live/${MAIN_DOMAIN}/fullchain.pem"
|
||||
SSL_KEY="/etc/letsencrypt/live/${MAIN_DOMAIN}/privkey.pem"
|
||||
else
|
||||
SSL_CERT="/etc/ssl/certs/${MAIN_DOMAIN}.crt"
|
||||
SSL_KEY="/etc/ssl/private/${MAIN_DOMAIN}.key"
|
||||
fi
|
||||
|
||||
# Создаем SSL конфигурацию
|
||||
sudo tee /etc/nginx/sites-available/catlink-ssl << EOF
|
||||
# CatLink SSL nginx configuration
|
||||
# Generated $(date)
|
||||
|
||||
# HTTP -> HTTPS redirect
|
||||
server {
|
||||
listen 80;
|
||||
server_name $MAIN_DOMAIN${ADDITIONAL_DOMAINS:+ $ADDITIONAL_DOMAINS};
|
||||
|
||||
# Let's Encrypt challenge
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
# Redirect all other requests to HTTPS
|
||||
location / {
|
||||
return 301 https://\$server_name\$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name $MAIN_DOMAIN${ADDITIONAL_DOMAINS:+ $ADDITIONAL_DOMAINS};
|
||||
|
||||
# SSL Configuration
|
||||
ssl_certificate $SSL_CERT;
|
||||
ssl_certificate_key $SSL_KEY;
|
||||
|
||||
# SSL Security
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||
|
||||
# Frontend (Next.js)
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_cache_bypass \$http_upgrade;
|
||||
|
||||
# Timeout settings
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
|
||||
# Timeout settings
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# Admin interface
|
||||
location /admin/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
|
||||
# Static files
|
||||
location /static/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Media files
|
||||
location /media/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# Security
|
||||
location ~ /\\.ht {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location = /robots.txt {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /favicon.ico {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
access_log off;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Включаем SSL конфигурацию
|
||||
sudo ln -sf /etc/nginx/sites-available/catlink-ssl /etc/nginx/sites-enabled/catlink
|
||||
|
||||
# Проверяем конфигурацию
|
||||
if sudo nginx -t; then
|
||||
success "Конфигурация nginx обновлена"
|
||||
sudo systemctl reload nginx
|
||||
else
|
||||
error "Ошибка в конфигурации nginx"
|
||||
fi
|
||||
}
|
||||
|
||||
# Настройка автообновления сертификатов
|
||||
setup_auto_renewal() {
|
||||
log "Настройка автообновления Let's Encrypt сертификатов..."
|
||||
|
||||
# Создаем скрипт обновления
|
||||
sudo tee /usr/local/bin/certbot-renew-catlink << 'EOF'
|
||||
#!/bin/bash
|
||||
# CatLink SSL certificate renewal script
|
||||
|
||||
certbot renew --quiet --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"
|
||||
|
||||
# Проверяем успешность обновления
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "$(date): SSL certificates renewed successfully" >> /var/log/catlink-ssl.log
|
||||
else
|
||||
echo "$(date): SSL certificate renewal failed" >> /var/log/catlink-ssl.log
|
||||
fi
|
||||
EOF
|
||||
|
||||
sudo chmod +x /usr/local/bin/certbot-renew-catlink
|
||||
|
||||
# Добавляем в crontab
|
||||
(sudo crontab -l 2>/dev/null | grep -v certbot-renew-catlink; echo "0 12 * * * /usr/local/bin/certbot-renew-catlink") | sudo crontab -
|
||||
|
||||
success "Автообновление сертификатов настроено"
|
||||
}
|
||||
|
||||
# Проверка статуса SSL
|
||||
check_ssl_status() {
|
||||
log "Проверка статуса SSL..."
|
||||
|
||||
if [ -f "/etc/letsencrypt/live/${MAIN_DOMAIN}/fullchain.pem" ]; then
|
||||
CERT_EXPIRY=$(openssl x509 -enddate -noout -in "/etc/letsencrypt/live/${MAIN_DOMAIN}/fullchain.pem" | cut -d= -f2)
|
||||
success "Let's Encrypt сертификат активен до: $CERT_EXPIRY"
|
||||
elif [ -f "/etc/ssl/certs/${MAIN_DOMAIN}.crt" ]; then
|
||||
CERT_EXPIRY=$(openssl x509 -enddate -noout -in "/etc/ssl/certs/${MAIN_DOMAIN}.crt" | cut -d= -f2)
|
||||
warning "Самоподписанный сертификат активен до: $CERT_EXPIRY"
|
||||
else
|
||||
error "SSL сертификат не найден"
|
||||
fi
|
||||
|
||||
# Проверяем HTTPS соединение
|
||||
if command -v curl &> /dev/null; then
|
||||
if curl -s -I "https://${MAIN_DOMAIN}" | grep -q "HTTP"; then
|
||||
success "HTTPS соединение работает"
|
||||
else
|
||||
warning "HTTPS соединение может не работать"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Основное меню
|
||||
main() {
|
||||
echo "🔒 ===== CatLink SSL Certificate Manager ====="
|
||||
echo ""
|
||||
|
||||
check_env
|
||||
|
||||
echo "Выберите действие:"
|
||||
echo "1) Получить Let's Encrypt сертификат (рекомендуется)"
|
||||
echo "2) Создать самоподписанный сертификат (для тестирования)"
|
||||
echo "3) Проверить статус существующих сертификатов"
|
||||
echo "4) Настроить автообновление"
|
||||
echo "5) Обновить nginx конфигурацию"
|
||||
echo ""
|
||||
read -p "Выберите опцию (1-5): " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
install_certbot
|
||||
check_dns
|
||||
obtain_letsencrypt
|
||||
update_nginx_ssl "letsencrypt"
|
||||
setup_auto_renewal
|
||||
check_ssl_status
|
||||
;;
|
||||
2)
|
||||
create_selfsigned
|
||||
update_nginx_ssl "selfsigned"
|
||||
check_ssl_status
|
||||
;;
|
||||
3)
|
||||
check_ssl_status
|
||||
;;
|
||||
4)
|
||||
setup_auto_renewal
|
||||
;;
|
||||
5)
|
||||
echo "Какой тип SSL использовать?"
|
||||
echo "1) Let's Encrypt"
|
||||
echo "2) Самоподписанный"
|
||||
read -p "Выберите (1-2): " ssl_type
|
||||
if [ "$ssl_type" = "1" ]; then
|
||||
update_nginx_ssl "letsencrypt"
|
||||
else
|
||||
update_nginx_ssl "selfsigned"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
error "Неверный выбор"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
success "SSL настройка завершена!"
|
||||
echo ""
|
||||
echo "🌐 Проверьте сайт: https://${MAIN_DOMAIN}"
|
||||
}
|
||||
|
||||
# Запуск
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user