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

167 lines
7.5 KiB
Markdown
Raw Permalink 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.
# 安全模型
| 项目 | 内容 |
| --- | --- |
| 状态 | 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 至少绑定:
```text
credential type + issuer + source subject
```
同名 ServiceAccount 在不同 cluster 中是不同来源。issuer discovery、JWKS URL 和允许的
签名算法必须由管理员配置,不能由未验证 token 中的 URL 动态决定。
### Claim injection 与权限扩大
请求中的 `scope`、`audience` 和 `client_id` 都是不可信输入。`scope` 和 `audience` 只
表示请求上限;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 必须包含 `iss`、`sub`、`aud`、`iat`、`nbf`、`exp` 和唯一 `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。