おうち K8s クラスタで HashiCorp Vault を動かしているのですが、Pod が再起動すると Vault が Seal 状態となってしまうので都度 Unseal Key を入力しています。 そろそろ煩わしさが限界なので Azure Key Vault と連携して Vault の Auto Unseal 機能を使ってみます。

Auto-unseal using Azure Key Vault | Vault | HashiCorp Developer

This tutorial demonstrates an example for enabling Auto-unseal with Azure Key Vault.


Auto Unseal 設定

Auto Unseal を使うためには以下の設定が必要になります。

  • Entra ID サービスプリンシパル
  • Azure Key Vault
  • Vault Config

サービスプリンシパル

Microsoft Entra ID に Vault の Auto Unseal で使用するサービスプリンシパルを作成します。

私の環境では Dynamic Secrets 用の Vault サービスプリンシパルを作成済みなので今回はこちらを流用します。 Vault サービスプリンシパルのシークレットは Vault がローテーションしてくれるのですが、Auto Uneal は Vault の設定ファイルに直接サービスプリンシパルのシークレットを記述する形式です。 そのため、サービスプリンシパルにもローテーションされない(有効期限を設定している)シークレットを作成します。

もしかしたら Auto Unseal 用のサービスプリンシパルを別途作成するのが安牌かもしれません。

Azure Key Vault

Azure Key Vault を用意して Auto Unseal 時に使用されるキー(今回は generated-key という名前)を作成します。 そして、Vault サービスプリンシパルがシークレットを参照できるように組み込みロール Key Vault Secrets User を設定します。

Vault

Vault on K8s は Helm を使ってインストール(実際には ArgoCD から呼び出す Kustomize で Helm を実行)していて、Vault の設定は ConfigMap vault-confg/vault/config/extraconfig-from-values.hcl にマウントされています。 Azure Key Vault と連携して Auto Unseal を使う場合は以下のようなサービスプリンシパルのシークレットを含む設定を使用します。

seal "azurekeyvault" {
  client_id      = "00000000-0000-0000-0000-000000000000"
  client_secret  = "************************************"
  tenant_id      = "00000000-0000-0000-0000-000000000000"
  vault_name     = "key-vault-name"
  key_name       = "generated-key"
}

名前の通りですが、各項目は以下のとおり。

項目
client_id サービスプリンシパル のクライアント ID
client_secret サービスプリンシパルのシークレット
tenant_id Microsoft Entra テナント ID
vault_name Azure Key Vault のリソース名
key_name Azure Key Vault 内のキー名

この設定を Vault が参照できるようにしてあげる必要があり Helm の values.yaml で ConfigMap を上書きするやりかたがあるのですが、Git 管理している values.yaml にサービスプリンシパルのシークレットを直接記入することはしたくありません。 このことは公式でも言及されており、Secret リソースをマウントして Vault に参照させることで回避可能なようです。

Running Vault - Kubernetes | Vault | HashiCorp Developer

Vault can run directly on Kubernetes in various configurations. For pure-Kubernetes workloads, this enables Vault to also exist purely within Kubernetes.


じゃあその Secret リソース自体の管理はどうするの……という疑問が湧いてきます。 そもそも Secretリソースを Git 管理しなくていいようにするために Vault(そして Vault Secrets Operator)を導入しているので本末転倒な感じはありますが、一旦ここは目を瞑って Auto Unseal 用の Secret リソースは手動管理、所謂「運用でカバー」します。

まずは以下のコマンドで Auto Unseal の設定を含む Secret リソースを作成します。

cat << EOF > config.hcl
seal "azurekeyvault" {
client_id      = "00000000-0000-0000-0000-000000000000"
client_secret  = ""************************************""
tenant_id      = "00000000-0000-0000-0000-000000000000"
vault_name     = "key-vault-name"
key_name       = "generated-key"
}
EOF

kubectl create secret generic vault-storage-config --from-file=config.hcl

