Vault Repositories

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

Working with VaultTemplate and responses mapped to Java classes allows basic data operations like read, write and delete. Vault repositories apply Spring Data’s repository concept on top of Vault. A Vault repository exposes basic CRUD functionality and supports query derivation with predicates constraining the identifier property, paging and sorting. Vault repositories use the key/value secrets engine functionality to persist and query data. As of version 2.4, Spring Vault can use additionally key/value version 2 secrets engine, the actual secrets engine version is discovered during runtime.

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

Deletes within versioned key/value secrets engine use the DELETE operation. Secrets are not destroyed through CrudRepository.delete(…).

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

Read more about Spring Data Repositories in the Spring Data Commons reference documentation. The reference documentation will give you an introduction to Spring Data repositories.

Usage

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

To access domain entities stored in Vault you can leverage repository support that eases implementing those quite significantly.

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 中。

We have a pretty simple domain object here. Note that it has a property named id annotated with org.springframework.data.annotation.Id and a @Secret annotation on its type. Those two are responsible for creating the actual key used to persist the object as JSON inside Vault.

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

Properties annotated with @Id as well as those named id are considered as the identifier properties. Those with the annotation are favored over others.

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

The next step is to declare a repository interface that uses the domain object.

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

}

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

As our repository extends CrudRepository it provides basic CRUD and query methods. Vault repositories require Spring Data components. Make sure to include spring-data-commons and spring-data-keyvalue artifacts in your class path.

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

The easiest way to achieve this, is by setting up dependency management and adding the artifacts to your pom.xml:

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

Then add the following to pom.xml dependencies section.

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 配置。

The thing we need in between to glue things together is the according Spring configuration.

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

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

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

Given the setup above we can go on and inject CredentialsRepository into our components.

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 Stores properties of Credentials inside Vault Hash with a key pattern keyspace/id, in this case credentials/heisenberg, in the key-value secret secrets engine.
2 Uses the provided id to retrieve the object stored at keyspace/id.
3 Counts the total number of entities available within the keyspace credentials defined by @Secret on Credentials.
4 Removes the key for the given object from 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,并将这些内容转换为实体,反之亦然。

Vault repositories store objects in Vault using JSON as interchange format. Object mapping between JSON and the entity is done by VaultConverter. The converter reads and writes SecretDocument that contains the body from a VaultResponse. VaultResponse`s are read from Vault and the body is deserialized by Jackson into a `Map of String and Object. The default VaultConverter implementation reads the Map with nested values, List and Map objects and converts these to entities and vice versa.

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

Given the Credentials type from the previous sections the default mapping is as follows:

{
  "_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 The _class attribute is included on root level as well as on any nested interface or abstract types.
2 Simple property values are mapped by path.
3 Properties of complex types are mapped as nested objects.

@Id 属性必须映射到 String

The @Id property must be mapped to 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 将删除内容并重新创建整个条目,因此未映射的数据将丢失。

You can customize the mapping behavior by registering a Converter in VaultCustomConversions. Those converters can take care of converting from/to a type such as LocalDate as well as SecretDocument whereas the first one is suitable for converting simple properties and the last one complex types to their JSON representation. The second option offers full control over the resulting SecretDocument. Writing objects to Vault will delete the content and re-create the whole entry, so not mapped data will be lost.

Queries and Query Methods

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

Query methods allow automatic derivation of simple queries from the method name. Vault has no query engine but requires direct access of HTTP context paths. Vault query methods translate Vault’s API possibilities to queries. A query method execution lists children under a context path, applies filtering to the Id, optionally limits the Id stream with offset/limit and applies sorting after fetching the results.

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

  List<Credentials> findByIdStartsWith(String prefix);
}

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

Query methods for Vault repositories support only queries with predicates on the @Id property.

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

Here’s an overview of the keywords supported for 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 中提取。通过这种方式,查询方法只能提取作为结果的一部分返回的结果。

Query methods support sorting and paging by selecting in memory a sublist (offset/limit) Id’s retrieved from a Vault context path. Sorting has is not limited to a particular field, unlike query method predicates. Unpaged sorting is applied after Id filtering and all resulting secrets are fetched from Vault. This way a query method fetches only results that are also returned as part of the result.

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

Using paging and sorting requires secret fetching before filtering the Id’s which impacts performance. Sorting and paging guarantees to return the same result even if the natural order of Id returned by Vault changes. Therefore, all Id’s are fetched from Vault first, then sorting is applied and afterwards filtering and offset/limiting.

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,并且秘密不会被更新。

Vaults key/value secrets engine version 2 can maintain versioned secrets. Spring Vault supports versioning through a version property in the domain model that are annotated with @Version. Using optimistic locking makes sure updates are only applied to secrets with a matching version. Therefore, the actual value of the version property is added to the update request through the cas property. If another operation altered the secret in the meantime, then an OptimisticLockingFailureException is thrown and the secret isn’t updated.

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

Version properties must be numeric properties such as int or long and map to the cas property when updating secrets.

Example 8. Sample Versioned Entity
@Secret
class VersionedCredentials {

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

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

The following example shows these features:

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 Obtain a secret by its Id sample-credentials.
2 Obtain a second instance of the secret by its Id sample-credentials.
3 Update the secret and let Vault increment the version.
4 Update the second instance that uses the previous version. The operation fails with an OptimisticLockingFailureException as the version was incremented in Vault in the meantime.

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

When deleting versioned secrets, delete by Id deletes the most recent secret. Delete by entity deletes the secret at the provided version.

Accessing versioned secrets

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

Key/Value version 2 secrets engine maintains versions of secrets that can be accessed by implementing RevisionRepository in your Vault repository interface declaration. Revision repositories define lookup methods to obtain revisions for a particular identifier. Identifiers must be String.

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

}
1 The first type parameter (Credentials) denotes the entity type, the second (String) denotes the type of the id property, and the last one (Integer) is the type of the revision number. Vault supports only String identifiers and Integer revision numbers.

Usage

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

You can now use the methods from RevisionRepository to query the revisions of the entity, as the following example shows:

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));