Files
qinglong/docs/adr/ADR-0233-authority-gated-mounted-cluster-secret-provider.md
T

7.9 KiB
Raw Blame History

ADR-0233Cluster Secret 使用 authority-gated mounted-files provider

  • 状态:Accepted
  • 日期:2026-07-30
  • 关联 RFCQL-RFC-0001 D-14、D-57、D-104、D-118、D-215、D-217
  • 关联 ADRADR-0059、ADR-0073、ADR-0112、ADR-0119、ADR-0231

背景

Remote Worker Secret delivery 已经在 Attempt advisory lock 下复验 Worker Session、Run/Attempt/Lease、generation/version/token、execution digest 和完整 SecretRef 集合,但 production process 只有注入 port,没有正式 material provider。 没有 provider 时 /secrets 按设计失败关闭,因此含 Secret 的 Remote Worker Task 无法运行。

把明文 Secret 放进 PostgreSQL或环境变量会扩大持久化、日志、进程快照和备份暴露 面;让 control Pod 直接访问 Kubernetes Secret API 会增加 ServiceAccount 权限和 网络故障域;把 Vault/KMS SDK 作为所有 Cluster、Standalone、Edge 的基础依赖,会 增加镜像、SBOM、credential chain、连接与小型设备成本。

Kubernetes Secret、CSI Secret Store 和 Vault Agent 都能把材料投影为只读文件, 并通过原子目录或 symlink 切换完成 rotation。Cluster 需要的是一个受限的 filesystem adapter,而不是在 v1 绑定某个外部 Secret 产品。

决策

1. provider 只在 Worker ingress 后、显式配置时加载

QL3_WORKER_SECRET_PROVIDER 首版只允许:

  • disabled 或未设置:不构造 Secret serviceSecret delivery 保持不可用;
  • mounted-files:要求绝对 QL3_WORKER_SECRET_ROOT_DIRECTORYproduction process 动态加载 provider 并在 Worker ingress 启动前验证 root。

Profile gate 先于这些环境变量。Edge、Standalone、Worker 及 disabled ingress 不会读取 root、加载 provider 模块或增加常驻资源。测试/embedding 可以继续经窄 RemoteWorkerSecretValueProvider port 注入其他 adapter。

2. authority 成功之后才能接触明文

调用顺序保持:

  1. mTLS + Worker credential 认证;
  2. PostgreSQL 在 Attempt authority 下复验 Session、Run、Attempt、dispatch lease、 generation/version/token、execution revision/digest 和 exact SecretRef set
  3. provider 只接收去除 lease token 的 durable authority
  4. provider 读取 exact material
  5. TLS response 完成后调用 dispose。

fence、replay drift、Project/SecretRef mismatch 或 repository unavailable 时绝不调用 provider。provider 异常、缺文件、损坏或超预算统一映射为低敏 unavailable。

3. SecretRef 永远不成为路径

每个投影文件名固定为:

lowercase_hex(SHA-256(canonical qlsecret:v1 SecretRef))

文件名只含 64 个 [0-9a-f]Project、name、version 不参与路径拼接。hash 是稳定 的 path-safe identifier,不被描述为加密或访问控制;部署者仍必须把 Secret projection 视为敏感材料。

每次 resolve 都重新解析 root 和 exact candidate

  • configured root 必须是直接目录,不能是 symlink;
  • candidate 的 resolved target 必须仍位于 root 下;
  • 允许 Kubernetes atomic-writer 的 in-root symlink
  • resolved target 必须是单链接 regular file
  • 文件不可 executable、group writable 或 other-accessible
  • 单值最多 16 KiB、整批最多 64 KiB
  • 值必须是严格 UTF-8 且不能含 NUL。

读取期间 projection target 漂移会失败关闭,由下一次请求读取新 generation。

4. rotation 不建立 watcher、cache 或 Kubernetes client

provider 不缓存值、不保存 manifest、不 watch 文件系统、不启动 timer,也不访问 Kubernetes API。每次已经授权的 delivery 重新打开文件,因此 Secret/CSI/Agent 原子替换会被下一请求观察。

无 version 的 current SecretRef 可以在相同 hashed key 下 rotation;带 version 的 SecretRef 使用不同 key,部署者在所有引用和 retention 完成前保留旧文件。自动 retention/GC、rotation approval 和用户可见 inventory 是后续独立产品切片。

