This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
workload-sts/docs/security.md
T

7.5 KiB
Raw Blame History

安全模型

项目 内容
状态 Approved
最后更新 2026-09-10

保护目标

  • 只有通过可信 verifier 验证并匹配 trust binding 的 workload 才能取得 JWT。
  • 签发的 JWT 只能用于明确的 audience,且权限不能超过 SA binding 的 issuance rule。
  • 客户端不能控制输出 subject、任意 claims、签名算法或最大 TTL。
  • subject token、输出 bearer token 和签名私钥不得进入数据库、日志、metric 或 trace。
  • 一个 issuer、principal 或策略配置错误不能静默扩大为所有 audience 的访问权。
  • OpenBao 与 Kubernetes 保留各自的最终授权权威。

信任边界

平台管理员可以配置 verifier、trust binding、audience、token profile 和 policy,因此是 本系统的高信任主体。上游 issuer 管理员能够为其信任域签发身份;其权限必须通过 trust binding 限制,不能因 issuer 被信任就获得所有 audience。

OpenBao Transit 保管 JWT 签名私钥。workload-sts 使用自己的 Kubernetes ServiceAccount JWT 登录 OpenBao,并取得只能请求指定 Transit key 签名和读取其公钥所需信息的短期 运行凭据。该凭据与客户端通过 JWT auth 获得的 Bao token 是不同安全域。资源服务器 通过 HTTPS 获取 metadata/JWKS 或使用固定信任配置验证 JWT。

数据库管理员可以改变签发配置,等价于 IAM 管理员;数据库备份和迁移必须按高敏感配置 处理,即使其中没有 bearer token 或私钥。

主要威胁

Subject token 重放

攻击者取得尚未过期的 Kubernetes JWT 或 JWT-SVID 后可能重复交换。缓解措施包括:

  • 上游 token 必须使用短 TTL
  • 严格校验其 audience 为 workload-sts
  • 输出 JWT 使用短 TTL 和独立 audience
  • 高风险 profile 可以要求在线状态验证或一次性机制;
  • 日志以安全指纹关联重复请求,不保存完整 token。

是否对所有 subject token 实现全局 jti 单次消费仍待决定;默认不能假定标准 ServiceAccount JWT 只能交换一次。

Audience confusion

验证必须检查上游 token 的 audience,而不只检查签名和 issuer。输出 token 的 aud 必须来自已登记 audience,禁止接受自由字符串后原样签入 JWT。

不同 Kubernetes cluster、OpenBao 实例和普通 API 应使用不同 audience。资源服务器必须 拒绝没有自己 audience 的 token。

Issuer confusion

不能只按 JWT sub 映射 principal。trust binding 至少绑定:

credential type + issuer + source subject

同名 ServiceAccount 在不同 cluster 中是不同来源。issuer discovery、JWKS URL 和允许的 签名算法必须由管理员配置,不能由未验证 token 中的 URL 动态决定。

Claim injection 与权限扩大

请求中的 scopeaudienceclient_id 都是不可信输入。scopeaudience 只 表示请求上限;v1 的必填 client_id 只用于审计且没有默认值。输出 claims 由 verifier 事实、trust binding、issuance rule 和服务端 token profile 共同生成。

CEL 只能读取经过类型化的上下文。未经验证的 Gitea repository、workflow、branch、 job ID、HTTP header 或自报 SPIFFE ID 不能用于扩大权限。

Algorithm 与密钥混淆

  • 每个 issuer 固定允许的算法集合;
  • 拒绝 none 和未配置算法;
  • 不根据 JWT header 中的任意 URL 获取密钥;
  • kid 必须解析到对应 issuer 的已知密钥;
  • 签名与验证密钥用途分离;
  • 轮换时同时发布当前及仍覆盖有效 token 的旧公钥。

SSRF 与 discovery

上游 issuer、discovery URL 和 JWKS URL 是管理员配置,不从 token exchange 请求动态 创建。HTTP client 必须设置超时、响应大小上限和可接受的 TLS 信任;默认拒绝重定向到 未批准 origin。

