Files
blackbird/ca/README.md
T
qing 407ce3c366 feat(ca): 认证独立化 CA 模块 v1.0.0 + gateway v0.9.0 纯转发瘦身
拓扑:waf:443 → ca:8443(认证+前置转发)→ gateway:8445(仅 loopback 纯转发),
WAF upstream 不变,core/dnfs2/前端零改动。

ca v1.0.0(新模块):
- 认证资产逐字搬迁自 gateway v0.8.0(9 条认证路由、Token 双轨吊销、
  PBKDF2、限流、WS 代理鉴权、wrapperHttp、优雅关闭)
- handleForward 前置转发层:固定上游 gateway:8445;无 Target-Url 公开直转,
  有则本地验签后注入 X-Auth-User/X-Auth-Admin/Authorization(strip-then-set)
- is_special_header 新增剥离 x-auth-user/x-auth-admin;上游不可达统一 502
- confs/configs/ca.ini 新增([Upstream] gateway + UPSTREAM_GATEWAY env)

gateway v0.9.0(2470→约 560 行):
- 删除全部认证代码,退居 127.0.0.1:8445
- 启动 loopback 强制自检(非 loopback 拒绝启动)
- X-Auth-User 存在性不变量:显式 Target-Url 无该头一律 401
- gateway.ini 精简至 [Server]/[Proxy]/[Downstream db]

验收:黑盒 34 断言对 8443 原样全绿(32 passed/2 skipped,与基线一致);
探针实测伪造 X-Auth-User 经 CA 被剥离(echo 实证)、直连 8445 无头 401、
白名单外 403、gateway 宕机 CA 502。core/init.txt 加入 ca/_run.sh。
2026-08-09 12:15:35 +00:00

