跳到主要内容

支持 Vault 的 Secret Engines

DeepSeek V3 中英对照 Supporting for Vault’s Secret Engines

Spring Vault 提供了多个扩展,以支持 Vault 的各种秘密引擎。

具体来说,Spring Vault 提供了以下扩展:

你可以直接通过 VaultTemplate 上的方法来使用所有其他后端(VaultTemplate.read(…)VaultTemplate.write(…))。

键值版本 1("未版本化的密钥")

kv 密钥引擎用于在 Vault 配置的物理存储中存储任意密钥。

当以非版本化的方式运行 kv 密钥引擎时,只会保留键的最新写入值。非版本化 kv 的好处是每个键的存储大小减小,因为不会存储额外的元数据或历史记录。此外,以这种方式配置的后端请求性能更高,因为存储调用更少,并且对于任何给定请求都不会进行锁定。

Spring Vault 附带了一个专用的 Key-Value API,用于封装各个 Key-Value API 实现之间的差异。VaultKeyValueOperations 遵循了 Vault CLI 的设计。Vault CLI 是 Vault 的主要命令行工具,提供了诸如 vault kv getvault kv put 等命令。

你可以通过指定版本和挂载路径,在 Key-Value 引擎的两个版本中使用此 API。以下示例使用了 Key-Value 版本 1:

VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultKeyValueOperations keyValueOperations = operations.opsForKeyValue("secret",
VaultKeyValueOperationsSupport.KeyValueBackend.KV_1);

keyValueOperations.put("elvis", Collections.singletonMap("password", "409-52-2002"));

VaultResponse read = keyValueOperations.get("elvis");
read.getRequiredData().get("social-security-number");
java

VaultKeyValueOperations 支持所有的 Key-Value 操作,例如 putgetdeletelist

或者,可以通过 VaultTemplate 使用该 API,因为它具有直接映射和简单的使用方式,键和响应直接映射到输入和输出键。以下示例展示了在 mykey 处写入和读取一个 secret。kv secrets 引擎挂载在 secret 路径下:

VaultOperations operations = new VaultTemplate(new VaultEndpoint());

operations.write("secret/elvis", Collections.singletonMap("social-security-number", "409-52-2002"));

VaultResponse read = operations.read("secret/elvis");
read.getRequiredData().get("social-security-number");
java

你可以在 Vault 参考文档中找到有关 Vault Key-Value version 1 API 的更多详细信息。

键值版本 2("版本化密钥")

你可以在两种版本中运行 kv 密钥引擎。本节介绍使用版本 2。在运行 kv 后端的版本 2 时,一个密钥可以保留可配置数量的版本。你可以检索旧版本的元数据和数据。此外,你可以使用检查并设置(check-and-set)操作来避免无意中覆盖数据。

类似于 Key-Value 版本 1("未版本化的密钥"),Spring Vault 提供了一个专门的 Key-Value API 来封装各个 Key-Value API 实现之间的差异。Spring Vault 提供了一个专门的 Key-Value API 来封装各个 Key-Value API 实现之间的差异。VaultKeyValueOperations 遵循 Vault CLI 的设计。Vault CLI 是 Vault 的主要命令行工具,提供了诸如 vault kv getvault kv put 等命令。

你可以通过指定版本和挂载路径,在两个 Key-Value 引擎版本中使用此 API。以下示例使用了 Key-Value 版本 2:

VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultKeyValueOperations keyValueOperations = operations.opsForKeyValue("secret",
VaultKeyValueOperationsSupport.KeyValueBackend.KV_2);

keyValueOperations.put("elvis", Collections.singletonMap("social-security-number", "409-52-2002"));

VaultResponse read = keyValueOperations.get("elvis");
read.getRequiredData().get("social-security-number");
java

VaultKeyValueOperations 支持所有 Key-Value 操作,例如 putgetdeletelist

您还可以与版本化的键值 API 的具体细节进行交互。如果您想获取特定的密钥或需要访问元数据,这将非常有用。

VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultVersionedKeyValueOperations versionedOperations = operations.opsForVersionedKeyValue("secret");

Versioned.Metadata metadata = versionedOperations.put("elvis", 1
Collections.singletonMap("social-security-number", "409-52-2002"));

Version version = metadata.getVersion(); 2

Versioned<Object> ssn = versionedOperations.get("elvis", Version.from(42)); 3

Versioned<SocialSecurityNumber> mappedSsn = versionedOperations.get("elvis", 4
Version.from(42), SocialSecurityNumber.class);

Versioned<Map<String,String>> versioned = Versioned.create(Collections 5
.singletonMap("social-security-number", "409-52-2002"),
Version.from(42));

versionedOperations.put("elvis", version);
java
  • 将密钥存储在 elvis 中,该存储位于 secret/ 挂载点下。

  • 在版本化后端中存储数据会返回元数据,例如版本号。

  • 版本化的 Key-Value API 允许根据版本号检索特定版本。

  • 版本化的键值密钥可以映射到值对象中。

  • 使用 CAS 更新版本化密钥时,输入必须引用先前获取的版本。

