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)
This commit is contained in:
Vendored
+5
-3
@@ -7,6 +7,7 @@ pipeline {
|
||||
NODE_ENV = 'production'
|
||||
NEXT_TELEMETRY_DISABLED = '1'
|
||||
npm_config_registry = 'https://registry.npmmirror.com'
|
||||
JENKINS_WEBHOOK_TOKEN = credentials('jenkins-webhook-token')
|
||||
}
|
||||
|
||||
triggers {
|
||||
@@ -19,12 +20,13 @@ pipeline {
|
||||
[key: 'repository.name', regexpFilter: '']
|
||||
],
|
||||
genericHeaderVariables: [
|
||||
[key: 'X-Gitea-Event', regexpFilter: '']
|
||||
[key: 'X-Gitea-Event', regexpFilter: ''],
|
||||
[key: 'X-Gitea-Signature', regexpFilter: '']
|
||||
],
|
||||
causeString: 'Gitea Webhook Trigger: $ref',
|
||||
token: 'novalon-website-webhook-token-2024',
|
||||
token: env.JENKINS_WEBHOOK_TOKEN,
|
||||
printContributedVariables: true,
|
||||
printPostContent: true,
|
||||
printPostContent: false,
|
||||
silentResponse: false,
|
||||
shouldNotFlatten: false,
|
||||
regexpFilterText: '$ref',
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import jenkins.model.*
|
||||
import org.jenkinsci.plugins.workflow.job.*
|
||||
|
||||
def jenkins = Jenkins.getInstance()
|
||||
def job = jenkins.getItem('novalon-website')
|
||||
|
||||
if (job != null) {
|
||||
println "Job found: ${job.fullName}"
|
||||
println "Job class: ${job.class}"
|
||||
|
||||
def triggers = job.getTriggers()
|
||||
println "Triggers: ${triggers}"
|
||||
|
||||
triggers.each { key, value ->
|
||||
println "Trigger: ${key} -> ${value}"
|
||||
}
|
||||
|
||||
def properties = job.getProperties()
|
||||
println "Properties: ${properties}"
|
||||
|
||||
properties.each { prop ->
|
||||
println "Property: ${prop.class}"
|
||||
if (prop instanceof org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty) {
|
||||
def pipelineTriggers = prop.getTriggers()
|
||||
println "Pipeline Triggers: ${pipelineTriggers}"
|
||||
pipelineTriggers.each { trigger ->
|
||||
println "Pipeline Trigger: ${trigger.class} -> ${trigger}"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println "Job not found"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,340 @@
|
||||
# Jenkins生产环境安全加固 - 对齐文档
|
||||
|
||||
**作者:** 张翔
|
||||
**日期:** 2026-04-07
|
||||
**版本:** 1.0
|
||||
**优先级:** 🔴 P0 - 紧急
|
||||
**风险等级:** 🔴 严重
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求理解
|
||||
|
||||
### 1.1 原始需求
|
||||
|
||||
**腾讯云安全报告:**
|
||||
- Jenkins服务暴露在公网8080端口
|
||||
- 黑客可利用该服务组件漏洞进行勒索攻击
|
||||
- 可能导致数据加密或文件勒索
|
||||
|
||||
**当前状态:**
|
||||
- ✅ 可以免密登录生产环境
|
||||
- ⚠️ Jenkins直接暴露在公网
|
||||
- ⚠️ 缺少访问控制和认证
|
||||
- ⚠️ Webhook Token硬编码在配置文件中
|
||||
|
||||
### 1.2 核心场景定义
|
||||
|
||||
**场景属性:**
|
||||
- **环境:** 生产环境(高可用要求)
|
||||
- **风险:** 勒索攻击、供应链攻击、凭证泄露
|
||||
- **影响范围:** Jenkins服务、CI/CD流水线、生产部署
|
||||
- **紧急程度:** 立即处理(24小时内完成加固)
|
||||
- **团队背景:** 有运维经验,熟悉Linux和Nginx
|
||||
|
||||
**关键约束:**
|
||||
1. 不能影响现有CI/CD流水线运行
|
||||
2. 加固过程需要可回滚
|
||||
3. 必须保留审计日志
|
||||
4. 需要零停机或最小化停机时间
|
||||
|
||||
---
|
||||
|
||||
## 2. 成功标准
|
||||
|
||||
### 2.1 功能性标准
|
||||
|
||||
- [ ] Jenkins不再直接暴露在公网8080端口
|
||||
- [ ] 所有访问必须经过Nginx反向代理
|
||||
- [ ] 启用HTTP Basic Auth认证
|
||||
- [ ] Webhook端点配置IP白名单
|
||||
- [ ] Webhook Token从配置文件中移除,使用环境变量
|
||||
|
||||
### 2.2 安全性标准
|
||||
|
||||
- [ ] 防火墙已阻止8080端口的外部访问
|
||||
- [ ] Jenkins仅监听127.0.0.1
|
||||
- [ ] 启用HTTPS强制重定向
|
||||
- [ ] 配置安全响应头(HSTS、X-Frame-Options等)
|
||||
- [ ] 启用访问审计日志
|
||||
|
||||
### 2.3 可验证性标准
|
||||
|
||||
- [ ] 外部无法直接访问http://SERVER_IP:8080
|
||||
- [ ] 匿名访问返回401未授权
|
||||
- [ ] 错误密码访问返回401
|
||||
- [ ] Webhook签名验证生效
|
||||
- [ ] CI/CD流水线正常运行
|
||||
|
||||
### 2.4 可维护性标准
|
||||
|
||||
- [ ] 所有配置已备份
|
||||
- [ ] 提供回滚方案
|
||||
- [ ] 文档完整(操作手册、应急响应)
|
||||
- [ ] 监控和告警已配置
|
||||
|
||||
---
|
||||
|
||||
## 3. 技术选型与决策
|
||||
|
||||
### 3.1 方案对比
|
||||
|
||||
#### 方案A:多层防御架构(推荐)
|
||||
|
||||
**技术栈:**
|
||||
- 网络层:防火墙(UFW/Firewalld)阻止8080端口
|
||||
- 应用层:Nginx反向代理 + HTTPS + HTTP Basic Auth
|
||||
- 认证层:Jenkins安全配置 + Webhook签名验证
|
||||
- 审计层:Nginx访问日志 + 监控脚本
|
||||
|
||||
**优势:**
|
||||
- ✅ 多层防御,深度安全
|
||||
- ✅ 不影响现有CI/CD流水线
|
||||
- ✅ 可逐步实施,风险可控
|
||||
- ✅ 已有完整脚本和文档
|
||||
|
||||
**劣势:**
|
||||
- ⚠️ 需要配置多个组件
|
||||
- ⚠️ 需要重启Jenkins和Nginx服务
|
||||
|
||||
**适用场景:** 生产环境,高安全要求,有运维能力
|
||||
|
||||
#### 方案B:VPN隔离方案
|
||||
|
||||
**技术栈:**
|
||||
- VPN服务器(WireGuard/OpenVPN)
|
||||
- Jenkins仅允许VPN网络访问
|
||||
- CI/CD通过VPN触发
|
||||
|
||||
**优势:**
|
||||
- ✅ 完全隔离,安全性极高
|
||||
- ✅ 适用于多服务隔离
|
||||
|
||||
**劣势:**
|
||||
- ❌ 需要额外VPN服务器
|
||||
- ❌ CI/CD配置复杂
|
||||
- ❌ 增加运维成本
|
||||
|
||||
**适用场景:** 多服务需要隔离,有VPN基础设施
|
||||
|
||||
#### 方案C:云厂商WAF方案
|
||||
|
||||
**技术栈:**
|
||||
- 腾讯云WAF
|
||||
- 安全组规则
|
||||
- 云防火墙
|
||||
|
||||
**优势:**
|
||||
- ✅ 托管服务,无需维护
|
||||
- ✅ 专业防护能力
|
||||
|
||||
**劣势:**
|
||||
- ❌ 需要额外费用
|
||||
- ❌ 依赖云厂商
|
||||
- ❌ 配置灵活性较低
|
||||
|
||||
**适用场景:** 预算充足,依赖云厂商生态
|
||||
|
||||
### 3.2 决策建议
|
||||
|
||||
**推荐方案:方案A - 多层防御架构**
|
||||
|
||||
**决策依据:**
|
||||
1. **安全性:** 多层防御满足安全要求
|
||||
2. **成本:** 无需额外硬件或服务费用
|
||||
3. **可控性:** 完全自主控制,不依赖第三方
|
||||
4. **已有基础:** 项目已有完整脚本和文档
|
||||
5. **快速实施:** 可在4小时内完成加固
|
||||
|
||||
---
|
||||
|
||||
## 4. 风险评估
|
||||
|
||||
### 4.1 实施风险
|
||||
|
||||
| 风险项 | 影响 | 概率 | 缓解措施 |
|
||||
|--------|------|------|----------|
|
||||
| Jenkins服务重启失败 | 高 | 低 | 提前备份,准备回滚脚本 |
|
||||
| Nginx配置错误导致服务不可用 | 高 | 中 | 配置测试,逐步部署 |
|
||||
| Webhook触发失败 | 中 | 中 | 保留原触发方式,验证后切换 |
|
||||
| 认证失败无法访问 | 高 | 低 | 保留SSH访问,准备应急账号 |
|
||||
|
||||
### 4.2 业务影响
|
||||
|
||||
| 影响项 | 影响程度 | 持续时间 | 缓解措施 |
|
||||
|--------|----------|----------|----------|
|
||||
| CI/CD流水线暂停 | 中 | 5-10分钟 | 选择低峰时段执行 |
|
||||
| Webhook不可用 | 中 | 5-10分钟 | 手动触发备份方案 |
|
||||
| 访问方式变更 | 低 | 持续 | 提前通知团队 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 执行计划
|
||||
|
||||
### 5.1 阶段划分
|
||||
|
||||
#### 阶段0:准备工作(30分钟)
|
||||
- [ ] 确认生产环境访问权限
|
||||
- [ ] 备份当前配置
|
||||
- [ ] 准备应急响应方案
|
||||
- [ ] 通知相关团队成员
|
||||
|
||||
#### 阶段1:快速响应(15分钟)
|
||||
- [ ] 检查Jenkins是否已被攻击
|
||||
- [ ] 临时阻止外部访问8080端口
|
||||
- [ ] 检查可疑进程
|
||||
- [ ] 备份当前配置
|
||||
|
||||
#### 阶段2:网络层加固(30分钟)
|
||||
- [ ] 修改Jenkins监听地址为127.0.0.1
|
||||
- [ ] 配置防火墙规则
|
||||
- [ ] 验证网络隔离
|
||||
|
||||
#### 阶段3:应用层防护(45分钟)
|
||||
- [ ] 生成HTTP Basic Auth密码
|
||||
- [ ] 配置Nginx反向代理
|
||||
- [ ] 配置HTTPS和SSL证书
|
||||
- [ ] 配置安全响应头
|
||||
|
||||
#### 阶段4:认证授权层(30分钟)
|
||||
- [ ] 配置Jenkins安全设置
|
||||
- [ ] 配置Webhook签名验证
|
||||
- [ ] 配置IP白名单
|
||||
- [ ] 移除硬编码Token
|
||||
|
||||
#### 阶段5:审计监控层(20分钟)
|
||||
- [ ] 配置访问日志
|
||||
- [ ] 配置日志轮转
|
||||
- [ ] 部署监控脚本
|
||||
- [ ] 配置告警
|
||||
|
||||
#### 阶段6:验证与测试(30分钟)
|
||||
- [ ] 运行安全验证脚本
|
||||
- [ ] 执行渗透测试
|
||||
- [ ] 验证CI/CD流水线
|
||||
- [ ] 验证Webhook触发
|
||||
|
||||
### 5.2 时间估算
|
||||
|
||||
- **总时间:** 约3小时
|
||||
- **停机时间:** 约10分钟(重启服务)
|
||||
- **建议执行时间:** 低峰时段(如凌晨2:00-5:00)
|
||||
|
||||
---
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
### 6.1 自动化验证
|
||||
|
||||
```bash
|
||||
# 运行安全验证脚本
|
||||
sudo /usr/local/bin/verify-jenkins-security.sh
|
||||
```
|
||||
|
||||
**预期结果:** 所有检查项通过
|
||||
|
||||
### 6.2 手动验证清单
|
||||
|
||||
#### 网络层
|
||||
- [ ] `netstat -tlnp | grep 8080` 显示 `127.0.0.1:8080`
|
||||
- [ ] `curl http://SERVER_IP:8080` 连接被拒绝
|
||||
- [ ] `ufw status | grep 8080` 显示 DENY
|
||||
|
||||
#### 应用层
|
||||
- [ ] `nginx -t` 配置测试通过
|
||||
- [ ] `curl -I https://DOMAIN/jenkins/` 返回 401
|
||||
- [ ] `curl -I -u admin:password https://DOMAIN/jenkins/` 返回 200
|
||||
|
||||
#### 认证层
|
||||
- [ ] Jenkins匿名访问被拒绝
|
||||
- [ ] Webhook签名验证生效
|
||||
- [ ] IP白名单生效
|
||||
|
||||
#### 审计层
|
||||
- [ ] `/var/log/nginx/jenkins-access.log` 正常记录
|
||||
- [ ] 日志轮转配置生效
|
||||
- [ ] 监控脚本运行正常
|
||||
|
||||
### 6.3 CI/CD验证
|
||||
|
||||
- [ ] 手动触发Jenkins构建成功
|
||||
- [ ] Webhook触发构建成功
|
||||
- [ ] 构建产物正常部署
|
||||
|
||||
---
|
||||
|
||||
## 7. 应急响应
|
||||
|
||||
### 7.1 回滚方案
|
||||
|
||||
```bash
|
||||
# 恢复Jenkins配置
|
||||
sudo cp /tmp/jenkins-security-backup-*/jenkins-default.bak /etc/default/jenkins
|
||||
|
||||
# 恢复Nginx配置
|
||||
sudo cp /tmp/jenkins-security-backup-*/nginx-conf/* /etc/nginx/conf.d/
|
||||
|
||||
# 重启服务
|
||||
sudo systemctl restart jenkins
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# 开放8080端口(仅应急)
|
||||
sudo ufw allow 8080/tcp
|
||||
```
|
||||
|
||||
### 7.2 应急联系
|
||||
|
||||
- **安全负责人:** 张翔
|
||||
- **运维支持:** [待填写]
|
||||
- **管理决策:** [待填写]
|
||||
|
||||
---
|
||||
|
||||
## 8. 后续改进
|
||||
|
||||
### 8.1 短期(1个月内)
|
||||
- [ ] 集成OAuth2/OIDC认证
|
||||
- [ ] 配置多因素认证(MFA)
|
||||
- [ ] 完善监控告警
|
||||
|
||||
### 8.2 中期(3个月内)
|
||||
- [ ] 部署WAF(Web应用防火墙)
|
||||
- [ ] 配置入侵检测系统(IDS)
|
||||
- [ ] 实施安全信息和事件管理(SIEM)
|
||||
|
||||
### 8.3 长期(6个月内)
|
||||
- [ ] 实施零信任架构
|
||||
- [ ] 微服务隔离
|
||||
- [ ] 持续安全验证
|
||||
|
||||
---
|
||||
|
||||
## 9. 文档交付物
|
||||
|
||||
- [x] 对齐文档(本文档)
|
||||
- [ ] 设计文档(DESIGN_JENKINS_SECURITY.md)
|
||||
- [ ] 执行检查清单(CHECKLIST_JENKINS_SECURITY.md)
|
||||
- [ ] 验证报告(VERIFICATION_REPORT.md)
|
||||
|
||||
---
|
||||
|
||||
## 10. 决策确认
|
||||
|
||||
**关键决策点:**
|
||||
|
||||
1. **技术方案:** 采用多层防御架构(方案A)
|
||||
2. **执行时间:** 建议低峰时段执行
|
||||
3. **停机时间:** 约10分钟
|
||||
4. **回滚策略:** 保留完整备份,可快速回滚
|
||||
|
||||
**需要确认的问题:**
|
||||
|
||||
1. ❓ 是否有特定的执行时间窗口要求?
|
||||
2. ❓ 是否需要通知外部团队或客户?
|
||||
3. ❓ 是否有其他依赖Jenkins的服务需要考虑?
|
||||
4. ❓ SSL证书是否已配置?
|
||||
|
||||
---
|
||||
|
||||
**文档状态:** ✅ 已完成
|
||||
**下一步:** 等待确认后进入Architect阶段
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,590 @@
|
||||
# Jenkins安全加固完整指南
|
||||
|
||||
**作者:** 张翔
|
||||
**日期:** 2026-04-07
|
||||
**版本:** 1.0
|
||||
**风险等级:** 🔴 严重
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [风险概述](#风险概述)
|
||||
2. [快速响应](#快速响应)
|
||||
3. [详细加固步骤](#详细加固步骤)
|
||||
4. [验证检查清单](#验证检查清单)
|
||||
5. [应急响应流程](#应急响应流程)
|
||||
6. [长期维护建议](#长期维护建议)
|
||||
|
||||
---
|
||||
|
||||
## 🚨 风险概述
|
||||
|
||||
### 当前风险
|
||||
|
||||
| 风险项 | 严重程度 | 影响 | 状态 |
|
||||
|--------|----------|------|------|
|
||||
| Jenkins暴露在公网8080端口 | 🔴 严重 | 勒索攻击、数据加密 | 待修复 |
|
||||
| Webhook Token硬编码 | 🔴 严重 | 供应链攻击 | 待修复 |
|
||||
| 缺少访问认证 | 🔴 严重 | 未授权访问 | 待修复 |
|
||||
| 无网络隔离 | 🟡 高危 | 直接攻击 | 待修复 |
|
||||
| 缺少审计日志 | 🟡 高危 | 无法追溯 | 待修复 |
|
||||
|
||||
### 攻击场景
|
||||
|
||||
1. **勒索软件攻击**
|
||||
- 黑客利用Jenkins已知漏洞(如CVE-2024-XXXX)
|
||||
- 加密Jenkins主目录和构建产物
|
||||
- 勒索赎金
|
||||
|
||||
2. **供应链攻击**
|
||||
- 利用暴露的Webhook Token
|
||||
- 恶意触发构建
|
||||
- 注入恶意代码到生产环境
|
||||
|
||||
3. **凭证泄露**
|
||||
- 获取Jenkins存储的密钥
|
||||
- 访问生产服务器、数据库
|
||||
- 全面接管系统
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 快速响应
|
||||
|
||||
### 立即执行(15分钟内)
|
||||
|
||||
```bash
|
||||
# 1. 检查Jenkins是否已被攻击
|
||||
sudo journalctl -u jenkins --since "1 hour ago" | grep -i "failed\|error\|attack"
|
||||
|
||||
# 2. 临时阻止外部访问8080端口
|
||||
sudo ufw deny 8080/tcp
|
||||
# 或
|
||||
sudo firewall-cmd --permanent --remove-port=8080/tcp
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# 3. 检查是否有可疑进程
|
||||
ps aux | grep -E "jenkins|java" | grep -v grep
|
||||
|
||||
# 4. 备份当前配置
|
||||
sudo tar -czf /tmp/jenkins-emergency-backup-$(date +%Y%m%d_%H%M%S).tar.gz \
|
||||
/var/lib/jenkins /etc/default/jenkins
|
||||
|
||||
# 5. 修改Jenkins监听地址(临时)
|
||||
sudo sed -i 's|httpPort=8080|httpPort=8080 --httpListenAddress=127.0.0.1|' \
|
||||
/etc/default/jenkins
|
||||
sudo systemctl restart jenkins
|
||||
```
|
||||
|
||||
### 1小时内执行
|
||||
|
||||
```bash
|
||||
# 运行完整的安全加固脚本
|
||||
cd /path/to/novalon-website/scripts/security
|
||||
chmod +x jenkins-security-hardening.sh
|
||||
sudo ./jenkins-security-hardening.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 详细加固步骤
|
||||
|
||||
### 步骤1:网络层隔离
|
||||
|
||||
#### 1.1 修改Jenkins监听地址
|
||||
|
||||
**目标:** Jenkins仅监听127.0.0.1,外部无法直接访问
|
||||
|
||||
**操作:**
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
sudo vim /etc/default/jenkins
|
||||
|
||||
# 添加或修改以下行
|
||||
JENKINS_ARGS="--httpListenAddress=127.0.0.1 --httpPort=8080"
|
||||
|
||||
# RHEL/CentOS
|
||||
sudo vim /etc/sysconfig/jenkins
|
||||
|
||||
# 修改
|
||||
JENKINS_LISTEN_ADDRESS="127.0.0.1"
|
||||
```
|
||||
|
||||
**验证:**
|
||||
|
||||
```bash
|
||||
# 检查监听地址
|
||||
sudo netstat -tlnp | grep 8080
|
||||
# 应显示:127.0.0.1:8080
|
||||
|
||||
# 尝试外部访问(应失败)
|
||||
curl -I http://YOUR_SERVER_IP:8080
|
||||
# 应返回:Connection refused
|
||||
```
|
||||
|
||||
#### 1.2 配置防火墙
|
||||
|
||||
**UFW (Ubuntu/Debian):**
|
||||
|
||||
```bash
|
||||
sudo ufw --force enable
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow 22/tcp comment 'SSH'
|
||||
sudo ufw allow 80/tcp comment 'HTTP'
|
||||
sudo ufw allow 443/tcp comment 'HTTPS'
|
||||
sudo ufw deny 8080/tcp comment 'Jenkins Direct Access'
|
||||
sudo ufw --force reload
|
||||
```
|
||||
|
||||
**Firewalld (RHEL/CentOS):**
|
||||
|
||||
```bash
|
||||
sudo systemctl start firewalld
|
||||
sudo systemctl enable firewalld
|
||||
sudo firewall-cmd --permanent --add-service=ssh
|
||||
sudo firewall-cmd --permanent --add-service=http
|
||||
sudo firewall-cmd --permanent --add-service=https
|
||||
sudo firewall-cmd --permanent --remove-port=8080/tcp
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤2:应用层防护
|
||||
|
||||
#### 2.1 配置Nginx反向代理
|
||||
|
||||
**创建配置文件:**
|
||||
|
||||
```bash
|
||||
sudo vim /etc/nginx/conf.d/jenkins-security.conf
|
||||
```
|
||||
|
||||
**配置内容:**(见脚本生成的配置)
|
||||
|
||||
**关键安全配置:**
|
||||
|
||||
```nginx
|
||||
# 频率限制
|
||||
limit_req_zone $binary_remote_addr zone=jenkins_limit:10m rate=10r/m;
|
||||
|
||||
# 安全响应头
|
||||
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;
|
||||
|
||||
# 客户端限制
|
||||
client_max_body_size 100m;
|
||||
client_body_timeout 60s;
|
||||
```
|
||||
|
||||
#### 2.2 配置HTTP Basic Auth
|
||||
|
||||
```bash
|
||||
# 生成密码文件
|
||||
sudo htpasswd -c /etc/nginx/conf.d/.jenkins-htpasswd admin
|
||||
|
||||
# 或使用openssl
|
||||
sudo openssl passwd -apr1 YOUR_PASSWORD | \
|
||||
sed "s|^|admin:|" | \
|
||||
sudo tee /etc/nginx/conf.d/.jenkins-htpasswd
|
||||
|
||||
# 设置权限
|
||||
sudo chmod 600 /etc/nginx/conf.d/.jenkins-htpasswd
|
||||
sudo chown www-data:www-data /etc/nginx/conf.d/.jenkins-htpasswd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤3:认证授权层
|
||||
|
||||
#### 3.1 配置Jenkins安全设置
|
||||
|
||||
**禁用匿名访问:**
|
||||
|
||||
```bash
|
||||
# 方法1:通过Jenkins UI
|
||||
# 访问:https://your-domain.com/jenkins/configureSecurity
|
||||
# 设置:授权策略 -> 安全矩阵 -> 取消匿名用户的所有权限
|
||||
|
||||
# 方法2:通过配置文件
|
||||
sudo vim /var/lib/jenkins/config.xml
|
||||
```
|
||||
|
||||
```xml
|
||||
<useSecurity>true</useSecurity>
|
||||
<authorizationStrategy class="hudson.security.FullControlOnceLoggedInAuthorizationStrategy">
|
||||
<denyAnonymousReadAccess>true</denyAnonymousReadAccess>
|
||||
</authorizationStrategy>
|
||||
```
|
||||
|
||||
#### 3.2 Webhook签名验证
|
||||
|
||||
**Gitea Webhook配置:**
|
||||
|
||||
1. 进入Gitea仓库设置 -> Webhooks
|
||||
2. 添加Webhook:
|
||||
- 目标URL:`https://your-domain.com/generic-webhook-trigger/invoke`
|
||||
- HTTP方法:POST
|
||||
- 触发条件:Push events
|
||||
- **启用签名验证**
|
||||
- 签名密钥:使用生成的`WEBHOOK_SECRET`
|
||||
|
||||
**Nginx验证配置:**
|
||||
|
||||
```nginx
|
||||
location ~ ^/generic-webhook-trigger(/.*)?$ {
|
||||
# IP白名单
|
||||
allow YOUR_GITEA_SERVER_IP;
|
||||
deny all;
|
||||
|
||||
# 验证签名头
|
||||
if ($http_x_gitea_signature = "") {
|
||||
return 403;
|
||||
}
|
||||
|
||||
proxy_pass http://jenkins_backend;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤4:审计监控层
|
||||
|
||||
#### 4.1 配置审计日志
|
||||
|
||||
**Nginx日志格式:**
|
||||
|
||||
```nginx
|
||||
log_format jenkins_security '$remote_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent" '
|
||||
'request_time=$request_time '
|
||||
'ssl_protocol=$ssl_protocol';
|
||||
|
||||
access_log /var/log/nginx/jenkins-access.log jenkins_security;
|
||||
```
|
||||
|
||||
#### 4.2 日志轮转
|
||||
|
||||
```bash
|
||||
sudo vim /etc/logrotate.d/jenkins-security
|
||||
```
|
||||
|
||||
```
|
||||
/var/log/nginx/jenkins-*.log {
|
||||
daily
|
||||
rotate 90
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 www-data adm
|
||||
sharedscripts
|
||||
postrotate
|
||||
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
|
||||
endscript
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 监控脚本
|
||||
|
||||
```bash
|
||||
# 创建监控脚本
|
||||
sudo vim /usr/local/bin/monitor-jenkins-security.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 监控异常访问
|
||||
|
||||
# 检查失败的认证尝试
|
||||
FAILED_AUTH=$(grep "401" /var/log/nginx/jenkins-access.log | \
|
||||
tail -n 100 | \
|
||||
awk '{print $1}' | \
|
||||
sort | uniq -c | \
|
||||
awk '$1 > 10 {print $2}')
|
||||
|
||||
if [ -n "$FAILED_AUTH" ]; then
|
||||
echo "警告:检测到多次认证失败的IP:"
|
||||
echo "$FAILED_AUTH"
|
||||
# 可以添加自动封禁逻辑
|
||||
fi
|
||||
|
||||
# 检查异常请求
|
||||
grep -E "POST|DELETE|PUT" /var/log/nginx/jenkins-access.log | \
|
||||
tail -n 100 | \
|
||||
grep -v "200\|201" | \
|
||||
awk '{print $1, $7, $9}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证检查清单
|
||||
|
||||
### 自动验证
|
||||
|
||||
```bash
|
||||
# 运行验证脚本
|
||||
sudo /usr/local/bin/verify-jenkins-security.sh
|
||||
```
|
||||
|
||||
### 手动验证清单
|
||||
|
||||
- [ ] **网络层**
|
||||
- [ ] Jenkins仅监听127.0.0.1:8080
|
||||
- [ ] 防火墙已阻止8080端口
|
||||
- [ ] 仅允许Nginx代理访问
|
||||
|
||||
- [ ] **应用层**
|
||||
- [ ] Nginx配置语法正确
|
||||
- [ ] HTTPS强制重定向
|
||||
- [ ] 安全响应头已配置
|
||||
- [ ] 频率限制生效
|
||||
|
||||
- [ ] **认证层**
|
||||
- [ ] HTTP Basic Auth已启用
|
||||
- [ ] 匿名访问已禁用
|
||||
- [ ] Webhook签名验证已启用
|
||||
- [ ] IP白名单已配置
|
||||
|
||||
- [ ] **审计层**
|
||||
- [ ] 访问日志正常记录
|
||||
- [ ] 日志轮转已配置
|
||||
- [ ] 监控脚本运行正常
|
||||
|
||||
- [ ] **配置安全**
|
||||
- [ ] Jenkinsfile中无硬编码token
|
||||
- [ ] 敏感信息已移至环境变量
|
||||
- [ ] Jenkins Credentials已配置
|
||||
|
||||
### 渗透测试
|
||||
|
||||
```bash
|
||||
# 1. 尝试直接访问Jenkins(应失败)
|
||||
curl -I http://YOUR_SERVER_IP:8080
|
||||
|
||||
# 2. 尝试匿名访问(应返回401)
|
||||
curl -I https://your-domain.com/jenkins/
|
||||
|
||||
# 3. 使用错误密码(应返回401)
|
||||
curl -I -u admin:wrongpassword https://your-domain.com/jenkins/
|
||||
|
||||
# 4. 测试频率限制
|
||||
for i in {1..20}; do
|
||||
curl -I https://your-domain.com/jenkins/ &
|
||||
done
|
||||
|
||||
# 5. 测试Webhook签名验证
|
||||
curl -X POST https://your-domain.com/generic-webhook-trigger/invoke \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"test": "data"}'
|
||||
# 应返回403
|
||||
|
||||
# 6. 使用正确签名
|
||||
PAYLOAD='{"ref": "refs/heads/release/test"}'
|
||||
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
|
||||
curl -X POST https://your-domain.com/generic-webhook-trigger/invoke \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Gitea-Signature: sha256=$SIGNATURE" \
|
||||
-d "$PAYLOAD"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 应急响应流程
|
||||
|
||||
### 检测到攻击时的响应
|
||||
|
||||
#### Level 1:可疑活动
|
||||
|
||||
**触发条件:**
|
||||
- 多次认证失败(>10次/分钟)
|
||||
- 异常请求模式
|
||||
- 非白名单IP访问Webhook
|
||||
|
||||
**响应措施:**
|
||||
|
||||
```bash
|
||||
# 1. 记录事件
|
||||
echo "$(date): 可疑活动检测 - IP: $ATTACKER_IP" >> /var/log/jenkins-security-events.log
|
||||
|
||||
# 2. 临时封禁IP
|
||||
sudo ufw deny from $ATTACKER_IP
|
||||
|
||||
# 3. 通知管理员
|
||||
./scripts/notify-wechat.sh "安全警告:检测到可疑访问 - IP: $ATTACKER_IP"
|
||||
```
|
||||
|
||||
#### Level 2:确认攻击
|
||||
|
||||
**触发条件:**
|
||||
- 成功利用漏洞
|
||||
- 恶意代码注入
|
||||
- 数据泄露迹象
|
||||
|
||||
**响应措施:**
|
||||
|
||||
```bash
|
||||
# 1. 立即隔离
|
||||
sudo systemctl stop jenkins
|
||||
sudo ufw deny 443/tcp
|
||||
|
||||
# 2. 保存证据
|
||||
sudo tar -czf /tmp/incident-$(date +%Y%m%d_%H%M%S).tar.gz \
|
||||
/var/lib/jenkins \
|
||||
/var/log/nginx/jenkins-*.log \
|
||||
/var/log/jenkins-security-events.log
|
||||
|
||||
# 3. 检查完整性
|
||||
find /var/lib/jenkins -type f -mtime -1 -ls
|
||||
|
||||
# 4. 通知管理层
|
||||
./scripts/notify-wechat.sh "严重安全事件:Jenkins遭受攻击,已隔离系统"
|
||||
```
|
||||
|
||||
#### Level 3:数据泄露
|
||||
|
||||
**触发条件:**
|
||||
- 凭证被窃取
|
||||
- 生产数据泄露
|
||||
- 系统被完全控制
|
||||
|
||||
**响应措施:**
|
||||
|
||||
```bash
|
||||
# 1. 完全断网
|
||||
sudo ifdown eth0
|
||||
|
||||
# 2. 备份现场
|
||||
sudo dd if=/dev/sda of=/backup/incident-disk-image.img
|
||||
|
||||
# 3. 更换所有凭证
|
||||
# - Jenkins管理员密码
|
||||
# - Webhook Token
|
||||
# - SSH密钥
|
||||
# - 数据库密码
|
||||
# - API密钥
|
||||
|
||||
# 4. 通知所有相关方
|
||||
# - 管理层
|
||||
# - 安全团队
|
||||
# - 客户(如涉及客户数据)
|
||||
|
||||
# 5. 启动事件响应计划
|
||||
```
|
||||
|
||||
### 恢复流程
|
||||
|
||||
```bash
|
||||
# 1. 从干净备份恢复
|
||||
sudo rm -rf /var/lib/jenkins
|
||||
sudo tar -xzf /backup/jenkins-clean-backup.tar.gz -C /
|
||||
|
||||
# 2. 应用所有安全补丁
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# 3. 重新配置安全设置
|
||||
sudo ./scripts/security/jenkins-security-hardening.sh
|
||||
|
||||
# 4. 全面验证
|
||||
sudo /usr/local/bin/verify-jenkins-security.sh
|
||||
|
||||
# 5. 逐步恢复服务
|
||||
sudo systemctl start jenkins
|
||||
# 监控日志
|
||||
tail -f /var/log/nginx/jenkins-access.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 长期维护建议
|
||||
|
||||
### 定期安全审计
|
||||
|
||||
**每日:**
|
||||
- 检查访问日志异常
|
||||
- 监控失败认证次数
|
||||
- 检查系统资源使用
|
||||
|
||||
**每周:**
|
||||
- 审查用户权限
|
||||
- 检查插件更新
|
||||
- 分析安全日志
|
||||
|
||||
**每月:**
|
||||
- 更新Jenkins和插件
|
||||
- 更换敏感凭证
|
||||
- 进行渗透测试
|
||||
|
||||
**每季度:**
|
||||
- 全面安全评估
|
||||
- 灾难恢复演练
|
||||
- 安全培训
|
||||
|
||||
### 自动化监控
|
||||
|
||||
```bash
|
||||
# 添加到crontab
|
||||
crontab -e
|
||||
```
|
||||
|
||||
```cron
|
||||
# 每小时检查异常访问
|
||||
0 * * * * /usr/local/bin/monitor-jenkins-security.sh
|
||||
|
||||
# 每天备份配置
|
||||
0 2 * * * tar -czf /backup/jenkins-config-$(date +\%Y\%m\%d).tar.gz /var/lib/jenkins
|
||||
|
||||
# 每周更新检查
|
||||
0 3 * * 0 apt update && apt list --upgradable | grep jenkins
|
||||
|
||||
# 每月更换Webhook Token
|
||||
0 4 1 * * /usr/local/bin/rotate-jenkins-secrets.sh
|
||||
```
|
||||
|
||||
### 安全改进路线图
|
||||
|
||||
**Phase 1(当前):基础防护**
|
||||
- ✅ 网络隔离
|
||||
- ✅ HTTP Basic Auth
|
||||
- ✅ Webhook签名验证
|
||||
|
||||
**Phase 2(1个月内):增强认证**
|
||||
- 🔲 集成OAuth2/OIDC
|
||||
- 🔲 多因素认证(MFA)
|
||||
- 🔲 细粒度权限控制
|
||||
|
||||
**Phase 3(3个月内):高级防护**
|
||||
- 🔲 Web应用防火墙(WAF)
|
||||
- 🔲 入侵检测系统(IDS)
|
||||
- 🔲 安全信息和事件管理(SIEM)
|
||||
|
||||
**Phase 4(6个月内):零信任架构**
|
||||
- 🔲 零信任网络访问(ZTNA)
|
||||
- 🔲 微服务隔离
|
||||
- 🔲 持续安全验证
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
**安全负责人:** 张翔
|
||||
**应急响应:** security@your-domain.com
|
||||
**技术支持:** devops@your-domain.com
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
- [Jenkins Security Best Practices](https://www.jenkins.io/doc/book/security/)
|
||||
- [OWASP CI/CD Security Guide](https://owasp.org/www-project-devsecops-guideline/)
|
||||
- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)
|
||||
- [Jenkins Security Advisory](https://www.jenkins.io/security/advisories/)
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2026-04-07
|
||||
**文档版本:** 1.0
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 修复Jenkins Nginx配置
|
||||
cat > /tmp/jenkins-nginx-fix.conf << 'EOF'
|
||||
# Jenkins CI/CD Server
|
||||
server {
|
||||
listen 80;
|
||||
server_name ci.f.novalon.cn;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ci.f.novalon.cn;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/ci.f.novalon.cn/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/ci.f.novalon.cn/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
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;
|
||||
|
||||
# Jenkins webhook端点 - 不需要/jenkins前缀
|
||||
location /generic-webhook-trigger/ {
|
||||
proxy_pass http://172.17.0.1:8080/generic-webhook-trigger/;
|
||||
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;
|
||||
client_max_body_size 100m;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# Jenkins主应用
|
||||
location /jenkins/ {
|
||||
proxy_pass http://172.17.0.1:8080/jenkins/;
|
||||
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;
|
||||
client_max_body_size 100m;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# 默认location - 重定向到/jenkins/
|
||||
location / {
|
||||
return 301 https://$host/jenkins/;
|
||||
}
|
||||
|
||||
access_log /var/log/nginx/jenkins-access.log;
|
||||
error_log /var/log/nginx/jenkins-error.log;
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Jenkins Nginx配置已生成"
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version='1.1' encoding='UTF-8'?>
|
||||
<flow-definition plugin="workflow-job@1571.vb_423c255d6d9">
|
||||
<description>novalon-website CI/CD Pipeline</description>
|
||||
<keepDependencies>false</keepDependencies>
|
||||
<properties>
|
||||
<org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty>
|
||||
<abortPrevious>false</abortPrevious>
|
||||
</org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty>
|
||||
<org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
|
||||
<triggers>
|
||||
<hudson.triggers.SCMTrigger>
|
||||
<spec>H/5 * * * *</spec>
|
||||
<ignorePostCommitHooks>false</ignorePostCommitHooks>
|
||||
</hudson.triggers.SCMTrigger>
|
||||
</triggers>
|
||||
</org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
|
||||
</properties>
|
||||
<definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition" plugin="workflow-cps@4275.vb_0565eb_a_3d36">
|
||||
<scm class="hudson.plugins.git.GitSCM" plugin="git@5.10.1">
|
||||
<configVersion>2</configVersion>
|
||||
<userRemoteConfigs>
|
||||
<hudson.plugins.git.UserRemoteConfig>
|
||||
<url>git@gitea.novalon.cn:novalon/novalon-website.git</url>
|
||||
</hudson.plugins.git.UserRemoteConfig>
|
||||
</userRemoteConfigs>
|
||||
<branches>
|
||||
<hudson.plugins.git.BranchSpec>
|
||||
<name>*/release/*</name>
|
||||
</hudson.plugins.git.BranchSpec>
|
||||
</branches>
|
||||
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
|
||||
<submoduleCfg class="empty-list"/>
|
||||
<extensions/>
|
||||
</scm>
|
||||
<scriptPath>Jenkinsfile</scriptPath>
|
||||
<lightweight>true</lightweight>
|
||||
</definition>
|
||||
<disabled>false</disabled>
|
||||
</flow-definition>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version='1.1' encoding='UTF-8'?>
|
||||
<flow-definition plugin="workflow-job@1571.vb_423c255d6d9">
|
||||
<description>novalon-website CI/CD Pipeline</description>
|
||||
<keepDependencies>false</keepDependencies>
|
||||
<properties>
|
||||
<org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty>
|
||||
<abortPrevious>false</abortPrevious>
|
||||
</org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty>
|
||||
<org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
|
||||
<triggers>
|
||||
<org.jenkinsci.plugins.gwt.GenericWebhookTrigger plugin="generic-webhook-trigger@2.4.1">
|
||||
<spec></spec>
|
||||
<regexpFilterText>$ref</regexpFilterText>
|
||||
<regexpFilterExpression>^refs/heads/release/.*$</regexpFilterExpression>
|
||||
<genericHeaderVariables>
|
||||
<org.jenkinsci.plugins.gwt.GenericHeaderVariable>
|
||||
<key>X-Gitea-Event</key>
|
||||
<regexpFilter></regexpFilter>
|
||||
</org.jenkinsci.plugins.gwt.GenericHeaderVariable>
|
||||
</genericHeaderVariables>
|
||||
<genericRequestVariables>
|
||||
<org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
<key>ref</key>
|
||||
<regexpFilter></regexpFilter>
|
||||
</org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
<org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
<key>repository.name</key>
|
||||
<regexpFilter></regexpFilter>
|
||||
</org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
</genericRequestVariables>
|
||||
<printPostContent>true</printPostContent>
|
||||
<printContributedVariables>true</printContributedVariables>
|
||||
<causeString>Gitea Webhook Trigger: $ref</causeString>
|
||||
<token>novalon-website-webhook-token-2024</token>
|
||||
<silentResponse>false</silentResponse>
|
||||
<shouldNotFlattern>false</shouldNotFlattern>
|
||||
</org.jenkinsci.plugins.gwt.GenericWebhookTrigger>
|
||||
</triggers>
|
||||
</org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
|
||||
</properties>
|
||||
<definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition" plugin="workflow-cps@4275.vb_0565eb_a_3d36">
|
||||
<scm class="hudson.plugins.git.GitSCM" plugin="git@5.10.1">
|
||||
<configVersion>2</configVersion>
|
||||
<userRemoteConfigs>
|
||||
<hudson.plugins.git.UserRemoteConfig>
|
||||
<url>git@gitea.novalon.cn:novalon/novalon-website.git</url>
|
||||
</hudson.plugins.git.UserRemoteConfig>
|
||||
</userRemoteConfigs>
|
||||
<branches>
|
||||
<hudson.plugins.git.BranchSpec>
|
||||
<name>*/release/*</name>
|
||||
</hudson.plugins.git.BranchSpec>
|
||||
</branches>
|
||||
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
|
||||
<submoduleCfg class="empty-list"/>
|
||||
<extensions/>
|
||||
</scm>
|
||||
<scriptPath>Jenkinsfile</scriptPath>
|
||||
<lightweight>true</lightweight>
|
||||
</definition>
|
||||
<disabled>false</disabled>
|
||||
</flow-definition>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version='1.1' encoding='UTF-8'?>
|
||||
<flow-definition plugin="workflow-job@1571.vb_423c255d6d9">
|
||||
<actions/>
|
||||
<description>novalon-website CI/CD Pipeline</description>
|
||||
<keepDependencies>false</keepDependencies>
|
||||
<properties>
|
||||
<org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty>
|
||||
<abortPrevious>false</abortPrevious>
|
||||
</org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty>
|
||||
<org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
|
||||
<triggers>
|
||||
<org.jenkinsci.plugins.gwt.GenericWebhookTrigger plugin="generic-webhook-trigger@2.4.1">
|
||||
<spec></spec>
|
||||
<regexpFilterText>$ref</regexpFilterText>
|
||||
<regexpFilterExpression>^refs/heads/release/.*$</regexpFilterExpression>
|
||||
<genericHeaderVariables>
|
||||
<org.jenkinsci.plugins.gwt.GenericHeaderVariable>
|
||||
<key>X-Gitea-Event</key>
|
||||
<regexpFilter></regexpFilter>
|
||||
</org.jenkinsci.plugins.gwt.GenericHeaderVariable>
|
||||
</genericHeaderVariables>
|
||||
<genericRequestVariables>
|
||||
<org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
<key>ref</key>
|
||||
<regexpFilter></regexpFilter>
|
||||
</org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
<org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
<key>repository.name</key>
|
||||
<regexpFilter></regexpFilter>
|
||||
</org.jenkinsci.plugins.gwt.GenericRequestVariable>
|
||||
</genericRequestVariables>
|
||||
<printPostContent>true</printPostContent>
|
||||
<printContributedVariables>true</printContributedVariables>
|
||||
<causeString>Gitea Webhook Trigger: $ref</causeString>
|
||||
<token>novalon-website-webhook-token-2024</token>
|
||||
<silentResponse>false</silentResponse>
|
||||
<shouldNotFlattern>false</shouldNotFlattern>
|
||||
</org.jenkinsci.plugins.gwt.GenericWebhookTrigger>
|
||||
</triggers>
|
||||
</org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
|
||||
</properties>
|
||||
<definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition" plugin="workflow-cps@4275.vb_0565eb_a_3d36">
|
||||
<scm class="hudson.plugins.git.GitSCM" plugin="git@5.10.1">
|
||||
<configVersion>2</configVersion>
|
||||
<userRemoteConfigs>
|
||||
<hudson.plugins.git.UserRemoteConfig>
|
||||
<url>git@gitea.novalon.cn:novalon/novalon-website.git</url>
|
||||
</hudson.plugins.git.UserRemoteConfig>
|
||||
</userRemoteConfigs>
|
||||
<branches>
|
||||
<hudson.plugins.git.BranchSpec>
|
||||
<name>*/release/*</name>
|
||||
</hudson.plugins.git.BranchSpec>
|
||||
</branches>
|
||||
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
|
||||
<submoduleCfg class="empty-list"/>
|
||||
<extensions/>
|
||||
</scm>
|
||||
<scriptPath>Jenkinsfile</scriptPath>
|
||||
<lightweight>true</lightweight>
|
||||
</definition>
|
||||
<disabled>false</disabled>
|
||||
</flow-definition>
|
||||
@@ -0,0 +1,77 @@
|
||||
# Jenkins安全配置环境变量示例
|
||||
# 作者:张翔
|
||||
# 日期:2026-04-07
|
||||
# 说明:复制此文件为 .env.jenkins.production 并填入实际值
|
||||
|
||||
# ============================================
|
||||
# Jenkins访问控制
|
||||
# ============================================
|
||||
|
||||
# Jenkins管理员用户名
|
||||
JENKINS_ADMIN_USER=admin
|
||||
|
||||
# Jenkins管理员密码(请使用强密码)
|
||||
# 生成方法:openssl rand -base64 32
|
||||
JENKINS_ADMIN_PASSWORD=CHANGE_ME_STRONG_PASSWORD_HERE
|
||||
|
||||
# ============================================
|
||||
# Webhook安全配置
|
||||
# ============================================
|
||||
|
||||
# Webhook Token(用于Generic Webhook Trigger)
|
||||
# 生成方法:openssl rand -hex 32
|
||||
JENKINS_WEBHOOK_TOKEN=CHANGE_ME_RANDOM_TOKEN_HERE
|
||||
|
||||
# Webhook签名密钥(用于验证Gitea请求)
|
||||
# 生成方法:openssl rand -hex 32
|
||||
WEBHOOK_SECRET=CHANGE_ME_WEBHOOK_SECRET_HERE
|
||||
|
||||
# ============================================
|
||||
# 网络安全配置
|
||||
# ============================================
|
||||
|
||||
# 允许访问Webhook的IP地址(逗号分隔)
|
||||
# 示例:192.168.1.100,10.0.0.50
|
||||
ALLOWED_IPS=127.0.0.1
|
||||
|
||||
# Jenkins域名
|
||||
DOMAIN=your-domain.com
|
||||
|
||||
# ============================================
|
||||
# SSL/TLS配置
|
||||
# ============================================
|
||||
|
||||
# SSL证书路径
|
||||
SSL_CERT_PATH=/etc/letsencrypt/live/your-domain.com/fullchain.pem
|
||||
SSL_KEY_PATH=/etc/letsencrypt/live/your-domain.com/privkey.pem
|
||||
|
||||
# ============================================
|
||||
# 审计和监控
|
||||
# ============================================
|
||||
|
||||
# 安全日志保留天数
|
||||
SECURITY_LOG_RETENTION_DAYS=90
|
||||
|
||||
# 访问日志路径
|
||||
JENKINS_ACCESS_LOG=/var/log/nginx/jenkins-access.log
|
||||
JENKINS_ERROR_LOG=/var/log/nginx/jenkins-error.log
|
||||
|
||||
# ============================================
|
||||
# 频率限制
|
||||
# ============================================
|
||||
|
||||
# 每分钟最大请求数
|
||||
RATE_LIMIT_REQUESTS=10
|
||||
|
||||
# 并发连接数限制
|
||||
CONNECTION_LIMIT=10
|
||||
|
||||
# ============================================
|
||||
# 备份配置
|
||||
# ============================================
|
||||
|
||||
# 备份目录
|
||||
BACKUP_DIR=/backup/jenkins
|
||||
|
||||
# 备份保留天数
|
||||
BACKUP_RETENTION_DAYS=30
|
||||
@@ -0,0 +1,371 @@
|
||||
# Jenkins安全加固快速部署指南
|
||||
|
||||
**作者:** 张翔
|
||||
**日期:** 2026-04-07
|
||||
**紧急程度:** 🔴 立即执行
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 5分钟快速响应
|
||||
|
||||
### 情况紧急?立即执行以下命令
|
||||
|
||||
```bash
|
||||
# 1. 阻止外部访问8080端口
|
||||
sudo ufw deny 8080/tcp && sudo ufw --force reload
|
||||
|
||||
# 2. 修改Jenkins监听地址
|
||||
sudo sed -i 's|httpPort=8080|httpPort=8080 --httpListenAddress=127.0.0.1|' /etc/default/jenkins
|
||||
sudo systemctl restart jenkins
|
||||
|
||||
# 3. 验证
|
||||
sudo netstat -tlnp | grep 8080
|
||||
# 应显示:127.0.0.1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 完整部署流程(30分钟)
|
||||
|
||||
### 前置准备
|
||||
|
||||
```bash
|
||||
# 1. 克隆或进入项目目录
|
||||
cd /path/to/novalon-website
|
||||
|
||||
# 2. 检查当前状态
|
||||
sudo netstat -tlnp | grep 8080
|
||||
curl -I http://localhost:8080
|
||||
```
|
||||
|
||||
### 步骤1:配置环境变量
|
||||
|
||||
```bash
|
||||
# 1. 复制环境变量模板
|
||||
cp scripts/security/.env.jenkins.example scripts/security/.env.jenkins.production
|
||||
|
||||
# 2. 编辑配置文件
|
||||
vim scripts/security/.env.jenkins.production
|
||||
|
||||
# 3. 生成随机密钥
|
||||
# Webhook Token
|
||||
openssl rand -hex 32
|
||||
# 将输出复制到 JENKINS_WEBHOOK_TOKEN
|
||||
|
||||
# Webhook Secret
|
||||
openssl rand -hex 32
|
||||
# 将输出复制到 WEBHOOK_SECRET
|
||||
|
||||
# 管理员密码
|
||||
openssl rand -base64 32
|
||||
# 将输出复制到 JENKINS_ADMIN_PASSWORD
|
||||
```
|
||||
|
||||
### 步骤2:配置Jenkins Credentials
|
||||
|
||||
```bash
|
||||
# 方法1:通过Jenkins UI
|
||||
# 访问:https://your-domain.com/jenkins/credentials/store/system/domain/_/
|
||||
# 添加Secret text:
|
||||
# ID: jenkins-webhook-token
|
||||
# Secret: [步骤1生成的token]
|
||||
|
||||
# 方法2:通过Jenkins CLI
|
||||
java -jar jenkins-cli.jar -s http://localhost:8080/ create-credentials-by-xml system::system::jenkins << EOF
|
||||
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
|
||||
<scope>GLOBAL</scope>
|
||||
<id>jenkins-webhook-token</id>
|
||||
<description>Jenkins Webhook Token</description>
|
||||
<username></username>
|
||||
<password>${JENKINS_WEBHOOK_TOKEN}</password>
|
||||
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
|
||||
EOF
|
||||
```
|
||||
|
||||
### 步骤3:运行安全加固脚本
|
||||
|
||||
```bash
|
||||
# 1. 设置权限
|
||||
chmod +x scripts/security/jenkins-security-hardening.sh
|
||||
|
||||
# 2. 加载环境变量
|
||||
export $(cat scripts/security/.env.jenkins.production | xargs)
|
||||
|
||||
# 3. 运行脚本
|
||||
sudo -E ./scripts/security/jenkins-security-hardening.sh
|
||||
|
||||
# 按照提示输入:
|
||||
# - 管理员密码
|
||||
# - 是否立即重启服务
|
||||
```
|
||||
|
||||
### 步骤4:配置SSL证书(如未配置)
|
||||
|
||||
```bash
|
||||
# 使用Let's Encrypt
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
|
||||
# 或使用已有证书
|
||||
sudo mkdir -p /etc/letsencrypt/live/your-domain.com
|
||||
sudo cp your-cert.pem /etc/letsencrypt/live/your-domain.com/fullchain.pem
|
||||
sudo cp your-key.pem /etc/letsencrypt/live/your-domain.com/privkey.pem
|
||||
```
|
||||
|
||||
### 步骤5:配置Gitea Webhook
|
||||
|
||||
```bash
|
||||
# 1. 进入Gitea仓库设置
|
||||
# Settings -> Webhooks -> Add Webhook
|
||||
|
||||
# 2. 配置Webhook
|
||||
# 目标URL: https://your-domain.com/generic-webhook-trigger/invoke
|
||||
# HTTP方法: POST
|
||||
# 触发条件: Push events
|
||||
# 启用签名验证: 是
|
||||
# 签名密钥: [步骤1生成的WEBHOOK_SECRET]
|
||||
|
||||
# 3. 测试Webhook
|
||||
# 点击"Test Delivery"按钮
|
||||
```
|
||||
|
||||
### 步骤6:验证安全配置
|
||||
|
||||
```bash
|
||||
# 1. 运行自动验证
|
||||
sudo /usr/local/bin/verify-jenkins-security.sh
|
||||
|
||||
# 2. 手动测试
|
||||
# 测试1:直接访问8080端口(应失败)
|
||||
curl -I http://YOUR_SERVER_IP:8080
|
||||
|
||||
# 测试2:匿名访问(应返回401)
|
||||
curl -I https://your-domain.com/jenkins/
|
||||
|
||||
# 测试3:认证访问(应成功)
|
||||
curl -I -u admin:YOUR_PASSWORD https://your-domain.com/jenkins/
|
||||
|
||||
# 测试4:Webhook签名验证
|
||||
PAYLOAD='{"ref": "refs/heads/release/test"}'
|
||||
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
|
||||
curl -X POST https://your-domain.com/generic-webhook-trigger/invoke \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Gitea-Signature: sha256=$SIGNATURE" \
|
||||
-d "$PAYLOAD"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 文件清单
|
||||
|
||||
```
|
||||
scripts/security/
|
||||
├── jenkins-security-hardening.sh # 主加固脚本
|
||||
├── .env.jenkins.example # 环境变量模板
|
||||
└── README.md # 本文档
|
||||
|
||||
docs/security/
|
||||
└── JENKINS_SECURITY_HARDENING_GUIDE.md # 详细安全指南
|
||||
|
||||
Jenkinsfile # 已更新(移除硬编码token)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 验证检查清单
|
||||
|
||||
执行以下命令确认所有配置正确:
|
||||
|
||||
```bash
|
||||
# ✅ Jenkins仅监听127.0.0.1
|
||||
sudo netstat -tlnp | grep 8080
|
||||
# 预期:127.0.0.1:8080
|
||||
|
||||
# ✅ 防火墙已阻止8080
|
||||
sudo ufw status | grep 8080
|
||||
# 预期:8080/tcp DENY
|
||||
|
||||
# ✅ Nginx配置正确
|
||||
sudo nginx -t
|
||||
# 预期:test is successful
|
||||
|
||||
# ✅ HTTP Basic Auth已配置
|
||||
ls -la /etc/nginx/conf.d/.jenkins-htpasswd
|
||||
# 预期:文件存在且权限为600
|
||||
|
||||
# ✅ Jenkinsfile无硬编码token
|
||||
grep -r "token.*=.*['\"].*['\"]" Jenkinsfile
|
||||
# 预期:无输出
|
||||
|
||||
# ✅ SSL证书有效
|
||||
openssl s_client -connect your-domain.com:443 -servername your-domain.com 2>/dev/null | openssl x509 -noout -dates
|
||||
# 预期:显示证书有效期
|
||||
|
||||
# ✅ 服务运行正常
|
||||
sudo systemctl status jenkins nginx
|
||||
# 预期:active (running)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 常见问题
|
||||
|
||||
### Q1: 脚本执行失败
|
||||
|
||||
**问题:** `permission denied`
|
||||
|
||||
**解决:**
|
||||
```bash
|
||||
chmod +x scripts/security/jenkins-security-hardening.sh
|
||||
sudo ./scripts/security/jenkins-security-hardening.sh
|
||||
```
|
||||
|
||||
### Q2: Jenkins无法启动
|
||||
|
||||
**问题:** 修改监听地址后Jenkins无法启动
|
||||
|
||||
**解决:**
|
||||
```bash
|
||||
# 检查配置文件
|
||||
cat /etc/default/jenkins | grep JENKINS_ARGS
|
||||
|
||||
# 恢复备份
|
||||
sudo cp /tmp/jenkins-security-backup-*/jenkins-default.bak /etc/default/jenkins
|
||||
sudo systemctl restart jenkins
|
||||
```
|
||||
|
||||
### Q3: Nginx配置错误
|
||||
|
||||
**问题:** `nginx: [emerg] unknown directive`
|
||||
|
||||
**解决:**
|
||||
```bash
|
||||
# 检查Nginx版本
|
||||
nginx -v
|
||||
|
||||
# 确保版本 >= 1.18
|
||||
sudo apt update && sudo apt upgrade nginx
|
||||
|
||||
# 验证配置
|
||||
sudo nginx -t
|
||||
```
|
||||
|
||||
### Q4: Webhook触发失败
|
||||
|
||||
**问题:** Webhook返回403
|
||||
|
||||
**解决:**
|
||||
```bash
|
||||
# 检查IP白名单
|
||||
grep "allow" /etc/nginx/conf.d/jenkins-security.conf
|
||||
|
||||
# 检查签名验证
|
||||
# 确保Gitea配置的签名密钥与WEBHOOK_SECRET一致
|
||||
|
||||
# 查看Nginx错误日志
|
||||
tail -f /var/log/nginx/jenkins-error.log
|
||||
```
|
||||
|
||||
### Q5: 认证失败
|
||||
|
||||
**问题:** HTTP Basic Auth无法登录
|
||||
|
||||
**解决:**
|
||||
```bash
|
||||
# 重新生成密码文件
|
||||
sudo htpasswd -c /etc/nginx/conf.d/.jenkins-htpasswd admin
|
||||
|
||||
# 重启Nginx
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 安全监控
|
||||
|
||||
### 设置定时监控
|
||||
|
||||
```bash
|
||||
# 添加到crontab
|
||||
crontab -e
|
||||
```
|
||||
|
||||
```cron
|
||||
# 每小时检查异常访问
|
||||
0 * * * * /usr/local/bin/monitor-jenkins-security.sh >> /var/log/jenkins-security-monitor.log 2>&1
|
||||
|
||||
# 每天备份配置
|
||||
0 2 * * * tar -czf /backup/jenkins-config-$(date +\%Y\%m\%d).tar.gz /var/lib/jenkins
|
||||
|
||||
# 每周发送安全报告
|
||||
0 9 * * 1 /usr/local/bin/jenkins-security-report.sh | mail -s "Jenkins Security Report" admin@your-domain.com
|
||||
```
|
||||
|
||||
### 查看实时日志
|
||||
|
||||
```bash
|
||||
# 监控访问日志
|
||||
tail -f /var/log/nginx/jenkins-access.log
|
||||
|
||||
# 监控错误日志
|
||||
tail -f /var/log/nginx/jenkins-error.log
|
||||
|
||||
# 监控Jenkins日志
|
||||
sudo journalctl -u jenkins -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 回滚方案
|
||||
|
||||
如果出现问题,可以快速回滚:
|
||||
|
||||
```bash
|
||||
# 1. 恢复Jenkins配置
|
||||
sudo cp /tmp/jenkins-security-backup-*/jenkins-default.bak /etc/default/jenkins
|
||||
|
||||
# 2. 恢复Nginx配置
|
||||
sudo rm /etc/nginx/conf.d/jenkins-security.conf
|
||||
sudo cp -r /tmp/jenkins-security-backup-*/nginx-conf/* /etc/nginx/conf.d/
|
||||
|
||||
# 3. 重启服务
|
||||
sudo systemctl restart jenkins nginx
|
||||
|
||||
# 4. 恢复防火墙规则
|
||||
sudo ufw allow 8080/tcp
|
||||
sudo ufw --force reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
**文档:**
|
||||
- [完整安全指南](./JENKINS_SECURITY_HARDENING_GUIDE.md)
|
||||
- [Jenkins官方安全文档](https://www.jenkins.io/doc/book/security/)
|
||||
|
||||
**应急联系:**
|
||||
- 安全负责人:张翔
|
||||
- 技术支持:devops@your-domain.com
|
||||
|
||||
---
|
||||
|
||||
## ✅ 部署后确认
|
||||
|
||||
完成所有步骤后,确认以下事项:
|
||||
|
||||
- [ ] Jenkins仅监听127.0.0.1:8080
|
||||
- [ ] 防火墙已阻止外部访问8080
|
||||
- [ ] Nginx反向代理正常工作
|
||||
- [ ] HTTP Basic Auth认证生效
|
||||
- [ ] Webhook签名验证通过
|
||||
- [ ] SSL证书有效
|
||||
- [ ] 所有日志正常记录
|
||||
- [ ] 监控脚本运行正常
|
||||
- [ ] 备份策略已配置
|
||||
- [ ] 团队成员已通知
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2026-04-07
|
||||
**文档版本:** 1.0
|
||||
@@ -0,0 +1,544 @@
|
||||
#!/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"
|
||||
}
|
||||
|
||||
# 检查1:Jenkins是否仅监听127.0.0.1
|
||||
echo "检查1:Jenkins监听地址"
|
||||
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
|
||||
|
||||
# 检查3:Nginx配置是否正确
|
||||
echo ""
|
||||
echo "检查3:Nginx配置"
|
||||
if nginx -t 2>/dev/null; then
|
||||
check_pass "Nginx配置语法正确"
|
||||
else
|
||||
check_fail "Nginx配置存在错误"
|
||||
fi
|
||||
|
||||
# 检查4:HTTPS是否启用
|
||||
echo ""
|
||||
echo "检查4:HTTPS配置"
|
||||
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
|
||||
|
||||
# 检查6:HTTP Basic Auth
|
||||
echo ""
|
||||
echo "检查6:HTTP Basic Auth"
|
||||
if [ -f "/etc/nginx/conf.d/.jenkins-htpasswd" ]; then
|
||||
check_pass "HTTP Basic Auth密码文件存在"
|
||||
else
|
||||
check_fail "HTTP Basic Auth密码文件不存在"
|
||||
fi
|
||||
|
||||
# 检查7:Jenkinsfile中是否还有硬编码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 "======================================================================"
|
||||
@@ -102,6 +102,7 @@ export const mockLucideReact = () => {
|
||||
Calendar: () => <span data-testid="calendar-icon" />,
|
||||
Quote: () => <span data-testid="quote-icon" />,
|
||||
User: () => <span data-testid="user-icon" />,
|
||||
Users: () => <span data-testid="users-icon" />,
|
||||
Lock: () => <span data-testid="lock-icon" />,
|
||||
Eye: () => <span data-testid="eye-icon" />,
|
||||
EyeOff: () => <span data-testid="eye-off-icon" />,
|
||||
@@ -129,6 +130,10 @@ export const mockLucideReact = () => {
|
||||
ChevronUp: () => <span data-testid="chevron-up" />,
|
||||
ExternalLink: () => <span data-testid="external-link-icon" />,
|
||||
TrendingUp: () => <span data-testid="trending-up-icon" />,
|
||||
Target: () => <span data-testid="target-icon" />,
|
||||
MessageCircle: () => <span data-testid="message-circle-icon" />,
|
||||
Layers: () => <span data-testid="layers-icon" />,
|
||||
CreditCard: () => <span data-testid="credit-card-icon" />,
|
||||
Code: () => <span data-testid="code-icon" />,
|
||||
Cloud: () => <span data-testid="cloud-icon" />,
|
||||
BarChart3: () => <span data-testid="bar-chart-icon" />,
|
||||
|
||||
@@ -26,9 +26,11 @@ jest.mock('@/db', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href }: { children: React.ReactNode; href: string }) => {
|
||||
const MockLink = ({ children, href }: { children: React.ReactNode; href: string }) => {
|
||||
return <a href={href}>{children}</a>;
|
||||
};
|
||||
MockLink.displayName = 'MockLink';
|
||||
return MockLink;
|
||||
});
|
||||
|
||||
describe('AdminDashboard', () => {
|
||||
|
||||
@@ -2,6 +2,18 @@ import { POST, setSecurityMiddleware } from './route';
|
||||
import { NextRequest } from 'next/server';
|
||||
import { generateCaptcha } from '@/lib/security/captcha';
|
||||
import { SecurityMiddleware } from '@/lib/security/middleware';
|
||||
import Resend from 'resend';
|
||||
|
||||
interface MockResponse {
|
||||
status: number;
|
||||
json(): Promise<unknown>;
|
||||
}
|
||||
|
||||
interface MockSend {
|
||||
mockResolvedValue: (value: unknown) => void;
|
||||
mockClear: () => void;
|
||||
toHaveBeenCalled: () => boolean;
|
||||
}
|
||||
|
||||
if (!global.Response) {
|
||||
global.Response = class Response {
|
||||
@@ -14,12 +26,12 @@ if (!global.Response) {
|
||||
async json() {
|
||||
return JSON.parse(this._body);
|
||||
}
|
||||
} as any;
|
||||
} as unknown as typeof global.Response;
|
||||
}
|
||||
|
||||
if (!(global.Response as any).json) {
|
||||
(global.Response as any).json = function(data: any, init?: { status?: number }) {
|
||||
return new Response(JSON.stringify(data), init);
|
||||
if (!(global.Response as unknown as { json?: unknown }).json) {
|
||||
(global.Response as unknown as { json: (data: unknown, init?: { status?: number }) => MockResponse }).json = function(data: unknown, init?: { status?: number }) {
|
||||
return new Response(JSON.stringify(data), init) as unknown as MockResponse;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,19 +54,24 @@ jest.mock('resend', () => {
|
||||
|
||||
describe('/api/contact', () => {
|
||||
let mockRequest: NextRequest;
|
||||
let mockSend: any;
|
||||
let mockSend: MockSend;
|
||||
|
||||
beforeEach(() => {
|
||||
const { default: Resend } = require('resend');
|
||||
const resendInstance = new Resend();
|
||||
mockSend = resendInstance.emails.send;
|
||||
process.env.RESEND_API_KEY = 'test-api-key';
|
||||
|
||||
const resendInstance = new Resend('test-key');
|
||||
mockSend = resendInstance.emails.send as unknown as MockSend;
|
||||
mockSend.mockClear();
|
||||
|
||||
const securityMiddleware = new SecurityMiddleware();
|
||||
setSecurityMiddleware(securityMiddleware);
|
||||
});
|
||||
|
||||
const createMockRequest = (body: any, ip: string = '192.168.1.1'): NextRequest => {
|
||||
afterEach(() => {
|
||||
delete process.env.RESEND_API_KEY;
|
||||
});
|
||||
|
||||
const createMockRequest = (body: Record<string, unknown>, ip: string = '192.168.1.1'): NextRequest => {
|
||||
const headers = new Headers();
|
||||
headers.set('x-forwarded-for', ip);
|
||||
headers.set('user-agent', 'test-agent');
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 修复Jenkins Nginx配置 - 更新webhook路径
|
||||
|
||||
# 在服务器上执行此脚本
|
||||
|
||||
# 1. 备份当前配置
|
||||
docker cp novalon-nginx-secure:/etc/nginx/nginx.conf /tmp/nginx.conf.bak
|
||||
|
||||
# 2. 创建新的Jenkins配置
|
||||
cat > /tmp/jenkins-server.conf << 'EOF'
|
||||
# Jenkins CI/CD Server
|
||||
server {
|
||||
listen 80;
|
||||
server_name ci.f.novalon.cn;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ci.f.novalon.cn;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/ci.f.novalon.cn/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/ci.f.novalon.cn/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
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;
|
||||
|
||||
# Jenkins webhook端点 - 直接代理到Jenkins根路径
|
||||
location /generic-webhook-trigger/ {
|
||||
proxy_pass http://172.17.0.1:8080/generic-webhook-trigger/;
|
||||
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;
|
||||
client_max_body_size 100m;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# Jenkins主应用
|
||||
location /jenkins/ {
|
||||
proxy_pass http://172.17.0.1:8080/jenkins/;
|
||||
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;
|
||||
client_max_body_size 100m;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# 默认location - 重定向到/jenkins/
|
||||
location / {
|
||||
return 301 https://$host/jenkins/;
|
||||
}
|
||||
|
||||
access_log /var/log/nginx/jenkins-access.log;
|
||||
error_log /var/log/nginx/jenkins-error.log;
|
||||
}
|
||||
EOF
|
||||
|
||||
# 3. 替换Jenkins配置部分
|
||||
sed -i '/# Jenkins CI\/CD Server/,/^ }$/d' /tmp/nginx.conf.bak
|
||||
sed -i "/^}/i $(cat /tmp/jenkins-server.conf)" /tmp/nginx.conf.bak
|
||||
|
||||
# 4. 复制回容器并重载
|
||||
docker cp /tmp/nginx.conf.bak novalon-nginx-secure:/etc/nginx/nginx.conf
|
||||
docker exec novalon-nginx-secure nginx -t && docker exec novalon-nginx-secure nginx -s reload
|
||||
|
||||
echo "Jenkins Nginx配置已更新"
|
||||
Reference in New Issue
Block a user