策略拒绝服务

CEL 环境只暴露固定变量和函数,限制表达式大小、编译时间与运行成本。策略在发布前编译 验证;无效策略不能替换最后一个有效版本。求值错误按拒绝签发处理。

Kubernetes ServiceAccount JWT

  • 只接受 projected、带过期时间和明确 audience 的 token
  • 使用 TokenReview 校验身份和 audience,并检查返回的兼容 audience;
  • cluster identity 属于 verifier 配置,不能只由 token 的通用 issuer 名推导;
  • TokenReview 不可用或失败时不得自动降级为离线 JWKS;
  • token 中的 namespace、ServiceAccount、Pod 和 Node 信息只有经过 TokenReview 返回的 已认证 user/extra 信息确认后才能进入 policy context。

未来的 SPIFFE verifier

SPIFFE 不属于第一阶段。未来接入时必须验证真正的 SVID 和 trust bundle,不能仅把内部 principal 写成 spiffe:// URI

  • JWT-SVID 必须校验 trust bundle、audience 和时效;
  • X.509-SVID 只能通过完成验证的 mTLS 连接接受;
  • 不能接受普通 header 中自报的 SPIFFE ID
  • SPIFFE ID 到内部 principal 的 trust binding 必须显式配置。

Token 签发

  • 输出 JWT 必须包含 isssubaudiatnbfexp 和唯一 jti
  • 默认不签发 refresh token
  • token profile 由匹配身份的 trust binding/issuance rule 唯一确定,并定义 TTL 和允许 claims
  • 客户端可以请求更少 scope,不能请求 TTL 或扩大 scope;
  • JWT 中不放置 secret、上游原始 token 或无界个人信息;
  • 面向 OpenBao 的 JWT 只是登录 assertion,不是 Bao client token。

日志与审计

允许记录:

  • request ID
  • 上游 issuer ID 和 credential type
  • 规范化 principal
  • 目标 audience、批准后的 scope/profile
  • 自报 client_id_claimed(每个 v1 请求必有,明确标记为未经认证);
  • policy/version
  • 结果、错误类别、输出 JWT jti 和时间。

禁止记录:

  • Authorization header
  • subject token 或输出 JWT
  • mTLS 私钥或完整证书链;
  • OpenBao token
  • OAuth client secret
  • token endpoint 请求/响应体原文。

OpenTelemetry HTTP instrumentation 不得配置 request/response body 或 Authorization、 Cookie 等凭据 header 捕获。span name 和 http.route 只能使用固定路由模板;原始 URL query、client_id_claimed、principal、subject、scope 和 jti 不得成为 HTTP metric label。业务 scope/profile 指标只能使用管理员配置的受控枚举。

发布前安全验收

  • 错误 issuer、签名、audience、算法、过期时间和未生效时间均被拒绝。
  • 同名但来自另一 Kubernetes cluster 的 ServiceAccount 不能匹配 trust binding。
  • 客户端不能通过请求 audience、scope、TTL 或 claims 扩大授权,且不能请求 token profile。
  • 缺少或格式错误的 client_id 被拒绝;改变合法 client_id 不改变 principal、授权、 profile 或输出 claims。
  • client_id 必须匹配 ^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$,在进入审计前完成校验, 防止控制字符、日志注入和无界字段大小。
  • 一个面向 OpenBao 的 JWT 不能用于 Kubernetes 或其他 API。
  • 删除或禁用 trust binding 后不再签发新 token。
  • 删除或禁用 trust binding 不会立即撤销已经签发的自包含 JWT;最大 TTL 是该变更的 最长生效延迟。
  • CEL 编译或运行失败时拒绝签发,且不破坏最后一个有效策略版本。
  • 日志、错误、metrics、trace、数据库和测试 artifact 不含 canary token。
  • JWKS 轮换期间,轮换前已签发且未过期的 token 仍可验证。
  • OpenBao、数据库或签名后端不可用时失败关闭,不签发降级 token。