APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

【AP Tech Blog Week】Secrets Sync を使って HashiCorp Vault のシークレットを Azure Key Vault と GitHub に同期する

本記事はAP Tech Blog Week Vol.4のものになります。


今年の4月にリリースされた HashiCorp Vault v1.16.1 で Secrets Sync という機能が登場しました。

Secrets Sync は Vault に格納したシークレットを他のシークレットマネージャーに同期できる機能です。 この機能を使うことでシークレットの一元管理がより便利になり、シークレット管理におけるガバナンスやコンプライアンスの強化に役立ちます。

引用:https://www.hashicorp.com/blog/secrets-sync-now-available-on-vault-enterprise-to-manage-secrets-sprawl

developer.hashicorp.com

www.hashicorp.com

対応している同期先のシークレットマネージャーは次のとおりです。

  • AWS Secret Manager
  • Google Cloud Secret Manager
  • Microsoft Azure Key Vault
  • GitHub Actions
  • Vercel

注意点としては、Secrets Sync は Vault Enterprise 限定の機能となっており、コミュニティ版の Vault は Secret Sync を使えません。

また、マネージドサービスの HCP Vault は Enterprise 相当の Vault クラスタを提供しているのですが、本記事の執筆時点では HCP Vault の Secrets Sync は無効化されています。 Secrets Sync がベータ公開されていたときには HCP Vault でも試せていましたが、現在は Vault Enterprise のみ利用できます。

【追記】2024年11月11日にリリースされた v1.18.1 で HCP Vault Dedicated でも Secrets Sync が正式に利用できるようになりました!

Secrets Sync の事前準備

Secrets Sync はシークレットマネージャーに対して複数同時に同期できます。 そこで今回は、Secrets Sync を使って私が普段利用している Microsoft Azure Key Vault と GitHub Actions に HashiCorp Vault のシークレットを同期してみます。

まずは Secrets Sync を試すための準備をします。 なお、本投稿では各サービスについて Secrets Sync に必要な設定のみ記載し、詳しい構築手順については記載しませんのでご了承ください。

Vault Enterprise

シークレットの同期元となる HashiCorp Vault Enterprise に Secrets Sync を設定します。 Vault は初期状態では Secrets Sync が無効化されているため、CLI で Vault にログインしてエンドポイント sys/activation-flags/secrets-sync/activate から有効化します。

$ vault write -f sys/activation-flags/secrets-sync/activate 
Key            Value
---            -----
activated      [secrets-sync]
unactivated    []

つぎに、同期対象となるシークレットを作成します。 今回はシークレットエンジンから設定し、my-secret というシークレットの中に Key が 'foo'、Value が 'bar' の値を設定しました。

# kv-v2 シークレットエンジンの有効化
$ vault secrets enable -path='sync-demo-kv' kv-v2
Success! Enabled the kv-v2 secrets engine at: sync-demo-kv/

# シークレットを追加
$ vault kv put -mount='sync-demo-kv' my-secret foo='bar'
======= Secret Path =======
sync-demo-kv/data/my-secret

======= Metadata =======
Key                Value
---                -----
created_time       2024-08-30T12:11:01.025870529Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

Azure Key Vault

1つ目のシークレット同期先として Azure Key Vault を用意します。

learn.microsoft.com

また、Vault クラスタが Azure Key Vault のシークレットを操作できるようにするために Microsoft Entra ID のサービスプリンシパルを作成し、サービスプリンシパルに対して Azure Key Vault の [Key Vault Secrets Officer] ロールを付与します。

vault-application サービスプリンシパルにロール付与

Azure Key Vault とサービスプリンシパルの用意ができたら、それらの情報を使って Vault にシークレットの同期先を設定します。

$ vault write sys/sync/destinations/azure-kv/sync-demo \
    key_vault_uri="$KEY_VAULT_URI" \
    client_id="$CLIENT_ID" \
    client_secret="$CLIENT_SECRET" \
    tenant_id="$TENANT_ID"

