为 Kata guest 设计可证明 workload 身份的 SPIFFE 接入 #94

Open
opened 2026-09-17 18:21:42 +00:00 by panxiao81 · 4 comments
Owner

背景

正式 sandbox 集群的 Kata 4.1.0 / clh-runtime-rs 已部署成功。两台节点上的 Kata guest 均能启动,guest kernel 为 6.18.35;但按普通 Pod 方式挂载 csi.spiffe.io 后,guest 内连接 Workload API 失败。

已验证现象

  • CSI volume 和 spire-agent.sock 路径在 Kata guest 内可见;
  • spire-agent api fetch x509 连接该路径返回 connection refused;
  • 两台节点结果一致;
  • 同一 ServiceAccount/ClusterSPIFFEID 的普通 runc Pod 已验证可以获得 spiffe://ddupan.top/sandbox/smoke;
  • 原因是现有 SPIFFE CSI driver 只把 host 上的 Unix socket 目录 bind mount 给 Pod,Unix socket 连接不能跨 Kata VM/virtiofs 边界。

因此,不能把普通 Pod 的 CSI Workload API 注入原样复用于 ci-vm,也不能把路径存在误判为身份可用。

目标

为每个动态 Kata Pod 提供其自身、可由 Kubernetes workload 语义证明的 SPIFFE identity:

  • 身份仍基于 namespace、ServiceAccount、repo/task 等稳定 selector,不使用一次性 job ID;
  • 不共享外层 OpenSandbox server、runner 或节点 Agent 的 workload 身份;
  • 不把私钥/JWT 持久化到 Secret、节点目录或镜像;
  • 支持 SVID 轮换、Pod 删除后自动回收;
  • 错误 ServiceAccount/selector 必须无法取得身份;
  • 同一机制最好同时适用于普通 Pod 与 Kata Pool,或至少保持一致的授权模型。

需要评估的路线

  1. Kata guest 内按 Pod 启动 SPIRE Agent,以 PSAT 或受限 delegated identity 完成 bootstrap;
  2. host Agent 的 Delegated Identity API + 面向 guest 的内存代理,通过 vsock/hybrid-vsock 暴露标准 Workload API;
  3. Kata/CSI 是否存在可把 socket RPC 代理进 guest、而不是只共享 socket inode 的官方机制;
  4. 文件式 SVID 投递仅作为最后备选,必须说明私钥生命周期、轮换与 guest 隔离边界。

验收

  • 正确 Kata Pod 能通过标准 SPIFFE Workload API 取得预期 SVID;
  • 错误身份被拒绝;
  • 同 Pod 多容器的授权边界有明确说明;
  • 删除 Pod 后无 agent、socket、credential 或 registration entry 残留;
  • 威胁模型与 OpenBao/zot 使用方式记录到 runbook;
  • 自动化 smoke test 覆盖两台节点。

关联 #83、#47。

## 背景 正式 sandbox 集群的 Kata 4.1.0 / clh-runtime-rs 已部署成功。两台节点上的 Kata guest 均能启动,guest kernel 为 6.18.35;但按普通 Pod 方式挂载 csi.spiffe.io 后,guest 内连接 Workload API 失败。 ## 已验证现象 - CSI volume 和 spire-agent.sock 路径在 Kata guest 内可见; - spire-agent api fetch x509 连接该路径返回 connection refused; - 两台节点结果一致; - 同一 ServiceAccount/ClusterSPIFFEID 的普通 runc Pod 已验证可以获得 spiffe://ddupan.top/sandbox/smoke; - 原因是现有 SPIFFE CSI driver 只把 host 上的 Unix socket 目录 bind mount 给 Pod,Unix socket 连接不能跨 Kata VM/virtiofs 边界。 因此,不能把普通 Pod 的 CSI Workload API 注入原样复用于 ci-vm,也不能把路径存在误判为身份可用。 ## 目标 为每个动态 Kata Pod 提供其自身、可由 Kubernetes workload 语义证明的 SPIFFE identity: - 身份仍基于 namespace、ServiceAccount、repo/task 等稳定 selector,不使用一次性 job ID; - 不共享外层 OpenSandbox server、runner 或节点 Agent 的 workload 身份; - 不把私钥/JWT 持久化到 Secret、节点目录或镜像; - 支持 SVID 轮换、Pod 删除后自动回收; - 错误 ServiceAccount/selector 必须无法取得身份; - 同一机制最好同时适用于普通 Pod 与 Kata Pool,或至少保持一致的授权模型。 ## 需要评估的路线 1. Kata guest 内按 Pod 启动 SPIRE Agent,以 PSAT 或受限 delegated identity 完成 bootstrap; 2. host Agent 的 Delegated Identity API + 面向 guest 的内存代理,通过 vsock/hybrid-vsock 暴露标准 Workload API; 3. Kata/CSI 是否存在可把 socket RPC 代理进 guest、而不是只共享 socket inode 的官方机制; 4. 文件式 SVID 投递仅作为最后备选,必须说明私钥生命周期、轮换与 guest 隔离边界。 ## 验收 - 正确 Kata Pod 能通过标准 SPIFFE Workload API 取得预期 SVID; - 错误身份被拒绝; - 同 Pod 多容器的授权边界有明确说明; - 删除 Pod 后无 agent、socket、credential 或 registration entry 残留; - 威胁模型与 OpenBao/zot 使用方式记录到 runbook; - 自动化 smoke test 覆盖两台节点。 关联 #83、#47。
Author
Owner

