设计并验证基于 SPIFFE/SPIRE 的统一 Workload Identity #34

Open
opened 2026-09-10 08:58:57 +00:00 by panxiao81 · 3 comments
Owner

状态与优先级

基础设施与最小 PoC 已完成(2026-09-14)。 SPIRE、OIDC discovery 与 OpenBao JWT exchange 已上线并完成端到端验证;详细操作见 platform/spire/RUNBOOK.md。本 issue 保持 open,用于跟踪真实 CI/AI Agent、非 Kubernetes workload、SeaweedFS STS、HA 与恢复验证。

目标

为 Kubernetes、CI、AI Agent、长期 Linux 服务及未来的 Proxmox/microVM 临时计算实例建立统一的 workload identity:运行中的 workload 不持有预置长期凭据,而是由基础设施完成节点与进程证明后,取得短期、自动轮换的身份。

统一的机器主体使用 SPIFFE ID,例如:

spiffe://ddupan.top/k8s/<cluster>/ns/<namespace>/sa/<service-account>
spiffe://ddupan.top/ci/gitea/<repository>
spiffe://ddupan.top/agent/<class>/<instance>
spiffe://ddupan.top/compute/instance/<instance-id>
spiffe://ddupan.top/host/<host>/service/<systemd-unit>

架构决策

采用职责分离,而不是自研完整 OIDC/IAM:

人类身份:现有 Samba AD + Authelia
  AD 用户目录、交互式登录、MFA、OIDC及 legacy forward-auth

机器身份:SPIFFE/SPIRE
  Node Attestation、Workload Attestation、X.509-SVID、JWT-SVID

机密与凭据:OpenBao
  Secret、PKI、SSH CA、动态凭据及目标服务凭据交换

资源执行面:各目标服务
  OpenBao policy、SeaweedFS IAM policy、Kubernetes RBAC、Compute/DBaaS RBAC

Authelia 与 SPIRE 分别作为当前的人类身份 issuer 和机器身份根。消费端可以同时信任两个 issuer;不强制把 SPIRE JWT 再包装成人类 IdP 签发的 Token。Keycloak 仅作为未来人类 IAM 明确出现协议或管理能力缺口时的备选,不属于本方案依赖,也不安排迁移。

SPIRE 负责回答“当前调用进程是否有资格获得某个 SPIFFE ID”,不承担人类登录或通用资源授权。目标服务根据稳定的 SPIFFE ID 映射本地角色,避免为每个 workload 重复维护独立身份。

初始身份与证明方式

Kubernetes

  • SPIRE Agent 以 DaemonSet 运行;
  • 使用 Kubernetes PSAT 完成节点证明;
  • 使用 namespace、ServiceAccount、Pod 等 selector 完成 workload attestation;
  • workload 通过本地 Workload API 获取 SVID,不直接使用长期凭据。

Proxmox / microVM 临时实例

  • 控制面创建 SPIRE registration entry;
  • 创建实例时通过 cloud-init、vsock 或等价安全通道注入一次性 join token;
  • SPIRE Agent 首次注册后依靠自动轮换的 SVID 运行;
  • 实例销毁时删除或禁用对应 registration entry。

后续评估 x509popsshpop、TPM DevID 或自定义 NodeAttestor,替代通用 join token。

长期 Linux 主机及服务

  • 节点可使用 x509popsshpop 或未来的 TPM attestation;
  • workload 优先使用 Unix/systemd selector 区分具体服务,而不是把整台主机视为同一身份;
  • 本地 AI Agent 也必须取得独立 SPIFFE ID,不继承主机的全局权限。

外部 CI

GitHub-hosted Actions 等无法直接访问本地 SPIRE Agent 的环境,保留外部 OIDC federation/适配器方案。适配器只负责验证外部 proof 并映射稳定 principal,不拥有用户目录、签名根或资源 policy。

首个 PoC(已完成,2026-09-14)

只验证以下最短链路:

