Files
qinglong/docs/adr/ADR-0073-node-sqlite-encrypted-local-secret-store-and-keyring-lifecycle.md
T

101 lines
7.8 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-0073Node SQLite 加密本机 SecretStore 与 Keyring 生命周期
- 状态:Proposed
- 日期:2026-07-20
- 关联:D-05、D-17、D-25、D-37、D-62、D-65、D-70、D-72、ADR-0024、ADR-0025、ADR-0063、ADR-0066、ADR-0071
- 取代:ADR-0025 中位于 `back/runtime``0014` 和 Sequelize adapter 的实现事实;保留其 SecretRef、envelope 与威胁模型决策
## 上下文
ADR-0025 已定义 Project-bound SecretRef、AES-256-GCM append-only envelope 和数据库外 keyring,但实现仍位于 2.x 根目录的 `back/runtime`repository 依赖 Sequelize migration `0014`。新的 `@qinglong/local-application` 只接受可选内存 provider;因此全新 3.0 package graph、Node 24 SQLite authority 和真实 dispatcher 没有可达的默认本机 SecretStore。继续把 legacy 原型视为 3.0 已实现,会让 RFC、产物依赖和运行事实相互矛盾。
本机实现还必须兼顾路由设备:不能引入 KMS sidecar、第二 SQLite 连接、常驻 key cache、轮询 timer 或每任务 Secret worker。另一方面,runtime 自动生成主密钥会让数据库迁移、密钥备份和应用启动形成不可审计的隐式副作用。
## 决策
### 1. 契约进入 runtime-core,密码学进入独立包
`@qinglong/runtime-core/local-secret` 拥有 canonical `qlsecret:v1` codec、envelope、repository、key provider 和 environment provider port。`@qinglong/local-secret` 生产只依赖 runtime-core,拥有 AES-256-GCM、私有 keyring provider、加密服务和显式 keyring 管理函数;不得导入 SQLite、Profile、legacy、cluster、ORM、HTTP 或 Worker。
依赖方向固定为:
```text
runtime-core <- local-secret <- local-application
^
|
local-sqlite
```
SQLite adapter 实现 envelope port,但不依赖密码学包;local-secret service 消费 port,但不打开数据库。这样 cluster 可以实现 PostgreSQL + KMS/Vault adapter,而不在领域服务中增加 Profile 分支。
### 2. Node 24 SQLite 是唯一 envelope authority
`0007-local-secret-envelopes` 新增 `QingLong3LocalSecretEnvelopes`,以 `(project_id, secret_name, version)` 为主键,只保存 key ID、固定算法、12-byte nonce、最大 16 KiB ciphertext、16-byte tag、mutation ID 和时间。mutation/current/key-usage 三个索引分别服务幂等、当前版本和未来 rekey 证明。
`0008-capability-v4` 将 local-control-core 推进到 v4。repository 直接加入现有 `LocalSqliteRunRepository`,与 Run、dispatch、receipt 和 control 共用一个 DatabaseSync、256 operation 队列和 close fence;禁止创建 Secret 专属 SQLite 连接。append 使用 `BEGIN IMMEDIATE`,先查 mutation replay,再比较 current version,最后写 envelope;两个 authority 对同一 expected version 只能有一个 winner。
### 3. Keyring 生成和轮换必须显式、原子、可恢复
keyring 是数据库外、最多 16 个 32-byte key 的私有 JSON 文件:
- 首次 provision 使用 CSPRNG,写入同目录 0600 临时文件并 fsync,再用 hard-link no-replace 发布;已存在目标绝不覆盖;
- active-key rotation 要求调用方提供 expected active key ID,并以 0600 exclusive lock file 串行化管理者;新快照同目录写入、fsync、rename 和 directory fsync
- lock 残留、active ID 漂移、key 数耗尽、symlink、宽权限、畸形/超大文件全部 fail closed;不得自动猜测或删除 stale lock;
- runtime 每次解析按需重读 keyring,不建立常驻 key cache、watcher 或 timer;同一批次相同 key 只复制一次并在结束时清零。
轮换只改变新写入使用的 active key,不等于历史 rekey。历史 key 在其 envelope、数据库备份和恢复窗口都有可达引用前不得删除。
### 4. Application 必须在恢复和 admission 前证明 SecretStore ready
enabled local-application 必须提供绝对 `secretKeyringPath`。ADR-0140 后顺序固定为
adopted storage ready → bounded Plugin Package recovery safe → keyring
private/readable/active-key preflight → `secrets_ready` → Run/domain startup recovery →
lifecycle → admission。Package stage source 不可用时不读取 keyring;缺失、宽权限、
symlink、错误 key 或损坏 keyring 必须在 stack factory、dispatcher、Artifact 和 spawn
前失败,并按 storage/fence ownership 反向清理。
application 私有构造 `EncryptedLocalSecretService(storage.localSecrets, keyring)` 并注入 dispatcher;不再接受调用方用 `undefined` 绕过本机 SecretStore。Task recipe 仍只保存 opaque ref,明文只在 Attempt materialization 窗口进入环境对象,不进入 SQLite、Event、Artifact metadata、审计或错误。
### 5. 不自动把 2.x Envs 变成 Secret
旁路 adoption 保留 legacy `Envs`,但不会复制到新 envelope 表,也不会生成 keyring。首次 provision、Secret 创建/轮换和未来 legacy import 必须由短生命周期管理 authority 执行。runtime migration 和 application bootstrap 都不得自动生成 key、选择 Secret、回显明文或删除旧数据。
## 替代方案
1. **继续调用 `back/runtime` 的 Sequelize SecretStore**:让全新 3.0 反向依赖 legacy ORM/migration,拒绝。
2. **application 缺 provider 时把 Secret 解析为 missing**:可在无 Secret 的测试中假装 active,生产配置错误直到任务运行才暴露,拒绝。
3. **首次启动自动生成 keyring**:密钥副作用与数据库 adoption、备份和容器重建耦合,拒绝。
4. **把 keyring 放入 SQLite 或共享集群卷**:数据库泄漏即可解密,且没有 KMS/多副本审计语义,拒绝。
5. **常驻解密 cache 或 keyring watcher**:扩大明文/密钥驻留并给 edge 增加隐藏资源,拒绝。
6. **rotation 后立即删除旧 key**:历史版本与备份不可恢复,拒绝。
## 影响与未完成项
已完成:
- runtime-core exact-shape Secret contract 与 canonical Project-bound ref
- 独立 local-secret 包的 AES-GCM、通用错误和有界批量解析;
- Node 24 SQLite v4 migration/schema/readiness/repository 与双 authority CAS
- 私有 keyring no-replace provision、expected-active rotation、历史 key 保留和运行期 reload
- application `secrets_ready` 门禁和真实 dispatcher provider 注入;
- edge/standalone 均无新增 timer、sidecar、第二连接或 cluster dependency。
仍未完成:
- ADR-0074 已补齐 `secret.manage` 的强 Principal、Project Policy、durable security audit、撤权 fence 与不回显领域服务;仍缺首 owner bootstrap、`secret.use` 全入口装配和不回显 CLI/HTTP/UI
- 按 key usage 有界扫描、历史 envelope rekey、key retirement proof 与 crash-resume journal
- 数据库/keyring 配对备份、恢复演练、丢失告警和容器 Secret mount 文档;
- 日志已知值掩码、heap/core dump 策略和物理 edge 的写放大/ENOSPC/断电门禁;
- PostgreSQL envelope 与 KMS/Vault contract、多副本并发和 Remote Worker 临时 Secret delivery。
因此本 ADR 关闭“全新本机运行链没有 SecretStore”的架构断层,但不授权未认证入口管理 Secret,也不表示 cluster Secret 生命周期完成。
## 验证
1. canonical ref、固定 AES-GCM vector、AAD/密文/key/tag 篡改均有测试。
2. 原始 SQLite row 不包含 plaintextcurrent/exact/history 保持位置并跨 active-key rotation 可解密。
3. mutation replay 比较解密后语义,stale expected version 稳定冲突;双 SQLite authority 只有一个 rotation winner。
4. keyring 必须 0600、普通文件、无 symlinkprovision 不覆盖,rotation 保留历史 key并 fenced active ID。
5. application 在 stack factory 前验证 keyring,失败不启动 recovery/lifecycle/admission 并释放 storage/fence。
6. dependency/source/lock、Profile advisory 门禁包含第二十二个 importerapplication tarball 明确不包含短生命周期 Secret 管理 authority。