虽然通过 VaultTemplate 使用 kv v2 密钥引擎是可行的,但这并不是最方便的方法,因为 API 提供了不同的上下文路径和输入/输出表示方式。具体来说,与实际密钥的交互需要对数据部分进行包装和解包,并在挂载点和密钥之间引入一个 data/ 路径段。

VaultOperations operations = new VaultTemplate(new VaultEndpoint());

operations.write("secret/data/elvis", Collections.singletonMap("data",
Collections.singletonMap("social-security-number", "409-52-2002")));

VaultResponse read = operations.read("secret/data/ykey");
Map<String,String> data = (Map<String, String>) read.getRequiredData().get("data");
data.get("social-security-number");
java

你可以在 Vault 参考文档中找到有关 Vault Key-Value version 2 API 的更多详细信息。

PKI(公钥基础设施)

pki 密钥引擎通过实现证书颁发机构操作来表示证书的后端。

PKI 密钥引擎生成动态的 X.509 证书。通过这个密钥引擎,服务可以获取证书,而无需经历通常的手动过程,即生成私钥和 CSR、提交给 CA 并等待验证和签名过程完成。Vault 内置的身份验证和授权机制提供了验证功能。

Spring Vault 支持通过 VaultPkiOperations 来颁发、签名、撤销证书以及检索 CRL。所有其他的 PKI 功能都可以通过 VaultOperations 来使用。

以下示例简要说明了如何颁发和撤销证书的使用方法:

VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultPkiOperations pkiOperations = operations.opsForPki("pki");

VaultCertificateRequest request = VaultCertificateRequest.builder() 1
.ttl(Duration.ofHours(48))
.altNames(Arrays.asList("prod.dc-1.example.com", "prod.dc-2.example.com"))
.withIpSubjectAltName("1.2.3.4")
.commonName("hello.example.com")
.build();

VaultCertificateResponse response = pkiOperations.issueCertificate("production", request); 2
CertificateBundle certificateBundle = response.getRequiredData();

KeyStore keyStore = certificateBundle.createKeyStore("my-keystore"); 3

KeySpec privateKey = certificateBundle.getPrivateKeySpec(); 4
X509Certificate certificate = certificateBundle.getX509Certificate();
X509Certificate caCertificate = certificateBundle.getX509IssuerCertificate();

pkiOperations.revoke(certificateBundle.getSerialNumber()); 5
java
  • 使用 VaultCertificateRequest 构建器构造证书请求。

  • 向 Vault 请求证书。Vault 作为证书颁发机构,并返回一个已签名的 X.509 证书。实际响应是一个 CertificateBundle

  • 你可以直接获取生成的证书作为 Java KeyStore,其中包含公钥、私钥以及颁发者证书。KeyStore 有广泛的用途,这使得这种格式适合配置(例如 HTTP 客户端、数据库驱动程序或 SSL 加密的 HTTP 服务器)。

  • CertificateBundle 允许通过 Java Cryptography Extension API 直接访问私钥、公钥和颁发者证书。

  • 一旦证书不再使用(或已被泄露),你可以通过其序列号撤销它。Vault 会在其 CRL 中包含被撤销的证书。

你可以在 Vault 参考文档中找到更多关于 Vault PKI secrets API 的详细信息。

Token 认证后端

此后端是一个认证后端,它不与实际的密钥交互。相反,它提供了对访问令牌管理的访问。你可以在认证方法章节中阅读更多关于基于令牌的认证的内容。

token 认证方法是内置的,并自动在 /auth/token 路径下可用。它允许用户使用令牌进行认证,同时还可以创建新令牌、通过令牌撤销密钥等功能。

当任何其他认证方法返回一个身份时,Vault 核心会调用 token 方法来为该身份创建一个新的唯一令牌。

你也可以使用令牌存储来绕过任何其他认证方法。你可以直接创建令牌,也可以对令牌执行各种其他操作,例如续期和撤销。

Spring Vault 使用此后端来续期和撤销由配置的认证方法提供的会话令牌。

以下示例展示了如何在应用程序中请求、续期和撤销 Vault 令牌:

VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultTokenOperations tokenOperations = operations.opsForToken();

VaultTokenResponse tokenResponse = tokenOperations.create(); 1
VaultToken justAToken = tokenResponse.getToken();

VaultTokenRequest tokenRequest = VaultTokenRequest.builder().withPolicy("policy-for-myapp")
.displayName("Access tokens for myapp")
.renewable()
.ttl(Duration.ofHours(1))
.build();

VaultTokenResponse appTokenResponse = tokenOperations.create(tokenRequest); 2
VaultToken appToken = appTokenResponse.getToken();

tokenOperations.renew(appToken); 3

tokenOperations.revoke(appToken); 4
java
  • 通过应用角色默认值创建一个令牌。

  • 使用构建器 API,你可以为请求的令牌定义细粒度的设置。请求令牌会返回一个 VaultToken,它用作 Vault 令牌的值对象。

  • 你可以通过 Token API 续订令牌。通常,这是由 SessionManager 完成的,以跟踪 Vault 会话令牌。

  • 如果需要,可以通过 Token API 撤销令牌。通常,这是由 SessionManager 完成的,以跟踪 Vault 会话令牌。

