#!/bin/bash # 腾讯云SSL证书配置脚本 # 为答题红包APP配置HTTPS加密 set -e # 颜色定义 GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } show_help() { echo "腾讯云SSL证书配置工具" echo "用法: $0 [选项]" echo echo "选项:" echo " --tencent 使用腾讯云SSL证书(默认)" echo " --letsencrypt 使用Let's Encrypt免费证书" echo " --manual 手动配置已有证书" echo " --test 测试SSL配置" echo " --renew 续期证书(仅Let's Encrypt)" echo " --help 显示帮助信息" echo echo "示例:" echo " $0 --tencent 配置腾讯云SSL证书" echo " $0 --letsencrypt 使用Certbot自动获取证书" echo " $0 --manual 手动配置已有证书文件" } # 检查Nginx配置 check_nginx() { if ! command -v nginx &> /dev/null; then log_error "Nginx未安装,请先运行部署脚本" exit 1 fi # 检查是否已有配置 if [ -f "/etc/nginx/sites-available/question-redpacket" ]; then CONFIG_FILE="/etc/nginx/sites-available/question-redpacket" elif [ -f "/etc/nginx/conf.d/question-redpacket.conf" ]; then CONFIG_FILE="/etc/nginx/conf.d/question-redpacket.conf" else log_error "未找到Nginx配置文件,请先部署应用" exit 1 fi } # 方法1:腾讯云SSL证书配置 configure_tencent_ssl() { log_info "配置腾讯云SSL证书..." echo log_warning "请在腾讯云控制台完成以下步骤:" echo "1. 访问: https://console.cloud.tencent.com/ssl" echo "2. 点击'申请免费证书'" echo "3. 填写域名信息" echo "4. 完成DNS验证" echo "5. 下载证书文件(Nginx格式)" echo read -p "证书下载完成后,请将文件上传到服务器,按 Enter 继续..." # 创建证书目录 SSL_DIR="/etc/nginx/ssl/question-redpacket" mkdir -p $SSL_DIR chmod 700 $SSL_DIR echo log_info "请提供证书文件路径:" read -p "证书文件(.crt)路径: " CERT_FILE read -p "私钥文件(.key)路径: " KEY_FILE if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then log_error "证书文件不存在,请检查路径" exit 1 fi # 复制证书文件 cp "$CERT_FILE" $SSL_DIR/certificate.crt cp "$KEY_FILE" $SSL_DIR/private.key # 设置权限 chmod 600 $SSL_DIR/private.key chmod 644 $SSL_DIR/certificate.crt # 创建完整的SSL配置 create_ssl_config "tencent" log_success "腾讯云SSL证书配置完成" } # 方法2:Let's Encrypt自动证书 configure_letsencrypt_ssl() { log_info "配置Let's Encrypt免费证书..." # 检查Certbot if ! command -v certbot &> /dev/null; then log_info "安装Certbot..." if [ -f /etc/debian_version ]; then apt update apt install -y certbot python3-certbot-nginx else yum install -y epel-release yum install -y certbot python3-certbot-nginx fi fi # 获取域名 echo log_warning "请输入您的域名(如: api.yourdomain.com):" read -p "域名: " DOMAIN_NAME if [ -z "$DOMAIN_NAME" ]; then log_error "域名不能为空" exit 1 fi # 修改Nginx配置中的server_name sed -i "s/server_name .*/server_name $DOMAIN_NAME;/g" $CONFIG_FILE # 重启Nginx应用配置 systemctl reload nginx # 获取证书 log_info "正在获取Let's Encrypt证书..." certbot --nginx -d $DOMAIN_NAME --non-interactive --agree-tos --email admin@$DOMAIN_NAME if [ $? -eq 0 ]; then # 创建SSL配置 create_ssl_config "letsencrypt" # 设置自动续期 (crontab -l 2>/dev/null; echo "0 0,12 * * * certbot renew --quiet") | crontab - log_success "Let's Encrypt证书配置完成" log_info "证书位置: /etc/letsencrypt/live/$DOMAIN_NAME/" log_info "已设置自动续期任务" else log_error "获取证书失败,请检查域名解析和Nginx配置" exit 1 fi } # 方法3:手动配置证书 configure_manual_ssl() { log_info "手动配置SSL证书..." # 创建证书目录 SSL_DIR="/etc/nginx/ssl/question-redpacket" mkdir -p $SSL_DIR chmod 700 $SSL_DIR echo log_warning "请将证书文件放入以下目录:" echo "证书文件: $SSL_DIR/certificate.crt" echo "私钥文件: $SSL_DIR/private.key" echo log_info "如果需要,可以现在上传文件:" read -p "按 Enter 继续或 Ctrl+C 取消..." # 检查证书文件 if [ ! -f "$SSL_DIR/certificate.crt" ] || [ ! -f "$SSL_DIR/private.key" ]; then log_error "证书文件不存在,请先上传" echo "您可以使用以下命令上传:" echo "scp your_cert.crt root@服务器IP:$SSL_DIR/certificate.crt" echo "scp your_key.key root@服务器IP:$SSL_DIR/private.key" exit 1 fi # 验证证书格式 log_info "验证证书格式..." openssl x509 -in $SSL_DIR/certificate.crt -text -noout > /dev/null 2>&1 if [ $? -ne 0 ]; then log_error "证书格式错误,请检查证书文件" exit 1 fi openssl rsa -in $SSL_DIR/private.key -check > /dev/null 2>&1 if [ $? -ne 0 ]; then log_error "私钥格式错误,请检查私钥文件" exit 1 fi # 设置权限 chmod 600 $SSL_DIR/private.key chmod 644 $SSL_DIR/certificate.crt # 创建SSL配置 create_ssl_config "manual" log_success "手动SSL证书配置完成" } # 创建SSL配置 create_ssl_config() { CERT_TYPE=$1 SSL_DIR="" case $CERT_TYPE in "tencent"|"manual") SSL_DIR="/etc/nginx/ssl/question-redpacket" CERT_FILE="$SSL_DIR/certificate.crt" KEY_FILE="$SSL_DIR/private.key" ;; "letsencrypt") # 从Certbot配置中获取域名 DOMAIN=$(grep -h "server_name" $CONFIG_FILE | head -1 | awk '{print $2}' | tr -d ';') SSL_DIR="/etc/letsencrypt/live/$DOMAIN" CERT_FILE="$SSL_DIR/fullchain.pem" KEY_FILE="$SSL_DIR/privkey.pem" ;; esac # 备份原始配置 cp $CONFIG_FILE ${CONFIG_FILE}.backup.$(date +%Y%m%d) # 创建新的SSL配置 cat > ${CONFIG_FILE}.ssl << EOF # 答题红包APP - SSL配置 # 自动生成于: $(date) # HTTP重定向到HTTPS server { listen 80; server_name $DOMAIN; server_tokens off; # 重定向到HTTPS return 301 https://\$server_name\$request_uri; } # HTTPS服务器 server { listen 443 ssl http2; server_name $DOMAIN; server_tokens off; # SSL证书 ssl_certificate $CERT_FILE; ssl_certificate_key $KEY_FILE; # SSL协议和加密套件 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; ssl_prefer_server_ciphers off; # SSL会话缓存 ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; # DH参数(增强安全性) ssl_dhparam /etc/nginx/dhparam.pem; # HSTS(强制HTTPS) add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # 安全头部 add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; # 访问日志 access_log /var/log/nginx/question-redpacket-ssl-access.log; error_log /var/log/nginx/question-redpacket-ssl-error.log; # Gzip压缩 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; # 上传文件大小限制 client_max_body_size 50M; # API代理 location / { proxy_pass http://localhost: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_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_cache_bypass \$http_upgrade; } # 健康检查端点 location /api/health { access_log off; proxy_pass http://localhost:3000/api/health; proxy_set_header Host \$host; } # 静态文件服务 location /uploads/ { alias /var/www/question-redpacket/api-server/uploads/; expires 30d; access_log off; # 安全限制 limit_except GET { deny all; } } # Let's Encrypt ACME挑战(仅用于续期) location ^~ /.well-known/acme-challenge/ { root /var/www/html; allow all; } } EOF # 替换原配置文件 mv ${CONFIG_FILE}.ssl $CONFIG_FILE # 生成DH参数(如果需要) if [ ! -f "/etc/nginx/dhparam.pem" ]; then log_info "生成DH参数(可能需要几分钟)..." openssl dhparam -out /etc/nginx/dhparam.pem 2048 fi # 测试并重启Nginx nginx -t if [ $? -eq 0 ]; then systemctl reload nginx log_success "Nginx配置已更新并重载" else log_error "Nginx配置测试失败,已恢复备份" mv ${CONFIG_FILE}.backup.$(date +%Y%m%d) $CONFIG_FILE exit 1 fi } # 测试SSL配置 test_ssl_config() { log_info "测试SSL配置..." # 获取域名 DOMAIN=$(grep -h "server_name" $CONFIG_FILE | grep -v "^\s*#" | head -1 | awk '{print $2}' | tr -d ';') if [ -z "$DOMAIN" ]; then log_error "未找到域名配置" exit 1 fi echo log_info "SSL配置测试结果:" echo "----------------------------------------" # 测试HTTPS连接 log_info "1. 测试HTTPS连接..." if curl -I "https://$DOMAIN/api/health" --max-time 10 2>/dev/null | grep -q "200"; then log_success "HTTPS连接正常" else log_error "HTTPS连接失败" fi # 测试HTTP重定向 log_info "2. 测试HTTP重定向..." REDIRECT=$(curl -I "http://$DOMAIN" --max-time 10 2>/dev/null | grep -i "location" || true) if echo "$REDIRECT" | grep -q "https://"; then log_success "HTTP重定向到HTTPS正常" else log_warning "HTTP重定向可能未配置" fi # 测试SSL证书 log_info "3. 测试SSL证书..." if command -v openssl &> /dev/null; then CERT_INFO=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -dates 2>/dev/null || true) if [ ! -z "$CERT_INFO" ]; then log_success "SSL证书有效" echo "证书有效期:" echo "$CERT_INFO" | sed 's/notBefore=//;s/notAfter=//' else log_error "SSL证书测试失败" fi fi # 测试安全头部 log_info "4. 测试安全头部..." HEADERS=$(curl -I "https://$DOMAIN" --max-time 10 2>/dev/null || true) check_header() { if echo "$HEADERS" | grep -qi "$1"; then log_success "$2: ✓" else log_warning "$2: ✗ (未找到)" fi } check_header "Strict-Transport-Security" "HSTS" check_header "X-Frame-Options" "点击劫持防护" check_header "X-Content-Type-Options" "MIME类型嗅探防护" check_header "X-XSS-Protection" "XSS防护" echo "----------------------------------------" # 在线测试建议 echo log_info "建议的在线测试工具:" echo "1. SSL Labs: https://www.ssllabs.com/ssltest/analyze.html?d=$DOMAIN" echo "2. Security Headers: https://securityheaders.com/?q=https://$DOMAIN" echo "3. Mozilla Observatory: https://observatory.mozilla.org/analyze/$DOMAIN" } # 续期证书(仅Let's Encrypt) renew_certificates() { log_info "续期SSL证书..." if command -v certbot &> /dev/null; then certbot renew --dry-run if [ $? -eq 0 ]; then log_info "模拟续期成功,开始正式续期..." certbot renew # 重启Nginx systemctl reload nginx log_success "证书续期完成" else log_error "证书续期测试失败,请检查配置" fi else log_error "Certbot未安装,无法续期证书" exit 1 fi } # 主函数 main() { MODE="tencent" # 解析参数 case "$1" in "--tencent") MODE="tencent" ;; "--letsencrypt") MODE="letsencrypt" ;; "--manual") MODE="manual" ;; "--test") MODE="test" ;; "--renew") MODE="renew" ;; "--help"|"-h") show_help exit 0 ;; "") MODE="tencent" ;; *) log_error "未知选项: $1" show_help exit 1 ;; esac # 检查Nginx check_nginx # 执行对应模式 case $MODE in "tencent") configure_tencent_ssl ;; "letsencrypt") configure_letsencrypt_ssl ;; "manual") configure_manual_ssl ;; "test") test_ssl_config ;; "renew") renew_certificates ;; esac } # 执行 main "$@"