Files
qinglong/docs/adr/ADR-0061-worker-local-certificate-identity-and-renewal.md
T

8.2 KiB
Raw Blame History

ADR-0061Worker 本地证书身份与显式续期

  • 状态:Proposed
  • 日期:2026-07-20
  • 关联 RFCQL-RFC-0001 D-23、D-37、D-58、D-59、D-60
  • 关联 ADRADR-0012、ADR-0040、ADR-0058、ADR-0059、ADR-0060

上下文

ADR-0059/0060 已建立 Worker ingress 的 TLS 1.3 mutual TLS 与服务端信任热重载,但 Worker 侧仍缺少可独立部署的证书生命周期。若把私钥交给 control-plane 生成、直接覆盖正在使用的 PEM 文件,或让每个 Worker 自带一个常驻续期 timer,会分别扩大 CA/私钥 blast radius、制造断电后的混合身份,并让低性能路由设备承担不可见的常驻成本。

Worker transport certificate 还不能取代 ql3w credential:前者只证明某个客户端持有外部 CA 签发的私钥,后者才绑定 QingLong Worker principalSession generation/version 与 Run Lease 继续独立 fencing。

决策

1. 独立 Worker package,不反向依赖控制面

Worker 证书能力放入独立 @qinglong/worker-runtime workspace。该 package 不依赖 legacy 根应用、runtime-core、cluster-control、cluster-postgres、Express、Drizzle 或任一数据库 driver;根 edge importer 也禁止依赖或加载它。

证书 enrollment 使用 exact-pinned @peculiar/x509reflect-metadata,但只通过 enrollment 子入口和续期时的 dynamic import 加载。package 主入口只加载 Node 内建的证书校验、文件存储与协调器,steady state 不把 ASN.1/PKI 实现带入内存。禁用 Worker Profile 时不得安装到 edge 专属产物。

2. 私钥只在 Worker 本地生成

Worker 生成 ECDSA P-256/SHA-256 可导出 key pair,并创建带 clientAuthdigitalSignature 和 non-CA 约束的 PKCS#10 CSR。Worker ID 只进入 CSR subject 的描述字段,不建立 QingLong authority。

CA adapter 只取得 CSR、Worker ID、当前已验证 leaf fingerprint 和 AbortSignal,返回证书链;它不得返回或覆盖本地 trust anchor,也不能取得待安装私钥。当前 package 不提供匿名 enrollment endpoint,不持有 CA signing key,也不选择 cert-manager、SPIFFE/SPIRE、Vault 或离线 CA。

私钥以 Buffer 短暂存在;成功或失败后协调器都调用 dispose() 清零它。该措施不宣称能够清理 native heap、调用方副本、页缓存或受控硬件中的密钥。

3. 安装前完整验证,信任只来自本地配置

安装前必须验证:

  • PEM 总量、证书数量和输入形状有硬上限;
  • leaf 当前有效、不是 CA、包含 client-auth EKU,且剩余有效期达到 policy 下限;
  • private key 与 leaf SPKI constant-time 匹配;
  • 每个 intermediate/trust anchor 当前有效且是 CA
  • 有界证书路径可到达本地提供的 1 至 16 个 trust anchor
  • identity fingerprint 取 leaf DER SHA-256,不因 PEM 排版或附带 intermediate 改变。

Issuer response 不能成为 trust source。证书 subject/SAN/serial/fingerprint 都不能替代 ql3w credential、Worker Session 或 Run Lease。

4. 代际目录与单文件 active commit

身份 root 由单个 Worker runtime 独占,必须是绝对路径;root/generation 目录权限为 0700private key、certificate chain 和 metadata 为 0600,读取拒绝 symlink、不安全权限、超限文件和畸形 manifest。

安装顺序固定为:

  1. 在同一 generations/ 目录创建 staging generation
  2. 独占创建并 fsync key、chain、metadata
  3. fsync staging 后 rename 为正式 generation
  4. fsync generations/
  5. 独占创建临时 active manifestfsync 后 rename 为 active.json
  6. 在 rename 完成时标记 generation 已提交,再 fsync identity root
  7. 最后有界清理旧 generation,失败只报告 cleanupPending,不得回滚 active identity。

即使 active rename 后的目录 fsync 报错,也不得删除已经可见的新 generation。旧 generation 默认保留一份用于运维取证;总 generation 数有硬上限,达到上限时 fail closed,不做无界扫描。