你可以在 Vault 参考文档中找到更多关于 Vault Token 认证方法 API 的详细信息。

传输后端

Transit 密钥引擎处理传输中数据的加密功能。Vault 不会存储发送到此密钥引擎的数据。它也可以被视为“加密即服务”或“加密即服务”。Transit 密钥引擎还可以对数据进行签名和验证,生成数据的哈希和 HMAC,并作为随机字节源。

transit 的主要用例是在应用程序中对数据进行加密,同时仍然将加密后的数据存储在某些主数据存储中。这减轻了应用程序开发人员正确加密和解密的负担,并将这一负担转移到了 Vault 的操作者身上。

Spring Vault 支持广泛的 Transit 操作:

  • 密钥生成

  • 密钥重新配置

  • 加密/解密/重包装

  • HMAC 计算

  • 签名和签名验证

transit 中的所有操作都围绕着密钥展开。Transit 引擎支持密钥的版本控制以及多种密钥类型。需要注意的是,密钥类型可能会限制可以使用的操作。

以下示例展示了如何创建密钥以及如何加密和解密数据:

VaultOperations operations = new VaultTemplate(new VaultEndpoint());
VaultTransitOperations transitOperations = operations.opsForTransit("transit");

transitOperations.createKey("my-aes-key", VaultTransitKeyCreationRequest.ofKeyType("aes128-gcm96")); 1

String ciphertext = transitOperations.encrypt("my-aes-key", "plaintext to encrypt"); 2

String plaintext = transitOperations.decrypt("my-aes-key", ciphertext); 3
java
  • 首先,我们需要一个密钥来开始。每个密钥都需要指定类型。aes128-gcm96 支持加密、解密、密钥派生和收敛加密,在本例中我们需要的是加密和解密。

  • 接下来,我们加密一个包含明文文本的 String。输入的 String 使用默认的 Charset 将字符串编码为二进制表示。请求令牌会返回一个 VaultToken,它用作 Vault 令牌的值对象。encrypt 方法返回 Base64 编码的密文,通常以 vault: 开头。

  • 要将密文解密为明文,调用 decrypt 方法。它会解密密文并返回一个使用默认字符集解码的 String

前面的示例在加密操作中使用了简单的字符串。虽然这种方法简单,但它存在字符集配置错误的风险,并且不是二进制安全的。当明文使用二进制表示数据(如图像、压缩数据或二进制数据结构)时,需要二进制安全性。

要对二进制数据进行加密和解密,可以使用 PlaintextCiphertext 值对象,这些对象可以保存二进制值:

byte [] plaintext = "plaintext to encrypt".getBytes();

Ciphertext ciphertext = transitOperations.encrypt("my-aes-key", Plaintext.of(plaintext)); 1

Plaintext decrypttedPlaintext = transitOperations.decrypt("my-aes-key", ciphertext); 2
java
  • 假设已经存在一个密钥 my-aes-key,我们正在加密 Plaintext 对象。作为返回,encrypt 方法会返回一个 Ciphertext 对象。

  • Ciphertext 对象可以直接用于解密,并返回一个 Plaintext 对象。

PlaintextCiphertext 都附带一个上下文对象 VaultTransitContext。它用于为收敛加密提供 nonce 值,并为使用密钥派生提供一个上下文值。

Transit 允许对纯文本进行签名以及验证给定纯文本的签名。签名操作需要一个非对称密钥,通常使用椭圆曲线加密(Elliptic Curve Cryptography)或 RSA。

备注

签名使用公钥/私钥分离来确保真实性。
签名者使用其私钥创建签名。否则,任何人都能够以你的名义签署消息。验证者使用公钥部分来验证签名。实际的签名通常是一个哈希值。

在内部,哈希值会被计算并使用私钥加密以创建最终的签名。验证过程会解密签名消息,为纯文本计算自己的哈希值,并比较两个哈希值以检查签名是否有效。

byte [] plaintext = "plaintext to sign".getBytes();

transitOperations.createKey("my-ed25519-key", VaultTransitKeyCreationRequest.ofKeyType("ed25519")); 1

Signature signature = transitOperations.sign("my-ed25519-key", Plaintext.of(plaintext)); 2

boolean valid = transitOperations.verify("my-ed25519-key", Plaintext.of(plaintext), signature); 3
java
  • 签名需要非对称密钥。你可以使用任何椭圆曲线加密(Elliptic Curve Cryptography)或 RSA 密钥类型。一旦密钥创建完成,你就具备了创建签名的所有先决条件。

  • 签名是针对纯文本消息创建的。返回的 Signature 包含一个使用 Base64 字符的 ASCII 安全字符串。

  • 为了验证签名,验证过程需要一个 Signature 对象和纯文本消息。作为返回值,你将得到签名是否有效的结果。

你可以在 Vault 参考文档中找到更多关于 Vault Transit Backend 的详细信息。