おうち K8s クラスタで HashiCorp Vault を動かしているのですが、Pod が再起動すると Vault が Seal 状態となってしまうので都度 Unseal Key を入力しています。 そろそろ煩わしさが限界なので Azure Key Vault と連携して Vault の Auto Unseal 機能を使ってみます。
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 に参照させることで回避可能なようです。
じゃあその 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 設定の移行が必要です。
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"
}