第 7 章 与外部集成
原书:Chapter 7. Integrating with Others(p.133–145) 翻译策略:技术直译 + 段落重排;专有项目名、协议名、API 名、CLI 命令、配置字段、证书/加密术语一律保留英文;首次出现的关键概念以"中文(English)"形式给出,后续沿用英文。术语对照见
TERMS.md。
章节导言
本章探讨 SPIFFE 和 SPIRE 如何与你的环境集成。
SPIFFE 从一开始就被设计成可插拔、可扩展的——所以"将 SPIFFE 和 SPIRE 与其他软件系统集成"是一个很大的话题。任何给定集成的架构都超出了本书的范围。本章旨在抓住一些可能的常见集成、给出高层概述,以及进行集成工作的一套策略。
让软件能够使用 SVID
在考虑如何改造软件以使用 SVID 时,有很多选项。本节会描述其中一些选项、以及与之相关的考量。
原生 SPIFFE 支持
这种方法需要修改现有服务,让它们 SPIFFE 化。当所需修改很少、或者可以引入到跨服务使用的某个通用库或框架中时,它是首选。对那些对延迟敏感的数据面服务、或者那些想在应用层利用身份的服务来说,原生集成是最佳选择。
SPIFFE 提供了 GO-SPIFFE(Go 编程语言)和 JAVA-SPIFFE(Java 编程语言)这些库——便于 SPIFFE 化工作负载的开发。
图 7.1 两个 Envoy 代理位于两个服务之间的高层图——使用 SPIRE Agent 的 SDS 实现建立互信 TLS(见
assets/pages/page-135.png)。
在使用那些有 SPIFFE 支持库的语言构建软件时,这通常是最直接的 SPIFFE 化方式。上面提到的 Go 和 Java 库有使用 SPIFFE 配合 gRPC 和 HTTPS 客户端/服务端的示例。
话虽如此,应该指出的是:你不局限于 Java 和 Go 这两种语言。这些库是基于开放规范实现的。在撰写本书时,社区正在为 Python、Rust 和 C 编程语言开发 SPIFFE 库。
SPIFFE 感知的代理
通常,重构成本被认为太高、或者服务跑的是无法修改的第三方代码。在这些场景下,务实之选是在应用前面挂一个支持 SPIFFE 的代理。取决于应用部署模型,可以是独立代理,也可以是几组并置的代理。
并置代理的优点是:代理与非安全服务之间的流量仍然保持在本地——如果使用独立代理,"代理到应用"这一段的安全性仍然需要单独考虑。
Envoy 是这类场景的流行选择,Ghostunnel 是另一个不错的选择。其他代理比如 NGINX 和 Apache 也能用,但它们在 SPIFFE 相关的功能上比较有限。
Ghostunnel 是一个 L3/L4 代理,对整个 SPIFFE 规范集提供完整原生支持,包括 SPIFFE Workload API 和 Federation。对需要 L7 能力的应用来说,推荐 Envoy。虽然 Envoy 本身并不原生支持 SPIFFE Workload API,但 SPIRE 实现了 Secret Discovery Service API(SDS API)——这是 Envoy 用于获取和维护证书、私钥的 API。
关键洞察:通过实现 SDS API,SPIRE 可以直接把 TLS 证书、私钥和可信 CA 证书推到 Envoy 里。然后 SPIRE 负责按需轮换短生命周期的密钥和证书——把更新推到 Envoy 而无需重启。
服务网格
像 Envoy 这样的 L7 代理可以执行远超 SPIFFE 安全的多种功能。例如,服务发现、请求授权、负载均衡都是 Envoy 带来的能力。当"使用共享库"成本过高时(比如应用是用很多不同语言写的,或者根本改不了),把这种能力下放到代理是特别有吸引力的。这种方式也会把代理部署推向"并置模型"——每个应用实例旁边跑一个专属代理。
然而,这会带来一个新问题:如何管理这些代理?
服务网格是有倾向性的代理舰队部署,加上相应的代理控制平面。它们通常允许在工作负载部署时自动注入并配置并置的代理、并对这些代理进行持续管理。通过把许多平台关注的事情下放到服务网格,应用可以保持对这些功能的无感。
截至目前,大多数服务网格实现都利用 SPIFFE 身份认证来做服务间流量。有些用 SPIRE 来实现这一点,其他的实现则自建产品特定的 SPIFFE 身份提供方。
Helper 程序
对那些原生不支持 SPIFFE Workload API、但仍支持用证书做认证的工作负载,跑一个 helper 程序与工作负载并列运行——可以起到桥梁作用。这种 helper 程序的一个例子是 SPIFFE Helper(参考 github.com/spiffe/spiffe-helper)。SPIFFE Helper 从 SPIFFE Workload API 拉取 SVID、把它们写到磁盘,让应用能读取。SPIFFE Helper 可以持续运行,确保磁盘上的证书在轮换时被持续更新。当一次更新发生时,helper 可以向应用发信号(或者跑一个可配置的命令),让运行中的应用能拾起这些变化。
许多支持 TLS 的开箱即用应用都可以用这种方式 SPIFFE 化。SPIFFE Helper 仓库里有配置 MySQL 和 PostgreSQL 的示例。许多 web server 比如 Apache HTTPD 和 NGINX 都可以类似地配置。这对"只能被配置成使用磁盘上证书"的客户端软件也很有用:
- openssl
- x509curl
- grpcurl
关键洞察:重要的是要意识到:这种方式比"原生 SPIFFE 集成"少一些灵活性——特别是,它可能不允许"信任配置"做到同样的粒度。比如,使用 SPIFFE Helper 给 Apache HTTPD 配置互信 TLS 时,无法把 mod_ssl 配置成"只接受具有特定 SPIFFE ID 的客户端"。
用 SVID 配合"不 SPIFFE 化"的软件
由于 SVID 基于众所周知的文档类型,相对常见的情况是遇到"支持这种文档类型、但本身不 SPIFFE 化"的软件。好消息是:这是一种相对被预期的场景,SPIFFE / SPIRE 在设计时就很好地处理了这种情况。
X509-SVID 的双重用途
许多非 SPIFFE 系统支持使用 TLS(或互信 TLS),但它们依赖证书在 Subject 的 Common Name(CN)或 SAN 扩展的 DNS name 中携带身份信息。SPIRE 支持为每个工作负载(在注册条目中)签发带特定 CN 和 DNS SAN 值的 X.509 证书。
这个能力是一个重要的细节——它允许把 X509-SVID 用在"不能直接理解如何使用 SPIFFE ID"的软件上。举例来说,HTTPS 客户端通常期望"出示的证书与请求的 DNS 名匹配"。再举一个例子,MySQL 和 PostgreSQL 可以用 Common Name 来识别互信 TLS 客户端。通过利用 SPIRE 这一特性——以及 SPIFFE 本身所提供的灵活性——这些用例可以使用"与 SPIFFE 用例同款的 SVID"来满足。
JWT-SVID 的双重用途
与 X509-SVID 即可用于 SPIFFE 身份认证、也可用于更传统的 X.509 用法类似,JWT-SVID 也支持这种双重性。虽然 JWT-SVID 确实用标准的 subject(即 sub claim)来存储 SPIFFE ID,但它的验证方法与 OIDC 相似且兼容。
更具体地说,SPIFFE Federation API 通过一个由 HTTPS 端点服务的 JWKS 文档暴露公钥——这与"OIDC 验证时获取公钥"的机制是同一种。因此,任何支持 OIDC 身份联邦的技术都会支持接受 JWT-SVID——无论它是否 SPIFFE 化。
支持这种身份联邦的集成之一是 Amazon Web Services(AWS)IAM(Identity and Access Management)。通过在 AWS 账户里把 IAM 配置为接受来自 SPIRE 的身份作为 OIDC 身份提供方,就可以使用从 SPIFFE Workload API 获取的 JWT-SVID 来扮演 AWS IAM 角色。当需要访问 AWS 资源的工作负载并非跑在 AWS 上时,这种方式特别强大——基本上免去了存储、共享、管理长期 AWS 访问密钥的需求。关于如何做到这一点的详细示例,请参考 SPIFFE 网站上的 AWS OIDC Authentication tutorial(spiffe.io/spire/try/oidc-federation-aws)。
你可以在 SPIFFE 之上构建什么
一旦 SPIFFE 作为通用身份基础存在于你的生态中、并且与应用集成完毕,可能就是考虑"在上面构建什么"的好时机。本节我们想覆盖"在 SPIFFE 和 SPIRE 之上可以构建什么"。项目不一定已经拥有让一切开箱即用的全部积木——某些集成部件需要去实现;具体怎么实现则因部署而异。
日志、监控、可观测性与 SPIFFE
SPIFFE 可以为其他系统提供"可验证的身份证明"——这给下列组件带来了优势:
- 指标基础设施
- 日志基础设施
- 可观测性
- 计量
- 分布式追踪
你可以用 SVID 来保证这些系统的客户端-服务端通信安全。但你也可以扩展所有这些组件、用 SPIFFE ID 来丰富数据。这样做带来了一系列好处——比如能在多种平台和运行时之间关联事件。它甚至能帮你识别那些仍未使用 SPIFFE 身份的应用和服务,或者发现发生在基础设施任何角落的运营异常和攻击。
审计
对任何安全系统(比如你基于 SPIRE 构建的系统)来说,日志不仅是"帮开发者和运维了解系统发生了什么"的信息。任何安全系统的日志都是"正在发生什么"的证据——所以把日志集中存放在一个地方是个好主意。一旦发生安全事件,这类信息对取证分析极其宝贵。
SPIFFE 可以通过"对集中式审计系统使用已认证的调用"来增强审计数据,提供不可抵赖性。举例来说,在与审计系统建立会话时使用 X509-SVID 和互信 TLS——我们就能确定日志行的来源——攻击者不能简单地操纵跨网络传输的标签或其他数据。
证书透明度(Certificate Transparency)
证书透明度(Certificate Transparency,CT)通过提供一个"近实时"监控和审计 X.509 证书的开放框架,帮助发现对证书基础设施的攻击。证书透明度允许检测"被恶意从被攻破的 CA 获取的"证书。它也让识别"已经失控、正在恶意签发证书的"CA 成为可能。要了解更多关于证书透明度的信息,请阅读入门文档(www.certificate-transparency.org/what-is-ct)。
SPIRE 与证书透明度的集成有多种可能方式。通过这种集成,可以记录你系统签发的所有证书的信息,并用一种叫做 Merkle Tree Hashes 的特殊加密机制来保护它免受篡改和不当行为的影响。
你可能考虑的另一种方法是:在你的所有系统上强制实施证书透明度。这可以阻止与"未在 CT 服务器中登记证书信息"的应用程序和服务建立 TLS 与互信 TLS 连接。
关键洞察:与 CT 的集成超出了本书的范围。请查阅 SPIFFE / SPIRE 社区获取更多信息和最新动态。
软件供应链安全
关于 SPIFFE 的预期用途,大部分都集中在运行时保护软件系统之间的通信。然而,在软件被部署之前的阶段保护它同样至关重要。软件供应链被攻破是一个潜在的攻击向量。因此,我们希望保护软件供应链的完整性——防止恶意行为者引入后门或脆弱的库。验证软件制品的出处(provenance)、以及流水线中执行的那一组步骤,是一种验证软件未被篡改的方式。
你可以考虑用 SPIFFE 来为签名提供信任根。它也可以为供应链系统中的软件组件签发身份。有几种方式可以与 TUF(The Update Framework)这类补充软件、或者 Notary 这样的制品签名服务配合工作;或者与 In-Toto 这样的供应链日志一起使用。
SPIRE 与供应链组件的集成可以发生在两个层面:
首先,你可以用它来标识供应链系统的不同元素——保护机制本身和控制平面。其次,通过自定义 selector,确保只有已知出处的二进制才会被签发身份。作为后者的一个很基础的例子,这些属性可以作为 label 传递到容器镜像中——使用现有的 docker selector,或者开发一个能检查供应链元数据的工作负载 attestor。
把 SPIFFE 集成给用户
SPIFFE 和 SPIRE 架构的主要关注点是软件身份。它不把用户身份纳入考虑——因为这个问题已经算是被解决得不错了,而且向人类签发身份和向软件签发身份之间存在显著差异。话虽如此,这不意味着你不能把 SPIFFE 身份分发给用户。
给用户的可验证身份
用户应该如何在 SPIFFE 化的生态中交互?记住 SPIFFE 代表着"面向所有人的生产级安全身份框架"。虽然本书大部分内容聚焦于软件身份,SPIFFE 可验证身份(SVID)发放给用户同样有效、甚至更可取。这样,工作负载能用 SVID 做的所有事情,人也能做——比如对服务的互信 TLS 访问。这对于"正在构建软件、并需要访问自己软件一旦部署就会用到的那些资源"的开发者来说特别有用。
正如 SPIFFE 规范对 SPIFFE ID 的 scheme 持开放态度,如何表达人类身份也由你决定。可能用你的用户名作为 SPIFFE ID path 就够了——比如 spiffe://example.com/users/zero_the_turtle。另一种做法是为用户和工作负载分别建立独立的 Trust Domain**——比如 spiffe://users.example.com/zero_the_turtle。
在理想场景下,你现有的 SSO 提供方能为你的用户生成 JWT——OIDC 身份提供方就是这种。在这种情况下,如果你能把 SSO 提供方配置为使用 SPIFFE ID 作为 sub claim,你就不必做任何额外工作来为你的用户生成 SVID。
如果你无法从身份提供方那里直接拿到 SPIFFE JWT,但你确实有办法拿到可验证的身份 token,那么你可以换一种方式——用一个自定义 SPIRE attestor,把身份提供方发来的身份 token 作为基础的证明手段。
图 7.2 用 OIDC ID token 做 SPIRE 身份认证的示例(见
assets/pages/page-143.png)。
关键洞察:如果上述情况都不适用,你仍然可以构建一个独立服务、集成进你现有的 SSO 方案——它能根据用户已认证的会话来为用户生成 SVID。可以参考 SPIFFE 网站上的示例项目(github.com/JackOfMostTrades/spiffe-user-demo)。
把 SPIFFE 和 SPIRE 配合 SSH 使用
OpenSSH 支持使用 CA 和证书做身份认证(github.com/openssh/openssh-portable/blob/master...)。虽然 OpenSSH 证书的格式与 X.509 不同,但可以构建一个服务、用 SVID 做身份认证来签发 SSH 证书。这让你能同样用 SPIFFE 身份玩 SSH。
图 7.3 用 SVID 来 bootstrap SSH 证书(见
assets/pages/page-144.png)。
对需要 SSH 访问你生态中工作负载的用户来说,这种模型提供了一组"短期、ephemeral、可审计"的 SSH 凭据——并且提供了一个单一的控制点——通过它来执行访问控制策略或多因子认证。
这也让工作负载能拿到服务端(即"host"端)的 SSH 证书——让工作负载能向用户证明自己的身份。有了这种证书,用户不再需要在首次连接时被询问"是否信任该 server 的 host key"——也就不会再出现 SSH 连接被中断。
微服务的 UI
虽然本书大部分关注工作负载到工作负载的认证,但用户向工作负载认证的需求也常常存在。如果用户通过 CLI 或其他桌面工具进行这种认证,就可以使用互信 TLS 配合用户的 SVID。然而,许多微服务也想托管某种基于浏览器的用户界面。这可能是因为开发者要访问为他们的服务专门构建的管理或运维界面,或者消费者在使用类似 Swagger UI 的工具(github.com/swagger-api/swagger-ui)
去探索和试验一个服务的 API。
为那些有浏览器 UI 的服务提供好的体验,需要在"对浏览器友好的身份认证"和"SPIFFE 互信 TLS 身份认证"之间架桥。最简单的做法是开两个 API 端口——一个使用互信 TLS,另一个接受对浏览器友好的身份认证方法,比如现有的基于 web 的 SSO 机制或 OAuth2 / OIDC。
对副端口的请求做后置认证的过滤器——应当在"基于浏览器的身份认证主体"与"对应的 SPIFFE ID"之间提供一个翻译层。如果你已经为用户建立了一种直接获取 SVID 的机制(如上所述),那这里应该用相同的翻译。这样一来,底层应用对具体的身份认证机制是无感的——所以由某个用户发起的 web 请求在功能上等同于"使用该用户的 SVID 经由互信 TLS 发出的"同一请求。
下一章:第 8 章 用 SPIFFE 身份驱动授权