Vault リポジトリ

VaultTemplate と Java クラスにマップされたレスポンスを操作すると、読み取り、書き込み、削除などの基本的なデータ操作が可能になります。Vault リポジトリは、Vault に加えて Spring Data のリポジトリの概念を適用します。Vault リポジトリは、基本的な CRUD 機能を公開し、識別子プロパティ、ページング、並べ替えを制約する述語を使用したクエリ導出をサポートします。Vault リポジトリは、キー / 値シークレットエンジン機能を使用してデータを永続化し、クエリします。バージョン 2.4、Spring Vault では、さらにキー / 値バージョン 2 シークレットエンジンを使用できます。実際のシークレットエンジンのバージョンは実行時に検出されます。

バージョン管理されたキー / 値シークレットエンジン内での削除には、DELETE 操作が使用されます。シークレットは CrudRepository.delete(…) を通じて破棄されません。
Spring Data Commons リファレンスドキュメントの Spring Data リポジトリの詳細を参照してください。リファレンスドキュメントでは、Spring Data リポジトリの概要が説明されています。

使用方法

Vault に格納されているドメインエンティティにアクセスするには、リポジトリサポートを利用して、それらの実装を大幅に簡素化できます。

例 1: サンプル認証情報エンティティ
@Secret
class Credentials {

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

ここには非常に単純なドメインオブジェクトがあります。org.springframework.data.annotation.Id でアノテーションが付けられた id という名前のプロパティと、その型に @Secret アノテーションがあることに注意してください。これら 2 つは、Vault 内でオブジェクトを JSON として永続化するために使用される実際のキーを作成するロールを果たします。

@Id でアノテーションが付けられたプロパティ、および id という名前のプロパティは、識別子プロパティと見なされます。アノテーションのあるものは他のものよりも好まれます。

次のステップは、ドメインオブジェクトを使用するリポジトリインターフェースを宣言することです。

例 2: Credentials エンティティの基本リポジトリインターフェース
interface CredentialsRepository extends CrudRepository<Credentials, String> {

}

リポジトリは CrudRepository を継承するため、基本的な CRUD とクエリメソッドを提供します。Vault リポジトリには Spring Data コンポーネントが必要です。クラスパスに spring-data-commons および spring-data-keyvalue アーティファクトを必ず含めてください。

これを実現する最も簡単な方法は、依存関係管理を設定し、アーティファクトを pom.xml に追加することです。

次に、次を pom.xml の依存関係セクションに追加します。

例 3: Spring Data BOM の使用
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>2023.1.2</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>3.1.1</version>
  </dependency>

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

</dependencies>

物事を接着するために間に必要なものは、それに応じた Spring 構成です。

例 4: Vault リポジトリ用の JavaConfig
@Configuration
@EnableVaultRepositories
class ApplicationConfig {

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

上記の設定があれば、続行して CredentialsRepository をコンポーネントに注入できます。

例 5: 個人エンティティへのアクセス
@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 キー値シークレットシークレットエンジンのキーパターン keyspace/id (この場合は credentials/heisenberg) を使用して、Credentials のプロパティを Vault ハッシュ内に格納します。
2 指定された ID を使用して、keyspace/id に格納されているオブジェクトを取得します。
3Credentials で @Secret によって定義されたキースペース資格情報内で使用可能なエンティティの総数をカウントします。
4 指定されたオブジェクトのキーを Vault から削除します。

オブジェクトから Vault JSON へのマッピング

Vault リポジトリは、JSON を交換形式として使用して Vault にオブジェクトを格納します。JSON とエンティティ間のオブジェクトマッピングは VaultConverter によって行われます。コンバーターは、VaultResponse からの本体を含む SecretDocument の読み取りと書き込みを行います。VaultResponse は Vault から読み取られ、本体は Jackson によって String と Object の Map に逆直列化されます。デフォルトの VaultConverter 実装は、ネストされた値、List および Map オブジェクトを含む 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 にマップする必要があります。
表 1: デフォルトのマッピングルール
タイプ サンプル マップされた値

シンプル型
(例: 弦)

String firstname=" ウォルター ";

" ファーストネーム ": 「ウォルター」

複合型
(例: 住所)

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

" 住所 ": { " 街 ": "308 ネグラアロヨレーン " }

リスト
シンプル型

List<String> ニックネーム = asList("walt" , "heisenberg" );

「ニックネーム」: [「ウォルト」、「ハイゼンベルグ」]

地図
シンプル型

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

「アッツ」: {" 年 ": 51}

リスト
複合型

リスト <Address> アドレス = asList(新しいアドレス ("308 …

" 住所 ": [{ " 街 ": "308 Negra Arroyo Lane" }, … ]

VaultCustomConversions に Converter を登録することで、マッピングの動作をカスタマイズできます。これらのコンバーターは、LocalDate や SecretDocument などの型との間の変換を処理できますが、最初のコンバーターは単純なプロパティの変換に適しており、最後のコンバーターは複雑な型を JSON 表現に変換するのに適しています。2 番目のオプションでは、結果の SecretDocument を完全に制御できます。オブジェクトを Vault に書き込むと、コンテンツが削除され、エントリ全体が再作成されるため、マップされていないデータは失われます。

クエリとクエリメソッド

クエリメソッドを使用すると、メソッド名から単純なクエリを自動的に派生させることができます。Vault にはクエリエンジンはありませんが、HTTP コンテキストパスに直接アクセスする必要があります。Vault クエリメソッドは、Vault の API の可能性をクエリに変換します。クエリメソッドの実行は、コンテキストパスに子を一覧表示し、Id にフィルター処理を適用し、オプションでオフセット / 制限を使用して Id ストリームを制限し、結果をフェッチした後に並べ替えを適用します。

例 6: サンプルリポジトリクエリメソッド
interface CredentialsRepository extends CrudRepository<Credentials, String> {

  List<Credentials> findByIdStartsWith(String prefix);
}
Vault リポジトリのクエリメソッドは、@Id プロパティの述語を含むクエリのみをサポートします。

Vault でサポートされているキーワードの概要を次に示します。

表 2: クエリメソッドでサポートされるキーワード
キーワード サンプル

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

並べ替えとページング

クエリメソッドは、Vault コンテキストパスから取得したサブリスト (オフセット / 制限) ID をメモリ内で選択することにより、並べ替えとページングをサポートします。クエリメソッドの述語とは異なり、並べ替えは特定のフィールドに限定されません。非ページソートは ID フィルタリングの後に適用され、結果のすべてのシークレットは Vault からフェッチされます。このようにして、クエリメソッドは、結果の一部としても返される結果のみをフェッチします。

ページングとソートを使用するには、パフォーマンスに影響を与える ID をフィルタリングする前にシークレットフェッチが必要です。並べ替えとページングは、Vault によって返される Id の自然順序が変更された場合でも、同じ結果を返すことを保証します。最初にすべての Id が Vault から取得され、次に並べ替えが適用され、その後フィルタリングとオフセット / 制限が適用されます。

例 7: ページングとソートのリポジトリ
interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {

  List<Credentials> findTop10ByIdStartsWithOrderBySocialSecurityNumberDesc(String prefix);

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

楽観的ロック

Vaults キー / 値シークレットエンジンバージョン 2 は、バージョン付きシークレットを維持できます。Spring Vault は、@Version のアノテーションが付けられたドメインモデルのバージョンプロパティを通じてバージョン管理をサポートします。オプティミスティックロックを使用すると、バージョンが一致するシークレットにのみ更新が適用されます。バージョンプロパティの実際の値は、cas プロパティを通じて更新リクエストに追加されます。その間に別の操作によってシークレットが変更された場合、OptimisticLockingFailureException がスローされ、シークレットは更新されません。

バージョンプロパティは、int や long などの数値プロパティである必要があり、シークレットを更新するときに cas プロパティにマップする必要があります。

例 8: バージョン管理されたエンティティのサンプル
@Secret
class VersionedCredentials {

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

次の例は、これらの機能を示しています。

例 9: バージョン管理されたエンティティのサンプル
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)
1ID sample-credentials によってシークレットを取得します。
2ID sample-credentials によってシークレットの 2 番目のインスタンスを取得します。
3 シークレットを更新し、Vault のバージョンをインクリメントします。
4 以前のバージョンを使用する 2 番目のインスタンスを更新します。その間にバージョンが Vault でインクリメントされたため、操作は OptimisticLockingFailureException で失敗します。
バージョン管理されたシークレットを削除する場合、ID による削除では最新のシークレットが削除されます。エンティティごとに削除では、指定されたバージョンでシークレットが削除されます。

バージョン管理されたシークレットへのアクセス

Key/Value バージョン 2 シークレットエンジンは、Vault リポジトリインターフェース宣言で RevisionRepository (Javadoc) を実装することでアクセスできるシークレットのバージョンを維持します。リビジョンリポジトリは、特定の識別子のリビジョンを取得するための検索メソッドを定義します。識別子は String である必要があります。

例 10: RevisionRepository の実装
interface RevisionCredentialsRepository extends CrudRepository<Credentials, String>,
                                        RevisionRepository<Credentials, String, Integer> (1)
{

}
1 最初の型パラメーター (Credentials) はエンティティの型を示し、2 番目 (String) は id プロパティの型を示し、最後の型パラメーター (Integer) はリビジョン番号の型を示します。Vault は、String 識別子と Integer リビジョン番号のみをサポートします。

使用方法

次の例に示すように、RevisionRepository のメソッドを使用してエンティティのリビジョンをクエリできるようになりました。

例 11: 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));