12 KiB
ADR-0268:签名的外部 Prompt 输出密钥托管与恢复证明
- 状态:Proposed
- 日期:2026-08-03
- 关联:RFC D-244、D-245、D-247、D-249;ADR-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/bytes;wrapped 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:
- 对 wrapped blob 复算 bytes/SHA-256,并验证 receipt digest、签名和 pinned key;
- 对外部 unwrap 后的 32 字节 material 复算 domain-separated material proof,要求与
rotation completion 或 retirement preparation 的 durable
keyId/materialProof/catalogDigest完全一致,再使用正式 AES-256-GCM/AAD 逻辑打开 exact Artifact。
成功只返回 content-free recovery proof:custody/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 显式 pin,authorization 不能自报信任根。 过期、self-approval、重复 User/auth/key、签名或 exact fact 漂移全部失败关闭。
7. Verifier 是无网络、无数据库的隔离产品入口
ql3-prompt-output-key-recovery-verify 只读一个私有 workspace:authorization、custody receipt、
wrapped blob、外部 unwrap 得到的 32 字节 material、durable fact、Artifact 及三个 pinned public
key。所有文件必须是稳定、单 hard-link、不可写/执行、不可跟随 symlink 的 regular file;私有
文件禁止 other 读取。CLI 完成后清零持有的 recovered/wrapped material buffer,stdout 只有
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 + 显式 CA,token 只从私有
只读文件读取;HTTP 仅允许 127.0.0.1 测试 fixture。wrap 调用 Transit encrypt 后生成签名 receipt
和单一 0400、no-replace bundle;exact 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 仍为 19,package consumer graph 和依赖树不变。
接受门
本 ADR 保持 Proposed,直到:
- 在 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 门;
- 把当前 pinned Ed25519 双人 authorization 接到真实外部 IdP/审批 ceremony,取得非 fixture 的不同 User/authentication 证据;
- 在隔离恢复环境中从 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;
- 覆盖 receipt/backup 丢失、错误 KMS key version、签名轮换、material drift、Artifact 篡改、COMMIT response-loss 和主库 promotion;
- 明确实际 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 和后续再次安全退役的状态机。