Kubernetes 测试 Pod
  -> SPIRE workload attestation
  -> 获取 aud=openbao 的 JWT-SVID
  -> OpenBao JWT auth 验证 SPIRE OIDC Discovery/JWKS
  -> 映射专用 Bao policy
  -> 签发短期 Bao token
  -> lookup-self 验证最小 policy,并主动 revoke-self

验收标准:

  • Pod 内无预置 Bao token、AppRole SecretID 或长期 AK/SK;
  • JWT-SVID 的 sub 是预期 SPIFFE ID,aud 严格绑定 openbao
  • 错误 namespace、ServiceAccount 或 workload selector 无法取得该身份;
  • OpenBao 仅授予 lookup-self/revoke-self 最小权限,不允许读取业务 secret;
  • Pod 重建后无需人工发放凭据;
  • SVID 自动轮换不影响后续登录;
  • 删除 registration entry 后无法再取得新 SVID;
  • 记录恢复、撤销、审计和 issuer 密钥轮换行为。

后续阶段

  • Gitea Actions runner 和 AI Agent 使用 SPIRE 身份登录 OpenBao;
  • SeaweedFS 验证 SPIRE OIDC,并通过 AssumeRoleWithWebIdentity 签发短期 S3 凭据;
  • credential-exec 从 Workload API 获取 JWT-SVID,再为子进程注入目标系统的短期凭据;
  • Proxmox 临时 VM 完成一次性节点注册、实例身份和销毁回收;
  • systemd/Unix workload attestation 覆盖长期主机服务;
  • 自建 Compute API、DBaaS API 原生验证 JWT-SVID 或 X.509-SVID;
  • 评估 SPIRE Server、Agent、OIDC Discovery Provider 的 HA、备份与灾难恢复;
  • 记录各消费端同时信任 Authelia 与 SPIRE 时的 issuer、audience 和主体命名约定。

Policy 边界

语义上只维护两类规则:

  1. 身份/信任规则:selector 或外部 proof 可以取得哪个 SPIFFE ID;
  2. 资源权限规则:稳定的 SPIFFE ID/平台角色在目标服务中可以执行什么操作。

JWT 的 audience、claims 和下游 role mapping 是授权传递与执行机制,不应成为额外的人工事实源。多个 workload 共享同一平台角色时,只新增主体到角色的绑定,不复制整份资源 policy。

SeaweedFS 与静态凭据

现有 SeaweedFS 继续由 Helm 管理。seaweedfs-operator 的 IAM CRD 要求关联 Operator 管理的 Seaweed CR,不适合作为当前 brownfield Helm 部署仅为 IAM 而引入,因此不采用 Operator 接管路线。

迁移期间继续把现有 -s3.config 视为静态 S3 身份的唯一运行配置。长期目标是使用 SPIRE JWT-SVID 调用 SeaweedFS Web Identity/STS,逐步移除 workload 的长期 AK/SK;Terraform/OpenTofu backend 等兼容性未经验证的调用方暂时保留独立、最小权限且可轮换的静态身份。

安全与恢复约束

  • SPIRE Server、OpenBao、Git 和集群恢复路径不得形成不可恢复的循环依赖;
  • SPIRE Agent socket 必须依靠 workload attestation 隔离,不能因能够访问 socket 就取得任意身份;
  • audience 必须按目标服务绑定,禁止签发全场通用 bearer token;
  • OIDC Discovery/JWKS endpoint 可被验证方访问,但 SPIRE Server 管理接口不得公开;
  • join token 仅用于 bootstrap,必须短 TTL、单次使用且绑定预期节点;
  • Delegated Identity API/Broker API 默认不启用,启用前必须评估其 impersonation 权限;
  • 每个长期凭据仍只能有一个事实源和写入方;
  • 生产迁移必须保留回退路径,不在 PoC 阶段撤销现有认证方式。

