Vault Repositories

使用 VaultTemplate 和映射到 Java 类的响应允许进行基本的读写删除等数据操作。Vault 存储库在 Vault 之上应用了 Spring Data 的存储库概念。Vault 存储库公开基本 CRUD 功能,并支持使用约束标识符属性、分页和排序的谓词进行查询推导。Vault 存储库使用键/值秘密引擎功能来持久保存和查询数据。从版本 2.4 开始,Spring Vault 还可以使用键/值版本 2 秘密引擎,实际秘密引擎版本在运行时发现。

版本化键/值机密引擎内的删除使用 DELETE 操作。机密不会通过 CrudRepository.delete(…) 销毁。

Spring Data Commons reference documentation 中阅读有关 Spring Data 存储库的更多信息。参考文档将为你介绍 Spring Data 存储库。

Usage

要访问存储在 Vault 中的域实体,您可以利用存储库支持,它可以大幅简化实现这些实体。

Example 1. Sample Credentials Entity
@Secret
class Credentials {

  @Id String id;
  String password;
  String socialSecurityNumber;
  Address address;
}

我们这里有一个非常简单的域对象。请注意,它有一个使用 org.springframework.data.annotation.Id 进行注释名为 id 的属性,并且其类型上有 @Secret 注释。这两个负责创建实际密钥,用于将对象作为 JSON 持久保存到 Vault 中。

使用 @Id 进行注释的属性以及名为 id 的属性被视为标识符属性。使用注释的比其他属性优先。

下一步是声明一个使用域对象的存储库接口。

Example 2. Basic Repository Interface for Credentials entities
interface CredentialsRepository extends CrudRepository<Credentials, String> {

}

由于我们的存储库扩展了 CrudRepository,它提供基本的 CRUD 和查询方法。Vault 存储库需要 Spring Data 组件。请务必在类路径中包含 spring-data-commonsspring-data-keyvalue 工件。

实现此目的最简单的方法是设置依赖项管理并向 pom.xml 添加工件:

然后将以下内容添加到 pom.xml 依赖项部分。

