摘要

  • -07 要求每枚 JWT 不得携带一个以上的受众,除非平台无法发出多枚凭证,或工作负载无法影响受众;-06 对单一受众只使用了 SHOULD。
  • 风险不是抽象的“令牌泄露”,而是接收端之间的冒用:拿到同一枚 bearer 令牌的任何一方,都可能把它提交给另一个接受方。
  • 能力例外回执应记录隔离为何不可能、完整接收端图、令牌寿命与可达范围、各端验证、补偿控制、复核时间以及退出例外的条件。

设想一个 Pod 把同一枚 projected service-account token 用在两处:先交给 Kubernetes API 服务器执行基础设施操作,再交给外部身份提供方换取另一种访问令牌。两个接收端都是真实、合法的系统,也都因此持有这枚 bearer 凭证。只要 aud 允许这两种用途,外部身份提供方就能把令牌交回 API 服务器,并以该工作负载的身份出现。

这条路径不需要中间人,不需要日志泄漏,也不要求攻击者攻破签发方。问题恰恰来自一次“成功”的验证:本来只负责认证工作负载的接收端,因为凭证没有保留接收端之间的边界,获得了在另一端冒充它的能力。

这就是 2026 年 9 月 22 日上传的 draft-ietf-wimse-workload-identity-practices-07 最关键的操作变化。它目前仍是计划归入 Informational 的活跃 Internet-Draft。Datatracker 显示的状态是 AD Evaluation::AD Followup,动作持有人为 Charles Eckel。这是审阅状态,不是 IESG 批准,不是 RFC,也不是任何产品已经部署或违规的证据。

从 SHOULD 到 MUST,改变的是默认责任

-06 已经给出了正确方向。其通用要求写道,凭证应尽可能缩小范围;只要平台支持多枚凭证,工作负载就应为每个资源或身份提供方获取不同凭证。受众章节则说,每枚 JWT 应只携带一个受众。

-07 把边界改为规范性要求:凭证 MUST 尽可能收窄;直接访问平台资源的凭证 MUST 只覆盖该资源;用于身份联合的凭证 MUST 以身份提供方为唯一受众;除明确定义的能力例外外,每枚 JWT MUST NOT 携带多个受众。

这不是把大写字母加粗那么简单。建议允许实现方在权衡后选择其他方案;要求则把隔离设为默认条件,偏离者必须说明为何落入例外。-07 还补上了完整的风险机制:同一 aud 中列出的任何 relying party,都可能把令牌交给另一方并获得非预期访问。

因此,受众不是验证器里无关痛痒的路由标签。它列出了 bearer 凭证可以在哪些地方“开口说话”。每增加一个接受方,也增加了一个能够把同一凭证拿到其他接受方重放的持有者。

Kubernetes 把抽象风险变成一条回路

Kubernetes 能够为 service account 发出多枚 projected token,并分别指定受众和寿命。-07 用这项能力拆开三条路径:访问 API 服务器、访问集群内其他资源,以及与外部身份提供方联合。三处所用令牌必须不同,受众也必须不同。

这比“API 服务器会检查 aud”更严格。若两个接收端接受同一份 bearer 材料,一端的本地校验无法保护另一端。外部身份提供方完全可以正确检查 issuer、签名、过期时间和受众,同时仍持有 API 服务器也会接受的令牌。每个局部检查都通过,系统边界依然可能失败。

SPIFFE 示例采用相同规则:供内部资源使用的 JWT-SVID 与供外部身份提供方联合使用的 JWT-SVID,必须是受众不同的两枚令牌。云平台示例里,-06 对内部访问与外部 STS 联合使用 SHOULD/SHOULD NOT;-07 改成 MUST/MUST NOT。不同平台背后的不变量,都是接收端图。

例外承认能力缺口,并不把两种设计说成等价

草案没有假装所有平台都能遵守。有的平台每个工作负载只能签发一枚凭证,有的平台不允许工作负载影响受众。-07 因此允许在“无法取得多枚凭证”或“无法影响受众”时,不执行单一受众要求。

但这不是为了集成方便预留的出口。上下文明确说,共用一枚跨场景凭证的部署不能依靠 audience scoping 限制影响范围,必须把寿命缩到平台允许的最短,限制能够接触凭证的组件,并把每个接收端都视为能够在其他接受端冒充工作负载的一方。

此时的安全状态并不等价。接收端图变大,控制从密码学上的受众隔离,转移到时间、组件访问、网络路径、验证纪律和监测。补偿控制可能合理,但必须以“缺少能力的补偿”被看见,不能被一枚绿色合规标志抹平。

-07 对 proof of possession 也采取同样做法。平台和 relying party 都不支持 PoP 时,较短寿命、更严格的受众范围和额外网络控制,从 SHOULD 变成 MUST。缩短寿命只能压缩重放窗口,不能把 bearer token 变成只允许原发送方使用的凭证。

在令牌真正使用之处保存能力例外回执

一张能力例外回执,应把具体部署决定固定在可复核的边界上:

回执字段 应保留的证据
签发能力 平台、issuer、确切版本,以及能否发出多枚凭证
受众控制 工作负载能否请求或影响 aud,并附 API 或配置证据
预期用途 平台 API、内部资源、身份联合或外部资源
凭证标识 非秘密、不可逆的指纹或代次编号,绝不保存 bearer 值
接收端图 全部接受方,以及接收端之间可能互相提交的路径
寿命 请求与实际 TTL、续期行为、撤销或失效路径
可达范围 能接触或携带凭证的组件、挂载、sidecar、代理、日志与出口
验证 各接收端对 issuer、audience、type、PoP 与网络条件的检查
例外理由 缺少哪项平台能力,为何无法分离凭证
补偿措施 较短寿命、组件隔离、网络控制、监测与告警
复核 责任人、决定时间、下次复核、修正触发器与退出条件

这里的指纹必须是非秘密、单向的。回执绝不能成为把 bearer token 原文塞进工单的理由。它的作用,是把风险决定绑定到当时覆盖的凭证代次、接收端和控制措施。

回执还要区分“不能”与“没有做”。如果签发方支持独立凭证,只是集成为了省事而复用,能力例外并不适用。如果后续版本加入了受众控制,退出条件就已经出现。没有复核时间的例外,很容易悄悄变成架构。

BTW 先前的《工作负载凭证不是工作负载》梳理了从启动到最终结果的八类证明。本文刻意只处理其中一个更窄的问题:这一枚 bearer token 能在哪些接收端代表工作负载,又有哪些接收端能把它拿去向别人发言?

来源与边界

主要依据包括 Datatracker 的 -07 记录、文档历史、不可变的-07 纯文本和用于比较的-06 纯文本。把协调文件与运行现实分开的写作原则,来自最小初始规范、本地化未来决策与自愿采用。

这些来源能证明文档状态、规范文本和安全机制,不能证明现实中已经发生事故,不能提供完整实施普查,也不能证明某个具名运营方违规。能力例外回执是 Daniel Kade 提出的编辑性控制建议,并非 WIMSE 草案中的强制字段。