已否决或后置的方向

  • 自研完整 OIDC/IAM:协议、安全和维护成本过高;
  • 以 OpenBao 为中心构造完整 IAM:OpenBao继续聚焦 Secret、PKI和动态凭据;
  • SeaweedFS Operator 接管 IAM:与现有 Helm 部署的资源所有权不匹配;
  • 在本议题中自研 Authelia OIDC client controller:OIDC client 生命周期与 workload identity 是不同问题;若配置管理成为实际负担,应另行立项,不与 SPIRE PoC 耦合;
  • ZITADEL 作为 workload identity 核心:标准 IdP/Service Account 能力完整,但对任意基础设施 proof 的可定制验证接口不足;
  • 为 Keycloak 编写多套 workload SPI:Java SPI、版本兼容和各基础设施 SDK 的维护成本高于采用 SPIRE;
  • 当前迁移到 Keycloak:SPIRE 已解决机器身份,现有 Samba AD + Authelia 足以承担人类登录;在出现明确的人类 IAM、协议或管理能力缺口前,迁移收益不足以覆盖复杂度和风险;
  • 现在引入 Keto/OpenFGA:当前不需要额外的关系授权事实源,复杂资源关系出现后再评估。
## 状态与优先级 **基础设施与最小 PoC 已完成(2026-09-14)。** SPIRE、OIDC discovery 与 OpenBao JWT exchange 已上线并完成端到端验证;详细操作见 `platform/spire/RUNBOOK.md`。本 issue 保持 open,用于跟踪真实 CI/AI Agent、非 Kubernetes workload、SeaweedFS STS、HA 与恢复验证。 ## 目标 为 Kubernetes、CI、AI Agent、长期 Linux 服务及未来的 Proxmox/microVM 临时计算实例建立统一的 workload identity:运行中的 workload 不持有预置长期凭据,而是由基础设施完成节点与进程证明后,取得短期、自动轮换的身份。 统一的机器主体使用 SPIFFE ID,例如: ```text spiffe://ddupan.top/k8s/<cluster>/ns/<namespace>/sa/<service-account> spiffe://ddupan.top/ci/gitea/<repository> spiffe://ddupan.top/agent/<class>/<instance> spiffe://ddupan.top/compute/instance/<instance-id> spiffe://ddupan.top/host/<host>/service/<systemd-unit> ``` ## 架构决策 采用职责分离,而不是自研完整 OIDC/IAM: ```text 人类身份:现有 Samba AD + Authelia AD 用户目录、交互式登录、MFA、OIDC及 legacy forward-auth 机器身份:SPIFFE/SPIRE Node Attestation、Workload Attestation、X.509-SVID、JWT-SVID 机密与凭据:OpenBao Secret、PKI、SSH CA、动态凭据及目标服务凭据交换 资源执行面:各目标服务 OpenBao policy、SeaweedFS IAM policy、Kubernetes RBAC、Compute/DBaaS RBAC ``` Authelia 与 SPIRE 分别作为当前的人类身份 issuer 和机器身份根。消费端可以同时信任两个 issuer;不强制把 SPIRE JWT 再包装成人类 IdP 签发的 Token。Keycloak 仅作为未来人类 IAM 明确出现协议或管理能力缺口时的备选,不属于本方案依赖,也不安排迁移。 SPIRE 负责回答“当前调用进程是否有资格获得某个 SPIFFE ID”,不承担人类登录或通用资源授权。目标服务根据稳定的 SPIFFE ID 映射本地角色,避免为每个 workload 重复维护独立身份。 ## 初始身份与证明方式 ### Kubernetes - SPIRE Agent 以 DaemonSet 运行; - 使用 Kubernetes PSAT 完成节点证明; - 使用 namespace、ServiceAccount、Pod 等 selector 完成 workload attestation; - workload 通过本地 Workload API 获取 SVID,不直接使用长期凭据。 ### Proxmox / microVM 临时实例 - 控制面创建 SPIRE registration entry; - 创建实例时通过 cloud-init、vsock 或等价安全通道注入一次性 join token; - SPIRE Agent 首次注册后依靠自动轮换的 SVID 运行; - 实例销毁时删除或禁用对应 registration entry。 后续评估 `x509pop`、`sshpop`、TPM DevID 或自定义 NodeAttestor,替代通用 join token。 ### 长期 Linux 主机及服务 - 节点可使用 `x509pop`、`sshpop` 或未来的 TPM attestation; - workload 优先使用 Unix/systemd selector 区分具体服务,而不是把整台主机视为同一身份; - 本地 AI Agent 也必须取得独立 SPIFFE ID,不继承主机的全局权限。 ### 外部 CI GitHub-hosted Actions 等无法直接访问本地 SPIRE Agent 的环境,保留外部 OIDC federation/适配器方案。适配器只负责验证外部 proof 并映射稳定 principal,不拥有用户目录、签名根或资源 policy。 ## 首个 PoC(已完成,2026-09-14) 只验证以下最短链路: ```text Kubernetes 测试 Pod -> SPIRE workload attestation -> 获取 aud=openbao 的 JWT-SVID -> OpenBao JWT auth 验证 SPIRE OIDC Discovery/JWKS -> 映射专用 Bao policy -> 签发短期 Bao token -> lookup-self 验证最小 policy,并主动 revoke-self ``` 验收标准: - [x] Pod 内无预置 Bao token、AppRole SecretID 或长期 AK/SK; - [x] JWT-SVID 的 `sub` 是预期 SPIFFE ID,`aud` 严格绑定 `openbao`; - [ ] 错误 namespace、ServiceAccount 或 workload selector 无法取得该身份; - [x] OpenBao 仅授予 `lookup-self`/`revoke-self` 最小权限,不允许读取业务 secret; - [x] Pod 重建后无需人工发放凭据; - [ ] SVID 自动轮换不影响后续登录; - [ ] 删除 registration entry 后无法再取得新 SVID; - [x] 记录恢复、撤销、审计和 issuer 密钥轮换行为。 ## 后续阶段 - [ ] Gitea Actions runner 和 AI Agent 使用 SPIRE 身份登录 OpenBao; - [ ] SeaweedFS 验证 SPIRE OIDC,并通过 `AssumeRoleWithWebIdentity` 签发短期 S3 凭据; - [ ] `credential-exec` 从 Workload API 获取 JWT-SVID,再为子进程注入目标系统的短期凭据; - [ ] Proxmox 临时 VM 完成一次性节点注册、实例身份和销毁回收; - [ ] systemd/Unix workload attestation 覆盖长期主机服务; - [ ] 自建 Compute API、DBaaS API 原生验证 JWT-SVID 或 X.509-SVID; - [ ] 评估 SPIRE Server、Agent、OIDC Discovery Provider 的 HA、备份与灾难恢复; - [ ] 记录各消费端同时信任 Authelia 与 SPIRE 时的 issuer、audience 和主体命名约定。 ## Policy 边界 语义上只维护两类规则: 1. **身份/信任规则**:selector 或外部 proof 可以取得哪个 SPIFFE ID; 2. **资源权限规则**:稳定的 SPIFFE ID/平台角色在目标服务中可以执行什么操作。 JWT 的 audience、claims 和下游 role mapping 是授权传递与执行机制,不应成为额外的人工事实源。多个 workload 共享同一平台角色时,只新增主体到角色的绑定,不复制整份资源 policy。 ## SeaweedFS 与静态凭据 现有 SeaweedFS 继续由 Helm 管理。`seaweedfs-operator` 的 IAM CRD 要求关联 Operator 管理的 `Seaweed` CR,不适合作为当前 brownfield Helm 部署仅为 IAM 而引入,因此不采用 Operator 接管路线。 迁移期间继续把现有 `-s3.config` 视为静态 S3 身份的唯一运行配置。长期目标是使用 SPIRE JWT-SVID 调用 SeaweedFS Web Identity/STS,逐步移除 workload 的长期 AK/SK;Terraform/OpenTofu backend 等兼容性未经验证的调用方暂时保留独立、最小权限且可轮换的静态身份。 ## 安全与恢复约束 - SPIRE Server、OpenBao、Git 和集群恢复路径不得形成不可恢复的循环依赖; - SPIRE Agent socket 必须依靠 workload attestation 隔离,不能因能够访问 socket 就取得任意身份; - audience 必须按目标服务绑定,禁止签发全场通用 bearer token; - OIDC Discovery/JWKS endpoint 可被验证方访问,但 SPIRE Server 管理接口不得公开; - join token 仅用于 bootstrap,必须短 TTL、单次使用且绑定预期节点; - Delegated Identity API/Broker API 默认不启用,启用前必须评估其 impersonation 权限; - 每个长期凭据仍只能有一个事实源和写入方; - 生产迁移必须保留回退路径,不在 PoC 阶段撤销现有认证方式。 ## 已否决或后置的方向 - **自研完整 OIDC/IAM**:协议、安全和维护成本过高; - **以 OpenBao 为中心构造完整 IAM**:OpenBao继续聚焦 Secret、PKI和动态凭据; - **SeaweedFS Operator 接管 IAM**:与现有 Helm 部署的资源所有权不匹配; - **在本议题中自研 Authelia OIDC client controller**:OIDC client 生命周期与 workload identity 是不同问题;若配置管理成为实际负担,应另行立项,不与 SPIRE PoC 耦合; - **ZITADEL 作为 workload identity 核心**:标准 IdP/Service Account 能力完整,但对任意基础设施 proof 的可定制验证接口不足; - **为 Keycloak 编写多套 workload SPI**:Java SPI、版本兼容和各基础设施 SDK 的维护成本高于采用 SPIRE; - **当前迁移到 Keycloak**:SPIRE 已解决机器身份,现有 Samba AD + Authelia 足以承担人类登录;在出现明确的人类 IAM、协议或管理能力缺口前,迁移收益不足以覆盖复杂度和风险; - **现在引入 Keto/OpenFGA**:当前不需要额外的关系授权事实源,复杂资源关系出现后再评估。
panxiao81 changed title from 设计基于 CRD 的凭据签发与轮换控制器 to 后置:评估凭据签发与轮换的声明式管理 2026-09-10 09:08:28 +00:00
panxiao81 changed title from 后置:评估凭据签发与轮换的声明式管理 to 设计并验证基于 SPIFFE/SPIRE 的统一 Workload Identity 2026-09-13 14:39:56 +00:00
Author
Owner

