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

163 lines
7.9 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-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_DIRECTORY`production 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 stringv1 通过短请求生命周期、
不缓存、不记录、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。
## 当前验证
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 关闭。