Helidon Integration with Vault

原文はこちら。
The original article was written by Tomáš Langer (Oracle).
https://medium.com/helidon/helidon-integration-with-vault-8b98641bc519

HashiCorp Cloud Platform (HCP) Vaultはマイクロサービスでよく使われるVault(機密情報を管理するためのソフトウェア)です。 APIはRESTベースで、Helidonはリアクティブクライアントを使ってVaultとの連携を実装しています。

HCP VaultとHelidonの統合は現在実験的な段階であり、本番運用には適していません。APIや機能がまだ完全にテストされておらず、変更の可能性があります。

Vault統合では以下の機能をサポートします。

シークレットエンジンKey/Value version 1/2
Cubbyhole
PKI
Transit
Database
認証方法Token
Kubernetes (k8s)
AppRole
他のシステム運用および構成シークレットエンジンや認証方法の有効化・無効化

各機能は個別のモジュールとして実装されており、Vaultクラスがそれぞれをバインドしています。Helidon MPでは、インジェクションを使ってこのバインドを自動的に実施するので、好きなシークレットエンジンをインジェクションするだけでよいのです。

これらの機能に加えて、Vaultを以下のように認証することもできます。

トークン認証Vaultと接続時にトークンを構成する
AppRole認証AppRole IDとシークレットIDを構成し、Vaultと接続するために使われるテンポラリトークンのために
これらのIDを交換する
k8s認証k8sのJWTトークンを現在のノードで発見し、そのJWTトークンを使ってテンポラリトークンを取得し、Vaultに接続する

Extensibility

統合は(ServiceLoaderを使う)サービスプロバイダーを基にしているため、新たなシークレットエンジンや認証方法を極めて簡単に実装できます。これにより、Vaultクラスに複数のメソッドをしなくても、新たなシークレットエンジンや認証方法を追加できます。

以下のSPIをご覧ください。

io.helidon.integrations.vault.spi.AuthMethodProvider
io.helidon.integrations.vault.spi.SecretsEngineProvider
io.helidon.integrations.vault.spi.SysProvider
io.helidon.integrations.vault.spi.VaultAuth
io.helidon.integrations.vault.spi.InjectionProvider

Maven Coordinates for Vault Modules

以下はHelidon 2.3.0で利用可能な全てのVaultモジュールのMavenの依存関係です。

<dependency>
    <groupId>io.helidon.integrations.vault</groupId>
    <artifactId>helidon-integrations-vault</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.auths</groupId>
    <artifactId>helidon-integrations-vault-auths-token</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.auths</groupId>
    <artifactId>helidon-integrations-vault-auths-approle</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.auths</groupId>
    <artifactId>helidon-integrations-vault-auths-k8s</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.secrets</groupId>
    <artifactId>helidon-integrations-vault-secrets-kv1</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.secrets</groupId>
    <artifactId>helidon-integrations-vault-secrets-kv2</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.secrets</groupId>
    <artifactId>helidon-integrations-vault-secrets-cubbyhole</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.secrets</groupId>
    <artifactId>helidon-integrations-vault-secrets-transit</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.secrets</groupId>
    <artifactId>helidon-integrations-vault-secrets-database</artifactId>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.vault.sys</groupId>
    <artifactId>helidon-integrations-vault-sys</artifactId>
</dependency>

Local Testing

VaultはDockerイメージとして利用できるため、ローカルでテストする場合は単純に以下を実行するだけです。

docker run -e VAULT_DEV_ROOT_TOKEN_ID=my-token -d --name=vault -p8200:8200 vault

上記コマンドでVaultのDockerイメージを作成し、バックグランドで開始し、localhost:8220でカスタムルートトークンmy-tokenを使い、vaultという名前を使ってオープンします。ルートトークンには多くの権限があるため、これはもちろんローカルテスト用途限定ではありますが、以下の例を使い簡単に利用できます。

Vault Integration with Helidon MP

Helidon MPでVault統合を利用するには、すべての機能のインジェクションを可能にするため、以下のモジュールを追加する必要があります。

<dependency>
    <groupId>io.helidon.integrations.vault</groupId>
    <artifactId>helidon-integrations-vault-cdi</artifactId>
</dependency>

Vault接続の構成は以下の通りです(トークン認証とAppRole認証を使う場合)

vault.default.address=http://localhost:8200
vault.default.token=my-token
vault.approle.address=http://localhost:8200
vault.approle.auth.app-role.role-id=app-role-id
vault.approle.auth.app-role.secret-id=app-role-secret-id

(適切なモジュールがクラスパスにある場合)以下のクラスを任意のCDI beanにインジェクトできます。

Kv2SecretsKey/Value Version 2 Secrets (バージョン付きシークレット、デフォルト)
Kv1SecretsKey/Value Version 1 Secrets (バージョンなしシークレット、レガシー)
CubbyholeSecrets Cubbyhole secrets(トークンがバインドされたシークレット)
DbSecrets Database secrets(一時的なDBの資格証明生成用)
PkiSecrets PKI secrets(キーとX.509証明書生成用)
TransitSecrets Transit operations(暗号化、署名、HMAC)
AppRoleAuth AppRole認証(管理オペレーション)
K8sAuth Kubernetes認証(管理オペレーション)
TokenAuth トークン認証(管理オペレーション)
Sys システム運用(Vaultの管理。シークレットエンジンや認証方法の有効・無効化)
*Rx上記のクラスに対応するリアクティブカウンターパート
CDIがブロッキング環境であるため、通常はCDIでの利用は推奨されません。