不支持多个 Worker 进程共享 identity root、NFS/对象存储目录,或不保证同目录原子 rename/fsync 语义的文件系统。Profile composition 必须保证单 owner。

5. 续期协调器由外层显式触发

package 不注册 timer、watcher、signal handler、网络 client 或数据库连接。WorkerCertificateRenewalCoordinator.run() 只执行一次检查;edge 可以由低频已有 maintenance tick/cron 调用,集群节点可以由 Profile lifecycle 或外部 Secret controller 触发。

同一进程的并发调用合并为一个 in-flight operation。策略具有硬边界:renew-before 1 小时至 30 天、签发证书最小有效期 1 小时至 365 天且必须大于 renew-before、单次操作 1 至 120 秒、退避基数 1 秒至 1 小时、最大退避不超过 6 小时、连续失败计数最多 16。

失败使用 50%100% jitter 的指数退避,并把失败次数、下次尝试、最后尝试和最后成功时间原子写入 renewal.json,避免重启后形成 CA retry storm。调用方主动 Abort 只终止本次操作,不计为签发失败;内部 timeout、trust/enrollment/issuance/install 失败进入退避。证书到期或本地身份不可验证时返回 unavailable,不得继续建立新的受信 transport。

协调器只返回稳定、低敏状态和时间,不返回 PEM、private key、CA adapter 错误、文件内容或远端响应。告警/metric 由 Profile 根据 notAfterMsnextAttemptAtMs 和结果状态产生。

被否决的替代方案

  1. 由 cluster-control 生成 Worker private key:扩大常驻入口权限,并让密钥跨网络和服务边界流动。
  2. CA response 同时下发 trust root:被攻陷的 enrollment transport 可以自行建立信任。
  3. 直接覆盖固定 key/certificate 文件:断电或多文件更新会留下 key/certificate 混合代际。
  4. 默认后台 timer/watch Secret 文件:增加 edge 常驻成本,并与部署平台的调度/更新语义竞争。
  5. 续期失败后进程内立即重试:重启会清空退避并对 CA 形成惊群。
  6. 证书到期后继续使用旧连接:把明确失效的 transport possession 变成隐式宽限期。
  7. 在根 package 直接加入 PKI 依赖:即使 dynamic import,也会扩大 edge 安装体积和供应链。

影响与未完成项

正向影响:

  • Worker 私钥不离开本地 identity boundary
  • 外部 CA、QingLong credential 和 Session/Lease authority 保持分离;
  • 安装和断电恢复只需裁决一个 active pointer
  • edge/cluster 可选择不同触发 cadence,共享相同一次性协调语义;
  • 失败退避跨重启持久化,过期身份 fail closed
  • steady-state Worker 主入口不加载 PKI enrollment library。

仍未完成:

  • cert-manager、SPIFFE/SPIRE、Vault PKI 或离线 CA adapter 与部署模板;
  • production maintenance tick 与 Session fail-close/recovery 已由 ADR-0238 完成;Kubernetes identity generation/Recreate/单节点 PVC 已由 ADR-0239 完成;仍缺 ingress reload controller、生产 RBAC、CA overlap 分区/回滚与正式 PKI adapter
  • ql3w credential enrollment/recovery 产品流程;
  • 证书到期/续期失败 metric、低敏 audit 和用户告警;
  • TPM/PKCS#11/non-exportable key provider
  • 固定路由器、断电/文件系统故障注入与 x64/arm64 资源基线;
  • 完整 Worker transport、ACK/completion/Artifact 生命周期。

验证

  1. 真实 P-256 CSR 可以验签,private key dispose 后 Buffer 被清零。
  2. 真实测试 CA 签发的 client certificate 在 key、EKU、有效期和 trust chain 全部匹配时可安装/重读,错配 key 被拒绝。
  3. generation retention 有界,文件/目录权限分别保持 0600/0700
  4. 并发续期调用只签发一次;新身份安装后再次调用返回 not_due
  5. CA 失败产生持久化 jitter backoff,重启/重复调用在窗口内不再次访问 CA。
  6. 调用方 Abort 不写失败退避;identity 到期/不可验证时结果为 unavailable。
  7. package 主入口加载测试证明没有载入 @peculiar/x509
  8. edge 与 cluster dependency audit 同时证明根 importer 隔离和 Worker package 的 exact dependency/source boundary。