支持 Vault 的 Secret Engines
Spring Vault 提供了多个扩展,以支持 Vault 的各种秘密引擎。
具体来说,Spring Vault 提供了以下扩展:
-
Transform(企业版功能)
-
系统后端(System Backend)
你可以直接通过 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 get
、vault 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");
VaultKeyValueOperations 支持所有的 Key-Value 操作,例如 put
、get
、delete
、list
。
或者,可以通过 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");
你可以在 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 get
、vault 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");
VaultKeyValueOperations 支持所有 Key-Value 操作,例如 put
、get
、delete
、list
。
您还可以与版本化的键值 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);
将密钥存储在
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");
你可以在 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
使用 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
通过应用角色默认值创建一个令牌。
使用构建器 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
首先,我们需要一个密钥来开始。每个密钥都需要指定类型。
aes128-gcm96
支持加密、解密、密钥派生和收敛加密,在本例中我们需要的是加密和解密。接下来,我们加密一个包含明文文本的
String
。输入的String
使用默认的Charset
将字符串编码为二进制表示。请求令牌会返回一个VaultToken
,它用作 Vault 令牌的值对象。encrypt
方法返回 Base64 编码的密文,通常以vault:
开头。要将密文解密为明文,调用
decrypt
方法。它会解密密文并返回一个使用默认字符集解码的String
。
前面的示例在加密操作中使用了简单的字符串。虽然这种方法简单,但它存在字符集配置错误的风险,并且不是二进制安全的。当明文使用二进制表示数据(如图像、压缩数据或二进制数据结构)时,需要二进制安全性。
要对二进制数据进行加密和解密,可以使用 Plaintext 和 Ciphertext 值对象,这些对象可以保存二进制值:
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
假设已经存在一个密钥
my-aes-key
,我们正在加密 Plaintext 对象。作为返回,encrypt
方法会返回一个 Ciphertext 对象。Ciphertext 对象可以直接用于解密,并返回一个 Plaintext 对象。
Plaintext 和 Ciphertext 都附带一个上下文对象 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
你可以在 Vault 参考文档中找到更多关于 Vault Transit Backend 的详细信息。