以下は、(JAX-RSリソースにおいて)デフォルトのVault構成を使ってシークレットを暗号化する場合のVaultの利用例です。

private final TransitSecrets secrets;

@Inject
TransitResource(TransitSecrets secrets) {
    this.secrets = secrets;
}
//...
@Path("/encrypt/{secret: .*}")
@GET
public String encrypt(@PathParam("secret") String secret) {
    return secrets.encrypt(Encrypt.Request.builder()
                    .encryptionKeyName(ENCRYPTION_KEY)
                    .data(Base64Value.create(secret)))
            .encrypted()
            .cipherText();
}

そして以下はAppRole Vault構成を使ってシークレットを取得する場合のVaultの利用例です。

private final Kv2Secrets secrets;

@Inject
Kv2Resource(@VaultName("app-role") Kv2Secrets secrets) {
    this.secrets = secrets;
}
@GET
@Path("/secrets/{path: .*}")
public Response getSecret(@PathParam("path") String path) {

    Optional<Kv2Secret> secret = secrets.get(path);

    if (secret.isPresent()) {
        Kv2Secret kv2Secret = secret.get();
        return Response.ok()
                .entity("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString())
                .build();
    } else {
        return Response.status(Response.Status.NOT_FOUND).build();
    }
}

Advanced Configuration

エンジンに@VaultPath("path") をインジェクトして正しいパスを取得することにより、カスタムパスでのシークレットエンジンを有効化できます。上記のサンプルコードにて @VaultName の使い方を示していますが、これは設定を区別するために使用しています。

Vault Integration with Helidon SE

Helidon SEではインジェクションを使用しないため、リアクティブなコードではRxという接尾辞のついたAPIを使用し、「手作業」で取得する必要があります。

Vaultに接続するための最小限の構成です。

vault:
  token: "my-token"
  address: "http://localhost:8200"

Vaultのセットアップおよび特定のシークレットエンジンを取得するためのコードです。

Vault vault = Vault.builder()
        .config(config.get("vault"))
        .build();
Kv2SecretsRx secrets = vault.secrets(Kv2SecretsRx.ENGINE);

利用可能な任意のシークレットエンジンで同様のコードを利用できます。

Kv2SecretsRxKey/Value Version 2 Secrets (バージョン付きシークレット、デフォルト)
Kv1SecretsRxKey/Value Version 1 Secrets (バージョンなしシークレット、レガシー)
CubbyholeSecretsRx Cubbyhole secrets(トークンがバインドされたシークレット)
DbSecretsRxDatabase secrets(一時的なDBの資格証明生成用)
PkiSecretsRxPKI secrets(キーとX.509証明書生成用)
TransitSecretsRx Transit operations(暗号化、署名、HMAC)

特定の認証方法を取得するコードです。

K8sAuthRx auth = vault.auth(K8sAuthRx.AUTH_METHOD)

同様のコードを任意の利用可能な認証方法で利用できます。

AppRoleAuthRxAppRole認証(管理オペレーション)
K8sAuthRxKubernetes認証(管理オペレーション)
TokenAuthRxトークン認証(管理オペレーション)

最後にVaultのSysオペレーションを取得するためのコードです。

SysRx sys = vault.sys(SysRx.API);

Vault with WebServer

それでは、エンドツーエンドのリアクティブな動作を使って、WebServer を使った例を作ってみましょう。ここでは Vault API に加え、ServerRequest (req)ServerResponse (res)を使用します。
以下は、シークレットの取得やデータの暗号化/復号化を行うサーバーメソッドの例です。

private void getSecret(ServerRequest req, ServerResponse res) {
    String path = req.path().param("path");

    secrets.get(path)
            .thenAccept(secret -> {
                if (secret.isPresent()) {
                    // using toString so we do not need to depend on JSON-B
                    Kv2Secret kv2Secret = secret.get();
                    res.send("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString());
                } else {
                    res.status(Http.Status.NOT_FOUND_404);
                    res.send();
                }
            })
            .exceptionally(res::send);
}private void encryptSecret(ServerRequest req, ServerResponse res) {
    String secret = req.path().param("text");

    secrets.encrypt(Encrypt.Request.builder()
                            .encryptionKeyName(ENCRYPTION_KEY)
                            .data(Base64Value.create(secret)))
            .forSingle(response -> res.send(response.encrypted().cipherText()))
            .exceptionally(res::send);
}private void decryptSecret(ServerRequest req, ServerResponse res) {
    String encrypted = req.path().param("text");

    secrets.decrypt(Decrypt.Request.builder()
                            .encryptionKeyName(ENCRYPTION_KEY)
                            .cipherText(encrypted))
            .forSingle(response -> res.send(String.valueOf(response.decrypted().toDecodedString())))
            .exceptionally(res::send);
}

Learn More

Helidonの詳細は、プロジェクトのWebサイトやGitHubリポジトリをチェックしてください。

Project Helidon
https://helidon.io
Helidon: Java Libraries for Microservices
https://github.com/oracle/helidon


コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中