IAM1 介绍 SPIFFE / SPIRE

· Tech

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) 是一组用于软件身份的开放标准。它定义了一组接口和文档,用来以完全自动化的方式获取并验证加密身份。其主要包含五个部分:

  1. SPIFFE ID:用于表示一个软件服务的名称(或身份),即一种 URI
  2. SVID:全称 SPIFFE Verifiable Identity Document,一种可通过密码学验证的文件,用于向对等方证明服务身份
  3. SPIFFE Workload API:一个简单的节点本地 API,服务用它来获取自己的身份,无需认证
  4. SPIFFE Trust Bundle:一种用来表示某个 SPIFFE 签发机构正在使用的公钥集合的格式
  5. 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 工作流程

  1. SPIRE Server 启动。
  2. 除非配置 UpstreamAuthority 插件,否则 Server 将生成一张自签名证书(用自己私钥进行签署)。该证书用于为信任域内的 workload 签署 SVID。
  3. 若是首次启动,Server 会自动生成一个 trust bundle,将其存入 datastore 指定的位置中。
  4. Server 启用 registration API,以便注册 workload。
  5. SPIRE Agent 在 workload 所在的节点上启动。
  6. SPIRE Agent 执行节点认证(node attestation),向 Server 证明它所在节点的身份。下面的步骤以 AWS 为例。

Node Attestation 示例
e.g.1.e.g.1. AWS EC2 情形

  1. Agent 从 EC2 元数据服务取回 AWS 签名的实例身份文档。
  2. Agent 把文档连同签名交给 Server。
  3. Server 用 AWS 的公钥验签。此后,再调用 AWS API 做交叉核对与信息补全。
  4. Server 据已有信息生成 Agent 的 SPIFFE ID,并签发 Agent SVID。
  5. 同一节点只准认证一次(TOFU),防止节点上的其他进程冒用。

e.g.2e.g.2 unix 情形

  1. 管理员先在 Server 上生成一个一次性令牌(join_token),交给这台主机。
  2. 通过安全渠道把 token 投递到目标主机,写进 Agent 配置的 join_token 字段,或启动时用 -joinToken 参数传入。Agent 启动时把令牌交给 Server。
  3. Server 查库确认令牌有效且没用过,用掉即作废。
  4. 通过后签发 Agent SVID。
  1. Agent 通过一条 TLS 连接把这份身份证明交给 server,该连接使用 Agent 配置的 bootstrap bundle 进行认证。
  2. Server 调用 AWS API 校验这份证明。
  3. AWS 确认该文档有效。
  4. Server 执行额外的认证步骤,以核实该 agent 节点的更多属性,并据此更新相应的注册条目(selector)。
  5. Server 向 Agent 签发一个 SVID,代表 Agent 自身的身份。
  6. Agent 联系 Server(用 Agent 自己的 SVID 作为 TLS 客户端证书),以获取它被授权获得的注册条目。
  7. Server 用 Agent 的 SVID 对 Agent 进行认证。反过来,Agent 完成 mTLS 握手,并用 bootstrap bundle 认证 Server。
  8. Server 从数据存储中取出所有已授权的注册条目,并发送给 Agent。
  9. 接着 Agent 把自己所生成的工作负载的 CSR(证书签名请求)发送给 Server,Server 对其签名后作为工作负载 SVID 返回给客户端,客户端将它们放入缓存。这样,每一条条目都能够准备一份工作负载 SVID。
  10. 至此引导(bootstrap)完成,Agent 开始在 Workload API socket 上监听。
  11. 某个工作负载调用 Workload API 请求一个 SVID。
  12. Agent 启动工作负载认证流程:调用其 workload attestors,并把工作负载进程的 PID 交给它们。
  13.  workload attestors 使用内核与用户空间的调用,发现关于该工作负载的额外信息。
  14.  workload attestors 以 workload selectors 的形式,把发现的信息返回给 agent。
  15. Agent 通过将发现的 workload selectors 与注册条目比对,确定该工作负载的身份,并返回正确的 SVID(该 SVID 已在缓存中)。

参考

  1. SPIFFE Concepts | SPIFFE

    https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/

  2. Solving-the-bottom-turtle-SPIFFE-SPIRE-Book

    https://spiffe.io/pdf/Solving-the-bottom-turtle-SPIFFE-SPIRE-Book.pdf

cicada@blog:~