QAUP_Management/deploy/docker/security-setup.sh

738 lines
18 KiB
Bash
Executable File
Raw 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
# QAUP 安全配置脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[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<length; i++)); do
password+=$(echo $all_chars | fold -w1 | shuf | head -1)
done
# 打乱字符顺序
echo $password | fold -w1 | shuf | tr -d '\n'
}
# 主函数
case "$1" in
check)
if [ -z "$2" ]; then
echo "用法: $0 check <password>"
exit 1
fi
check_password_strength "$2"
;;
generate)
generate_strong_password "$2"
;;
*)
echo "用法: $0 {check|generate} [参数]"
echo " check <password> - 检查密码强度"
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 "$@"