Files
qinglong/docs/adr/ADR-0268-signed-external-prompt-output-key-custody-and-recovery-proof.md
T

181 lines
12 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-0268:签名的外部 Prompt 输出密钥托管与恢复证明
- 状态:Proposed
- 日期:2026-08-03
- 关联:RFC D-244、D-245、D-247、D-249ADR-0261、ADR-0262、ADR-0266
## 背景
QingLong 已能用 Kubernetes Secret CAS 完成 Prompt 输出 key rotation/retirement,并以
PostgreSQL append-only preparation/completion 抵抗响应丢失和主库晋升。但 Kubernetes
Secret 不是最终 KMS/HSM,也不是灾难备份。`create Secret` 不能按 `resourceNames` 收紧,
因此 QingLong Job 不应为了首次 provision 或灾难恢复取得 namespace 范围的 create 权限;
常驻 control 更不能持有 KMS unwrap、HSM session 或对象存储删除 authority。
即使 key material 已安全退役,现有 durable fact 仍保留 `keyId + materialProof +
catalogDigest`。这允许一个外部托管系统恢复 32 字节 material 后,在不重新激活该 key、
不改写生产 Secret 的情况下,证明它与原始 rotation/retirement 事实相同,并实际打开一份
历史 Artifact。
## 决策
### 1. KMS/HSM 与 QingLong runtime 分权
首次生成、wrap/unwrap、HSM quorum、wrapped blob 存储、备份复制和外部 Secret create 均由
部署方的独立 authority 负责。QingLong runtime、cluster-control、AI Pool 和 migration role
不得取得云 SDK credential、KMS decrypt、HSM session、Secret create 或 backup delete。
### 2. 外部托管必须产生签名 receipt
新增 `qinglong/plugin-package-prompt-output-external-custody-receipt@v1`。receipt 只保存:
- custody/key identity、material proof、source generation/catalog digest
- provider 名、wrapping key reference digest
- wrapped material digest/bytes
- Ed25519 signing public key digest、receipt digest 与 signature。
receipt 不包含 plaintext key、wrapped blob、KMS key ARN/path、credential、Artifact ciphertext
或模型输出。QingLong 只接受显式 pinned Ed25519 public key;换 key 必须是外部受审 ceremony
不能由 receipt 自报信任根。
receipt 与 wrapped blob 必须装入单一
`qinglong/plugin-package-prompt-output-external-custody-bundle@v1`。bundle digest 使用独立 domain
绑定 schema、custody ID、receipt digest、wrapped material digest/byteswrapped material 使用
canonical base64url。Verifier 不再接受两个独立文件,避免复制中断或跨 generation 拼接形成
“新 receipt + 旧 blob”的部分状态。bundle 是 provider-neutral contract,不包含 endpoint、KMS key
path、token 或 provider credential。
### 3. wrapped backup 与恢复 material 分别验证
`@qinglong/ai/plugin-package-prompt-output-external-custody` 提供两个纯 caller-driven gate
1. 对 wrapped blob 复算 bytes/SHA-256,并验证 receipt digest、签名和 pinned key
2. 对外部 unwrap 后的 32 字节 material 复算 domain-separated material proof,要求与
rotation completion 或 retirement preparation 的 durable `keyId/materialProof/catalogDigest`
完全一致,再使用正式 AES-256-GCM/AAD 逻辑打开 exact Artifact。
成功只返回 content-free recovery proofcustody/receipt/key/material proof、Artifact/content
digest、output bytes、request/recovery identity 和 verification time。不得返回 plaintext、
key、wrapped bytes、provider reference 或路径。
### 4. 恢复不等于重新激活
首版 recovery proof 不更新生产 Secret、不删除 retirement record、不改变 active key、
不写 ModelInvocation/Artifact,也不把恢复 material 挂给常驻 runtime。实际明文交付、
重建生产 keyring 或 re-encrypt 属于后续显式审批产品;不能因为一次验证成功就静默撤销
retirement 或绕过 `artifact.read`
### 5. 资源与包边界
能力留在既有 `@qinglong/ai` subpath,不新增 workspace package、第三方依赖、数据库表、
timer、watcher、listener、Pool 或云 SDK。Edge/Standalone/Cluster 默认均不加载;只有外部
恢复 ceremony 显式调用时付费。
### 6. 恢复必须由两个独立强身份共同授权
新增 `qinglong/plugin-package-prompt-output-external-recovery-authorization@v1`。授权固定
`artifact.read``lost-key-recovery-verification`,并绑定 recovery/request、custody receipt、
key、Artifact、Policy digest、请求者身份和最长 15 分钟有效期。两个 approver 必须使用不同
User、authentication、Ed25519 signing key,且均不得是请求者;每个认证在批准时最多 5 分钟。
两份签名覆盖同一个 canonical authorization digest,不能分别批准不同 Artifact 或 key 后拼接。
trusted approver public key 由 verifier 的私有 workspace 显式 pinauthorization 不能自报信任根。
过期、self-approval、重复 User/auth/key、签名或 exact fact 漂移全部失败关闭。
### 7. Verifier 是无网络、无数据库的隔离产品入口
`ql3-prompt-output-key-recovery-verify` 只读一个私有 workspaceauthorization、custody receipt、
wrapped blob、外部 unwrap 得到的 32 字节 material、durable fact、Artifact 及三个 pinned public
key。所有文件必须是稳定、单 hard-link、不可写/执行、不可跟随 symlink 的 regular file;私有
文件禁止 `other` 读取。CLI 完成后清零持有的 recovered/wrapped material bufferstdout 只有
authorization-bound content-free proof。
Kubernetes reference Job 位于独立 `qinglong3-recovery` namespace,不属于默认 Cluster
Kustomization。它没有 Role/RoleBinding、ServiceAccount token、env/envFrom、数据库连接、Secret
API、KMS endpoint 或 ingress/egress,只读一个由外部部署 authority 预置的 PVC。这个 Job 不能
执行 unwrap;因此 KMS credential 也不会因“恢复验证”进入 QingLong Pod。
### 8. 首个具体 adapter 是外部 Vault Transit CLI
仓库提供 caller-driven `custody:vault-transit:ql3` wrap/unwrap adapter,但不把 Vault SDK、token、
socket 或 timer 装入任何 QL3 package/runtime。production 只允许 HTTPS + 显式 CAtoken 只从私有
只读文件读取;HTTP 仅允许 `127.0.0.1` 测试 fixture。wrap 调用 Transit encrypt 后生成签名 receipt
和单一 `0400`、no-replace bundleexact replay 只验证已有 bundle,不重打 Vault。unwrap 先验证
bundle、pinned signing key、Vault endpoint/mount/key/version 的摘要绑定,再调用 Transit decrypt
仅在 32 字节 material proof 完全一致后创建 `0400`、no-replace 文件。错误和成功 stdout 均不包含
token、material、wrapped blob、endpoint、key name 或路径。
## 当前证据
- Ed25519 receipt digest/signature 和 signing-key digest 可复算;错误 key/signature 失败关闭;
- wrapped blob bytes/digest 漂移失败关闭;
- provider-neutral atomic bundle 已替代 receipt/blob 双文件输入;跨 bundle blob 替换、digest 漂移、
extra field 与 signing authority drift 均失败关闭;
- recovered material 必须同时匹配 signed receipt 与 durable key fact
- 使用正式 Artifact AES-256-GCM/AAD 路径打开历史密文,输出 proof 不含明文或 key;
- 两个不同强 User、authentication 和 Ed25519 key 对同一个 15 分钟 authorization 签名;请求者
self-approval、重复身份/会话/key、过期、签名和 exact fact 漂移失败关闭;
- package CLI 已从只读私有 workspace 完成 exact recovery,并在退出时清零 material/wrapped
buffer;输出不含明文、key 或 wrapped blob
- 隔离 Kubernetes Job 可由 Kustomize 渲染;deployment auditor 证明零 RBAC/token/env/network/
database/KMS authority、deny-all NetworkPolicy 和 read-only PVC
- Vault Transit concrete adapter 的 8 项 protocol test 已覆盖 immutable wrap、exact replay、
unwrap、wrong material、key authority drift、非 HTTPS production transport、unsafe authority
file、bounded response 和 content-free CLI
- opt-in live contract 已在 digest-pinned 官方 Vault 1.21.4 arm64 容器实际完成 TLS 1.3 + 显式
私有 CA、错误 CA 拒绝、3-share/2-threshold init/unseal、persistent file barrier、Transit key
create/encrypt、atomic bundle、整个容器替换、sealed 状态观察、re-unseal、同一 Transit key 存续、
decrypt、无 Vault exact replay、双 User authorization 与正式 Artifact open,最终
`gates.passed=true`。容器显式当前非 root UID/GID、read-only rootfs、no-new-privileges、capability
仅 IPC_LOCK、随机 loopback 端口且成功/失败均零临时容器/私有目录残留;该证据仍是单宿主 file
storage 和短期本地 CA/root token,不是 HA integrated storage、HSM auto-unseal、enterprise PKI
或外部 IdP/CNPG restore
- 独立 PostgreSQL 18 arm64 opt-in live contract 已用 digest-pinned 官方镜像和随机 loopback-only
端口运行完整 QL3 core/AI migration,以 production repository 写入 materialized revision、automation
publication、Prompt admission/start/completion/finalization、正式加密 Artifact 和 key-rotation
preparation/completion,再生成 734,708-byte custom-format backup。backup 不含明文或 raw key
删除整个源容器及匿名卷后,不同容器/匿名卷恢复出相同 52 条 core、16 条 AI migration history
digest,八类 lineage 行各 1,并由恢复后的 Artifact/rotation repository 重新读取 exact fact 与
Artifact,交给既有双 User authorization + offline verifier 实际打开,最终 `gates.passed=true`
成功/失败都清理随机容器、匿名卷和私有目录。该门已证明 production schema lineage 的逻辑
PostgreSQL backup→restore→recovery composition,但不替代 CloudNativePG Barman WAL/PITR
- workspace 仍为 19package consumer graph 和依赖树不变。
## 接受门
本 ADR 保持 Proposed,直到:
1. 在 production-grade 外部 KMS/HSM/Vault HA storage + HSM/受控 unseal authority 下完成
generate→wrap→immutable backup→unwrap,并证明 QingLong Pod 无 KMS credential;当前 TLS 1.3/
persistent single-host Vault live contract 已关闭 transport、barrier restart 和 seal threshold 的
实现风险,但不替代外部 HA/HSM 门;
2. 把当前 pinned Ed25519 双人 authorization 接到真实外部 IdP/审批 ceremony,取得非 fixture
的不同 User/authentication 证据;
3. 在隔离恢复环境中从 production schema lineage 的 PostgreSQL/CNPG backup 取得 exact Artifact
和 durable key fact,完成 content-free recovery proof;当前独立 PostgreSQL logical backup 门
已关闭完整 production tables、migration history、backup 序列化、源销毁、隔离 restore、
production repository reopen 和 verifier composition 风险,但尚未替代 CloudNativePG Barman
WAL/PITR
4. 覆盖 receipt/backup 丢失、错误 KMS key version、签名轮换、material drift、Artifact
篡改、COMMIT response-loss 和主库 promotion
5. 明确实际 plaintext 导出、生产 keyring 重建或 bulk re-encrypt 的独立授权与审计策略。
## 不采用方案
### 在 cluster-control 内集成所有云 KMS SDK
拒绝。会把 provider 重依赖、长期 decrypt credential 和网络故障域带入全部控制面副本。
### 让 rotation Job 同时负责首次 Secret create
拒绝。Kubernetes RBAC 无法把 create 收紧到一个未来对象名。
### 只备份 Kubernetes Secret 或只保存 material hash
拒绝。前者没有独立外部 custody,后者无法恢复;两者都不能证明 wrapped blob 与原 key、
durable fact 和历史 Artifact 同时一致。
### 恢复后自动把旧 key 重新加入生产 keyring
拒绝。会绕过 retirement、审批、运行时 reload 和后续再次安全退役的状态机。