Files
qinglong/docs/adr/ADR-0049-cluster-api-credential-and-durable-security-audit.md
T

133 lines
9.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.
# ADR-0049: Cluster API Credential 与 Durable Security Audit
- 状态:Proposed
- 日期:2026-07-19
- 关联 RFCQL-RFC-0001 D-08、D-27 至 D-32、D-36、D-39、D-43 至 D-48
## 上下文
ADR-0046 至 ADR-0048 已固定 `/api/v3` 的 route、Authentication、Policy、audit、body 与 handler 顺序,也把授权输入收敛到受审 route registry 和 PostgreSQL Project Policy。但此前 cluster-control 的 authenticator 与 audit sink 仍由 application factory 注入;测试 double 可以证明顺序,不能证明多副本控制面如何解析、撤销和审计真实 credential。
直接复用 2.x `Auths.authConfig``shareStore` token list 或 Open API scope 会重新引入三类问题:credential 与可变用户名/进程内 session 混合,token 自带 scope 变成授权事实,以及每个 pod 对撤销状态的观察不一致。把安全审计写成普通日志或异步 best-effort 事件,则会让“外部副作用已允许但审计丢失”成为正常路径。
该切片只建立 cluster API App、MCP Client、Agent 和 User bearer 的认证/审计底座,不替代 Worker mTLS/session fencing。credential 管理 authority、mutation ledger 与有界 audit query 后续由 ADR-0050 补齐;这不改变本 ADR 对常驻 runtime 最小权限的决定。
## 决策
### 1. Identity、credential 与 authentication principal 分离
`@qinglong/runtime-core/api-credential` 定义稳定 `IdentitySubject` 引用和 versioned `ApiCredentialRecord`
- subject vocabulary 复用 Project Policy 的 `user / api_app / mcp_client / agent / system / worker`
- 通用 bearer 只允许 `user / api_app / mcp_client / agent``system / worker` 必须使用各自的受审内部或节点认证协议;
- credential 保存 `credentialId/version/state/subject/secretDigest/notBefore/expires`,不保存 role、permission、Project 或明文 secret
- subject disable、credential revoke、未生效和过期均返回同一个未认证结果,不向 wire 暴露存在性;
- repository 损坏、驱动失败和取消不是“密码错误”,统一作为 authentication unavailable fail closed。
Authentication 只产生有界 TTL 的 `AuthenticatedPrincipal`。Policy 必须重新读取 Project/RoleBinding 权威事实,不能信任 bearer 自报 scope。
### 2. Bearer secret 使用 peppered digest,不落可逆材料
wire token 固定为:
```text
Bearer ql3c_<credentialId>_<base64url-32-byte-secret>
```
cluster-control 使用 32-byte canonical base64url deployment pepper,对 domain-separated `credentialId + secret` 计算 HMAC-SHA-256,并与数据库 64 位小写十六进制 digest constant-time 比较。pepper 通过部署 Secret 注入,不进入 PostgreSQL、日志、audit 或错误响应;格式错误必须在打开 Pool 和监听 HTTP 前失败。
认证实现必须清零临时 secret/digest buffer。首版只允许最长 60 秒、绝不超过 5 分钟的 principal TTL,避免 adapter 演变为长期授权缓存;credential revoke 的严格跨 pod 生效窗口由未来 version invalidation/短 TTL 决策继续收紧。
### 3. `pg-0005` 推进 capability v4
reviewed migration 新增:
- `ql3.identity_subjects`:稳定 subject 与 active/disabled 状态;
- `ql3.api_credentials`:以 `(credential_id, version)` 为主键的 append-only digest 事实;
- `ql3.security_audit_events`:低敏、append-only 的安全决策事实。
capability 从精确 v3 predecessor 推进为:
```json
{"api_credential":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1}
```
Drizzle metadata、reviewed SQL、checksum、schema contract 和 readiness catalog 必须保持 lockstep。`identity_subjects` 保留六类主体以便 Policy/audit 共用;数据库 CHECK 仍禁止 `system/worker` 获得通用 API credential。
### 4. Runtime role 使用不对称最小权限
cluster runtime role 只能:
- SELECT `identity_subjects``api_credentials`
- INSERT `security_audit_events`
它不得 INSERT/UPDATE/DELETE identity 或 credential,不得 SELECT/UPDATE/DELETE security audit。签发、轮换、撤销与审计查询属于不同的管理/运营 authority,不能因为运行时要验证 token 就合并到同一数据库角色。
### 5. 安全审计是 admission 同步门禁
`@qinglong/runtime-core/security-audit` 固定 exact-shape 记录:server-generated event ID、request/operation/Project、subject/authentication ID、outcome、bounded reason codes、Project/RoleBinding fence 和 occurred time。
- authentication rejected/unavailable 不保存 subject 或 authentication identity
- deny/approval/allow 必须保存已认证主体;
- reason 只能来自受控 vocabulary,不接受驱动错误、token 或自由文本;
- audit repository 只执行一次 INSERT,不 read-back、不在未知提交结果后自动重试;
- audit 写入失败返回稳定 503handler 不得运行。
这使 HTTP → credential repository → Policy repository → audit INSERT → handler 成为同一个 fail-closed admission 纵向链路。它不把业务 transaction 和 audit INSERT 伪装成跨资源原子事务;handler 自身的 durable command/event 仍按各领域 ADR 提交。
### 6. Profile 与资源边界保持隔离
credential/audit PostgreSQL adapter 只由 `cluster-control` composition root 在 capability v4 readiness 后创建。edge/standalone artifact 不安装或导入该 bundle,仍可使用本机 session/identity adapter。每次请求只做有界 token 解析、单条 credential point lookup、Policy point lookup 和单条 audit INSERT;不引入后台 timer、全表扫描或无界 cache。
## 被否决的替代方案
### 复用 legacy token list 与 Open API scope
拒绝。token membership 是 2.x 兼容认证事实,scope 不能成为 3.0 Project Policy,且进程内列表无法为多 pod 提供权威撤销。
### JWT/bearer 自带 subject、role 和 Project
拒绝。签名只证明 issuer,不证明当前 subject/credential/RoleBinding 仍 active;长 token 会放大撤权延迟,也让 route/Policy 输入回到请求自报。
### 保存明文、可逆密文或普通 SHA-256(secret)
拒绝。数据库泄漏会直接暴露或允许低成本离线验证 credential。deployment pepper 与 domain-separated HMAC 把数据库和部署 Secret 分离。
### 审计异步发送到日志或消息队列
拒绝作为安全事实源。可以异步导出已经持久化的 audit event,但 admission 的 authoritative append 必须同步完成;否则允许动作与审计之间存在正常丢失窗口。
### 让 runtime role 同时签发 credential 和查询 audit
拒绝。被 HTTP 业务漏洞驱动的 runtime role 会同时获得造身份、删改撤销状态和读取安全历史的能力,破坏最小权限与职责分离。
## 影响
### 正向
- cluster-control 不再依赖 allow-all 或 legacy session authenticator
- credential 不携带 role/ProjectAuthentication 与 Authorization 保持独立;
- revoke/disable/expiry 与多副本数据库观察共享同一权威事实;
- audit 为 append-only、低敏、write-only runtime fact,业务错误不能静默吞掉;
- edge artifact 继续不安装 PostgreSQL/cluster credential bundle。
### 代价与未完成项
- 每个受保护请求至少增加 credential lookup、Policy lookup 和 audit INSERT;后续缓存只能绑定 version/短 TTL 并证明撤销上界;
- deployment 必须管理独立 pepper Secret,轮换协议尚未实现;
- ADR-0050 已建立默认不可由 cluster-control 到达的 provisioning/rotation/revocation application service 与 PostgreSQL adapterADR-0051 已为常驻 `/api/v3` 建立认证前 process-local overload shield,但管理入口仍没有受审 CLI/API/UI、独立速率限制、negative cache 或异常登录告警;
- ADR-0050 已建立独立 admin role 的有界 audit query adapter,但尚无 retention、partition、export、备份恢复和告警产品面;
- User bearer 当前只形成 single-factor assuranceMFA、hardware/local-console 与 mTLS 各自需要独立 adapter
- Worker enrollment/session 继续遵循 ADR-0012/0013,不能复用通用 bearer。
## 验证
1. runtime-core contract 覆盖 exact shape、subject 类型、digest、时间窗和低敏错误。
2. authenticator 覆盖 token/pepper canonical encoding、constant-time digest、revoke/disable/not-before/expiry、abort 与 unavailable。
3. migration checksum、精确 v3 predecessor、capability v4、Drizzle/schema/catalog lockstep 全部通过。
4. fake Pool adapter test 覆盖 latest credential lookup、损坏 row、单次 audit INSERT 和驱动错误脱敏。
5. application test 覆盖真实 HTTP bearer → PostgreSQL credential → fenced Policy → durable audit → handler,并证明未认证请求在读取大 body 前结束。
6. 真实 PostgreSQL 证明 latest version 解析、audit 落库、`system` bearer 与身份不一致 audit 被 CHECK 拒绝。
7. readiness/CI 证明 runtime role 对 identity/credential 只读、对 audit 只写且无 schema DDLPostgreSQL 1618 仍是 production readiness 支持范围。
8. Node 22/24、Profile dependency/import audit、migration manifest 和 GitNexus detect-changes 继续作为合并门禁。