SPIRE 基础设施已通过 #51 上线并完成首次验收:

  • Flux root 与 spire Kustomization 均已收敛到 main revision bdacc03e
  • [email protected][email protected] HelmRelease Ready
  • SPIRE Server 2/2 Ready,成功连接共享 PostgreSQL
  • signing-key PVC 已绑定到 localpv-zfs-ceph
  • SPIRE Agent 已通过 k8s_psat 完成 node attestation
  • SPIFFE CSI Driver Ready
  • OIDC Discovery Provider 2/2 Ready
  • discovery document 已验证 issuerjwks_uri 均使用 https://spire-oidc.ad.ddupan.top

下一步仍是增加显式测试 workload/ClusterSPIFFEID,暴露 OIDC HTTPS route,并验证 JWT-SVID 登录 OpenBao。

SPIRE 基础设施已通过 #51 上线并完成首次验收: - Flux root 与 `spire` Kustomization 均已收敛到 main revision `bdacc03e` - `[email protected]` 与 `[email protected]` HelmRelease Ready - SPIRE Server `2/2` Ready,成功连接共享 PostgreSQL - signing-key PVC 已绑定到 `localpv-zfs-ceph` - SPIRE Agent 已通过 `k8s_psat` 完成 node attestation - SPIFFE CSI Driver Ready - OIDC Discovery Provider `2/2` Ready - discovery document 已验证 `issuer` 与 `jwks_uri` 均使用 `https://spire-oidc.ad.ddupan.top` 下一步仍是增加显式测试 workload/`ClusterSPIFFEID`,暴露 OIDC HTTPS route,并验证 JWT-SVID 登录 OpenBao。
Author
Owner

