Files
links/scripts/ssl-manager.sh
Andrey K. Choi e1bb1ab90a
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 сертификатов
2025-11-04 14:07:58 +09:00

423 lines
14 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "$@"