Files
qinglong/docs/adr/ADR-0123-worker-production-credential-material-boundary.md
T

6.1 KiB
Raw Blame History

ADR-0123Worker Production Credential Material Boundary

  • 状态:Accepted(请求时加载、轮换消费与 Session 拒绝分类已实现;远端签发/恢复仍为发布 Gate)
  • 日期:2026-07-23
  • 关联 RFCQL-RFC-0001 D-23、D-59、D-60、D-121、D-122
  • 关联 ADRADR-0058、ADR-0059、ADR-0060、ADR-0061、ADR-0121、ADR-0122

背景

Worker certificate store 已用 generation + active manifest 持久化本地私钥/证书,续期 coordinator 也已 保持无 timerCluster Admin 能一次性签发 ql3w credentialWorker HTTPS client 则在每个请求前调用 credential provider。但此前 product 入口只能注入测试 provider,没有具体、受限且支持轮换的 material adapter。若部署把 PEM/token 读一次后长期缓存在 composition root,证书 active pointer 或 token 原子替换 不会生效;若新增 watcher/timer,则会给路由设备增加第二套常驻生命周期。

证书 possession 与 ql3w principal 仍是两个 authority。把 token 写进 certificate manifest、Session journal 或 Agent pool key 都会错误耦合它们,并扩大 secret 的持久化和重放范围。

决策

1. 不新增 package,只开放显式 production credential subpath

具体 adapter 放在 @qinglong/worker-runtime/production-credentials,默认 package 根与本机 Edge/Standalone 制品都不加载它。它不创建数据库、socket、Agent、watcher、timer 或信号处理器,只实现既有 WorkerIngressHttpsCredentialProvider

product deployment 注入:

  • 既有 WorkerCertificateStore.readActive() authority
  • 本地 trust-anchor provider
  • 一个绝对、规范化、非根路径的 ql3w token 文件;
  • 可选 expected credential ID 和时钟。

2. 每个请求重新裁决 active certificate 与 token

provider 每次 load(signal) 都先检查取消和本地时钟,再读取 trust anchors,以同一 observation 调用 certificate store 验证 active generation。随后以 O_NOFOLLOW 重新打开 active key/chain,复验私有 parent/file 权限、普通文件、1 MiB hard cap,并再次执行 key/certificate/trust/validity 校验及 manifest fingerprint 对比,关闭 active-pointer 后文件替换的 TOCTOU 窗口。

token 文件最多 256 bytes,父目录不得对 group/other 开放,文件不得是 symlink 且权限不得对 group/other 开放。内容只能是一个 canonical ql3w_<credentialId>_<43-char-secret>,允许单个结尾 LF; 未知字符、多行、credential ID 漂移、宽权限或不可用存储全部 fail closed。部署以同目录原子 rename 替换 tokencertificate renewal 继续以 active manifest 切代;下一请求自然观察新 generation,不需要 watcher。

3. Client 复制后必须释放 provider-owned Buffer

WorkerIngressHttpsCredentials 增加可选同步 dispose()。共享 HTTPS client 在校验并复制 certificate、key、 trust 后,无论成功还是 malformed credential 都调用它;dispose 失败视为 credentials unavailable,且已经复制 的 Buffer 也清零。现有 provider 不实现该 hook 时保持兼容。

Agent pool identity 继续只由 certificate/key/trust digest 决定;token 每请求进入 Authorization header,轮换 token 不新建 Agent。证书代际变化产生新的 pool identity,旧 material 不参与新请求。

JavaScript header string 不能可靠清零;本决策只承诺 provider/file/Buffer 边界的显式清理,不宣称清除 V8、 TLS native heap、内核页缓存或运维备份中的所有副本。

4. Provider 不是 credential issuer 或 recovery authority

本 adapter 只消费已由可信部署 ceremony 原子发布的 token,不调用 Cluster Admin 签发接口,不保存服务端 digest/version/expiry,不自动轮换或撤销 credential,也不把认证拒绝解释为可自行签发。共享 transport 只 暴露低敏 HTTP status classSession client 将 401/403 映射为 credential rejected、409 映射为 fenced coordinator 立即停止暴露 Pull authority,但继续在原 Session/lease 上重试 heartbeat。token 修复后同一 Session 恢复,禁止自动 register 新 Session。远端 issue/rotate/revoke/recovery、一次性 secret delivery acknowledgement、deployment template 与其他 route 的产品策略仍需独立协议和 Gate。

被否决的替代方案

  1. 启动时只读一次 PEM/tokenactive pointer 与 Secret rotation 在进程重启前不生效。
  2. 为文件轮换建立 watcher/timer:增加路由设备常驻资源,并与原子 rename/平台 Secret controller 竞争。
  3. 把 token 放入 certificate manifest:混合 transport possession 与 QingLong principal authority。
  4. 把 token 加入 Agent pool key:每次 token 轮换都复制 TLS socket pool,且 header authority 本不属于 TLS。
  5. provider 收到 401 后自行签发新 credential:绕过管理 Policy、audit、delivery acknowledgement 与旧 Session fencing。
  6. 新建 credential package:该 adapter 与 Worker transport 总是同部署、没有独立依赖或发布责任,违反 D-85/ADR-0087。

验收证据

  1. 真实 certificate store + 测试 CA 证明 active certificate 与 token 原子替换在下一次 load 可见,旧/new material 不混代。
  2. credential ID 漂移、token 宽权限、相对路径和非法 ID 均在网络前失败关闭;pre-abort 零 trust/store/file 读取。
  3. provider 返回的 certificate/key/trust Buffer 可幂等 dispose;共享 client 在成功和拒绝路径都调用 hook。
  4. Worker Runtime 122/122 通过,覆盖 JSON、stream、TLS 1.3、Session、Offer、Activation、Artifact、 Completion、Lease 与 production lifecycle;严格类型检查通过。
  5. 非 200 response 不读取/保存错误 body401/403 立即暂停 Pull409 标记 Session fenced503 在已观察 lease 内保持原 Session,并证明 token 修复后只恢复 heartbeat、不创建新 Session。
  6. worker-credential/worker-credential-tokentypesVersions 与既有 exports 对齐,旧 Node module resolution 的 TS consumer 不再需要复制 credential ID contract。