Files
qinglong/docs/adr/ADR-0190-cluster-approved-publisher-trust-overlap-add-and-safe-retire.md
T

177 lines
7.9 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-0190Cluster Approved Publisher Trust Overlap Add 与 Safe Retire
- 状态:Accepted
- 日期:2026-07-28
- 关联:RFC D-175、D-177、D-178、D-179、D-180
ADR-0185、ADR-0187、ADR-0188、ADR-0189
## 背景
ADR-0188 只实现 compromised-key emergency revokeADR-0189 已保证挂载文件是 material、
durable head 才是 effective authority。正常 key rotation 仍缺少两种受批变更:
1. 在不删除旧 key 的前提下让一个预分发的新 key 生效;
2. 在所有当前安装均不再依赖旧 key 后安全退役旧 key。
若把 ConfigMap 更新直接当作生效,会绕过 Project Policy/Approval;若 retirement 只在
executor 启动时检查一次 head,并发 stage 仍可能在退役扫描后提交旧 signer provenance。
## 决策
### 1. 两种 action,不复用 emergency revoke
新增两个 action type
- `plugin_package.publisher_key.overlap_add`
- `plugin_package.publisher_key.safe_retire`
它们都要求 trust-authority Project 的 `package.manage`,固定创建
`separation_of_duty` Approval;请求人与确认人必须是不同的强认证 User。正常轮换不提供
break-glasssuspected/confirmed compromise 必须使用 D-178 emergency revoke。
客户端只提交:
- `actionRef`、approval/audit identity
- `mode`
- `publisher``keyId`
- strong authenticated principal。
客户端不得提交 PEM、candidate snapshot、previous/current digest、generation、影响集或
retirement proof。
### 2. overlap-add 从只读 material 派生
管理进程启动时读取并严格验证 publisher material 文件。首次 observation 创建 base/head
后续不同 snapshot 只以 digest-only candidate 写入 snapshot ledger,并返回当前 effective
head,不能隐式推进 generation。
提案时,服务用进程内已审 material snapshot 与数据库 effective snapshot计算差异:
- 所有 effective key 必须逐字段保留;
- 必须只新增请求指定的一个 `(publisher,keyId)`
- 新 key 在 proposal time 必须处于 lifetime
- 禁止 removal、rewrite、批量 addition 或跨 publisher 附带变更。
proposal 绑定 candidate digestexecutor 执行时再次从 durable snapshot ledger 解析并
重验同一 one-key addition。
### 3. safe-retire 从 durable effective set 派生
safe-retire 不要求 operator 先删除文件中的旧 PEM。服务从当前 effective snapshot 精确删除
请求 key,要求:
- 目标 key 当前存在;
- 同一 publisher 至少保留一个在 proposal time 当前有效的 successor
- 除目标删除外没有任何 key 或 lifetime 变化。
候选 digest 由服务派生并写入 snapshot ledger。head 生效后,D-179 verifier 会排除旧 key
operator 可在确认所有 recovery Job 已观察新 generation 后再清理旧 PEM。
### 4. v39 schema contract / pg-0040 durable proposal 与 receipt
control-core v39 由 `pg-0040-plugin-package-publisher-trust-transitions`
新增:
- `plugin_package_publisher_trust_transition_proposals`:不可变绑定 mode、Project/Policy
fence、proposer、generation、目标 key、previous/candidate digest、action/preview
digest
- `plugin_package_publisher_trust_transition_receipts`:绑定 approved dispatch、
proposal、previous/current generation/digest、执行 User/fence、retirement proof
summary 和数据库时间。
proposal/approval/dispatch/receipt 必须 exact replay。manager 只能插入 candidate/proposal/
approval/audit;只有 package-executor 可推进 head 和写 receipt。
### 5. retirement 与 stage 使用同一 signer fence
所有 `(publisher,keyId)` mutation 继续使用 ADR-0187/0188 的同一 domain-separated
advisory transaction lock。
safe-retire executor 在 SERIALIZABLE transaction 内:
1. 锁 signer
2. `FOR UPDATE` 精确 authority head 并重验 generation/digest
3. 扫描该 signer 对应的 current `staged|activating|active` install head
4. 要求 matching count 为零;
5. 写 zero-impact retirement proof receipt
6. 推进 effective head generation
7. 原子提交 head 与 receipt,二者共同构成唯一业务提交点。
通用 Approved Action dispatcher 在业务事务返回后,将 execution 投影为 `succeeded`。该投影
不参与 trust authority 判定,也不能单独推进 generation。若进程在业务 COMMIT 成功后、
execution 完成前崩溃,恢复执行必须以同一 dispatch、proposal、execution start time
重新调用 transition repositoryrepository 只允许返回完全相同的 receipt/head
dispatcher 再收敛 execution。不得为了消除该投影窗口,把通用 dispatcher 的 execution
协议或其他 handler 的事务所有权耦合进 publisher trust repository。
provenance stage commit 在 signer lock 后必须按安装显式绑定的 trust authority 查询当前
effective snapshot,并要求 signer 仍存在。这样顺序只有两种:
- stage 先获锁并提交,retire 随后看到 impact > 0 而拒绝;
- retire 先获锁并提交,stage 随后看到 key 已不在 effective set 而拒绝。
不得仅依赖 recovery 启动时缓存的 registry。
### 6. authority ID 必须显式贯穿 stage
Package recovery 配置已经具有 `QL3_PLUGIN_PACKAGE_TRUST_AUTHORITY_ID`。D-180 将该 ID
传入 provenance repository/commit commandrepository 不允许“任一 authority 包含该 key”
或隐式选择第一条 head。若未来需要一个集群多个 trust domain,必须在 Package proposal/
lock 中增加受批 authority binding,不能靠数据库枚举猜测。
### 7. 资源与包边界
复用现有:
- runtime-core trust/action contract
- cluster-postgres snapshot/head、Approved Action、provenance repository 与 migration
stream
- cluster-admin management transport/process 和 caller-driven executor。
不新增 workspace package、生产依赖、listener、timer、Service 或常驻 controller。现有
executor 的最多 2 个连接、批预算和 opt-in CronJob 不变;低配节点可继续外部调用一次性
CLI。
## 不采用方案
### ConfigMap rollout 自动推进 head
Kubernetes write authority 不能替代 Project Policy、双人 Approval 和 durable exact replay。
### safe-retire 复用 emergency revoke
正常零影响 retirement 不应制造 compromise reason、revocation receipt、Run/Tool emergency
deny 与 quarantine 语义。
### retirement 只检查当前 catalog 或只读快照
catalog 不是 Cluster 当前 install-head authority,事务外只读也无法与并发 stage 串行化。
### 查询任意 authority 是否包含 signer
会在多 authority 时错误放宽 signer,属于 confused-deputy。
### 新拆 trust-transition package
没有新的 Profile、credential、制品或第三方依赖边界,违反 ADR-0185。
## 验收证据
- 纯 contract 覆盖 exact one-key add/remove、same-publisher successor、lifetime 和客户端
digest injection 拒绝;
- PostgreSQL mock 与真实 PG 覆盖 candidate observation、proposal exact replay、
manager/executor ACL、receipt/head atomicity,以及业务 COMMIT 后 execution 投影丢失的
exact replay
- 并发真库门确定性覆盖 `stage wins → retire blocked`
`retire wins → stage blocked`
- management transport 只暴露 mode/publisher/key ID
- executor 同时消费 revoke 与 transition,保持 caller-driven bounded work
- 本机 arm64 PostgreSQL 18.4 真库集成覆盖 generation `1→2→3`、2 条 receipt、
safe-retire exact replay,以及共享 advisory lock 下 stage/retire 双向单赢家;
- `qinglong/postgresql-ha-contract@v1` 使用 physical streaming、`remote_apply`
timeline `1→2`、旧主 fencing、promotion、`pg_rewind` 与只读同步 rejoin,证明
old+new material 预分发、双人审批、2 proposals、2 receipts、2 succeeded
executions 和 generation `1→2→3` 在 WAL replay 与 promotion 后持续;
- workspace 保持 22 包;cluster dependency、cluster deployment、CloudNativePG
deployment 与 edge import audit 均无 finding。