演进 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(用于响应式场景)构建,并通过 UriBuilderFactory 和 VaultEndpointProvider 进行配置以确定目标 Vault 服务器。
虽然特定于引擎的 API 强制使用路径前缀并防止使用绝对路径,但通用的 VaultTemplate.read()/.write() 方法以及 doWithVault()/doWithSession() 回调通常操作的是不透明的路径字符串。如果不检查每个字符串(这并不现实),就无法保证给定的调用是针对配置的 Vault 实例中的相对路径,还是代表指向完全不同的服务器的绝对 URI。从安全角度来看,这并不理想。
还有另一个值得考虑的方面:回调方法直接暴露了原始的 HTTP 客户端,这从根本上将抽象级别从逻辑 Vault 操作降到了面向 HTTP 的请求处理。虽然这种设计为模板 API 未涵盖的低级操作提供了一个“逃生出口”,但它要求应用程序开发人员在抽象级别上进行上下文切换。这要求的安全意识程度远高于应有水平。一个设计良好的客户端库应默认引导开发者采用安全的使用模式,及早捕获意外操作,而不是默默地执行它们。
这些观察促使我们重新考虑客户端架构。现代 Spring Framework 组件中采用的流畅、函数式风格,特别是在 HTTP 客户端演进中,为修改后的 Vault 客户端应如何呈现提供了清晰的灵感。流畅的 API 自然地减少了任何给定类型上的方法扩散,同时引导开发者遵循组件的预期使用模式。
值得注意的是,VaultTemplate 是基于 RestTemplate 构建的,而 ReactiveVaultTemplate 一直使用流畅的 WebClient,这本身就强化了流畅设计方法在 Vault 交互中的优势。
引入 VaultClient
VaultClient 和 ReactiveVaultClient 大量借鉴了 RestClient 和 WebClient 的设计原语,用 Vault 特定的关注点丰富了流畅接口。如果您熟悉这些客户端中的任何一个,那么您会对 VaultClient 感到非常熟悉。Spring Vault 4.1 是一个引入这种新客户端架构并将所有 Spring Vault 功能提升到更精良基础之上的机会。
新的 VaultClient 提供了一个中间抽象层,在其核心强制进行相对路径处理,当为 VaultEndpoint 配置时,防止意外使用绝对路径:
|
|
在内部,VaultClient 利用 RestClient 及其连接器基础设施来处理 SSL 配置、超时、重试和其他客户端关注点,而 URL 构造则牢牢控制在 VaultClient 自身内部。同步和响应式客户端都会一致地抛出 VaultClientResponseException(通用 VaultException 的子类),从而提供对 HTTP 状态码、响应头和错误响应体的适当访问。
大多数 ClientAuthentication 实现都需要某种形式的 Vault 交互来执行登录操作。通常,这些实现直接与 RestTemplate(或用于 AuthenticationStepsOperator 的 WebClient)协同工作。特定于云的认证机制,如 GCP Compute、Azure MSI 实例元数据,也需要一个 RestTemplate 来与各自的平台服务通信。对 Vault 操作和特定于平台的调用使用相同的客户端类型,造成了混淆客户端实例的可能性,导致请求通过不恰当的通道被分发。
拥有一个专门的 Vault 客户端类型消除了整整一类潜在的 bug。
迁移考量
引入 VaultClient 是朝着更精良的 Spring Vault 设计迈出的一步。这一变更带来了对在 RestOperations API 级别上操作的构造函数和支持类的弃用,转而支持 VaultClient 和 ReactiveVaultClient 变体。
此次修订也是为了准备 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 的演进,我们欢迎对这些方向的反馈。