fix: P0遗留修复 + ADR/SOP文档
- requirements.txt: 添加 passlib[bcrypt] 依赖 - deploy-server/nginx.conf: /ws/ 路径添加 access_log off - docs/ADRs/: 新增 4 个 ADR 决策记录 - docs/SOPs/: 新增 4 个 SOP 操作规程
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# ADR-001: Gitea 自托管 + Tailscale Funnel 暴露
|
||||
|
||||
**状态**: ✅ 已采纳
|
||||
**日期**: 2026-06-14
|
||||
**决策者**: 宋献 + Claude 评审
|
||||
**关联**: [[Gitea部署指南]] / [[风险跟踪表]] 第十二节
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
项目主仓 `D:\资料\03-项目开发\wecom_it_smart_desk\` 需要:
|
||||
- 跨设备协作(simon's 电脑 + workbuddy 沙箱)
|
||||
- 推送评审 + 分支保护
|
||||
- 异地可访问(workbuddy 沙箱无 Tailscale 内网)
|
||||
|
||||
## 2. 评估方案
|
||||
|
||||
| 方案 | 优势 | 劣势 | 结论 |
|
||||
|---|---|---|---|
|
||||
| **A. GitHub 私有仓** | 零运维 + 全球 CDN + 完善 Actions | 代码在境外(企业合规风险)+ 付费 | ❌ 否决 |
|
||||
| **B. GitLab.com 私有仓** | 免费私有 + 完善 CI | 代码在境外 + workbuddy 沙箱访问延迟 | ❌ 否决 |
|
||||
| **C. Gitea 自托管(NAS)+ Tailscale Funnel** | 数据本地 + workbuddy 可访问 + 免费 | NAS 单点故障 + Funnel 稳定性依赖 Tailscale | ✅ **采纳** |
|
||||
| **D. Gitea 自托管 + 公网 IP 暴露** | 不依赖 Tailscale | 需配 SSL + DDOS 风险 + 国内带宽限制 | ❌ 否决 |
|
||||
|
||||
## 3. 决策
|
||||
|
||||
**采纳 C 方案**: Gitea 套件(DS923+ NAS)+ Tailscale Funnel 暴露公网。
|
||||
|
||||
## 4. 关键参数
|
||||
|
||||
| 项 | 值 | 备注 |
|
||||
|---|---|---|
|
||||
| Gitea 版本 | 1.22+ | 套件中心固定 |
|
||||
| 端口 | 8418 (HTTP) | 避开被占端口 |
|
||||
| 数据库 | SQLite3 | 单机够用,简化部署 |
|
||||
| Tailscale 私网 | `tail58d872.ts.net` | DSM 已配 |
|
||||
| Funnel 域名 | `https://ds923plus.tail58d872.ts.net` | 沙箱访问 |
|
||||
| 备份 | `scripts/backup-gitea.sh` cron 3 点 | 见 [[Gitea部署指南]] §6 |
|
||||
| 异地备份 | OSS / COS 推 | M-1 风险项待解决 |
|
||||
|
||||
## 5. 风险与缓解
|
||||
|
||||
| 风险 | 等级 | 缓解 |
|
||||
|---|---|---|
|
||||
| NAS 硬盘故障 | 🟠 高 | 异地 OSS 备份(待配) |
|
||||
| Tailscale Funnel 稳定性 | 🟡 中 | Funnel 故障时降级 LAN(`http://100.85.152.112:8418`) |
|
||||
| 卸载误操作数据丢失 | 🟡 中 | 备份脚本 + 卸载前 checklist |
|
||||
| token 泄露 | 🟠 高 | token 不入文件,走 wincred |
|
||||
|
||||
## 6. 决策影响
|
||||
|
||||
- ✅ 团队协作无需 VPN(workbuddy 沙箱直连 Funnel)
|
||||
- ✅ 推送评审 + 分支保护(PR + 1 reviewer)
|
||||
- ⚠️ NAS 单点是隐患,需异地备份
|
||||
- ⚠️ 卸载/迁移需严格按 [[Gitea部署指南]] §8 走
|
||||
|
||||
## 7. 后续评审
|
||||
|
||||
- 3 个月后(2026-09-14)评审:Funnel 稳定性 + 备份完整度
|
||||
- 6 个月后(2026-12-14)评审:是否切到企业 GitLab(如果合规要求)
|
||||
@@ -0,0 +1,80 @@
|
||||
# ADR-002: WebSocket Token 鉴权(走 Sec-WebSocket-Protocol)
|
||||
|
||||
**状态**: ✅ 已采纳
|
||||
**日期**: 2026-06-14
|
||||
**决策者**: 宋献 + Claude 评审
|
||||
**关联**: [[风险跟踪表]] 第十节 / 评审报告 `workbuddy-2026-06-14-P0安全.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
WebSocket 鉴权原方案:`ws://server/ws/?token=<JWT>` —— **token 在 URL 里**:
|
||||
- ❌ 被 nginx access_log 记录
|
||||
- ❌ 被 CDN / 反代记录
|
||||
- ❌ 被浏览器历史记录
|
||||
|
||||
**P0 漏洞**(H-11 风险项),已修复。
|
||||
|
||||
## 2. 评估方案
|
||||
|
||||
| 方案 | 浏览器支持 | token 泄露 | 实施难度 | 结论 |
|
||||
|---|---|---|---|---|
|
||||
| **A. Authorization: Bearer header** | ❌ 浏览器 WS API 不支持自定义 header | ✅ 不泄 | 中 | ❌ 否决(浏览器限制) |
|
||||
| **B. Sec-WebSocket-Protocol: bearer.<token>** | ✅ 现代浏览器都支持 | ✅ 不在 URL | 低 | ✅ **采纳** |
|
||||
| **C. 第一条消息传 token** | ✅ 全支持 | ⚠️ 需先开 WS 接受任意连接(无法鉴权) | 低 | ❌ 否决 |
|
||||
| **D. Cookie 自动带** | ✅ 全支持 | ⚠️ CSRF 风险 | 中 | ❌ 否决 |
|
||||
|
||||
## 3. 决策
|
||||
|
||||
**采纳 B 方案**: `Sec-WebSocket-Protocol: bearer.<token>`
|
||||
|
||||
服务端协商 subprotocol,客户端用第二个 subprotocol 传 token(浏览器 API `new WebSocket(url, [subprotocols])`)。
|
||||
|
||||
## 4. 实现
|
||||
|
||||
### 4.1 前端
|
||||
|
||||
```ts
|
||||
// frontend-agent/src/composables/useWebSocket.ts
|
||||
const ws = new WebSocket(wsUrl, [`bearer.${agentStore.token}`])
|
||||
```
|
||||
|
||||
### 4.2 后端
|
||||
|
||||
```python
|
||||
# backend/app/api/ws.py
|
||||
subprotocol = request.headers.get("sec-websocket-protocol", "")
|
||||
if subprotocol.startswith("bearer."):
|
||||
token = subprotocol[7:]
|
||||
else:
|
||||
# 降级:Authorization header
|
||||
auth = request.headers.get("Authorization", "")
|
||||
if auth.startswith("Bearer "):
|
||||
token = auth[7:]
|
||||
else:
|
||||
# 降级:query param(已废,只用于兼容旧前端)
|
||||
token = request.query_params.get("token", "")
|
||||
```
|
||||
|
||||
## 5. 降级路径
|
||||
|
||||
| 优先级 | 来源 | 用途 |
|
||||
|---|---|---|
|
||||
| 1 | Sec-WebSocket-Protocol | 标准(主) |
|
||||
| 2 | Authorization: Bearer | Postman / 测试工具 |
|
||||
| 3 | query `?token=` | 已废(留兼容) |
|
||||
|
||||
## 6. 风险与缓解
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|---|---|
|
||||
| 浏览器 API 不支持 subprotocol | 现代浏览器(2020+)都支持,无问题 |
|
||||
| 旧客户端不更新 | query param 降级仍可用,但提示更新 |
|
||||
| nginx 仍记录 subprotocol | `location /ws/ { access_log off; }` 配合 |
|
||||
|
||||
## 7. 决策影响
|
||||
|
||||
- ✅ WS 鉴权修复,token 不再泄
|
||||
- ✅ nginx access_log 关闭,旧 token 不留痕
|
||||
- ⚠️ 旧客户端需更新(发版通知)
|
||||
@@ -0,0 +1,106 @@
|
||||
# ADR-003: nginx 敏感路径 access_log 关闭
|
||||
|
||||
**状态**: ✅ 已采纳
|
||||
**日期**: 2026-06-14
|
||||
**决策者**: 宋献 + Claude 评审
|
||||
**关联**: [[风险跟踪表]] 第十节 / 评审报告 `workbuddy-2026-06-14-P0安全.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
nginx `access_log` 默认记录所有请求,含敏感信息:
|
||||
- `Authorization: Bearer <token>`
|
||||
- `?token=<JWT>`
|
||||
- `Cookie: session=<sid>`
|
||||
|
||||
敏感路径必须关闭 access_log,避免 token 永久落盘。
|
||||
|
||||
## 2. 决策
|
||||
|
||||
**敏感路径一律 `access_log off`**,具体见下表。
|
||||
|
||||
## 3. 关闭清单
|
||||
|
||||
| 路径 | 原因 | access_log |
|
||||
|---|---|---|
|
||||
| `/ws/` | WebSocket token 鉴权 | `off` |
|
||||
| `/api/v1/auth/login` | 密码登录 | `off` |
|
||||
| `/api/v1/auth/refresh` | token 刷新 | `off` |
|
||||
| `/api/v1/h5/oauth/callback` | OAuth2 回调 | `off` |
|
||||
| `/api/v1/wecom/callback` | 企微回调(验证 URL 含 echostr) | `off` |
|
||||
| `/api/v1/agents/login` | 坐席登录 | `off` |
|
||||
| `/api/v1/upload*` | 文件上传(可能含敏感文件名) | `off` |
|
||||
| `/health` `/healthz` `/readyz` | 健康检查(高频) | `off` |
|
||||
|
||||
## 4. 实现
|
||||
|
||||
```nginx
|
||||
server {
|
||||
# 全局
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
# WS(敏感)
|
||||
location /ws/ {
|
||||
access_log off;
|
||||
proxy_pass http://backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# 登录(敏感)
|
||||
location ~ ^/api/v1/(auth|agents)/login$ {
|
||||
access_log off;
|
||||
proxy_pass http://backend;
|
||||
}
|
||||
|
||||
# 健康检查(高频)
|
||||
location ~ ^/(health|healthz|readyz)$ {
|
||||
access_log off;
|
||||
proxy_pass http://backend;
|
||||
}
|
||||
|
||||
# 其它
|
||||
location / {
|
||||
proxy_pass http://backend;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. error_log 仍开启
|
||||
|
||||
⚠️ **error_log 仍开** —— 4xx/5xx 错误需要留痕(token 在 error log 里出现频率低,且 error log 有 TTL 自动切割)。
|
||||
|
||||
## 6. 日志清理脚本
|
||||
|
||||
`/etc/logrotate.d/nginx` 配:
|
||||
```
|
||||
/var/log/nginx/*.log {
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 www-data adm
|
||||
sharedscripts
|
||||
prerotate
|
||||
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
|
||||
run-parts /etc/logrotate.d/httpd-prerotate; \
|
||||
fi
|
||||
endscript
|
||||
postrotate
|
||||
invoke-rc.d nginx rotate >/dev/null 2>&1
|
||||
endscript
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 风险与缓解
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|---|---|
|
||||
| 漏关某个敏感路径 | 定期审计(任务 W-5,workbuddy 跑) |
|
||||
| 调试时无 access_log 难定位 | debug 时临时开 `access_log /tmp/debug.log;` |
|
||||
| 攻击者利用关闭日志 | error_log 仍开,异常请求有记录 |
|
||||
@@ -0,0 +1,101 @@
|
||||
# ADR-004: Token 不入文件,走 wincred 缓存
|
||||
|
||||
**状态**: ✅ 已采纳
|
||||
**日期**: 2026-06-14
|
||||
**决策者**: 宋献 + Claude 评审
|
||||
**关联**: [[风险跟踪表]] 第十二节 12.6 / 推送约定
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
之前 Gitea 推送 token 直接嵌入 `.git/config` 的 `origin.url`:
|
||||
```
|
||||
url = https://ae236991c3d5...@ds923plus.tail58d872.ts.net/...
|
||||
```
|
||||
|
||||
**风险**:
|
||||
- ❌ token 明文落盘
|
||||
- ❌ token 失效后难更新(URL 整体换)
|
||||
- ❌ 误 `git add .git/` 可能入仓(虽然 .git/config 本身不入仓,但 .git/ 目录其他文件可能)
|
||||
- ❌ auto-classifier 拒绝重写 URL(防误操作)
|
||||
|
||||
**事故**: 2026-06-14 workbuddy-claude token 失效后,`origin.url` 残留死凭据。
|
||||
|
||||
## 2. 决策
|
||||
|
||||
**`.git/config` 的 `origin.url` 只写用户名,token 走 git credential helper(wincred)缓存**。
|
||||
|
||||
## 3. 实现
|
||||
|
||||
### 3.1 配 remote URL(无 token)
|
||||
|
||||
```bash
|
||||
git remote add origin https://simon@ds923plus.tail58d872.ts.net/simon/wecom_it_smart_desk.git
|
||||
# 或修复现有:
|
||||
git remote set-url origin https://simon@ds923plus.tail58d872.ts.net/simon/wecom_it_smart_desk.git
|
||||
```
|
||||
|
||||
### 3.2 配 credential helper
|
||||
|
||||
`.git/config`:
|
||||
```ini
|
||||
[credential]
|
||||
helper = manager # Windows = wincred / Linux = git-credential-manager
|
||||
```
|
||||
|
||||
### 3.3 首次推(输一次 token)
|
||||
|
||||
```bash
|
||||
git push -u origin main
|
||||
# 弹窗 → username 留空,password = token
|
||||
# wincred 自动缓存
|
||||
```
|
||||
|
||||
### 3.4 换 token(必走)
|
||||
|
||||
```bash
|
||||
# 清旧缓存
|
||||
printf "protocol=https\nhost=ds923plus.tail58d872.ts.net\nusername=simon\n" | git credential reject
|
||||
|
||||
# 存新缓存(一次性,token 在 heredoc 不入文件)
|
||||
printf "protocol=https\nhost=ds923plus.tail58d872.ts.net\nusername=simon\npassword=NEW_TOKEN\n" | git credential approve
|
||||
|
||||
# 验证
|
||||
git push origin main
|
||||
# 应不弹窗
|
||||
```
|
||||
|
||||
## 4. workbuddy 推送同理
|
||||
|
||||
`.workbuddy/config.json` 是 workbuddy 自己的凭据存储(类比 .git/config),**入仓** ❌。
|
||||
|
||||
**正确做法**:
|
||||
- `.workbuddy/config.json` 写用户名/URL/其他配置,**不写 token**
|
||||
- workbuddy 启动时读 `gitea.token` 字段(从环境变量 / 启动参数传入)
|
||||
- 或者 workbuddy 自己也用 git credential helper
|
||||
|
||||
**已加 .gitignore**:
|
||||
```gitignore
|
||||
.workbuddy/config.json
|
||||
.workbuddy/config.local.json
|
||||
.workbuddy/*.token
|
||||
.workbuddy/credentials*
|
||||
.workbuddy/.env*
|
||||
```
|
||||
|
||||
## 5. 优势
|
||||
|
||||
- ✅ token 不入文件(只入 wincred 系统密钥环)
|
||||
- ✅ 换 token 简单(`credential reject` + `approve`)
|
||||
- ✅ 不会误入仓
|
||||
- ✅ auto-classifier 不拒绝(无 token 写文件)
|
||||
|
||||
## 6. 风险与缓解
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|---|---|
|
||||
| wincred 缓存被读(本机攻击) | 操作系统级防护 + 强密码 + BitLocker |
|
||||
| 跨设备不能用 wincred | Linux 用 `git-credential-manager`,Mac 用 `git-credential-osxkeychain` |
|
||||
| 换电脑忘缓存 | `git credential approve` 一次性配置 |
|
||||
| token 在环境变量 | 仍比文件安全 + CI 用 secret store |
|
||||
Reference in New Issue
Block a user