SPIRE → OpenBao JWT-SVID 端到端 PoC 已完成:

  • #52 已合并并上线 https://spire-oidc.ad.ddupan.top
  • AD DNS、Blocky、wildcard TLS、Gateway API HTTPRoute、discovery document 与 JWKS 均验证通过
  • #53 已合并并由 Terraform 创建 jwt-spire backend、spire-poc role 与最小 policy
  • Terraform apply:3 added / 0 changed / 0 destroyed
  • 临时 workload 取得 aud=openbao JWT-SVID,并以精确 subject spiffe://ddupan.top/ns/spire-poc/sa/spire-jwt-poc 登录成功
  • 返回 token 仅含 spire-poc policy,TTL 300 秒,无 default policy
  • lookup-self 成功,随后主动 revoke-self,并验证 token 已失效
  • 临时 Namespace、Pod/Job、ServiceAccount 与 ClusterSPIFFEID 已删除,registration entries 已回收
  • 定向 apply 后再次执行完整 Terraform plan,结果为 No changes

由此已验证完整链路:Kubernetes workload → SPIRE Workload API → JWT-SVID → OpenBao JWT auth → 短期最小权限 token。下一步可将 PoC role 替换/扩展为真实 CI 与 AI Agent workload policy。

SPIRE → OpenBao JWT-SVID 端到端 PoC 已完成: - #52 已合并并上线 `https://spire-oidc.ad.ddupan.top` - AD DNS、Blocky、wildcard TLS、Gateway API HTTPRoute、discovery document 与 JWKS 均验证通过 - #53 已合并并由 Terraform 创建 `jwt-spire` backend、`spire-poc` role 与最小 policy - Terraform apply:3 added / 0 changed / 0 destroyed - 临时 workload 取得 `aud=openbao` JWT-SVID,并以精确 subject `spiffe://ddupan.top/ns/spire-poc/sa/spire-jwt-poc` 登录成功 - 返回 token 仅含 `spire-poc` policy,TTL 300 秒,无 default policy - `lookup-self` 成功,随后主动 `revoke-self`,并验证 token 已失效 - 临时 Namespace、Pod/Job、ServiceAccount 与 `ClusterSPIFFEID` 已删除,registration entries 已回收 - 定向 apply 后再次执行完整 Terraform plan,结果为 `No changes` 由此已验证完整链路:Kubernetes workload → SPIRE Workload API → JWT-SVID → OpenBao JWT auth → 短期最小权限 token。下一步可将 PoC role 替换/扩展为真实 CI 与 AI Agent workload policy。
Author
Owner

