IAM1 介绍 SPIFFE / SPIRE
1. 故事
随着互联网上的服务越来越复杂,一个系统往往需要由许多不同的程序共同完成工作,而这种场景往往伴随下面几个问题:
- 程序所在的服务器通常迁移和更新,位置越来越不固定
- 程序的身份也越来越趋于多样化,如各种应用程序、微服务、容器中的进程及 AI Agent
- 程序与程序之间不再是默认互相信任的,和它们是否处于同一内部网络中无关
- 对内部服务拥有完全的访问权限的人也不能完全信任
- 程序访问其他服务的密钥,又该如何安全的获取(Secret Zero Problem)
尤其针对最后一个问题:人与应用之间、应用与应用之间日益增长的交互,使得访问控制的必要性逐渐提高。Shared secrets(密钥,如apikey等)为访问控制提供了一种方案,但这个方案本身也带来一系列问题:密钥很容易泄露。并且访问控制、密钥和身份三者存在着相互依赖的关系:大规模管理密钥等资源需要有合理的访问控制手段,实现访问控制需要身份认证,证明身份需要有一个秘密。所有这些解法都会陷入"底部的乌龟还是乌龟"(turtles all the way down)的困局:
- 访问控制,即“谁”能够访问资源的问题
- “谁”,意味着要给其提供一个身份,并且给出“认证和保护”
- “认证和保护”,意味着对授权的“凭据”进行保护和加密
- 但解密密钥本身又是一个需要被保护的东西
- 访问解密密钥又涉及回访问控制
总而言之,保护一个 secret 总会引出新的 secret 需要保护。
2. 介绍 SPIFFE
Secure Production Identity Framework For Everyone(SPIFFE) 是一组用于软件身份的开放标准。它定义了一组接口和文档,用来以完全自动化的方式获取并验证加密身份。其主要包含五个部分:
- SPIFFE ID:用于表示一个软件服务的名称(或身份),即一种 URI
- SVID:全称 SPIFFE Verifiable Identity Document,一种可通过密码学验证的文件,用于向对等方证明服务身份
- SPIFFE Workload API:一个简单的节点本地 API,服务用它来获取自己的身份,无需认证
- SPIFFE Trust Bundle:一种用来表示某个 SPIFFE 签发机构正在使用的公钥集合的格式
- SPIFFE Federation:一种用于共享 Trust Bundle 的简单机制
2.1 SPIFFE ID
SPIFFE ID 是一个字符串,充当服务的唯一名字,它能够唯一且明确地标识一个工作负载。SPIFFE ID 也可以分配给工作负载运行所依赖的中间系统(比如一组虚拟机、节点node)。
它是一种统一资源标识符(URI),由几部分组成:
- 前缀
spiffe:// - trust domain 的名字
- 具体工作负载标识符
要强调的是,SPIFFE ID 的目的之一是以一种”人机皆易消费“的方式灵活地表达工作负载身份。不宜将某些数据属性硬编码进该字符串中。
关于 SPIFFE Trust Domain
信任域(Trust Domain)对应的是一个系统的信任根(Trust root)。
一个信任域可以代表一个运行独立SPIFFE基础设施的个人、组织、环境或部门。在同一个信任域中标识的所有工作负载都会被签发身份文档,这些文档可以依据该信任域的根密钥进行验证。
- 由于不同的信任域有不同的签发机构,一个信任域被攻破不会导致另一个信任域也被攻破。
- 通常把位于不同物理位置或者应用了不同环境的工作负载,放在不同的信任域里。
2.2 SVID
SVID 是一份加密的可验证的文件,工作负载用它向资源或调用方证明自己的身份。如果 SVID 已由 SPIFFE ID 信任域内的权威机构签名,就被认为有效。
一个 SVID 包含一个 SPIFFE ID,这个 ID 代表出示该 SVID 的服务的身份。它把这个 SPIFFE ID 编码进一份可以通过密码学验证的文档里。
目前支持两种格式:
- X.509
- JWT
由于令牌容易遭到重放攻击(Replay Attack,是指攻击者截获一次合法的请求,然后将其原封不动地重新发送,冒充原来的请求者执行操作),也就是攻击者在中途拿到令牌后,就能用它来冒充某个工作负载,所以建议尽可能使用 X.509-SVID。
不过,有些情况下唯一的选择就是 JWT 令牌格式。比如当两个程序之间存在代理服务器时,这时两个程序不是端到端的,在使用 X.509-SVID 时就无法直接证明原始请求者的身份。
2.21 X.509-SVID
X509-SVID 把 SPIFFE 身份编码进一张标准的 X.509 证书。对应的 SPIFFE ID 会以 URI 类型设置在 Subject Alternative Name(SAN)扩展字段中。虽然一张 X509-SVID 上只允许设置一个 URI SAN 字段,但证书里可以包含任意数量的其他类型 SAN 字段,包括 DNS SAN。
当与 TLS 结合使用时,X.509 证书无法被中间人记录并重放。
2.22 JWT-SVID
JWT-SVID 把 SPIFFE 身份编码进一个标准 JWT。JWT-SVID 在应用层作为持有者令牌(bearer token) 用于向对端证明身份。因为它让 SPIFFE 身份认证能用于无法建立端到端通信信道的场景,所以 JWT-SVID 是 SPIFFE 规范集中重要的一部分。
2.23 WIT-SVID(optional)
WIT-SVID(Workload Identity Token SVID)是 SPIFFE 新增的一种身份凭证格式,可以理解为结合了 JWT 和 X.509 证书部分优点的方案。
2.3 SPIFFE Trust Bundle
Trust Bundle 是一个或多个证书颁发机构(CA)根证书的集合,工作负载应将这些根证书视为可信,其包含用于 X.509 和 JWT SVID 的公钥材料:
- 用于验证 X.509 SVID 的公钥材料是一组证书
- 用于验证 JWT 的公钥材料是原始公钥
Trust Bundle 的内容会经常轮换。工作负载在调用 Workload API 时可以获取 Trust Bundle。
由于 Trust Bundle 不包含任何机密信息(只有公钥),因此可以安全地公开分享,但它需要安全地分发,以保护其内容不被未经授权地修改。
SPIFFE 包被格式化为 JWK Set (or JWKS document)。
2.4 SPIFFE Federation
一种在不同 Trust Domain 之间共享 Trust Bundle 的机制。
2.5 SPIFFE Workload API
SPIFFE 工作负载 API 是一个本地的、非网络化的 API,工作负载用它来获取自己当前的身份文档(SPIFFE ID、X.509 certificate / JWT-token)、Trust Bundle 以及相关信息。
Workload API 无需认证,不要求工作负载事先拥有任何凭据。
3. 介绍 SPIRE
SPIFFE Runtime Environment(SPIRE) 是一套 SPIFFE 规范的完整开源实现。
3.1 架构
其包含两个关键组件:SPIRE Server 和 SPIRE Agent:
- SPIRE Server:管理身份注册规则、验证 Agent、签发 SVID
- SPIRE Agent:识别本机程序、获取和缓存凭证、向 Workload 提供 Workload API
Workload 通过 SPIRE Agent 暴露的 Workload API 接口,获取身份凭证。Server 和 Agent 之间使用网络通信,而 Workload 通常借助本地 Unix Domain Socket 与 Agent 通信。
3.2 工作流程
- SPIRE Server 启动。
- 除非配置
UpstreamAuthority插件,否则 Server 将生成一张自签名证书(用自己私钥进行签署)。该证书用于为信任域内的 workload 签署 SVID。 - 若是首次启动,Server 会自动生成一个 trust bundle,将其存入 datastore 指定的位置中。
- Server 启用 registration API,以便注册 workload。
- SPIRE Agent 在 workload 所在的节点上启动。
- SPIRE Agent 执行节点认证(node attestation),向 Server 证明它所在节点的身份。下面的步骤以 AWS 为例。
Node Attestation 示例
AWS EC2 情形
- Agent 从 EC2 元数据服务取回 AWS 签名的实例身份文档。
- Agent 把文档连同签名交给 Server。
- Server 用 AWS 的公钥验签。此后,再调用 AWS API 做交叉核对与信息补全。
- Server 据已有信息生成 Agent 的 SPIFFE ID,并签发 Agent SVID。
- 同一节点只准认证一次(TOFU),防止节点上的其他进程冒用。
unix 情形
- 管理员先在 Server 上生成一个一次性令牌(join_token),交给这台主机。
- 通过安全渠道把 token 投递到目标主机,写进 Agent 配置的 join_token 字段,或启动时用 -joinToken 参数传入。Agent 启动时把令牌交给 Server。
- Server 查库确认令牌有效且没用过,用掉即作废。
- 通过后签发 Agent SVID。
- Agent 通过一条 TLS 连接把这份身份证明交给 server,该连接使用 Agent 配置的 bootstrap bundle 进行认证。
- Server 调用 AWS API 校验这份证明。
- AWS 确认该文档有效。
- Server 执行额外的认证步骤,以核实该 agent 节点的更多属性,并据此更新相应的注册条目(selector)。
- Server 向 Agent 签发一个 SVID,代表 Agent 自身的身份。
- Agent 联系 Server(用 Agent 自己的 SVID 作为 TLS 客户端证书),以获取它被授权获得的注册条目。
- Server 用 Agent 的 SVID 对 Agent 进行认证。反过来,Agent 完成 mTLS 握手,并用 bootstrap bundle 认证 Server。
- Server 从数据存储中取出所有已授权的注册条目,并发送给 Agent。
- 接着 Agent 把自己所生成的工作负载的 CSR(证书签名请求)发送给 Server,Server 对其签名后作为工作负载 SVID 返回给客户端,客户端将它们放入缓存。这样,每一条条目都能够准备一份工作负载 SVID。
- 至此引导(bootstrap)完成,Agent 开始在 Workload API socket 上监听。
- 某个工作负载调用 Workload API 请求一个 SVID。
- Agent 启动工作负载认证流程:调用其 workload attestors,并把工作负载进程的 PID 交给它们。
- workload attestors 使用内核与用户空间的调用,发现关于该工作负载的额外信息。
- workload attestors 以 workload selectors 的形式,把发现的信息返回给 agent。
- Agent 通过将发现的 workload selectors 与注册条目比对,确定该工作负载的身份,并返回正确的 SVID(该 SVID 已在缓存中)。
参考
SPIFFE Concepts | SPIFFE
Solving-the-bottom-turtle-SPIFFE-SPIRE-Book
https://spiffe.io/pdf/Solving-the-bottom-turtle-SPIFFE-SPIRE-Book.pdf