Example 3. Using the Spring Data BOM
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>{springDataVersion}</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>org.springframework.vault</groupId>
    <artifactId>spring-vault-core</artifactId>
    <version>{version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-keyvalue</artifactId>
    <!-- Version inherited from the BOM -->
  </dependency>

</dependencies>

我们需要添加的内容是用于将所有内容粘合在一起的相应 Spring 配置。

Example 4. JavaConfig for Vault Repositories
@Configuration
@EnableVaultRepositories
class ApplicationConfig {

  @Bean
  VaultTemplate vaultTemplate() {
    return new VaultTemplate(…);
  }
}

给定上述设置,我们可以继续将 CredentialsRepository 注入我们的组件中。

Example 5. Access to Person Entities
@Autowired CredentialsRepository repo;

void basicCrudOperations() {

  Credentials creds = new Credentials("heisenberg", "327215", "AAA-GG-SSSS");
  rand.setAddress(new Address("308 Negra Arroyo Lane", "Albuquerque", "New Mexico", "87104"));

  repo.save(creds);                                        1

  repo.findOne(creds.getId());                             2

  repo.count();                                            3

  repo.delete(creds);                                      4
}
1 Credentials 的属性存储在使用密钥模式 keyspace/id 的 Vault Hash 中,在本例中为 credentials/heisenberg,位于 key-value 机密 secrets 引擎中。
2 使用提供的 ID 在 keyspace/id 存储的对象进行检索。
3 统计 @SecretCredentials 上定义的键空间 credentials 中可用实体的总数。
4 从 Vault 移除给定对象的密钥。

Object to Vault JSON Mapping

Vault 存储库使用 JSON 作为交换格式将对象存储在 Vault 中。由 VaultConverter 完成 JSON 与实体之间的对象映射。转换器读写 SecretDocument,其中包含 VaultResponse 的正文。StringObjectVaultResponse`s are read from Vault and the body is deserialized by Jackson into a `Map。默认 VaultConverter 实现使用嵌套值、ListMap 对象读取 Map,并将这些内容转换为实体,反之亦然。

鉴于前面章节中的 Credentials 类型,默认映射如下:

{
  "_class": "org.example.Credentials",                 1
  "password": "327215",                                2
  "socialSecurityNumber": "AAA-GG-SSSS",
  "address": {                                         3
    "street": "308 Negra Arroyo Lane",
    "city": "Albuquerque",
    "state": "New Mexico",
    "zip": "87104"
  }
}
1 _class 属性包含在根级别以及任何嵌套接口或抽象类型中。
2 简单属性值按路径映射。
3 复杂类型的属性映射为嵌套对象。

@Id 属性必须映射到 String

Table 1. Default Mapping Rules
Type Sample Mapped Value

Simple Type (eg. String)

String firstname = "Walter";

"firstname": "Walter"

Complex Type (eg. Address)

Address adress = new Address("308 Negra Arroyo Lane");

"address": { "street": "308 Negra Arroyo Lane" }

List of Simple Type

List<String> nicknames = asList("walt", "heisenberg");

"nicknames": ["walt", "heisenberg"]

Map of Simple Type

Map<String, Integer> atts = asMap("age", 51)

"atts" : {"age" : 51}

List of Complex Type

List<Address> addresses = asList(new Address("308…

"address": [{ "street": "308 Negra Arroyo Lane" }, …]

您可以通过在 VaultCustomConversions 中注册 Converter 来定制映射行为。这些转换器可以处理从/到 LocalDateSecretDocument 等类型的转换,其中第一个适用于转换简单属性,而最后一个适用于将复杂类型转换为其 JSON 表示。第二个选项可完全控制结果 SecretDocument。将对象写入 Vault 将删除内容并重新创建整个条目,因此未映射的数据将丢失。

Queries and Query Methods

查询方法允许从方法名称自动推导出简单查询。Vault 没有查询引擎,但需要直接访问 HTTP 上下文路径。Vault 查询方法将 Vault 的 API 可能性转换为查询。查询方法执行在上下文路径下列出子级,对 ID 应用过滤,必要时使用偏移量/限制限制 ID 流,并在获取结果后应用排序。

Example 6. Sample Repository Query Method
interface CredentialsRepository extends CrudRepository<Credentials, String> {

  List<Credentials> findByIdStartsWith(String prefix);
}

Vault 存储库的查询方法仅支持对 @Id 属性有谓词的查询。

以下是 Vault 支持的关键字概述。

Table 2. Supported keywords for query methods
Keyword Sample

After, GreaterThan

findByIdGreaterThan(String id)

GreaterThanEqual

findByIdGreaterThanEqual(String id)

Before, LessThan

findByIdLessThan(String id)

LessThanEqual

findByIdLessThanEqual(String id)

Between

findByIdBetween(String from, String to)

In

findByIdIn(Collection ids)

NotIn

findByIdNotIn(Collection ids)

Like, StartingWith, EndingWith

findByIdLike(String id)

NotLike, IsNotLike

findByIdNotLike(String id)

Containing

findByFirstnameContaining(String id)

NotContaining

findByFirstnameNotContaining(String name)

Regex

findByIdRegex(String id)

(No keyword)

findById(String name)

Not

findByIdNot(String id)

And

findByLastnameAndFirstname

Or

findByLastnameOrFirstname

Is,Equals

findByFirstname,findByFirstnameIs,findByFirstnameEquals

Top,First

findFirst10ByFirstname,findTop5ByFirstname

Sorting and Paging

查询方法通过在内存中选择从 Vault 上下文路径检索的子列表(偏移/限制)ID 来支持排序和分页。与查询方法谓词不同,排序不受特定字段的限制。在 ID 过滤后应用未分页排序,并将所有生成的秘密从 Vault 中提取。通过这种方式,查询方法只能提取作为结果的一部分返回的结果。

使用分页和排序要求在过滤 ID 之前提取秘密,这会影响性能。即使 Vault 返回的 ID 的自然顺序发生变化,排序和分页也保证返回相同的结果。因此,首先从 Vault 中提取所有 ID,然后应用排序,最后进行过滤和偏移/限制。

Example 7. Paging and Sorting Repository
interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {

  List<Credentials> findTop10ByIdStartsWithOrderBySocialSecurityNumberDesc(String prefix);

  List<Credentials> findByIdStarts(String prefix, Pageable pageRequest);
}

Optimistic Locking

Vault 的 key/value 秘密引擎版本 2 可以维护版本化的秘密。Spring Vault 通过域模型中使用 @Version 注解的版本属性支持版本控制。使用乐观锁可确保仅将更新应用于与版本匹配的秘密。因此,通过 cas 属性将版本属性的实际值添加到更新请求中。如果在此时另一个操作改变了秘密,则会抛出 OptimisticLockingFailureException,并且秘密不会被更新。

版本属性必须是数字属性,例如 intlong,并在更新秘密时映射到 cas 属性。

Example 8. Sample Versioned Entity
@Secret
class VersionedCredentials {

  @Id String id;
  @Version int version;
  String password;
  String socialSecurityNumber;
  Address address;
}

以下示例展示了这些功能:

Example 9. Sample Versioned Entity
VersionedCredentialsRepository repo = …;

VersionedCredentials credentials = repo.findById("sample-credentials").get();    1

VersionedCredentials concurrent = repo.findById("sample-credentials").get();     2

credentials.setPassword("something-else");

repos.save(credentials);                                                         3


concurrent.setPassword("concurrent change");

repos.save(concurrent); // throws OptimisticLockingFailureException              4
1 根据其 ID sample-credentials 获取一个密码。
2 根据其 ID sample-credentials 获取密码的第二个实例。
3 更新密码,并让 Vault 增加版本。
4 更新使用上一个版本的第二个实例。该操作将失败,并出现 OptimisticLockingFailureException,因为与此同时 Vault 中的版本已增加。

在删除版本化机密时,按 ID 删除将删除最新的机密。按实体删除将删除指定版本处的机密。

Accessing versioned secrets

Key/Value 版本 2 秘钥引擎维护可以通过在您的 Vault 存储库接口声明中实现 RevisionRepository 来访问的秘钥版本。修订存储库定义了查找方法,以获得特定标识符的修订。标识符必须是 String

Example 10. Implementing RevisionRepository
interface RevisionCredentialsRepository extends CrudRepository<Credentials, String>,
                                        RevisionRepository<Credentials, String, Integer> 1
{

}
1 第一个类型参数 (Credentials) 表示实体类型,第二个 (String) 表示 id 属性的类型,最后一个 (Integer) 是版本号的类型。Vault 仅支持 String 标识符和 Integer 版本号。

Usage

现在,您可以使用 RevisionRepository 中的方法查询实体的修订,如下所示:

Example 11. Using RevisionRepository
RevisionCredentialsRepository repo = …;

Revisions<Integer, Credentials> revisions = repo.findRevisions("my-secret-id");

Page<Revision<Integer, Credentials>> firstPageOfRevisions = repo.findRevisions("my-secret-id", Pageable.ofSize(4));