2026-09-17 已完成 live PoC,结论:方案可行。Kata Pod 内以 native sidecar 启动 SPIRE Agent,使用 audience=spire-server 的 Pod-bound PSAT 向中央 Server 注册;Server 开启 use_pod_uid_for_agent_id 后,Agent ID 为 spiffe://ddupan.top/spire/agent/k8s_psat/sandbox-kata-poc/pod/{pod-uid}。随后创建 parentID 精确绑定该 Agent、SPIFFE ID 为 spiffe://ddupan.top/ci/poc/task/build、selector 为 unix:uid:2000 的临时 entry。guest 内 uid 2000 的 workload 通过 emptyDir 中的本地 Unix socket 成功取得 X.509-SVID,Pod exit 0;日志确认 registered=true 且签发的就是业务身份。关键约束:Pod 必须设置 shareProcessNamespace: true,否则 unix attestor 无法在 Agent 容器的 /proc 中解析另一个容器的 SO_PEERCRED PID。控制器还必须先创建 entry 再放行 job,本次用临时 init gate 验证了该顺序。PoC 后已删除 Pod、ConfigMap、entry 与三个临时 Agent,移除临时 Server profile并恢复 HelmRelease;Flux Ready=True、中央 Server 1/1、sandbox 两个 Agent 与 CSI 均正常,无残留。SPIRE 1.15.3 本身已支持 use_pod_uid_for_agent_id;缺口是当前 hardened chart 0.30.2 未暴露该字段。正式实现应给 chart 补齐 values/template 并向上游提交,随后采用包含修复的 release,不能依赖手改 ConfigMap。

2026-09-17 已完成 live PoC,结论:方案可行。Kata Pod 内以 native sidecar 启动 SPIRE Agent,使用 audience=spire-server 的 Pod-bound PSAT 向中央 Server 注册;Server 开启 use_pod_uid_for_agent_id 后,Agent ID 为 spiffe://ddupan.top/spire/agent/k8s_psat/sandbox-kata-poc/pod/{pod-uid}。随后创建 parentID 精确绑定该 Agent、SPIFFE ID 为 spiffe://ddupan.top/ci/poc/task/build、selector 为 unix:uid:2000 的临时 entry。guest 内 uid 2000 的 workload 通过 emptyDir 中的本地 Unix socket 成功取得 X.509-SVID,Pod exit 0;日志确认 registered=true 且签发的就是业务身份。关键约束:Pod 必须设置 shareProcessNamespace: true,否则 unix attestor 无法在 Agent 容器的 /proc 中解析另一个容器的 SO_PEERCRED PID。控制器还必须先创建 entry 再放行 job,本次用临时 init gate 验证了该顺序。PoC 后已删除 Pod、ConfigMap、entry 与三个临时 Agent,移除临时 Server profile并恢复 HelmRelease;Flux Ready=True、中央 Server 1/1、sandbox 两个 Agent 与 CSI 均正常,无残留。SPIRE 1.15.3 本身已支持 use_pod_uid_for_agent_id;缺口是当前 hardened chart 0.30.2 未暴露该字段。正式实现应给 chart 补齐 values/template 并向上游提交,随后采用包含修复的 release,不能依赖手改 ConfigMap。
Author
Owner

已建立内部 fork:https://git.ddupan.top/panxiao81/helm-charts-hardened 。fork main 从当前部署的上游 tag spire-0.30.2(e5901d68)精确分叉,补丁提交为 c1deb08a,内部 chart tag 为 spire-0.30.2-ddupan.1。新增 nodeAttestor.k8sPSAT.usePodUIDForAgentID,以及 externalK8sPSAT defaults/单 cluster override,默认 false。已补单元渲染测试和生成参数文档;go test ./...、helm lint、helm template 均通过,渲染确认 sandbox cluster 输出 use_pod_uid_for_agent_id: true。本次只建立并验证 fork,尚未切换线上 HelmRelease。

