Files
qinglong/docs/adr/ADR-0050-cluster-administration-authority-and-mutation-ledgers.md

141 lines
9.4 KiB
Markdown
Raw Permalink 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-0050: Cluster Administration Authority 与 Mutation Ledgers
- 状态:Proposed
- 日期:2026-07-19
- 关联 RFCQL-RFC-0001 D-06、D-17、D-39 至 D-42、D-47 至 D-49
- 延伸:ADR-0037 至 ADR-0049
## 上下文
ADR-0049 已把 cluster API credential 认证和安全审计从 legacy token list 提升为 PostgreSQL 权威事实,并严格限制常驻 runtime role:它只能读取 Identity/credential、追加低敏 audit,不能签发 credential 或读取审计历史。但如果签发、轮换、撤销和 Identity disable 继续通过 cluster-control 的普通 HTTP 进程完成,运行时仍会间接取得管理数据库权限;任一 handler、依赖或路由漏洞都可能同时造身份、造 credential、读取安全历史并执行任务。
另一问题是管理动作跨越多个事实。只写 `identity_subjects``api_credentials` 再 best-effort 写 audit,会留下“权限已改变但没有操作者证据”的崩溃窗口;只依赖当前表又无法区分合法重放、mutation ID 碰撞和并发覆盖。credential secret 与服务端时间戳还会使字节级命令重放天然不同,若把随机 digest 当作幂等比较输入,合法重试会被错误判定为冲突。
因此管理面必须成为独立 authority,并以不可变 mutation ledger、版本 fence 和同事务 audit 定义其事实边界。
## 决策
### 1. 独立、短生命周期的管理组合根
新增私有包 `@qinglong/cluster-admin`。它是显式调用的短生命周期 composition root,不由 `cluster-control` 导入、启动或暴露路由:
- 只依赖公开的 `@qinglong/runtime-core` port 与 `@qinglong/cluster-postgres/admin` 子入口;
- 启动时先验证 exact-shape 配置和 32-byte canonical base64url pepper,再打开 Pool
- 只接受强认证 User`multi_factor / hardware / local_console`)或受审 System service principal
- readiness、assembly 或调用失败时关闭 Pool`close()` 幂等;
- 首个切片只提供 application service,不提供默认监听端口、CLI、UI 或 cluster-control route。
部署方必须把 admin credential 与 runtime/migration credential 分开注入。管理任务结束后必须关闭 admin 进程和 Pool,不能把 admin role 作为常驻控制面环境变量。
### 2. 三个 PostgreSQL role 互不包含
`migration``runtime``admin` 是并列 authority,不是逐级超集:
| 对象 | runtime | admin |
| --- | --- | --- |
| schema history/capability | SELECT | SELECT |
| Project/RoleBinding/Run/Attempt/Event/RetryPolicy | 按运行契约读写 | 无权限 |
| IdentitySubject | SELECT | SELECT/INSERT/UPDATE |
| API Credential | SELECT | SELECT/INSERT |
| Security Audit | INSERT,禁止 SELECT | SELECT/INSERT |
| Identity/Credential Mutation Ledger | 无权限 | SELECT/INSERT |
两种非 migration role 都不得拥有 schema、表,不得 `CREATE` schema object,不得 UPDATE/DELETE append-only credential、audit 或 mutation history。readiness 必须对每张表的 SELECT/INSERT/UPDATE/DELETE 与 ownership 逐项精确核对;权限过多和权限不足同样阻止 ready。
### 3. capability v5 与不可变管理流水
`pg-0006-identity-credential-administration` 从精确 capability v4 predecessor 推进 v5,新增:
- `identity_subject_mutations`register/enable/disable 的 mutation ID、subject version fence、目标状态、强 actor、audit identity 和原 Identity 创建时间;
- `api_credential_mutations`issue/rotate/revoke 的 mutation ID、credential version fence、不可变 subject/status、强 actor 和 audit identity。
capability 为:
```json
{"api_credential":1,"api_credential_admin":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}
```
升级时,v4 已有 Identity/credential 生成确定性的 `import` mutation 与低敏 migration audit。该事实明确表示“由 pg-0006 导入”,不伪造原始操作者。mutation ID 与 audit event ID 相同;所有外键、CHECK、唯一版本索引、reviewed SQL、Drizzle metadata、checksum、schema contract 和真实 catalog 必须 lockstep。
### 4. 每个管理动作是一个 `SERIALIZABLE` 原子提交
Identity 与 credential Repository 对目标 identity/credential 取得 transaction-scoped advisory lock,并使用 expected-current-version fence
1. 检查已有 mutation
2. 锁定并复验当前 Identity/credential 事实;
3. INSERT 低敏 security audit
4. INSERT/UPDATE 当前 Identity 或 INSERT 新 credential version
5. INSERT mutation ledger
6. COMMIT。
任一步失败整体 rollback。序列化失败、deadlock、lock unavailable 和唯一键竞态最多重试三次;稳定 version/mutation/subject conflict 使用低敏领域错误,未知驱动错误统一 unavailable。Identity disable 不改写历史 credential;认证时同时读取最新 credential 与 Identity 状态,因此 disable 立即使其 fail closed。
### 5. credential secret 一次返回,语义重放不恢复
issue/rotate 在进程内生成 32-byte CSPRNG secret,使用 ADR-0049 的同一 domain-separated HMAC 计算 digest,随后在 `finally` 清零可变 Buffer。PostgreSQL、mutation、audit、日志和错误都不保存 secrettoken 只在新 mutation 成功提交的单次结果返回。
mutation 重放按调用方语义比较,而不比较服务端生成字段:
- 必须相同:mutation ID、operation、expected version、credential/subject、caller、authentication ID、request ID、显式 not-before 与 expiry
- 不参与等价判断:随机 secret/digest、服务端 created/occurred timestamp
- 语义一致时返回已存事实与 `token: null`,并且预检路径不再次生成 secret;
- 任一调用方语义字段不同即 mutation conflict,不能用同一 ID 覆盖或探测已有 secret。
调用者若丢失首次 token 响应,只能用新的 mutation ID 执行 rotate,不能从存储或重放接口恢复 secret。`notBeforeAtMs` 必须显式提供,避免默认当前时间使幂等请求含义漂移。
### 6. Security audit query 有界且只属于 admin authority
admin 子入口提供 descending `(occurred_at_ms, event_id)` keyset query:最多 200 条,只允许 exact-shape project/subject/outcome filter,不接受 offset、任意 SQL、自由文本搜索或无界导出。返回值继续通过低敏 audit contract 归一化,损坏 row 和数据库错误 fail closed。
本 ADR 不实现 retention/delete。未来 retention 必须先证明 archive/export 已持久化并以独立运维 authority 执行,不能把 DELETE 授给当前 admin role。
## 被否决的替代方案
### 把管理路由加入 cluster-control
拒绝。常驻业务进程将同时持有认证、签发、审计读取与运行 authority,破坏职责分离并扩大远程攻击面。
### 让 migration role 兼任日常管理
拒绝。DDL owner 权限远大于 credential 管理所需权限,任何管理输入错误都可能越过 schema contract 和 append-only 约束。
### 只保存当前 Identity/credential,不保存 mutation ledger
拒绝。无法证明 actor、版本来源、精确重放和并发 winner,也无法把管理事实与 audit 外键绑定。
### 保存加密 secret 以支持重放返回
拒绝。可恢复 secret 会把数据库/KMS 读取权提升为 bearer 签发权,并扩大备份、日志和运维面的泄漏范围。
### 以随机 digest 做完整命令相等比较
拒绝。每次重试都会产生不同随机值,使合法幂等请求必然冲突。幂等 identity 必须绑定调用方语义,不绑定一次性生成材料。
## 影响
### 正向
- cluster-control compromise 不再直接获得 credential 签发或 audit read authority
- Identity/credential 变更、actor 与 audit 在同一事务中可追溯;
- 语义重放、并发版本冲突与 mutation ID 碰撞有明确且可测试的结果;
- 管理包、PostgreSQL driver 和运行包继续不进入 edge/standalone importer
- audit query 有硬页界限,不把运营查询变成控制面内存风险。
### 代价与未完成项
- 部署需要第三套最小权限数据库凭据,并负责短生命周期调用与 Secret 注入;
- 首个切片没有 CLI/API/UI、审批 ceremony、rate limit、break-glass 或双人复核;
- pepper rotation、credential 批量吊销、异常认证告警仍未实现;
- audit partition、retention、archive/export、备份恢复和 SIEM adapter 仍需后续 ADR
- 远端 CI 必须提供 PostgreSQL 16/18 × x64/arm64 的三角色证据。
## 验证
1. runtime-core 覆盖 Identity/credential mutation、版本 fence、强 actor、audit coupling、token digest 与有界 query exact shape。
2. cluster-admin 覆盖 pepper-before-Pool、强认证、未知字段拒绝、一次 token 返回、Buffer 清零、语义重放不再生成 secret 与 collision fail closed。
3. migration test 冻结 pg-0006 checksum、精确 v4 predecessor、capability v5、13 表和所有 CHECK/FK/index。
4. readiness test 同时证明 runtime 对两张 mutation ledger 零权限,以及 admin 的精确允许/拒绝矩阵。
5. PostgreSQL integration 覆盖 Identity 并发单 winner、issue/rotate/revoke、语义重放、audit keyset query 与 admin 无 Run/Project/RoleBinding 权限。
6. package entrypoint/dependency audit 证明 admin 不加载 migration DDL/Drizzle schemacluster-control 不依赖 adminedge 不安装 cluster bundle。
7. Node 22/24、本机 SQL 验证和 PostgreSQL 16/18 × x64/arm64 CI 继续作为合并门禁。