Spring Vault 的演进:全新 VaultClient 技术架构解析

本文介绍了Spring Vault如何演进其客户端架构,引入全新的VaultClient以提升安全性和易用性。内容涵盖了从传统Template API的设计反思,到新客户端基于RestClient和WebClient的流式API设计,以及迁移考量与未来整合方向。

演进 Spring Vault:引入 VaultClient

早在 2016 年 9 月,也就是大约十年前,我们推出了 Spring Vault,作为 Spring 应用程序中 HashiCorp Vault 的集成层,并辅以 Spring Cloud Vault 用于 Spring Boot 配置。其核心思想一直很明确:将密钥外部化到加密的 Vault 存储中,从而显著降低应用端的复杂性,特别是通过利用 Vault 的通信和身份验证安全原语。

VaultTemplate 及其响应式版本 ReactiveVaultTemplate 一直为我们提供良好服务,提供了一个熟悉的、基于 Java 的编程接口,用于与 Vault 的各种密钥引擎(键值、PKI、传输、转换和封装支持)进行交互。相对路径的使用一直是这里的一个关键设计要素,它允许客户端处理特定于环境的配置细节,而应用程序则专注于实际存储和检索的密钥。

重新审视 Template API 设计

与任何随时间演进的 API 一样,我们在基于模板的方法中积累了相当多的功能。实现基于 RestTemplate(用于同步操作)和 WebClient(用于响应式场景)构建,并通过 UriBuilderFactoryVaultEndpointProvider 进行配置以确定目标 Vault 服务器。

虽然特定于引擎的 API 强制使用路径前缀并防止使用绝对路径,但通用的 VaultTemplate.read()/.write() 方法以及 doWithVault()/doWithSession() 回调通常操作的是不透明的路径字符串。如果不检查每个字符串(这并不现实),就无法保证给定的调用是针对配置的 Vault 实例中的相对路径,还是代表指向完全不同的服务器的绝对 URI。从安全角度来看,这并不理想。

还有另一个值得考虑的方面:回调方法直接暴露了原始的 HTTP 客户端,这从根本上将抽象级别从逻辑 Vault 操作降到了面向 HTTP 的请求处理。虽然这种设计为模板 API 未涵盖的低级操作提供了一个“逃生出口”,但它要求应用程序开发人员在抽象级别上进行上下文切换。这要求的安全意识程度远高于应有水平。一个设计良好的客户端库应默认引导开发者采用安全的使用模式,及早捕获意外操作,而不是默默地执行它们。

这些观察促使我们重新考虑客户端架构。现代 Spring Framework 组件中采用的流畅、函数式风格,特别是在 HTTP 客户端演进中,为修改后的 Vault 客户端应如何呈现提供了清晰的灵感。流畅的 API 自然地减少了任何给定类型上的方法扩散,同时引导开发者遵循组件的预期使用模式。

值得注意的是,VaultTemplate 是基于 RestTemplate 构建的,而 ReactiveVaultTemplate 一直使用流畅的 WebClient,这本身就强化了流畅设计方法在 Vault 交互中的优势。

引入 VaultClient

VaultClientReactiveVaultClient 大量借鉴了 RestClientWebClient 的设计原语,用 Vault 特定的关注点丰富了流畅接口。如果您熟悉这些客户端中的任何一个,那么您会对 VaultClient 感到非常熟悉。Spring Vault 4.1 是一个引入这种新客户端架构并将所有 Spring Vault 功能提升到更精良基础之上的机会。

新的 VaultClient 提供了一个中间抽象层,在其核心强制进行相对路径处理,当为 VaultEndpoint 配置时,防止意外使用绝对路径:

1
2
3
4
5
6
7
8
VaultClient client = VaultClient.create(VaultEndpoint.create("vault.example.com", 8200));

VaultResponse response = client.get()
    .path("secret/my-secret")
    .token(VaultToken.of())
    .namespace()
    .retrieve()
    .requiredBody();

在内部,VaultClient 利用 RestClient 及其连接器基础设施来处理 SSL 配置、超时、重试和其他客户端关注点,而 URL 构造则牢牢控制在 VaultClient 自身内部。同步和响应式客户端都会一致地抛出 VaultClientResponseException(通用 VaultException 的子类),从而提供对 HTTP 状态码、响应头和错误响应体的适当访问。

大多数 ClientAuthentication 实现都需要某种形式的 Vault 交互来执行登录操作。通常,这些实现直接与 RestTemplate(或用于 AuthenticationStepsOperatorWebClient)协同工作。特定于云的认证机制,如 GCP Compute、Azure MSI 实例元数据,也需要一个 RestTemplate 来与各自的平台服务通信。对 Vault 操作和特定于平台的调用使用相同的客户端类型,造成了混淆客户端实例的可能性,导致请求通过不恰当的通道被分发。

拥有一个专门的 Vault 客户端类型消除了整整一类潜在的 bug。

迁移考量

引入 VaultClient 是朝着更精良的 Spring Vault 设计迈出的一步。这一变更带来了对在 RestOperations API 级别上操作的构造函数和支持类的弃用,转而支持 VaultClientReactiveVaultClient 变体。

此次修订也是为了准备 Spring Framework 计划在 Spring Framework 7.1 中弃用 RestTemplate,正如我们在最近的 HTTP 客户端路线图中所述。Spring Vault 和 Spring Cloud Vault 中的配置基础设施目前暂时继续使用 RestTemplate 以及 Spring Vault 的 RestTemplateCustomizer,现在还额外提供了 VaultClientCustomizer。我们将在后续阶段逐步从 RestTemplate 迁移到 RestClient,并移除已弃用的 API。

我们建议将与 Vault 相关的自定义从 RestTemplateCustomizer 迁移到 VaultClientCustomizer,同时将通用的 HTTP 客户端自定义迁移到 RestClientCustomizer(或其相应的响应式版本)。

我们正在努力为底层 VaultClient 客户端的操作提供专用接口,以涵盖未通过 Template API 直接暴露的 Vault 功能,作为 doWithVault()/doWithSession() 回调方法的直接替代品。

下一步是什么?

我们正在探索几个可以从 Vault 集成中受益的有状态组件的集成方案。

例如,Vault 的 PKI 密钥引擎可以作为 Spring Boot SSL 捆绑包的证书颁发机构,为嵌入式 Web 服务器提供信任锚点和证书颁发,而无需将证书部署为文件,而是直接从 Vault 获取证书。

另一个值得探索的想法是数据库连接的凭据轮换,这提供了广泛的配置方案,例如更新连接池组件的凭据或轮换数据源。

一如既往,随着 Spring Vault 的演进,我们欢迎对这些方向的反馈。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计