Files
novalon-website/docs/deployment/cicd/03-credential-incident.md
T
zhangxiang 4ab2f3cd8e chore(infra): 新增 Gitea+Jenkins CI/CD 部署与凭据整改
- infra/cicd:docker-compose(gitea/jenkins)、JCasC(凭据统一 ${ENV} 注入,无硬编码)、
  备份/恢复、健康检查、凭据轮换与 git 历史清除脚本
- docs/deployment/cicd:安装、高可用备份监控、凭据事故复盘
- .env.example 仅为占位模板;真实 .env 由 .gitignore 排除
2026-09-20 10:37:57 +08:00

100 lines
4.1 KiB
Markdown
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.
# 03 · P0 凭据入库事故(待处置)
> **状态:待执行。** 本文档是处置手册,不是已完成的整改记录。
> 2026-09-14 由会话内审计发现,张翔已确认「出方案、由他执行」。
## 1. 事故定性
**真实生产凭据已提交进 git 并推送到公网可达的远程仓库。**
- 远程:`https://git.f.novalon.cn/novalon/novalon-website.git`(公网可达,实测 HTTP 200)
- 泄露物:Gitea 登录密码、Jenkins admin 密码、Gitea OAuth2 应用 secret、CI webhook token
**任何能访问该仓库的人都可以直接登录生产 Gitea 与 Jenkins。**
即使是私有仓库,也等效于把密码写进了版本历史 —— 无法撤销,只能轮换。
## 2. 泄露位置(实测)
| 文件 | 泄露内容 | 首次入库 |
|------|----------|----------|
| `scripts/setup-cicd/cicd.config` | `GITEA_PASSWORD`、`JENKINS_ADMIN_PASSWORD`、`WEBHOOK_TOKEN` | `b8ab1fd` |
| `scripts/setup-registry-auth.sh` | Gitea OAuth2 应用 secret(`gto_…`,共 48 字符) | 同期 |
| `novalon-cicd/docker-compose.yml` | PostgreSQL 密码(仅服务器,未入库) | — |
确认命令(可复现):
```bash
git ls-files --error-unmatch scripts/setup-cicd/cicd.config # → 已跟踪
git log --oneline -- scripts/setup-cicd/cicd.config # → af57504, b8ab1fd
grep -rn "PASSWORD\s*=" scripts/setup-cicd/ scripts/setup-registry-auth.sh
```
## 3. 处置步骤(按序执行)
工具已备好:[`infra/cicd/security/`](../../../infra/cicd/security/)
### Step 1 — 轮换凭据(先做,别等清史)
```bash
ssh root@139.155.109.62 \
'cd /home/novalon/docker-app/infra/cicd/security && ./rotate-credentials.sh --dry-run'
# 确认无误后去掉 --dry-run 实际执行
```
脚本会:
1. 生成强随机新凭据(`openssl rand`)
2. 轮换 Gitea 用户密码(`gitea admin user change-password`)
3. 吊销泄露的 OAuth2 应用
4. 签发新的**只读**(`read:repository`)Gitea 令牌
5. Jenkins admin 密码经 `init.groovy.d` 在下次重启生效(无需已有管理员凭据)
6. 写回服务器 `.env`(600 权限)
**⚠️ 执行后旧凭据立即失效**,请在无人使用 CI/CD 的时间窗操作。
### Step 2 — 清除 git 历史
```bash
pip install git-filter-repo # 或 brew install git-filter-repo
cd /path/to/novalon-website
./infra/cicd/security/purge-git-history.sh --dry-run # 先扫描确认命中数
./infra/cicd/security/purge-git-history.sh # 会先做 mirror 备份
```
脚本做了三重保护:
- 改写前自动 `git clone --mirror` 备份
- 二次确认(输入 `PURGE`)
- 清史后重新扫描,仍有残留则**拒绝推送**并中止
**强制推送的连带影响(必须提前通知)**:
- 所有协作者必须 `rm -rf` 本地仓库后重新 clone —— 旧仓库 push 会把泄露内容带回
- 未合入的 PR / fork 可能失效
### Step 3 — 删除远程残留对象
清史后远程的旧 blob 仍可通过 commit sha 直接访问:
```
Gitea → 仓库 → 设置 → 维护 → 运行垃圾回收
```
### Step 4 — 防复发
- 删除 `scripts/setup-cicd/cicd.config`,改用 `.env` + `.env.example` 模式
- 装秘密扫描钩子(gitleaks / trufflehog),`pre-commit` + CI 双层
- 把「凭据只进 `.env`(600)或 Jenkins Credentials」写进 code review 检查项
## 4. 验收标准(执行完必须逐条核对)
- [ ] `git grep` 全历史无明文密码(`git log -p | grep -c` 为 0)
- [ ] 旧密码登录 Gitea/Jenkins 均失败
- [ ] 新令牌权限为 `read:repository`,Jenkins 能正常拉代码
- [ ] 一次 push 能触发流水线(验证凭据替换没破坏链路)
- [ ] Gitea OAuth2 应用列表中无泄露的 `gto_…` 应用
- [ ] 服务器 `.env` 权限为 600,且已加入 `.gitignore`
## 5. 为什么「清史」不能替代「轮换」
历史可能已被 clone / fork / 缓存 / 搜索引擎抓取,清除是**尽力而为**。
密码一旦进过公网仓库,唯一可靠的补救是**让它失效**。
所以顺序是:先轮换(让泄露的密码立即变成废纸),再清史(降低持续暴露面)。