7.9 KiB
ADR-0233:Cluster Secret 使用 authority-gated mounted-files provider
- 状态:Accepted
- 日期:2026-07-30
- 关联 RFC:QL-RFC-0001 D-14、D-57、D-104、D-118、D-215、D-217
- 关联 ADR:ADR-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 service,Secret delivery 保持不可用;mounted-files:要求绝对QL3_WORKER_SECRET_ROOT_DIRECTORY,production process 动态加载 provider 并在 Worker ingress 启动前验证 root。
Profile gate 先于这些环境变量。Edge、Standalone、Worker 及 disabled ingress
不会读取 root、加载 provider 模块或增加常驻资源。测试/embedding 可以继续经窄
RemoteWorkerSecretValueProvider port 注入其他 adapter。
2. authority 成功之后才能接触明文
调用顺序保持:
- mTLS + Worker credential 认证;
- PostgreSQL 在 Attempt authority 下复验 Session、Run、Attempt、dispatch lease、 generation/version/token、execution revision/digest 和 exact SecretRef set;
- provider 只接收去除 lease token 的 durable authority;
- provider 读取 exact material;
- 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 固定:
- provider:
mounted-files; - root:
/var/run/secrets/qinglong3/worker-values; - 独立 Secret:
ql3-cluster-worker-values; optional: true、defaultMode: 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。
当前验证
- provider 定向覆盖 stable hash、atomic replacement、in-root Kubernetes symlink、root/target escape、权限、严格 UTF-8、缺失 root/material 和 dispose;
- Worker config/process/Secret delivery 定向 21/21;
- cluster-control 全量 159 项:157 pass、2 条外部服务条件 skip、0 fail;
- deployment audit 无 findings,证明独立可选 Secret、
0440、只读 mount、固定 provider/root 和无 ServiceAccount token; - Linux arm64 Node 24.18.0 在 512 MiB、2 CPU、256 PID、零 swap、非 root、只读
root/workspace 下通过:
memory.peak=42033152bytes、模块加载 RSS 增量24526848bytes、零 OOM/oom_kill; - workspace 仍为 20 个 QL3 package,没有新增 migration、表、生产依赖、timer、 watcher、listener、Pool、连接、sidecar 或 Kubernetes API 权限。
- 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。
尚未关闭
- Secret 管理 CLI/API/UI、Project Policy/Approval、rotation inventory/receipt、 version retention/GC 和告警;
- 直接 Vault/KMS/HSM adapter 的可选供应链、认证、rate limit 和 outage contract;
- Worker materialization 后的 tmpfs/文件清除、Executor-specific injection 与真实 Pod/节点回收证据;
- 直接 CSI/Vault Agent adapter 自身的 live rotation/credential outage、raw-wire response loss、节点丢失与 Kubernetes control-plane 多副本故障证据;基础 Kubernetes Secret 双副本 atomic rotation 已由 ADR-0494 关闭。