Key                   Value
---                   -----
connection_details    map[client_id:00000000-0000-0000-0000-000000000000 client_secret:***** key_vault_uri:https://secretssync.vault.azure.net/ tenant_id:00000000-0000-0000-0000-000000000000]
name                  sync-demo
options               map[custom_tags:map[] granularity_level:secret-path secret_name_template:vault-{{ .MountAccessor | replace "_" "-" }}-{{ .SecretPath }}]
type                  azure-kv

Azure Key Vault への同期準備は以上です。

GitHub Actions

2つ目のシークレット同期先として GitHub リポジトリとパーソナルアクセストークンを用意します。 リポジトリは [Private] に設定しても Secrets Sync はシークレットを同期できます。

developer.hashicorp.com

パーソナルアクセストークンには [Fine-grained personal access tokens] を利用し、トークンでシークレットを更新できるよう [Repository permissions] の [Secrets] に Read and write を設定します。

Secrets のみ [Read and write] を設定

GitHub リポジトリとパーソナルアクセストークンの用意ができたら、それらの情報を使って Vault にシークレットの同期先を設定します。

$ vault write sys/sync/destinations/gh/sync-demo \
    access_token="$ACCESS_TOKEN" \
    repository_owner="$OWNER" \
    repository_name="$NAME"
Key                   Value
---                   -----
connection_details    map[access_token:***** repository_name:vault-secrets-sync repository_owner:apc-nonoshita secrets_location:*****]
name                  sync-demo
options               map[custom_tags:map[] granularity_level:secret-key secret_name_template:VAULT_{{ .MountAccessor | uppercase }}_{{ .SecretPath | uppercase }}_{{ .SecretKey | uppercase }}]
type                  gh

GitHub への同期準備は以上です。

シークレットの同期を開始してみる

それでは Secrets Sync を試してみましょう!

Vault から Azure Key Vault、GitHub へのシークレットを同期を開始します。 同期を始めるには Azure Key Vault と GitHub それぞれの同期先設定に対してどのシークレットを同期するか指定します。 対象のシークレットは事前に準備したマウントパス sync-demo-kv のシークレット my-secret です。

まずは Azure Key Vault から同期します。

$ vault write sys/sync/destinations/azure-kv/sync-demo/associations/set \
    mount='sync-demo-kv' \
    secret_name='my-secret'

Key                   Value
---                   -----
associated_secrets    map[kv_5a8d942c/my-secret:map[accessor:kv_5a8d942c external_name:vault-kv-5a8d942c-my-secret last_operation:Write mount:sync-demo-kv secret_name:my-secret sync_status:SYNCED updated_at:2024-08-30T21:12:19.951151528+09:00]]
store_name            sync-demo
store_type            azure-kv

Azure ポータルから Azure Key Vault を確認するとシークレットが追加されていました。

Azure Key Vault にシークレットが追加された

追加されたシークレットの値を確認すると {"foo":"bar"} と登録されていました。

Azure Key Vault のシークレット値

続いて GitHub の同期を始めます。

$ vault write sys/sync/destinations/gh/sync-demo/associations/set \
    mount=sync-demo-kv \
    secret_name=my-secret
Key                   Value
---                   -----
associated_secrets    map[kv_5a8d942c/my-secret/foo:map[accessor:kv_5a8d942c external_name:VAULT_KV_5A8D942C_MY_SECRET_FOO last_operation:Write mount:sync-demo-kv secret_name:my-secret sub_key:foo sync_status:SYNCED updated_at:2024-08-30T21:35:16.738659733+09:00]]
store_name            sync-demo
store_type            gh

GitHub リポジトリのシークレット管理画面を見るとシークレットが追加されていました。

GitHub リポジトリにシークレットが作成された

GitHub の場合は登録したシークレットの値を参照できません。 GitHub Actions でシークレットを表示してみると bar 登録されていました。

GitHub Actions でシークレットの値を確認

シークレットをローテーションしてみる

次にシークレットの値を bar から baz に変更、シークレットをローテーションしてみます。

 $ vault kv put -mount=sync-demo-kv my-secret foo=baz
======= Secret Path =======
sync-demo-kv/data/my-secret

======= Metadata =======
Key                Value
---                -----
created_time       2024-08-30T12:52:19.330632681Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2

Vault 側のシークレットを更新してすぐに Azure Key Vault と GitHub を確認したところ、両者ともシークレットの値が更新されていました。 Vault から同期先には即座に新しいシークレットの情報が反映されるようです。

Azure Key Vault はシークレットのバージョンが追加された

Azure Key Vault のシークレット値が更新された

GitHub のシークレットも更新された

シークレットに Key-Value を追加してみる

次に、my-secret シークレットに新しい Key-Value hoge=fuga を追加してみます。

$ vault kv patch -mount=sync-demo-kv my-secret hoge=fuga
======= Secret Path =======
sync-demo-kv/data/my-secret

======= Metadata =======
Key                Value
---                -----
created_time       2024-08-30T13:00:27.595736074Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            3

 $ vault kv get -mount=sync-demo-kv my-secret
======= Secret Path =======
sync-demo-kv/data/my-secret

======= Metadata =======
Key                Value
---                -----
created_time       2024-08-30T13:00:27.595736074Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            3

==== Data ====
Key     Value
---     -----
foo     baz
hoge    fuga

Azure Key Vault ではシークレットのバージョンが追加され、シークレットの値も {"foo":"baz","hoge":"fuga"} と更新されていました。

Azure Key Vault のバージョンが追加された

Azure Key Vault のシークレット値が更新された

一方、GitHub には新たにシークレットが追加されました。

GitHub はシークレットが追加された

GitHub ではシークレットの持ち方が Vault と異なるため、Vault のシークレットのキー名が GitHub のシークレット名に組み込まれて Key-Value が分割されてシークレットが管理されます。 そのため、Vault 側で Key-Value を増やした場合は GitHub のシークレットが増えます。

シークレットを削除してみる

それでは Vault から同期対象のシークレットを削除するとどうなるでしょうか。 my-secret シークレットを削除してみます。

$ vault kv delete -mount=sync-demo-kv my-secret
Success! Data deleted (if it existed) at: sync-demo-kv/data/my-secret

すると、Azure Key Vault と GitHub ともにシークレットが削除されました。 シークレットの削除も即座に同期されるようです。

Azure Key Vault からシークレットが削除された

GitHub からもシークレットが削除された

シークレットを復元させてみる

それでは先ほど Vault で削除したシークレットを復元させてみましょう。

kv-v2 シークレットエンジンではメタデータを削除していなければシークレットデータを復元できます。 復元させるとシークレットのバージョンは先ほど続きの '4' になっています。

$ vault kv put -mount=sync-demo-kv my-secret foo=bar
======= Secret Path =======
sync-demo-kv/data/my-secret

======= Metadata =======
Key                Value
---                -----
created_time       2024-08-30T13:15:39.665142806Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            4

Azure Key Vault も GitHub もシークレット同期開始時と同じシークレットが作られていました。 一度同期設定をしたシークレットであれば、シークレット削除した場合でも Vault で復元すると同期先にも再度シークレットが同期されるようです。

Vault にないシークレットは設定できない

ここまでの動作確認は 「Vault シークレット作成 → 同期設定」という流れでシークレットを同期していました。 この手順であれば一度同期設定をすると「シークレットの削除 → 復元」が可能であることも確認できました。

しかし、Vault にないシークレットに対して同期設定しようとすると以下のようにエラーになってしまいました。 Secrets Sync は「同期設定 → Vault シークレット作成」という流れでは同期できないようです。

$ vault write sys/sync/destinations/azure-kv/sync-demo/associations/set \
    mount='sync-demo-kv' \
    secret_name='my-secret2'
Error writing data to sys/sync/destinations/azure-kv/sync-demo/associations/set: Error making API request.

URL: PUT http://127.0.0.1:8200/v1/sys/sync/destinations/azure-kv/sync-demo/associations/set
Code: 400. Errors:

* couldn't set association because the secret referenced by mount "sync-demo-kv" and name "my-secret2" does not exist

おわりに

今回は HashiCorp Vault に追加された Secrets Sync というシークレットを同期する機能を使って、Azure Key Vault と GitHub にシークレットを同期してみました。

現在は Vault Enterprise 限定の機能ではありますが、Vault でシークレットを一元管理するメリットが大幅に増える機能追加だと思います。

今後もシークレット同期先として選べるサービスを増やしていく予定らしいので、HCP Vault で使えるようになる日が待ち遠しいですね!