读取 Buffer 在 response dispose 或失败时覆盖为零。现有 wire contract 使用 JS string,因此不能承诺垃圾回收前擦除 immutable string;v1 通过短请求生命周期、 不缓存、不记录、TLS、硬字节预算和及时 dispose 缩小暴露面,不能把它表述为硬件级 内存清除。

5. Kubernetes base 使用独立可选只读 Secret

base 固定:

  • providermounted-files
  • root/var/run/secrets/qinglong3/worker-values
  • 独立 Secretql3-cluster-worker-values
  • optional: truedefaultMode: 0440、read-only mount
  • Pod fsGroup: 10001
  • automountServiceAccountToken: false

base 不提交业务 Secret 内容。Secret 缺失时 volume/root 为空,启动仍可服务无 Secret Run;具体 SecretRef resolve 失败关闭。TLS/Worker credential/Artifact credential 继续使用原来分离的 Secret,不与业务值合并。

不采用的方案

将明文值保存到 PostgreSQL

拒绝。数据库角色、WAL、备份、复制、诊断和 SQL 查询面都会获得不必要的明文 custody,也破坏现有 digest/reference-only contract。

直接读取 Kubernetes Secret API

拒绝。control Pod 不应获得 Secret list/get/watch 权限;API client还会引入 token、 RBAC、网络、缓存和 watch 生命周期。只读 projection 已能提供所需 rotation 语义。

在 v1 内置 Vault/KMS SDK

拒绝作为基础闭包。部署者可以让 CSI/Vault Agent 投影文件,或后续通过同一 provider port 增加显式可选 adapter。基础镜像不应预付特定云厂商依赖、credential chain 和 空闲连接成本。

使用 Project/name 作为目录层级

拒绝。即使先校验,也会让用户控制的数据参与路径、权限和运维命名,并产生 traversal、 Unicode、大小写和 Kubernetes key 兼容问题。canonical ref hash 更小且稳定。

把全部值放进一个 JSON manifest

拒绝。一个值 rotation 会重写整个明文集合,解析时同时把无关 Project material 加载进内存,也更难实现单值权限、大小和 retention。

当前验证

  1. provider 定向覆盖 stable hash、atomic replacement、in-root Kubernetes symlink、root/target escape、权限、严格 UTF-8、缺失 root/material 和 dispose
  2. Worker config/process/Secret delivery 定向 21/21
  3. cluster-control 全量 159 项:157 pass、2 条外部服务条件 skip、0 fail;
  4. deployment audit 无 findings,证明独立可选 Secret、0440、只读 mount、固定 provider/root 和无 ServiceAccount token
  5. Linux arm64 Node 24.18.0 在 512 MiB、2 CPU、256 PID、零 swap、非 root、只读 root/workspace 下通过:memory.peak=42033152 bytes、模块加载 RSS 增量 24526848 bytes、零 OOM/oom_kill
  6. workspace 仍为 20 个 QL3 package,没有新增 migration、表、生产依赖、timer、 watcher、listener、Pool、连接、sidecar 或 Kubernetes API 权限。
  7. ADR-0494 已在真实三节点 K3s 中以两个跨节点 observer 证明 Kubernetes Secret atomic-writer rotation:两副本无重启观察新 generation,均无 Secret API 权限、 ServiceAccount token 与网络权限,projection 为只读 0440;删除 projection 后以 QL3_CLUSTER_MOUNTED_SECRET_UNAVAILABLE 失败关闭。v2 私有报告 24/24 gates 为 true,且报告/termination message 不含值或 SecretRef。

尚未关闭

  1. Secret 管理 CLI/API/UI、Project Policy/Approval、rotation inventory/receipt、 version retention/GC 和告警;
  2. 直接 Vault/KMS/HSM adapter 的可选供应链、认证、rate limit 和 outage contract
  3. Worker materialization 后的 tmpfs/文件清除、Executor-specific injection 与真实 Pod/节点回收证据;
  4. 直接 CSI/Vault Agent adapter 自身的 live rotation/credential outage、raw-wire response loss、节点丢失与 Kubernetes control-plane 多副本故障证据;基础 Kubernetes Secret 双副本 atomic rotation 已由 ADR-0494 关闭。