143 lines
8.3 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.
# ca
UniServe Framework 的中央认证服务(Central Auth),C++20 实现,v1.0.0 起承接原 gateway v0.8.0 的全部认证职责。
## 拓扑定位(waf → ca → gateway 前置拓扑)
```
客户端 ──HTTPS──▶ waf:443 (终止 TLS + coraza 规则) ──HTTP──▶ ca:8443 (认证+前置转发) ──HTTP──▶ gateway:8445 (纯转发) ──▶ static:9200 / dnfs2:8006 / core:5200
```
- CA 接管原 gateway 的 **8443** 端口,WAF upstream 配置不变(`waf/config.yaml` 仍指向 8443)。
- gateway 退居 **8445**(仅 loopback),退化为纯转发服务,不再验签。
- 验签在 CA 本地完成(RSA 密钥同进程),对受保护请求零网络跳。
- 下游 core / dnfs2 / 前端 / WAF 全部零改动。
## 职责
- **账号鉴权**:`/register` 注册(需邮箱验证码,经 pymsg 调 smtp:8008 校验)、`/login` 登录,签发 RSA 签名的 Token。
- **Token 生命周期**:`/logout`(jti 吊销)、`/refresh_token`(宽限期刷新 + 次数上限)、`/whoami`、`/admin/verify`(供 core 等内部服务调用)。
- **前置转发层**:通配 `GET/POST *` 把请求转发到唯一上游 gateway:8445:
- 无 `Target-Url` 头 = 公开请求:剥离伪造身份头(`X-Auth-User`/`X-Auth-Admin`)后直转。
- 有 `Target-Url` 头 = 受保护请求:本地验签(RSA + 过期 + jti 黑名单 + 用户版本号),通过后注入 `X-Auth-User`(管理员追加 `X-Auth-Admin: 1`)与 `Authorization: Bearer` 再转发。
- 上游不可达/超时统一返回 `502 Bad Gateway`(区别于 CA 自身故障的 500)。
- **WebSocket 反向代理(仅管理员)**:`[WsProxy]` 段配置的路径在 HTTP Upgrade 握手时校验 Token(`is_admin=true`),通过后透明桥接到后端 ws:// 或 wss:// 服务。
- **CAPTCHA 代理**:`GET /captcha/image` 代理 captcha2 签发图形验证码(PNG + `X-Captcha-Token` 响应头,10 次/分钟/IP,禁缓存)。
- **邮箱验证码**:`/register/send_code`、`/reset/send_code` 发送 6 位验证码邮件(10 分钟有效);`/reset_password` 重置密码(防枚举恒成功 + 改密后版本号吊销全端 Token)。
- **速率限制**:登录 5 次/5 分钟、注册 3 次/10 分钟、发验证码 5 次/10 分钟(IP 级)+ 3 次/小时/10 次/天(邮箱级),按 `X-Forwarded-For` 客户端 IP 计数(由 WAF 注入),超限一律返回 429。
- **OPTIONS 预检**:由 libhttp 处理,`Access-Control-Allow-Origin` 反射请求的 Origin 头(见审计 P2-3)。
## 身份头契约(strip-then-set)
| 头 | 方向 | 语义 |
|----|------|------|
| `X-Auth-User` | CA → gateway → 下游 | 已认证用户名(邮箱)。客户端携带的同名单被 CA 剥离,仅 CA 注入的值可信 |
| `X-Auth-Admin: 1` | CA → gateway → 下游 | 仅当 Token `is_admin=true` 时注入 |
| `Authorization: Bearer <token>` | CA → gateway → 下游 | Token 来自 Cookie 时由 CA 补齐;来自客户端 Authorization 头时透传 |
gateway 侧仅做**存在性不变量**检查(显式 `Target-Url` 请求必须携带 `X-Auth-User`,否则 401),不复验签名——信任边界是 loopback。
## 安全特性
| 特性 | 实现 |
|------|------|
| 密码哈希 | PBKDF2-HMAC-SHA256,100000 轮,16 字节随机盐,32 字节哈希 |
| 密码强度校验 | 至少 8 字符;大写/小写/数字/特殊字符中至少 3 类 |
| Token 机制 | `Base64(payload) + "_" + RSA-Sign(payload)`,验签 + 过期校验(默认 1 小时) |
| Token 吊销 | 双轨纯内存:jti 黑名单(5000 条阈值清理)+ 用户版本号(改密全端吊销) |
| 常数时间比较 | PBKDF2 验证用 `diff |= hash[i] ^ expected[i]` 防 timing attack |
| 防用户枚举 | 登录对不存在用户执行 dummy PBKDF2 拉平时序;重置密码恒返回成功 |
| CAPTCHA | 统一走 captcha2 远端校验(Token 一次性消费);`/login` 强制 |
| TLS 可选 | `Server.tls = off` 时纯 HTTP(配合 WAF),`on` 时用 `full_chain.crt` / `server.key` |
| 下游容错 | pymsg 客户端 `retry_on_refused`:db/confs 未就绪时阻塞等待;日志客户端 fail-safe 静默丢弃 |
## 接口
### `GET /captcha/image`
代理 captcha2 签发图形验证码:响应体为 PNG,答案 Token 在 `X-Captcha-Token` 响应头(64 位 hex,一次性消费),`Cache-Control: no-store`。限流 10 次/分钟/IP(429)。
### `POST /login`
```json
{
"username": "user@example.com",
"password": "P@ssw0rd",
"captcha_token": "a1b2c3d4...",
"captcha_answer": "Ab3xy"
}
```
成功:`{"success": true, "data": {"token": "<base64_payload>_<rsa_sign>"}}`,同时签发 `HttpOnly + Secure + SameSite=Lax` 的 `q4_token` Cookie。
错误码:`400`(参数/CAPTCHA 错误)、`401`(账号/密码错)、`429`(限流)。
### `POST /register` / `POST /register/send_code` / `POST /reset/send_code` / `POST /reset_password`
与 gateway v0.8.0 契约完全一致(详见 `docs/reports/auth_service_plan.md` §5 与黑盒 tests/blackbox/test_gateway.py)。
### `POST /logout` / `POST /refresh_token` / `POST /whoami` / `POST /admin/verify`
- `/logout`:吊销当前 Token 的 jti 并清除 Cookie。
- `/refresh_token`:宽限期(iat + keep_time × refresh_grace_multiplier)内换发新 Token,旧 jti 吊销,刷新次数超 `max_refresh` 后需重新登录。
- `/whoami`:恒 200,未登录返回 `{"login": false}`。
- `/admin/verify`:fail-closed,非管理员/无效 Token 一律 401(供 core:5200 管理员接口鉴权)。
### `GET/POST *`(前置转发)
```
Authorization: Bearer <base64_payload>_<rsa_sign> // 或 Cookie: q4_token=...
Target-Url: http://127.0.0.1:8006 // 可选;携带即视为受保护请求
```
- 无 `Target-Url`:公开请求,剥身份头直转 gateway(由其 `Proxy.default` 路由)。
- 有 `Target-Url`:验签通过后注入身份头转发;`Target-Url` 白名单由 gateway 侧 `Proxy.opened` 裁决(403)。
- 上游故障:`502 {"success": false, "message": "Bad Gateway"}`。
### WebSocket 代理(`[WsProxy]` 配置的路径)
握手阶段从 `Authorization: Bearer` / `Cookie: q4_token` 提取 Token,验签且 `is_admin=true` 才升级连接并桥接后端;否则拒绝。路由精确匹配,优先级高于 HTTP 通配 `*`。
## 配置
两层加载:**confs 统一配置服务**(启动时 pymsg `get` 拉取 `ca.ini`,`retry_on_refused=true`)→ **环境变量覆盖**(最高优先级)。
配置文件:[confs/configs/ca.ini](../confs/configs/ca.ini)。
- `Server.address`/`port`/`tls`:监听地址/端口/TLS(默认 `127.0.0.1:8443`,env `LISTEN_ADDR`/`LISTEN_PORT`)。
- `Upstream.gateway`:唯一上游 gateway 地址(默认 `http://127.0.0.1:8445`,env `UPSTREAM_GATEWAY`)。
- `Token.*`:`keep_time` / `refresh_grace_multiplier` / `max_refresh`。
- `Admin.users`:管理员白名单(分号分隔邮箱),登录命中时 Token payload 写入 `is_admin=true`。
- `CORS.allowed_origins`:已读取但尚未用于预检响应(审计 P2-3)。
- `WsProxy.routes`:`/path=ws://host:port/target`,分号分隔。
- `Downstream.*`:db/captcha/smtp 地址(env `DB_*`/`CAPTCHA_*`/`SMTP_*`/`CONFS_*`)。
## 编译与运行
```bash
bash _set.sh # make clean && make
bash _run.sh # 从 confs 拉取配置启动(先 sleep 2 等待 confs/db)
```
命令行参数:`-h/--help`、`-v/--version`。日志经 LogC 异步批量推送 db:8005 `Logs` 表(service 名 `ca`)。
## 目录结构
```
ca/
├── src/
│ └── main.cpp # 全部实现:认证 handler、Token 机制、限流、前置转发层、WS 代理
├── inc/
│ ├── DbC.hpp # db 服务客户端(pymsg,retry_on_refused=true;client 名 "ca")
│ ├── LogC.hpp # 统一日志客户端(异步批量,fail-safe;client 名 "ca-log")
│ ├── Conf.hpp # ConfIni:ini 解析 + env 覆盖
│ ├── ExpBase.hpp # 异常基类(带 source_location)
│ └── LocalTime.hpp # 本地时间工具
├── Makefile
├── _set.sh / _run.sh
└── README.md
```
## 版本
`1.0.0`:认证独立化首发——自 gateway v0.8.0 整体搬迁全部认证资产(逐字保留实现),新增前置转发层(固定上游 + strip-then-set 身份头契约 + 上游 502 语义)。原 gateway 同步瘦身为 v0.9.0 纯转发服务。