已建立内部 fork:https://git.ddupan.top/panxiao81/helm-charts-hardened 。fork main 从当前部署的上游 tag spire-0.30.2(e5901d68)精确分叉,补丁提交为 c1deb08a,内部 chart tag 为 spire-0.30.2-ddupan.1。新增 nodeAttestor.k8sPSAT.usePodUIDForAgentID,以及 externalK8sPSAT defaults/单 cluster override,默认 false。已补单元渲染测试和生成参数文档;go test ./...、helm lint、helm template 均通过,渲染确认 sandbox cluster 输出 use_pod_uid_for_agent_id: true。本次只建立并验证 fork,尚未切换线上 HelmRelease。
Author
Owner

2026-09-18 已完成线上切换。PR #96 已合并,Flux GitRepository 固定到内部 fork tag spire-0.30.2-ddupan.1@c1deb08a;Helm revision 8 使用 chart [email protected].2-ddupan.1+c1deb08ad4be,Ready=True 且无 drift。最终 Server 配置为 homelab=false、sandbox=false、sandbox-kata=true,现有 homelab/sandbox DaemonSet Agent ID 保持 node UID 不变。使用永久 sandbox-kata profile 再次启动 Kata smoke Agent,Pod UID 6709f320-97b9-449c-ab1e-10b83060660f 成功得到精确匹配的 .../sandbox-kata/pod/6709f320-97b9-449c-ab1e-10b83060660f Agent ID。测试 Pod、ConfigMap 和 Agent 记录均已清理。

2026-09-18 已完成线上切换。PR #96 已合并,Flux GitRepository 固定到内部 fork tag spire-0.30.2-ddupan.1@c1deb08a;Helm revision 8 使用 chart [email protected]+c1deb08ad4be,Ready=True 且无 drift。最终 Server 配置为 homelab=false、sandbox=false、sandbox-kata=true,现有 homelab/sandbox DaemonSet Agent ID 保持 node UID 不变。使用永久 sandbox-kata profile 再次启动 Kata smoke Agent,Pod UID 6709f320-97b9-449c-ab1e-10b83060660f 成功得到精确匹配的 .../sandbox-kata/pod/6709f320-97b9-449c-ab1e-10b83060660f Agent ID。测试 Pod、ConfigMap 和 Agent 记录均已清理。
Author
Owner

完成永久 sandbox-kata profile 的 CI workload 正反向端到端验收:Kata Pod 内 Agent 以 Pod UID parent 注册;动态 entry 绑定 spiffe://ddupan.top/ci/poc/task/build 与 unix:uid:2000。uid 2000 workload 成功获取 X.509-SVID并 exit 0;同 guest 内 uid 2001 返回 PermissionDenied/no identity issued并 exit 1。日志确认 registered=true、SPIFFE ID 和 TTL 均正确。测试 Pod、ConfigMap、entry 与 Agent 已清理。结论:Kata+SPIRE 数据面已就绪,但尚未达到可直接承载真实 CI 的控制面就绪状态;仍需调度器自动创建 Pod/entry、等待 entry 同步后放行 workload、复制/注入 trust bundle、删除时清理 entry/Agent,并将正式 CI ServiceAccount 加入 sandbox-kata allowlist。若 CI 需要访问 Bao/zot,还需为正式业务 SPIFFE ID 配置并验收对应授权映射。

完成永久 sandbox-kata profile 的 CI workload 正反向端到端验收:Kata Pod 内 Agent 以 Pod UID parent 注册;动态 entry 绑定 spiffe://ddupan.top/ci/poc/task/build 与 unix:uid:2000。uid 2000 workload 成功获取 X.509-SVID并 exit 0;同 guest 内 uid 2001 返回 PermissionDenied/no identity issued并 exit 1。日志确认 registered=true、SPIFFE ID 和 TTL 均正确。测试 Pod、ConfigMap、entry 与 Agent 已清理。结论:Kata+SPIRE 数据面已就绪,但尚未达到可直接承载真实 CI 的控制面就绪状态;仍需调度器自动创建 Pod/entry、等待 entry 同步后放行 workload、复制/注入 trust bundle、删除时清理 entry/Agent,并将正式 CI ServiceAccount 加入 sandbox-kata allowlist。若 CI 需要访问 Bao/zot,还需为正式业务 SPIFFE ID 配置并验收对应授权映射。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: panxiao81/homelab-infra#94