支持 Vault 的秘密引擎
Spring Vault 附带了几个扩展,以支持 Vault 的各种 Secret Engines。
具体来说,Spring Vault 附带了针对以下 Secret Engines 的扩展:
-
转换(企业功能)
-
系统后端
您可以通过 VaultTemplate 上的方法直接使用所有其他后端(VaultTemplate.read(…),VaultTemplate.write(…))。
键值版本 1(“未版本化 Secret”)
kv Secret Engine 用于在 Vault 配置的物理存储中存储任意 Secret。
当以非版本化方式运行 kv Secret Engine 时,只保留键的最新写入值。非版本化 kv 的好处是每个键的存储大小减少,因为不存储额外的元数据或历史记录。此外,以此方式配置的后端请求性能更高,因为存储调用更少,并且任何给定请求都没有锁定。
Spring Vault 附带了一个专用的键值 API,用于封装各个键值 API 实现之间的差异。VaultKeyValueOperations 遵循 Vault CLI 设计。这是 Vault 的主要命令行工具,提供诸如 vault kv get、vault kv put 等命令。
您可以通过指定版本和挂载路径将此 API 与两个键值引擎版本一起使用。以下示例使用键值版本 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 支持所有键值操作,例如 put、get、delete、list。
或者,由于其直接映射和简单使用,可以通过 VaultTemplate 使用该 API,因为键和响应直接映射到输入和输出键。以下示例演示了在 mykey 处写入和读取 Secret。kv Secret Engine 挂载在 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 键值版本 1 API 的更多详细信息。
Vault 通过 Vault 的 sys/internal/ui/mounts/… 端点确定挂载路径。请确保您的策略允许访问该路径,否则您将无法使用键值 API。 |
键值版本 2(“版本化 Secret”)
您可以运行 kv Secret Engine 的两个版本之一。本节解释了如何使用版本 2。当运行 kv 后端版本 2 时,一个键可以保留可配置数量的版本。您可以检索旧版本的元数据和数据。此外,您可以使用检查和设置操作来避免无意中覆盖数据。
与 键值版本 1(“未版本化 Secret”) 类似,Spring Vault 附带了一个专用的键值 API,用于封装各个键值 API 实现之间的差异。Spring Vault 附带了一个专用的键值 API,用于封装各个键值 API 实现之间的差异。VaultKeyValueOperations 遵循 Vault CLI 设计。这是 Vault 的主要命令行工具,提供诸如 vault kv get、vault kv put 等命令。
您可以通过指定版本和挂载路径将此 API 与两个键值引擎版本一起使用。以下示例使用键值版本 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 支持所有键值操作,例如 put、get、delete、list。
您还可以与版本化键值 API 的具体细节进行交互。如果您想获取特定 Secret 或需要访问元数据,这将非常有用。
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);
| 1 | 将 Secret 存储在 elvis 中,该 Secret 在 secret/ 挂载下可用。 |
| 2 | 将数据存储在版本化后端会返回元数据,例如版本号。 |
| 3 | 版本化键值 API 允许检索由版本号标识的特定版本。 |
| 4 | 版本化键值 Secret 可以映射到值对象中。 |
| 5 | 当使用 CAS 更新版本化 Secret 时,输入必须引用之前获取的版本。 |
虽然可以通过 VaultTemplate 使用 kv v2 Secret Engine。但这不是最方便的方法,因为该 API 提供了不同的上下文路径方法以及输入/输出的表示方式。具体来说,与实际 Secret 的交互需要对数据部分进行包装和解包,并在挂载和 Secret 键之间引入一个 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 键值版本 2 API 的更多详细信息。
Vault 通过 Vault 的 sys/internal/ui/mounts/… 端点确定挂载路径。请确保您的策略允许访问该路径,否则您将无法使用键值 API。 |
PKI(公钥基础设施)
pki Secret Engine 通过实现证书颁发机构操作来表示证书的后端。
PKI Secret Engine 生成动态 X.509 证书。通过此 Secret Engine,服务无需经过通常生成私钥和 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)
| 1 | 使用 VaultCertificateRequest 构建器构建证书请求。 |
| 2 | 从 Vault 请求证书。Vault 充当证书颁发机构,并响应一个签名的 X.509 证书。实际响应是一个 CertificateBundle。 |
| 3 | 您可以直接将生成的证书作为 Java KeyStore 获取,其中包含公钥和私钥以及颁发者证书。KeyStore 具有广泛的用途,这使得此格式适合配置(例如 HTTP 客户端、数据库驱动程序或 SSL 安全的 HTTP 服务器)。 |
| 4 | CertificateBundle 允许通过 Java 密码学扩展 API 直接访问私钥以及公钥和颁发者证书。 |
| 5 | 一旦证书不再使用(或已泄露),您可以通过其序列号吊销它。Vault 将吊销的证书包含在其 CRL 中。 |
您可以在 Vault 参考文档中找到有关 Vault PKI Secret API 的更多详细信息。
令牌认证后端
token 认证方法是内置的,并自动在 /auth/token 处可用。它允许用户使用令牌进行认证,以及创建新令牌、按令牌撤销 Secret 等。
当任何其他认证方法返回身份时,Vault 核心会调用令牌方法为该身份创建一个新的唯一令牌。
您还可以使用令牌存储绕过任何其他认证方法。您可以直接创建令牌,并对令牌执行各种其他操作,例如续订和撤销。
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)
| 1 | 通过应用角色默认值创建令牌。 |
| 2 | 使用构建器 API,您可以为要请求的令牌定义细粒度设置。请求令牌会返回一个 VaultToken,它用作 Vault 令牌的值对象。 |
| 3 | 您可以通过令牌 API 续订令牌。通常,这由 SessionManager 完成,以跟踪 Vault 会话令牌。 |
| 4 | 如果需要,可以通过令牌 API 撤销令牌。通常,这由 SessionManager 完成,以跟踪 Vault 会话令牌。 |
您可以在 Vault 参考文档中找到有关 Vault 令牌认证方法 API 的更多详细信息。
传输后端
传输 Secret Engine 处理传输中数据的加密功能。Vault 不存储发送到此 Secret Engine 的数据。它也可以被视为“加密即服务”或“加密作为服务”。传输 Secret Engine 还可以对数据进行签名和验证,生成数据的哈希和 HMAC,并充当随机字节源。
传输的主要用例是加密应用程序中的数据,同时仍将加密数据存储在某个主要数据存储中。这减轻了应用程序开发人员正确加密和解密的负担,并将负担转移到 Vault 的运营商身上。
Spring Vault 支持广泛的传输操作
-
密钥创建
-
密钥重新配置
-
加密/解密/重新封装
-
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)
| 1 | 首先,我们需要一个密钥。每个密钥都需要指定类型。aes128-gcm96 支持加密、解密、密钥派生和会聚加密,其中我们在此示例中需要加密和解密。 |
| 2 | 接下来,我们加密一个包含要加密的纯文本的 String。输入 String 使用默认的 Charset 将字符串编码为其二进制表示。请求令牌返回一个 VaultToken,它用作 Vault 令牌的值对象。encrypt 方法返回 Base64 编码的密文,通常以 vault: 开头。 |
| 3 | 要将密文解密为纯文本,请调用 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)
| 1 | 假设密钥 my-aes-key 已经存在,我们正在加密 Plaintext 对象。作为回报,encrypt 方法返回一个 Ciphertext 对象。 |
| 2 | Ciphertext 对象可以直接用于解密并返回 Plaintext 对象。 |
Plaintext 和 Ciphertext 附带一个上下文对象 VaultTransitContext。它用于为 会聚加密 提供一个随机数,并用于上下文值以利用密钥派生。
Transit 允许对纯文本进行签名并验证给定纯文本的签名。签名操作需要非对称密钥,通常使用椭圆曲线密码学或 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 后端 的更多详细信息。