Files
novalon-website/scripts/security/jenkins-security-hardening.sh
T
张翔 042f66499a fix: complete test suite fixes - achieve 99.8% pass rate
- Add missing lucide-react icons (Users, Target, MessageCircle, Layers, CreditCard)
- Fix admin/page.test.tsx ESLint errors (add displayName)
- Fix api/contact/route.test.ts ESLint errors (remove any types, use import)
- Add RESEND_API_KEY environment variable for API tests
- All 122 test suites now passing
- Test pass rate: 99.8% (1499/1502 passed, 3 skipped)
2026-04-09 17:33:21 +08:00

545 lines
16 KiB
Bash
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
# Jenkins生产环境安全加固脚本
# 作者:张翔
# 日期:2026-04-07
# 版本:1.0
# 用途:系统性解决Jenkins暴露在公网8080端口的安全风险
set -euo pipefail
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# 配置参数
JENKINS_HOME="${JENKINS_HOME:-/var/lib/jenkins}"
NGINX_CONF_DIR="${NGINX_CONF_DIR:-/etc/nginx/conf.d}"
BACKUP_DIR="${BACKUP_DIR:-/tmp/jenkins-security-backup-$(date +%Y%m%d_%H%M%S)}"
DOMAIN="${DOMAIN:-your-domain.com}"
# 安全参数
ADMIN_USER="${JENKINS_ADMIN_USER:-admin}"
WEBHOOK_SECRET="${WEBHOOK_SECRET:-$(openssl rand -hex 32)}"
ALLOWED_IPS="${ALLOWED_IPS:-}"
echo "======================================================================"
echo " Jenkins生产环境安全加固脚本"
echo " 作者:张翔 | 日期:2026-04-07 | 版本:1.0"
echo "======================================================================"
echo ""
# 前置检查
log_step "执行前置检查..."
if [ "$EUID" -ne 0 ]; then
log_error "请使用root权限运行此脚本"
exit 1
fi
if ! command -v nginx &> /dev/null; then
log_error "Nginx未安装,请先安装Nginx"
exit 1
fi
if ! command -v openssl &> /dev/null; then
log_error "OpenSSL未安装"
exit 1
fi
log_info "前置检查通过"
# 创建备份目录
log_step "创建备份目录..."
mkdir -p "$BACKUP_DIR"
log_info "备份目录:$BACKUP_DIR"
# 备份现有配置
log_step "备份现有配置..."
if [ -d "$JENKINS_HOME" ]; then
cp -r "$JENKINS_HOME" "$BACKUP_DIR/jenkins-home" 2>/dev/null || true
fi
if [ -d "$NGINX_CONF_DIR" ]; then
cp -r "$NGINX_CONF_DIR" "$BACKUP_DIR/nginx-conf" 2>/dev/null || true
fi
log_info "配置已备份"
# 步骤1:修改Jenkins监听地址
log_step "步骤1/7:修改Jenkins监听地址为127.0.0.1..."
if [ -f "/etc/default/jenkins" ]; then
JENKINS_DEFAULT="/etc/default/jenkins"
elif [ -f "/etc/sysconfig/jenkins" ]; then
JENKINS_DEFAULT="/etc/sysconfig/jenkins"
else
log_warn "未找到Jenkins配置文件,跳过此步骤"
JENKINS_DEFAULT=""
fi
if [ -n "$JENKINS_DEFAULT" ]; then
cp "$JENKINS_DEFAULT" "$BACKUP_DIR/jenkins-default.bak"
if grep -q "JENKINS_ARGS" "$JENKINS_DEFAULT"; then
if grep -q "httpListenAddress" "$JENKINS_DEFAULT"; then
sed -i 's/httpListenAddress=[^ ]*/httpListenAddress=127.0.0.1/' "$JENKINS_DEFAULT"
else
sed -i '/JENKINS_ARGS=/ s/"$/ --httpListenAddress=127.0.0.1"/' "$JENKINS_DEFAULT"
fi
else
echo 'JENKINS_ARGS="--httpListenAddress=127.0.0.1"' >> "$JENKINS_DEFAULT"
fi
log_info "Jenkins配置已更新,仅监听127.0.0.1"
fi
# 步骤2:生成HTTP Basic Auth密码
log_step "步骤2/7:生成HTTP Basic Auth密码..."
read -sp "请输入Jenkins访问密码: " JENKINS_PASSWORD
echo ""
read -sp "请再次确认密码: " JENKINS_PASSWORD_CONFIRM
echo ""
if [ "$JENKINS_PASSWORD" != "$JENKINS_PASSWORD_CONFIRM" ]; then
log_error "两次密码输入不一致"
exit 1
fi
if [ -z "$JENKINS_PASSWORD" ]; then
log_error "密码不能为空"
exit 1
fi
HTPASSWD_FILE="$NGINX_CONF_DIR/.jenkins-htpasswd"
htpasswd -bc "$HTPASSWD_FILE" "$ADMIN_USER" "$JENKINS_PASSWORD" 2>/dev/null || \
openssl passwd -apr1 "$JENKINS_PASSWORD" | sed "s|^|$ADMIN_USER:|" > "$HTPASSWD_FILE"
chmod 600 "$HTPASSWD_FILE"
log_info "HTTP Basic Auth密码文件已生成:$HTPASSWD_FILE"
# 步骤3:创建Nginx安全配置
log_step "步骤3/7:创建Nginx反向代理安全配置..."
NGINX_JENKINS_CONF="$NGINX_CONF_DIR/jenkins-security.conf"
cat > "$NGINX_JENKINS_CONF" << 'NGINX_CONF_EOF'
# Jenkins安全反向代理配置
# 作者:张翔
# 日期:2026-04-07
# 说明:多层安全防护 - 认证、频率限制、IP白名单、审计日志
# 上游Jenkins服务
upstream jenkins_backend {
server 127.0.0.1:8080;
keepalive 32;
}
# 频率限制区域
limit_req_zone $binary_remote_addr zone=jenkins_limit:10m rate=10r/m;
limit_conn_zone $binary_remote_addr zone=jenkins_conn:10m;
# 日志格式(包含安全审计信息)
log_format jenkins_security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'request_time=$request_time '
'upstream_response_time=$upstream_response_time '
'ssl_protocol=$ssl_protocol '
'ssl_cipher=$ssl_cipher';
# HTTP重定向到HTTPS
server {
listen 80;
server_name DOMAIN_PLACEHOLDER;
# Let's Encrypt验证路径
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/letsencrypt;
}
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS主配置
server {
listen 443 ssl http2;
server_name DOMAIN_PLACEHOLDER;
# SSL配置
ssl_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/privkey.pem;
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';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全响应头
add_header Strict-Transport-Security "max-age=31536000; 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;
# 访问日志
access_log /var/log/nginx/jenkins-access.log jenkins_security;
error_log /var/log/nginx/jenkins-error.log warn;
# 频率限制
limit_req zone=jenkins_limit burst=20 nodelay;
limit_conn jenkins_conn 10;
# 客户端请求限制
client_max_body_size 100m;
client_body_timeout 60s;
client_header_timeout 60s;
# Webhook端点(IP白名单 + 签名验证)
location ~ ^/generic-webhook-trigger(/.*)?$ {
# IP白名单(仅允许Gitea服务器)
# ALLOWED_IPS_PLACEHOLDER
# 验证Webhook签名
# if ($http_x_gitea_signature = "") {
# return 403;
# }
# 代理到Jenkins
proxy_pass http://jenkins_backend;
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_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Jenkins主界面(需要认证)
location /jenkins/ {
# HTTP Basic Auth
auth_basic "Jenkins Production Access";
auth_basic_user_file HTPASSWD_FILE_PLACEHOLDER;
# 代理到Jenkins
proxy_pass http://jenkins_backend/;
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_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 默认拒绝其他路径
location / {
return 404;
}
}
NGINX_CONF_EOF
# 替换占位符
sed -i "s|DOMAIN_PLACEHOLDER|$DOMAIN|g" "$NGINX_JENKINS_CONF"
sed -i "s|HTPASSWD_FILE_PLACEHOLDER|$HTPASSWD_FILE|g" "$NGINX_JENKINS_CONF"
# 添加IP白名单
if [ -n "$ALLOWED_IPS" ]; then
IP_ALLOW_RULE="allow $ALLOWED_IPS; deny all;"
sed -i "s|# ALLOWED_IPS_PLACEHOLDER|$IP_ALLOW_RULE|g" "$NGINX_JENKINS_CONF"
fi
log_info "Nginx安全配置已创建:$NGINX_JENKINS_CONF"
# 步骤4:配置防火墙规则
log_step "步骤4/7:配置防火墙规则..."
if command -v ufw &> /dev/null; then
ufw --force enable
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw deny 8080/tcp comment 'Jenkins Direct Access Blocked'
ufw --force reload
log_info "UFW防火墙规则已配置"
elif command -v firewall-cmd &> /dev/null; then
systemctl start firewalld
systemctl enable firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --remove-port=8080/tcp
firewall-cmd --reload
log_info "Firewalld防火墙规则已配置"
else
log_warn "未检测到防火墙,请手动配置iptables规则"
fi
# 步骤5:创建Webhook签名验证脚本
log_step "步骤5/7:创建Webhook签名验证脚本..."
WEBHOOK_VERIFY_SCRIPT="/usr/local/bin/verify-jenkins-webhook.sh"
cat > "$WEBHOOK_VERIFY_SCRIPT" << 'WEBHOOK_EOF'
#!/bin/bash
# Webhook签名验证脚本
# 用途:验证来自Gitea的Webhook请求签名
set -euo pipefail
WEBHOOK_SECRET="${WEBHOOK_SECRET:-}"
PAYLOAD_FILE="${1:-/dev/stdin}"
if [ -z "$WEBHOOK_SECRET" ]; then
echo "ERROR: WEBHOOK_SECRET not set" >&2
exit 1
fi
# 读取请求体
PAYLOAD=$(cat "$PAYLOAD_FILE")
# 计算HMAC签名
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
echo "sha256=$SIGNATURE"
WEBHOOK_EOF
chmod +x "$WEBHOOK_VERIFY_SCRIPT"
log_info "Webhook验证脚本已创建:$WEBHOOK_VERIFY_SCRIPT"
# 步骤6:配置Jenkins安全设置
log_step "步骤6/7:配置Jenkins安全设置..."
JENKINS_CONFIG_XML="$JENKINS_HOME/config.xml"
if [ -f "$JENKINS_CONFIG_XML" ]; then
cp "$JENKINS_CONFIG_XML" "$BACKUP_DIR/config.xml.bak"
# 禁用匿名访问
if grep -q "<useSecurity>true</useSecurity>" "$JENKINS_CONFIG_XML"; then
log_info "Jenkins安全已启用"
else
sed -i 's|<useSecurity>.*</useSecurity>|<useSecurity>true</useSecurity>|' "$JENKINS_CONFIG_XML" 2>/dev/null || true
fi
log_info "Jenkins安全配置已更新"
fi
# 步骤7:创建安全验证脚本
log_step "步骤7/7:创建安全验证脚本..."
VERIFY_SCRIPT="/usr/local/bin/verify-jenkins-security.sh"
cat > "$VERIFY_SCRIPT" << 'VERIFY_EOF'
#!/bin/bash
# Jenkins安全验证脚本
# 作者:张翔
# 用途:验证Jenkins安全加固是否成功
set -euo pipefail
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "=========================================="
echo " Jenkins安全验证"
echo "=========================================="
echo ""
PASS=0
FAIL=0
check_pass() {
echo -e "${GREEN}[✓]${NC} $1"
((PASS++))
}
check_fail() {
echo -e "${RED}[✗]${NC} $1"
((FAIL++))
}
check_warn() {
echo -e "${YELLOW}[!]${NC} $1"
}
# 检查1Jenkins是否仅监听127.0.0.1
echo "检查1Jenkins监听地址"
if netstat -tlnp 2>/dev/null | grep -q ":8080.*127.0.0.1"; then
check_pass "Jenkins仅监听127.0.0.1:8080"
elif netstat -tlnp 2>/dev/null | grep -q ":8080.*0.0.0.0"; then
check_fail "Jenkins监听0.0.0.0:8080(风险!)"
else
check_warn "Jenkins未运行或监听地址未知"
fi
# 检查2:直接访问8080端口是否被拒绝
echo ""
echo "检查2:直接访问8080端口"
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://localhost:8080 2>/dev/null | grep -q "000"; then
check_pass "直接访问8080端口被拒绝"
else
check_fail "可以直接访问8080端口(风险!)"
fi
# 检查3Nginx配置是否正确
echo ""
echo "检查3Nginx配置"
if nginx -t 2>/dev/null; then
check_pass "Nginx配置语法正确"
else
check_fail "Nginx配置存在错误"
fi
# 检查4HTTPS是否启用
echo ""
echo "检查4HTTPS配置"
if [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]; then
check_pass "SSL证书已配置"
else
check_warn "SSL证书未找到,请手动配置"
fi
# 检查5:防火墙规则
echo ""
echo "检查5:防火墙规则"
if command -v ufw &> /dev/null; then
if ufw status | grep -q "8080.*DENY"; then
check_pass "防火墙已阻止8080端口"
else
check_fail "防火墙未阻止8080端口"
fi
elif command -v firewall-cmd &> /dev/null; then
if ! firewall-cmd --list-ports | grep -q "8080"; then
check_pass "防火墙已阻止8080端口"
else
check_fail "防火墙未阻止8080端口"
fi
else
check_warn "未检测到防火墙"
fi
# 检查6HTTP Basic Auth
echo ""
echo "检查6HTTP Basic Auth"
if [ -f "/etc/nginx/conf.d/.jenkins-htpasswd" ]; then
check_pass "HTTP Basic Auth密码文件存在"
else
check_fail "HTTP Basic Auth密码文件不存在"
fi
# 检查7Jenkinsfile中是否还有硬编码token
echo ""
echo "检查7:敏感信息检查"
if [ -f "Jenkinsfile" ]; then
if grep -q "token.*=.*['\"].*['\"]" Jenkinsfile 2>/dev/null; then
check_fail "Jenkinsfile中存在硬编码token"
else
check_pass "Jenkinsfile中未发现硬编码token"
fi
else
check_warn "未找到Jenkinsfile"
fi
# 汇总
echo ""
echo "=========================================="
echo " 验证结果:通过 $PASS 项,失败 $FAIL 项"
echo "=========================================="
if [ $FAIL -eq 0 ]; then
echo -e "${GREEN}安全加固验证通过!${NC}"
exit 0
else
echo -e "${RED}安全加固存在风险,请检查失败项!${NC}"
exit 1
fi
VERIFY_EOF
chmod +x "$VERIFY_SCRIPT"
log_info "安全验证脚本已创建:$VERIFY_SCRIPT"
# 重启服务
log_step "重启服务..."
echo ""
read -p "是否立即重启Jenkins和Nginx服务?(y/N): " RESTART_CHOICE
if [[ "$RESTART_CHOICE" =~ ^[Yy]$ ]]; then
if command -v systemctl &> /dev/null; then
systemctl restart jenkins
systemctl restart nginx
log_info "服务已重启"
else
service jenkins restart
service nginx restart
log_info "服务已重启"
fi
else
log_warn "请手动重启服务:systemctl restart jenkins nginx"
fi
# 输出安全信息
echo ""
echo "======================================================================"
echo " 安全加固完成"
echo "======================================================================"
echo ""
echo "📋 重要信息:"
echo " - Jenkins访问地址: https://$DOMAIN/jenkins/"
echo " - 管理员用户: $ADMIN_USER"
echo " - Webhook密钥: $WEBHOOK_SECRET"
echo ""
echo "📁 备份位置: $BACKUP_DIR"
echo ""
echo "✅ 后续步骤:"
echo " 1. 运行安全验证: $VERIFY_SCRIPT"
echo " 2. 更新Jenkinsfile中的webhook token为环境变量"
echo " 3. 配置SSL证书(如未配置)"
echo " 4. 设置定期安全审计"
echo ""
echo "⚠️ 安全提醒:"
echo " - 请妥善保管管理员密码和Webhook密钥"
echo " - 定期更新密码(建议每90天)"
echo " - 监控访问日志:/var/log/nginx/jenkins-access.log"
echo ""
echo "📞 如遇问题,请检查:"
echo " - Jenkins日志: journalctl -u jenkins -f"
echo " - Nginx日志: tail -f /var/log/nginx/jenkins-error.log"
echo "======================================================================"