#!/bin/bash # QAUP 安全配置脚本 set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' print_message() { local color=$1 local message=$2 echo -e "${color}${message}${NC}" } # 创建非 root 用户配置 setup_non_root_users() { print_message $BLUE "配置容器非 root 用户..." # 更新应用 Dockerfile 以使用非 root 用户 cat > "$PROJECT_ROOT/docker/app-security.Dockerfile" << 'EOF' # 安全版本的应用 Dockerfile FROM openjdk:17-jre-slim # 创建应用用户和组 RUN groupadd -r qaup && useradd -r -g qaup -d /app -s /bin/bash qaup # 安装必要工具 RUN apt-get update && apt-get install -y \ curl \ netcat-openbsd \ fontconfig \ fonts-dejavu-core \ && rm -rf /var/lib/apt/lists/* \ && apt-get clean # 设置工作目录 WORKDIR /app # 创建必要目录并设置权限 RUN mkdir -p /app/logs /app/uploadPath /app/temp \ && chown -R qaup:qaup /app \ && chmod 755 /app \ && chmod 750 /app/logs /app/uploadPath \ && chmod 700 /app/temp # 复制应用文件 COPY --chown=qaup:qaup qaup-admin/target/qaup-admin.jar /app/app.jar COPY --chown=qaup:qaup docker/start.sh /app/start.sh # 设置文件权限 RUN chmod 644 /app/app.jar \ && chmod 750 /app/start.sh # 切换到非 root 用户 USER qaup # 暴露端口 EXPOSE 8080 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # 启动应用 ENTRYPOINT ["/app/start.sh"] EOF print_message $GREEN "✓ 应用容器非 root 用户配置完成" } # 配置网络安全 setup_network_security() { print_message $BLUE "配置网络安全..." # 创建安全的 Docker Compose 网络配置 cat > "$PROJECT_ROOT/docker-compose.security.yml" << 'EOF' version: '3.8' # 安全网络配置覆盖 services: qaup-postgres: networks: - db-network # 不暴露数据库端口到主机 ports: [] qaup-redis: networks: - cache-network # 不暴露 Redis 端口到主机 ports: [] qaup-app: networks: - app-network - db-network - cache-network # 不暴露应用端口到主机 ports: [] qaup-nginx: networks: - app-network # 只暴露必要的 HTTP/HTTPS 端口 ports: - "80:80" - "443:443" # 网络隔离配置 networks: app-network: driver: bridge internal: false ipam: config: - subnet: 172.20.1.0/24 db-network: driver: bridge internal: true # 内部网络,不能访问外网 ipam: config: - subnet: 172.20.2.0/24 cache-network: driver: bridge internal: true # 内部网络,不能访问外网 ipam: config: - subnet: 172.20.3.0/24 EOF print_message $GREEN "✓ 网络安全配置完成" } # 配置文件权限 setup_file_permissions() { print_message $BLUE "配置文件权限..." # 设置配置文件权限 if [ -f "$PROJECT_ROOT/.env" ]; then chmod 600 "$PROJECT_ROOT/.env" print_message $GREEN "✓ 环境文件权限已设置为 600" fi # 设置数据目录权限 if [ -d "$PROJECT_ROOT/data" ]; then chmod 750 "$PROJECT_ROOT/data" chmod 700 "$PROJECT_ROOT/data/postgres" 2>/dev/null || true chmod 755 "$PROJECT_ROOT/data/redis" 2>/dev/null || true chmod 755 "$PROJECT_ROOT/data/uploads" 2>/dev/null || true print_message $GREEN "✓ 数据目录权限已设置" fi # 设置日志目录权限 if [ -d "$PROJECT_ROOT/logs" ]; then chmod 755 "$PROJECT_ROOT/logs" chmod 644 "$PROJECT_ROOT/logs"/*.log 2>/dev/null || true print_message $GREEN "✓ 日志目录权限已设置" fi # 设置脚本权限 find "$PROJECT_ROOT/docker" -name "*.sh" -exec chmod 750 {} \; chmod 750 "$PROJECT_ROOT/deploy.sh" 2>/dev/null || true print_message $GREEN "✓ 脚本文件权限已设置" # 设置配置文件权限 find "$PROJECT_ROOT/config" -type f -exec chmod 644 {} \; 2>/dev/null || true find "$PROJECT_ROOT/docker" -name "*.conf" -exec chmod 644 {} \; 2>/dev/null || true print_message $GREEN "✓ 配置文件权限已设置" } # 配置 SSL/TLS setup_ssl_tls() { print_message $BLUE "配置 SSL/TLS..." # 创建 SSL 目录 mkdir -p "$PROJECT_ROOT/ssl" chmod 700 "$PROJECT_ROOT/ssl" # 创建自签名证书(用于测试) if [ ! -f "$PROJECT_ROOT/ssl/server.crt" ]; then print_message $YELLOW "生成自签名 SSL 证书(仅用于测试)..." openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout "$PROJECT_ROOT/ssl/server.key" \ -out "$PROJECT_ROOT/ssl/server.crt" \ -subj "/C=CN/ST=State/L=City/O=Organization/CN=localhost" \ 2>/dev/null chmod 600 "$PROJECT_ROOT/ssl/server.key" chmod 644 "$PROJECT_ROOT/ssl/server.crt" print_message $YELLOW "⚠ 生产环境请使用正式的 SSL 证书" fi # 创建支持 HTTPS 的 Nginx 配置 cat > "$PROJECT_ROOT/docker/nginx/https.conf" << 'EOF' # HTTPS 配置 server { listen 443 ssl http2; server_name localhost; # SSL 证书配置 ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; # SSL 安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 安全头 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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; # 其他配置与 HTTP 相同 root /usr/share/nginx/html; index index.html; location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } location /prod-api/ { proxy_pass http://qaup-app:8080/; 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 30s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; client_max_body_size 20M; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; try_files $uri =404; } location / { try_files $uri $uri/ /index.html; location ~* \.html$ { add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } } } # HTTP 重定向到 HTTPS server { listen 80; server_name localhost; return 301 https://$server_name$request_uri; } EOF print_message $GREEN "✓ SSL/TLS 配置完成" } # 配置防火墙规则 setup_firewall() { print_message $BLUE "配置防火墙规则..." # 检查是否有 root 权限 if [ "$EUID" -ne 0 ]; then print_message $YELLOW "⚠ 需要 root 权限来配置防火墙" print_message $YELLOW "请手动执行以下命令:" echo "" echo "# 启用 UFW 防火墙" echo "sudo ufw --force enable" echo "" echo "# 允许 SSH 连接" echo "sudo ufw allow 22/tcp" echo "" echo "# 允许 HTTP 和 HTTPS" echo "sudo ufw allow 80/tcp" echo "sudo ufw allow 443/tcp" echo "" echo "# 拒绝其他端口的外部访问" echo "sudo ufw deny 8080/tcp" echo "sudo ufw deny 5432/tcp" echo "sudo ufw deny 6379/tcp" echo "" echo "# 查看防火墙状态" echo "sudo ufw status verbose" return fi # 配置 UFW 防火墙 ufw --force enable # 允许必要的端口 ufw allow 22/tcp comment 'SSH' ufw allow 80/tcp comment 'HTTP' ufw allow 443/tcp comment 'HTTPS' # 拒绝直接访问内部服务端口 ufw deny 8080/tcp comment 'Block direct app access' ufw deny 5432/tcp comment 'Block direct database access' ufw deny 6379/tcp comment 'Block direct Redis access' # 显示防火墙状态 ufw status verbose print_message $GREEN "✓ 防火墙配置完成" } # 配置容器安全选项 setup_container_security() { print_message $BLUE "配置容器安全选项..." # 创建安全的 Docker Compose 配置 cat > "$PROJECT_ROOT/docker-compose.security-options.yml" << 'EOF' version: '3.8' # 容器安全选项配置 services: qaup-postgres: security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE - FOWNER - SETGID - SETUID read_only: false tmpfs: - /tmp:noexec,nosuid,size=100m qaup-redis: security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - SETGID - SETUID read_only: false tmpfs: - /tmp:noexec,nosuid,size=50m qaup-app: security_opt: - no-new-privileges:true cap_drop: - ALL read_only: false tmpfs: - /tmp:noexec,nosuid,size=200m qaup-nginx: security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE - SETGID - SETUID read_only: false tmpfs: - /tmp:noexec,nosuid,size=50m - /var/cache/nginx:noexec,nosuid,size=100m EOF print_message $GREEN "✓ 容器安全选项配置完成" } # 配置日志审计 setup_audit_logging() { print_message $BLUE "配置审计日志..." # 创建审计日志配置 cat > "$PROJECT_ROOT/docker/audit-config.yml" << 'EOF' # 审计日志配置 audit: enabled: true log_file: "./logs/audit.log" # 审计事件类型 events: - login_attempts - admin_operations - data_access - configuration_changes - security_events # 日志格式 format: json # 日志轮转 rotation: max_size: 100MB max_files: 10 compress: true EOF # 创建审计日志脚本 cat > "$PROJECT_ROOT/docker/audit-logger.sh" << 'AUDIT_EOF' #!/bin/bash # 审计日志记录脚本 AUDIT_LOG="./logs/audit.log" # 记录审计事件 log_audit_event() { local event_type=$1 local user=${2:-"system"} local action=$3 local resource=${4:-""} local result=${5:-"success"} local details=${6:-""} local timestamp=$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ') local event_id=$(uuidgen 2>/dev/null || echo "$(date +%s)-$$") # 创建 JSON 格式的审计日志 cat >> "$AUDIT_LOG" << EOF { "timestamp": "$timestamp", "event_id": "$event_id", "event_type": "$event_type", "user": "$user", "action": "$action", "resource": "$resource", "result": "$result", "details": "$details", "source_ip": "${SSH_CLIENT%% *}", "session_id": "${SSH_TTY:-console}" } EOF } # 监控 Docker 事件 monitor_docker_events() { docker events --format '{{.Time}} {{.Type}} {{.Action}} {{.Actor.Attributes.name}}' | while read timestamp type action container; do if [[ "$type" == "container" && "$action" =~ ^(start|stop|restart|kill|die)$ ]]; then log_audit_event "container_event" "docker" "$action" "$container" "success" "Container $action event" fi done & } # 启动审计监控 if [ "$1" = "start" ]; then echo "启动审计日志监控..." monitor_docker_events echo "审计日志监控已启动" fi # 导出函数供其他脚本使用 if [ "$1" != "start" ]; then log_audit_event "$@" fi AUDIT_EOF chmod +x "$PROJECT_ROOT/docker/audit-logger.sh" print_message $GREEN "✓ 审计日志配置完成" } # 配置密码策略 setup_password_policy() { print_message $BLUE "配置密码策略..." # 创建密码策略检查脚本 cat > "$PROJECT_ROOT/docker/password-policy.sh" << 'POLICY_EOF' #!/bin/bash # 密码策略检查脚本 # 密码策略配置 MIN_LENGTH=12 REQUIRE_UPPERCASE=true REQUIRE_LOWERCASE=true REQUIRE_NUMBERS=true REQUIRE_SPECIAL=true FORBIDDEN_PATTERNS=("password" "123456" "admin" "qaup") # 检查密码强度 check_password_strength() { local password=$1 local errors=() # 检查长度 if [ ${#password} -lt $MIN_LENGTH ]; then errors+=("密码长度至少需要 $MIN_LENGTH 位") fi # 检查大写字母 if [ "$REQUIRE_UPPERCASE" = true ] && ! [[ "$password" =~ [A-Z] ]]; then errors+=("密码必须包含大写字母") fi # 检查小写字母 if [ "$REQUIRE_LOWERCASE" = true ] && ! [[ "$password" =~ [a-z] ]]; then errors+=("密码必须包含小写字母") fi # 检查数字 if [ "$REQUIRE_NUMBERS" = true ] && ! [[ "$password" =~ [0-9] ]]; then errors+=("密码必须包含数字") fi # 检查特殊字符 if [ "$REQUIRE_SPECIAL" = true ] && ! [[ "$password" =~ [^a-zA-Z0-9] ]]; then errors+=("密码必须包含特殊字符") fi # 检查禁用模式 for pattern in "${FORBIDDEN_PATTERNS[@]}"; do if [[ "${password,,}" == *"${pattern,,}"* ]]; then errors+=("密码不能包含常见词汇: $pattern") fi done # 返回结果 if [ ${#errors[@]} -eq 0 ]; then echo "密码强度检查通过" return 0 else echo "密码强度检查失败:" for error in "${errors[@]}"; do echo " - $error" done return 1 fi } # 生成强密码 generate_strong_password() { local length=${1:-16} # 确保包含所有必需的字符类型 local uppercase="ABCDEFGHIJKLMNOPQRSTUVWXYZ" local lowercase="abcdefghijklmnopqrstuvwxyz" local numbers="0123456789" local special="!@#$%^&*()_+-=[]{}|;:,.<>?" local password="" # 至少包含一个每种类型的字符 password+=$(echo $uppercase | fold -w1 | shuf | head -1) password+=$(echo $lowercase | fold -w1 | shuf | head -1) password+=$(echo $numbers | fold -w1 | shuf | head -1) password+=$(echo $special | fold -w1 | shuf | head -1) # 填充剩余长度 local all_chars="$uppercase$lowercase$numbers$special" for ((i=4; i" exit 1 fi check_password_strength "$2" ;; generate) generate_strong_password "$2" ;; *) echo "用法: $0 {check|generate} [参数]" echo " check - 检查密码强度" echo " generate [length] - 生成强密码" exit 1 ;; esac POLICY_EOF chmod +x "$PROJECT_ROOT/docker/password-policy.sh" print_message $GREEN "✓ 密码策略配置完成" } # 应用所有安全配置 apply_all_security() { print_message $GREEN "开始应用所有安全配置..." echo "" setup_non_root_users echo "" setup_network_security echo "" setup_file_permissions echo "" setup_ssl_tls echo "" setup_firewall echo "" setup_container_security echo "" setup_audit_logging echo "" setup_password_policy echo "" print_message $GREEN "=========================================" print_message $GREEN "安全配置完成!" print_message $GREEN "=========================================" echo "" print_message $BLUE "安全配置摘要:" echo " ✓ 容器非 root 用户配置" echo " ✓ 网络隔离和安全配置" echo " ✓ 文件权限安全设置" echo " ✓ SSL/TLS 加密配置" echo " ✓ 防火墙规则配置" echo " ✓ 容器安全选项配置" echo " ✓ 审计日志配置" echo " ✓ 密码策略配置" echo "" print_message $YELLOW "重要提醒:" echo " 1. 生产环境请使用正式的 SSL 证书" echo " 2. 定期更新密码和证书" echo " 3. 监控审计日志" echo " 4. 定期进行安全扫描" echo "" } # 显示帮助信息 show_help() { echo "QAUP 安全配置脚本" echo "" echo "用法: $0 [命令]" echo "" echo "命令:" echo " all 应用所有安全配置" echo " users 配置非 root 用户" echo " network 配置网络安全" echo " permissions 配置文件权限" echo " ssl 配置 SSL/TLS" echo " firewall 配置防火墙" echo " containers 配置容器安全选项" echo " audit 配置审计日志" echo " password 配置密码策略" echo "" echo "示例:" echo " $0 all # 应用所有安全配置" echo " sudo $0 firewall # 配置防火墙(需要 root)" echo " $0 ssl # 配置 SSL/TLS" } # 主函数 main() { if [ $# -eq 0 ]; then show_help exit 0 fi local command=$1 case $command in all) apply_all_security ;; users) setup_non_root_users ;; network) setup_network_security ;; permissions) setup_file_permissions ;; ssl) setup_ssl_tls ;; firewall) setup_firewall ;; containers) setup_container_security ;; audit) setup_audit_logging ;; password) setup_password_policy ;; help|--help|-h) show_help ;; *) print_message $RED "未知命令: $command" show_help exit 1 ;; esac } main "$@"