次に Helm の values.yaml に以下のような設定をして、Secret リソースをマウントします。

server:
  volumes:
    - name: userconfig-vault-storage-config
      secret:
        defaultMode: 420
        secretName: vault-storage-config
  volumeMounts:
    - mountPath: /vault/userconfig/vault-storage-config
      name: userconfig-vault-storage-config
      readOnly: true
  extraArgs: "-config=/vault/userconfig/vault-storage-config/config.hcl"

以上で Vault の設定は完了です。

上述のとおり Secret リソースは手動管理となっています。 もしかしたら Vault Secrets Operator を使ったらいい感じに Auto Unseal の Secret リソースも管理外にすることができるかもしれませんが、一旦今回はここまで。

Vault 移行

設定が完了したら Auto Seal が機能するか確認します。 Vault の初期構築段階で Auto Unseal を使う場合は特に気にする必要なく vault init コマンドするだけで良いようですが、既に Vault が動いている環境で Auto Unseal を使う場合は Seal 設定の移行が必要です。

Seal/Unseal | Vault | HashiCorp Developer

A Vault must be unsealed before it can access its data. Likewise, it can be sealed to lock it down.


Vault Pod を再起動すると Seal 状態になっているので vault operator unseal -migrate コマンドを実行します。 いつも通り Unseal キーを 3 回入力すると Unseal されるとともに Auto Unseal が有効になります。

==> Vault server configuration:

Administrative Namespace: 
             Api Address: http://10.0.4.33:8200
                     Cgo: disabled
         Cluster Address: https://vault-0.vault-internal:8201
   Environment Variables: GODEBUG, HOME, HOSTNAME, HOST_IP, KUBERNETES_PORT, KUBERNETES_PORT_443_TCP, KUBERNETES_PORT_443_TCP_ADDR, KUBERNETES_PORT_443_TCP_PORT, KUBERNETES_PORT_443_TCP_PROTO, KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT, KUBERNETES_SERVICE_PORT_HTTPS, NAME, PATH, POD_IP, PWD, SHLVL, SKIP_CHOWN, SKIP_SETCAP, TERM, VAULT_ADDR, VAULT_API_ADDR, VAULT_CLUSTER_ADDR, VAULT_DISABLE_FILE_PERMISSIONS_CHECK, VAULT_K8S_NAMESPACE, VAULT_K8S_POD_NAME, VAULT_PORT, VAULT_PORT_8200_TCP, VAULT_PORT_8200_TCP_ADDR, VAULT_PORT_8200_TCP_PORT, VAULT_PORT_8200_TCP_PROTO, VAULT_PORT_8201_TCP, VAULT_PORT_8201_TCP_ADDR, VAULT_PORT_8201_TCP_PORT, VAULT_PORT_8201_TCP_PROTO, VAULT_SERVICE_HOST, VAULT_SERVICE_PORT, VAULT_SERVICE_PORT_HTTP, VAULT_SERVICE_PORT_HTTPS_INTERNAL, VERSION, container
              Go Version: go1.21.1
              Listener 1: tcp (addr: "[::]:8200", cluster address: "[::]:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
               Log Level: 
                   Mlock: supported: true, enabled: false
           Recovery Mode: false
                 Storage: file
                 Version: Vault v1.15.0, built 2023-09-22T16:53:10Z
             Version Sha: b4d07277a6c5318bb50d3b94bbd6135dccb4c601

==> Vault server started! Log data will stream in below:

2023-11-20T18:32:49.634Z [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
2023-11-20T18:32:50.427Z [INFO]  incrementing seal generation: generation=1
2023-11-20T18:32:50.429Z [WARN]  core: entering seal migration mode; Vault will not automatically unseal even if using an autoseal: from_barrier_type=shamir to_barrier_type=azurekeyvault
2023-11-20T18:32:50.429Z [INFO]  core: Initializing version history cache for core
2023-11-20T18:32:50.429Z [INFO]  events: Starting event system
2023-11-20T18:34:28.654Z [INFO]  core: unsealing using migration seal
2023-11-20T18:34:38.707Z [INFO]  core: unsealing using migration seal
2023-11-20T18:34:43.661Z [INFO]  core: unsealing using migration seal
2023-11-20T18:34:43.669Z [INFO]  core.cluster-listener.tcp: starting listener: listener_address=[::]:8201
2023-11-20T18:34:43.669Z [INFO]  core.cluster-listener: serving cluster requests: cluster_listen_address=[::]:8201
2023-11-20T18:34:43.670Z [INFO]  core: seal migration initiated
2023-11-20T18:34:43.670Z [INFO]  core: migrating from shamir to auto-unseal: to=azurekeyvault
2023-11-20T18:34:44.153Z [INFO]  core: seal migration complete
2023-11-20T18:34:44.153Z [INFO]  core: post-unseal setup starting
2023-11-20T18:34:44.155Z [INFO]  core: loaded wrapping token key
2023-11-20T18:34:44.155Z [INFO]  core: successfully setup plugin runtime catalog
2023-11-20T18:34:44.155Z [INFO]  core: successfully setup plugin catalog: plugin-directory=""
2023-11-20T18:34:44.157Z [INFO]  core: successfully mounted: type=system version="v1.15.0+builtin.vault" path=sys/ namespace="ID: root. Path: "
2023-11-20T18:34:44.158Z [INFO]  core: successfully mounted: type=identity version="v1.15.0+builtin.vault" path=identity/ namespace="ID: root. Path: "
2023-11-20T18:34:44.158Z [INFO]  core: successfully mounted: type=azure version="v0.16.3+builtin" path=azure/ namespace="ID: root. Path: "
2023-11-20T18:34:44.158Z [INFO]  core: successfully mounted: type=cubbyhole version="v1.15.0+builtin.vault" path=cubbyhole/ namespace="ID: root. Path: "
2023-11-20T18:34:44.162Z [INFO]  core: successfully mounted: type=token version="v1.15.0+builtin.vault" path=token/ namespace="ID: root. Path: "
2023-11-20T18:34:44.162Z [INFO]  core: successfully mounted: type=kubernetes version="v0.17.1+builtin" path=kubernetes/ namespace="ID: root. Path: "
2023-11-20T18:34:44.163Z [INFO]  rollback: Starting the rollback manager with 256 workers
2023-11-20T18:34:44.163Z [INFO]  rollback: starting rollback manager
2023-11-20T18:34:44.163Z [INFO]  core: restoring leases
2023-11-20T18:34:44.165Z [INFO]  expiration: lease restore complete
2023-11-20T18:34:44.167Z [INFO]  identity: entities restored
2023-11-20T18:34:44.167Z [INFO]  identity: groups restored
2023-11-20T18:34:44.167Z [INFO]  core: usage gauge collection is disabled
2023-11-20T18:34:44.251Z [INFO]  core: post-unseal setup complete
2023-11-20T18:34:44.251Z [INFO]  core: vault is unsealed

これ以降 Vault Pod が再起動しても自動的に Unseal されるので、頻繁に Pod が落ちる我が家の環境でもオペレーションなしで Vault が使えるようになりました。 めでたしめでたし。

Auto Unseal をやめる場合

Auto Unseal をやめてデフォルトの Shamir Seal(3 つの Unseal キーを入力する方式)に戻す場合、設定ファイルの seal ブロックに disabled = "true" を追加して Pod 再起動後に vault operator unseal -migrate コマンドを実行します。 seal ブロックをコメントアウトするだけだとエラーになって Pod が起動してきませんでした。

seal "azurekeyvault" {
  disabled       = "true"
  client_id      = "00000000-0000-0000-0000-000000000000"
  client_secret  = "************************************"
  tenant_id      = "00000000-0000-0000-0000-000000000000"
  vault_name     = "key-vault-name"
  key_name       = "generated-key"
}