详细使用文档已通过 #54 合并。新增 platform/spire/RUNBOOK.md,覆盖:

  • 当前架构、稳定标识与各层职责边界
  • 新 workload 的 ServiceAccount、ClusterSPIFFEID、CSI socket 接入模板
  • OpenBao policy 与精确 JWT role 的声明方式
  • JWT-SVID → OpenBao token 的无泄漏 exchange 示例
  • CI/AI Agent 的凭据生命周期与禁止事项
  • Flux、SPIRE、OIDC、OpenBao、CSI、PostgreSQL 日常检查
  • no identity issued、JWT login 400、CSI mount、数据库连接排障
  • PostgreSQL 与 signing-key PVC 的独立备份和恢复顺序
  • 2026-09-14 PoC 的验收结论和后续真实 workload 接入清单

SPIRE README 与 OpenBao README 均已增加入口,并修正 PoC 状态。

详细使用文档已通过 #54 合并。新增 `platform/spire/RUNBOOK.md`,覆盖: - 当前架构、稳定标识与各层职责边界 - 新 workload 的 ServiceAccount、ClusterSPIFFEID、CSI socket 接入模板 - OpenBao policy 与精确 JWT role 的声明方式 - JWT-SVID → OpenBao token 的无泄漏 exchange 示例 - CI/AI Agent 的凭据生命周期与禁止事项 - Flux、SPIRE、OIDC、OpenBao、CSI、PostgreSQL 日常检查 - `no identity issued`、JWT login 400、CSI mount、数据库连接排障 - PostgreSQL 与 signing-key PVC 的独立备份和恢复顺序 - 2026-09-14 PoC 的验收结论和后续真实 workload 接入清单 SPIRE README 与 OpenBao README 均已增加入口,并修正 PoC 状态。
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#34