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 сертификатов
423 lines
14 KiB
Bash
Executable File
423 lines
14 KiB
Bash
Executable File
#!/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 "$@" |