ここのブログでHashicorp Vaultを扱うのはそういえば初めてだったけど、Vault使う上で面倒なunsealなどを自動処理する方法を調べる機会があったのでまとめました。
なお、unseal keyやroot tokenを記事用に全部書き出してますが、機密情報なので実際は厳重に管理してください。
環境は以下の通り、HelmでK8sクラスタへデプロイしたHashicorp Vaultです。
- HashiCorp Vault 1.15.2
- Kubernetes: 1.29.1
- K3s(docker compose版)
- ホストOS: Fedora 39
- オンプレのProxmox VEのVMに構築
あと、Azureの権限周りはあまり詳しくなくてだいぶ適当なので、不要な権限を与えてるかもしれない。詳しい人教えてください。
なお、この記事はHashicorp Vaultのデプロイ方法について書いてるだけで、アプリケーションとしてのVaultの使用方法は全く書いていません。
Helmチャートの設定
helm repo add hashicorp https://helm.releases.hashicorp.com
helm search repo hashicorp/vault
$ helm search repo hashicorp
NAME CHART VERSION APP VERSION DESCRIPTION
hashicorp/consul 1.3.2 1.17.2 Official HashiCorp Consul Chart
hashicorp/terraform 1.1.2 Install and configure Terraform Cloud Operator ...
hashicorp/terraform-cloud-operator 2.2.0 2.2.0 Official Helm chart for HashiCorp Terraform Clo...
hashicorp/terraform-enterprise 1.1.1 1.16.0 Official HashiCorp Terraform-Enterprise Chart
hashicorp/vault 0.27.0 1.15.2 Official HashiCorp Vault Chart
hashicorp/vault-secrets-operator 0.4.3 0.4.3 Official Vault Secrets Operator Chart
hashicorp/waypoint 0.1.21 0.11.3 Official Helm Chart for HashiCorp Waypoint
スタンドアロンモードのインストール(前置き)
読み飛ばし可
デプロイ
デフォルト値でVaultをインストール。(values.yaml
は不要)
$ helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace
Release "my-vault" does not exist. Installing it now.
NAME: my-vault
LAST DEPLOYED: Sat Feb 10 16:01:21 2024
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!
Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:
https://developer.hashicorp.com/vault/docs
Your release is named my-vault. To learn more about the release, try:
$ helm status my-vault
$ helm get manifest my-vault
StatefulSetでVaultのpodがデプロイされますがnot readyになります。
$ kubectl get pod,sts -n vault
NAME READY STATUS RESTARTS AGE
pod/my-vault-agent-injector-6b66d76649-xmvbc 1/1 Running 0 25s
pod/my-vault-0 0/1 Running 0 25s
NAME READY AGE
statefulset.apps/my-vault 0/1 25s
ログを見ると以下の通り。
2024-02-10T07:01:56.144Z [INFO] core: security barrier not initialized
2024-02-10T07:01:56.144Z [INFO] core: seal configuration missing, not initialized
Vaultのステータスをvault status
コマンドを実行して確認すると、未初期化(Initializedがtrue
)であることが確認できます。
$ kubectl exec -n vault my-vault-0 -- vault status
Key Value
--- -----
Seal Type shamir
Initialized false
Sealed true
Total Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type file
HA Enabled false
初期化とunseal
Vaultを使える状態にするにはunsealが必要で、そのためにはまず初期化します。
初期化はVaultのコマンドvault operator init
を実行します。
$ kubectl exec -n vault my-vault-0 -- vault operator init
Unseal Key 1: PZuSm+GpsG6xoLz/1hm+kdke/q/UWm+kx2ewtyp1yJ3x
Unseal Key 2: 2eW+IlscQ1cKEu/pATh3ZJvJ5EZ9D5jopRbrUgACiC8h
Unseal Key 3: DbVJzW0vcT4a4bhS3lNIihwTizYridY6Rzt282Prl34m
Unseal Key 4: Ba4+tWfLXtyQ0hXvdHQbXlOOfHqCPvTgDDC7wtN6Ok86
Unseal Key 5: avg1gIAbhDDk4SRpSqby6gDENhOJPJzqk93CE8yCar+c
Initial Root Token: hvs.jnkpJnAEN4PDyPZZF3RJoGJV
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!
It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
これでrootトークンと5つのunsealキーを取得できました。
ステータスを確認すると、Initializedがtrue
になります。
※ これは記事用に全部書き出してますが、機密情報なので実際は厳重に管理してください。
$ kubectl exec -n vault my-vault-0 -- vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed true
Total Shares 5
Threshold 3
Unseal Progress 0/3
Unseal Nonce n/a
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type file
HA Enabled false
初期化が済んだらunsealを行います。
unsealはvault operator unseal
を実行します。
ここでは記事用に引数に指定してますが、機密情報をローカルエコーするのはよくないので、引数無しで実行して対話的に入力した方がよいです。(が、自動化するならこの方法かな?)
$ kubectl exec -n vault my-vault-0 -- vault operator unseal PZuSm+GpsG6xoLz/1hm+kdke/q/UWm+kx2ewtyp1yJ3x
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed true
Total Shares 5
Threshold 3
Unseal Progress 1/3
Unseal Nonce b8ff2c62-5fdd-18b0-6e20-ddff956f7b2f
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type file
HA Enabled false
Unseal Progressが1/3になりました。あと2回実行する必要があるので、5つあるキーのうちあと2個使って同じコマンドを実行します。
$ kubectl exec -n vault my-vault-0 -- vault operator unseal 2eW+IlscQ1cKEu/pATh3ZJvJ5EZ9D5jopRbrUgACiC8h
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed true
Total Shares 5
Threshold 3
Unseal Progress 2/3
Unseal Nonce b8ff2c62-5fdd-18b0-6e20-ddff956f7b2f
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type file
HA Enabled false
$ kubectl exec -n vault my-vault-0 -- vault operator unseal DbVJzW0vcT4a4bhS3lNIihwTizYridY6Rzt282Prl34m
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type file
Cluster Name vault-cluster-2f48696e
Cluster ID e109d3d3-5d52-c081-667f-8fefcc8067f6
HA Enabled false
これでSealedの値がfalse
になり、unseal状態になりました。
$ kubectl get pod -n vault
NAME READY STATUS RESTARTS AGE
my-vault-agent-injector-6b66d76649-xmvbc 1/1 Running 0 4m47s
my-vault-0 1/1 Running 0 4m47s
この通りunsealされるとPodはready状態になり、利用準備ができました。
HAクラスターでのインストール
以下は、スタンドアロンでデプロイした環境はすべて削除済みで新規に構築しています。
ここからは耐障害性を高めるためのHAクラスタ構成でデプロイします。
※ AntiAffinityの設定によって1pod1nodeデプロイになるため、あらかじめノードは5ノードでKubernetesを構築しています。
$ kubectl get node
NAME STATUS ROLES AGE VERSION
b513c8231aad Ready <none> 6h41m v1.29.1+k3s2
9952de1bc166 Ready <none> 6h41m v1.29.1+k3s2
2f5893c02aa3 Ready <none> 6h40m v1.29.1+k3s2
412b213f3d10 Ready <none> 6h41m v1.29.1+k3s2
4c1f8de42bfe Ready control-plane,master 6h41m v1.29.1+k3s2
8239e04d52ef Ready <none> 6h41m v1.29.1+k3s2
values.yamlは以下の通り。
config
の部分はデフォルトのままですが、本題で使うので含めています。
setNodeId
は無くても良いですが、Vaultノード名が分かりやすくなるので有効にしています。
server:
ha:
enabled: true
replicas: 1
raft:
enabled: true
setNodeId: true
config: |
ui = true
listener "tcp" {
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
}
storage "raft" {
path = "/vault/data"
}
service_registration "kubernetes" {}
helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-0 0/1 Running 0 5m47s 10.42.4.13 9952de1bc166 <none> <none>
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 5m47s 10.42.5.12 b513c8231aad <none> <none>
my-vault-1 0/1 Running 0 5m46s 10.42.3.5 8239e04d52ef <none> <none>
my-vault-2 0/1 Running 0 5m46s 10.42.2.5 4c1f8de42bfe <none> <none>
3podがデプロイされましたが、スタンドアロンの場合同様に、初期化とunsealが必要です。
リーダーノードの初期化とunseal
レプリカ3のStatefulSetなので、0番のpodでまず初期化とunsealを実施。
$ kubectl exec -n vault my-vault-0 -- vault operator init
Unseal Key 1: ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
Unseal Key 2: ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
Unseal Key 3: pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
Unseal Key 4: 6q2Fi8q7AOc0n6iPJeoWTnio6QjCDMlxwZFyD7uw9GqE
Unseal Key 5: zLEVdCLOMXAptoZmBZRYVpSG8o8lCfK/bJi5pQ7jE8Sr
Initial Root Token: hvs.GuR8WbGjN7X3roX6UoJYztON
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!
It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
kubectl exec -n vault my-vault-0 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-0 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-0 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
これでvault-0がunsealされreadyになりました。
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 11m 10.42.5.12 b513c8231aad <none> <none>
my-vault-1 0/1 Running 0 11m 10.42.3.5 8239e04d52ef <none> <none>
my-vault-2 0/1 Running 0 11m 10.42.2.5 4c1f8de42bfe <none> <none>
my-vault-0 1/1 Running 0 11m 10.42.4.13 9952de1bc166 <none> <none>
ノードのクラスターへの参加
現時点ではまだシングルノード状態。
/ $ vault login hvs.GuR8WbGjN7X3roX6UoJYztON
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token hvs.GuR8WbGjN7X3roX6UoJYztON
token_accessor GpmzT1ZyBYs1bduOTRYVnALW
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
/ $ vault operator members
Host Name API Address Cluster Address Active Node Version Upgrade Version Redundancy Zone Last Echo
--------- ----------- --------------- ----------- ------- --------------- --------------- ---------
my-vault-0 http://10.42.4.13:8200 https://my-vault-0.my-vault-internal:8201 true 1.15.2 1.15.2 n/a n/a
HA構成にするため残りの2podをクラスターに参加させるには、それぞれraft join
操作をリーダーであるvault-0に対して行います。
podではHTTPでlistenしているので、http://
を付与するのをお忘れなく。
URL(pod名service名)はHelmのリリース名に依存するのでそこは読み替えること。
$ kubectl exec -n vault my-vault-1 -- vault operator raft join http://my-vault-0.my-vault-internal:8200
Key Value
--- -----
Joined true
$ kubectl exec -n vault my-vault-2 -- vault operator raft join http://my-vault-0.my-vault-internal:8200
Key Value
--- -----
Joined true
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 33m 10.42.5.12 b513c8231aad <none> <none>
my-vault-1 0/1 Running 0 33m 10.42.3.5 8239e04d52ef <none> <none>
my-vault-2 0/1 Running 0 33m 10.42.2.5 4c1f8de42bfe <none> <none>
my-vault-0 1/1 Running 0 33m 10.42.4.13 9952de1bc166 <none> <none>
これでvault-1およびvault-2は初期化済みseal状態になるので、それぞれunsealを行います。
kubectl exec -n vault my-vault-1 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-1 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-1 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
kubectl exec -n vault my-vault-2 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-2 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-2 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
これでようやく全podがready状態になり、クラスターに参加できた状態になりました。
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 35m 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 35m 10.42.4.13 9952de1bc166 <none> <none>
my-vault-1 1/1 Running 0 35m 10.42.3.5 8239e04d52ef <none> <none>
my-vault-2 1/1 Running 0 35m 10.42.2.5 4c1f8de42bfe <none> <none>
$ kubectl exec -it -n vault my-vault-0 -- vault operator members
Host Name API Address Cluster Address Active Node Version Upgrade Version Redundancy Zone Last Echo
--------- ----------- --------------- ----------- ------- --------------- --------------- ---------
my-vault-2 http://10.42.2.5:8200 https://my-vault-2.my-vault-internal:8201 false 1.15.2 1.15.2 n/a 2024-02-10T12:30:09Z
my-vault-1 http://10.42.3.5:8200 https://my-vault-1.my-vault-internal:8201 false 1.15.2 1.15.2 n/a 2024-02-10T12:30:07Z
my-vault-0 http://10.42.4.13:8200 https://my-vault-0.my-vault-internal:8201 true 1.15.2 1.15.2 n/a n/a
これでHA構成でのVaultの利用準備ができました。
スケールアウト/スケールイン
values.yaml
にreplicas: 3
を指定したため、初期状態で3つのpodがデプロイされていますが、StatefulSetのレプリカ数を変更することでスケールできます。
例えば5台構成にするには、普通にkubectl scale
で設定できます。
$ kubectl get sts -n vault
NAME READY AGE
my-vault 3/3 11h
$ kubectl scale -n vault statefulset --replicas 5 my-vault
statefulset.apps/my-vault scaled
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 11h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 11h 10.42.4.13 9952de1bc166 <none> <none>
my-vault-1 1/1 Running 0 11h 10.42.3.5 8239e04d52ef <none> <none>
my-vault-2 1/1 Running 0 11h 10.42.2.5 4c1f8de42bfe <none> <none>
my-vault-3 0/1 Running 0 34s 10.42.0.5 2f5893c02aa3 <none> <none>
my-vault-4 0/1 Running 0 34s 10.42.1.5 412b213f3d10 <none> <none>
ただし、新しく作成されたPodはやはりraft join
とunseal
が必要です。
kubectl exec -n vault my-vault-3 -- vault operator raft join http://my-vault-0.my-vault-internal:8200
kubectl exec -n vault my-vault-4 -- vault operator raft join http://my-vault-0.my-vault-internal:8200
kubectl exec -n vault my-vault-3 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-3 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-3 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
kubectl exec -n vault my-vault-4 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-4 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-4 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
これで5台クラスターとなりました。
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
my-vault-3 my-vault-3.my-vault-internal:8201 follower true
my-vault-4 my-vault-4.my-vault-internal:8201 follower true
スケールインの場合は、いきなりレプリカ数を減らすのはNGで、まずVault的にクラスターから除外する必要があり、join
と逆でremove-peer
を行います。
ここでは追加でスケールしたvault-3とvault-4を削除し、レプリカ3に戻す作業を行ってみます。
といってもコマンドは簡単で、remove-peer
の引数にlist-peers
でリストされているNode名を指定します。
この例ではvalues.yaml
でsetNodeId: true
の設定を入れているのでNode名=Pod名になってます。(指定がオフの場合はランダムなハッシュ値になります)
$ kubectl exec -n vault my-vault-0 -- vault operator raft remove-peer my-vault-4
Peer removed successfully!
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
my-vault-3 my-vault-3.my-vault-internal:8201 follower true
これでmy-vault-4
がクラスターから除外されました。
同じ要領でmy-vault-3
も除外します。
$ kubectl exec -n vault my-vault-0 -- vault operator raft remove-peer my-vault-3
Peer removed successfully!
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
これでStatefulSetのレプリカ数を3に戻せばOKです。
$ kubectl scale -n vault statefulset --replicas 3 my-vault
statefulset.apps/my-vault scaled
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 11h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 11h 10.42.4.13 9952de1bc166 <none> <none>
my-vault-1 1/1 Running 0 11h 10.42.3.5 8239e04d52ef <none> <none>
my-vault-2 1/1 Running 0 11h 10.42.2.5 4c1f8de42bfe <none> <none>
my-vault-4 1/1 Terminating 0 19m 10.42.1.5 412b213f3d10 <none> <none>
my-vault-3 1/1 Terminating 0 19m 10.42.0.5 2f5893c02aa3 <none> <none>
remove-peer
を行わずにスケールインを行うと、削除された分のPodはクラスターには残ったままunhealth状態で残ったままとなります。
いきなりレプリカ3にすると、以下のようになります。
peersのリストは5台ある状態。
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
my-vault-3 my-vault-3.my-vault-internal:8201 follower true
my-vault-4 my-vault-4.my-vault-internal:8201 follower true
ただし、ステータスはhealthがfalseになります。
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state
Healthy: false
Failure Tolerance: 0
Leader: my-vault-0
Voters:
my-vault-0
my-vault-1
my-vault-2
my-vault-3
my-vault-4
Servers:
my-vault-0
:
:
my-vault-3
Name: my-vault-3
Address: my-vault-3.my-vault-internal:8201
Status: voter
Node Status: alive
Healthy: false
:
:
my-vault-4
Name: my-vault-4
Address: my-vault-4.my-vault-internal:8201
Status: voter
Node Status: alive
Healthy: false
この状態からでもremove-peer my-vault-3
remove-peer my-vault-4
を実行すれば3台構成へ変更可能。
自動unsealと自動join (ここから本題)
ここまで手動での初期化とunsealを行ったデプロイについてみてきましたが…
Pod起動時に必ずunsealが必要なため、率直に言ってKubernetesのオートヒーリングなどとの相性も悪く、運用でこのコマンド実行はやってられないと思います。
例えばkubectl delete pod
して再作成されたpodもnot readyなので、都度unsealする必要があります。
最初はこの仕様を知ってnot readyなpodを検出してunsealを行うプログラムとか書いてやろうと思いましたが、そんなことしなくても標準でクラウドサービスの機密情報を保持する機能と連携してauto unsealを行う機能が備わっています。
本エントリでは、AzureのKey Vaultサービスを使ったauto unsealについて説明します。
※ ここまでは単にVaultと雑に記載してましたが、ここからはAzure Key Vaultとややこしくなるので、「HashiCorp Vault」「Azure Key Vault」と表記します。
auto unseal with Azure Key Vault
公式ドキュメントにチュートリアルがあります。
developer.hashicorp.com
チュートリアルの流れをざっくり紹介すると、以下の通り
- TerraformでAzureにVMやAzure Key Vaultなどのリソース作成
- VM上にAzure Key Vault連携するHashiCorp Vaultのデプロイ
なので仕組みを把握するにはこの中身を追っていけばOK
言い方を変えると「自分の環境のVaultで自動unsealをするには」を実現するには中身を読み解く必要がありました。
アプリケーションとシークレットの作成
「アプリの登録」で、「新規登録」から新しくAuto-unseal用のトークン発行のためのアプリを登録します。
作成できたら、概要ページで以下の値をメモ
- アプリケーション(クライアント)ID
- ディレクトリ(テナント)ID
次に「証明書とシークレット」で「クライアント シークレット」を新規作成。
今回は動作確認用なので、デフォルトの180日期限。
作成できたらシークレットの値(IDでなく)をメモ(一度画面を離れると非表示になるので注意)。
次に「APIのアクセス許可」で「アクセス許可の追加」し、公式ドキュメントは「Azure Active Directory Graph」を追加するよう記載されてるけど2022年6月に非推奨になったようなので、かわりに「Microsoft Graph」を選択。
まず「委任されたアクセス許可」を押下し、「User」の項目にある「User.Read」にチェックされているのを確認(デフォルトでこれがチェックされてる)。
次に「アプリケーションの許可」を押下し、以下をチェック
- 「Application」の項目にある「Application.ReadWrite.All」
- 「Directory」の項目にある「Directory.ReadWrite.All」
最後に下部の「アクセス許可の追加」を押下。
APIのアクセス許可の画面に戻ると、状態が「既定のディレクトリに付与されていません」になっているので、「既定のディレクトリに管理者の同意を与えます」を押下。
「既定のディレクトリに付与されました」になればOK
次にサブスクリプションの画面で、まずサブスクリプションIDを確認。
次に「アクセス制御(IAM)」の画面で「+追加」から「ロール割り当ての追加」を押下。
「ロール」タブでOwner
を選択とあるけど……おそらく「特権管理者ロール」で「所有者」で検索して出てくるものを選択。ちょっと権限強すぎそうだけど、とりあえず。
次に「メンバー」タブを押下し、「アクセスの割り当先」に「ユーザー、グループ、またはサービスプリンシパル」をチェック、「メンバーを選択する」で、先ほど作成したアプリケーション名を入力。
条件はドキュメントには何も書かれてない(「所有者」を選択するとこれが必要)けど、デフォルトのまま進めると割り当てできないので、適当だけど「ロールとプリンシパルの選択」から「ロールの制約」->「ロールの追加」で「所有者」をチェック。これで「レビューと割り当て」が可能になります。
キーコンテナーの作成と権限付与
ここからはチュートリアルのドキュメントでなく、資材として用意されてるTerraformのコードを参考にします。
github.com
まずはキーコンテナーを作成します。
アクセス構成の画面では、「自分」に操作権限を付与するために、「アクセス許可モデル」を「コンテナーのアクセスポリシー」にチェックし、下部に追加される「アクセスポリシー」で自分をチェック。
ネットワークはデフォルトのままで作成します。
作成が完了したら、再度作成済みのキーコンテナーに移動し、「アクセスポリシー」画面で「作成」し、「キーのアクセス許可」で以下3つにチェックを入れます。
プリンシパルでは作成済みアプリケーションのIDか名前を入力して選択、あとはそのまま作成します。
次に「キー」に移動し、unsealに使うキーを作成します。
パラメタはデフォルトのRSA2048ビットのままでOKです。
TerraformのコードにはキーのオプションでwrapKey
/unwrapKey
がありますが、作成時に選択できる画面がないのでそのまま作成します。
作成後に詳細を見ると必要な項目はチェックがついてるのでそのまま使用します。
これでAzure Key Vaultの名前と、キーの名前がそろいました。
HashiCorp Vault設定
Azure Key Vaultを使った設定はこちら。
developer.hashicorp.com
auto unseal設定
取得できた各値をセットして再デプロイします。
自動join設定はまだ入れてないので、いったんレプリカ数は1にし、消えたPodが使っていたストレージ(pvc/pv)も削除しておきます。
server:
ha:
enabled: true
replicas: 1
raft:
enabled: true
setNodeId: true
config: |
ui = true
listener "tcp" {
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
}
storage "raft" {
path = "/vault/data"
}
service_registration "kubernetes" {}
seal "azurekeyvault" {
client_id = "YOUR-APP-ID"
client_secret = "YOUR-APP-PASSWORD"
tenant_id = "YOUR-AZURE-TENANT-ID"
vault_name = "Azure Key Value name on Azure"
key_name = "Key name on Azure"
}
helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml
ただし、設定のConfigMapは更新されるけど、Podは再作成されず設定が反映されないのでPodを削除します。
$ kubectl delete pod -n vault my-vault-0
pod "my-vault-0" deleted
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 17h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 0/1 Running 0 3s 10.42.4.18 9952de1bc166 <none> <none>
起動しましたが、まだAuto-unseal設定が反映されていないためnot readyになります。ここで-migrate
オプションをつけてunsealを行います。
$ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
Key Value
--- -----
Recovery Seal Type shamir
Initialized true
Sealed true
Total Recovery Shares 5
Threshold 3
Unseal Progress 1/3
...
$ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
Key Value
--- -----
Recovery Seal Type shamir
Initialized true
Sealed true
Total Recovery Shares 5
Threshold 3
Unseal Progress 2/3
...
$ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
Key Value
--- -----
Recovery Seal Type shamir
Initialized true
Sealed false
Total Recovery Shares 5
Threshold 3
Seal Migration in Progress true
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 17h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 49s 10.42.4.22 9952de1bc166 <none> <none>
これでunsealとともにAuto-unsealも有効になります。
$ kubectl delete pod -n vault my-vault-0
pod "my-vault-0" deleted
ためしにPodを削除してみると、
$ kubectl get pod -n vault -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 17h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 0/1 Running 0 1s 10.42.4.25 9952de1bc166 <none> <none>
my-vault-0 1/1 Running 0 5s 10.42.4.25 9952de1bc166 <none> <none>
この通り、自動でunsealされready状態になります。
auto join設定
この時点で自動unsealは動作しますが、レプリカ数を増やした時のクラスターへのjoinは別途必要です。
が、これも自動で処理するための設定があり、retry_join
でリーダーのサーバーを指定しておけば良いです。
developer.hashicorp.com
config: |
ui = true
listener "tcp" {
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
}
storage "raft" {
path = "/vault/data"
retry_join {
leader_api_addr = "http://my-vault-0.my-vault-internal:8200"
}
retry_join {
leader_api_addr = "http://my-vault-1.my-vault-internal:8200"
}
retry_join {
leader_api_addr = "http://my-vault-2.my-vault-internal:8200"
}
}
service_registration "kubernetes" {}
seal "azurekeyvault" {
:
}
これでレプリカを3にすると、
$ kubectl scale -n vault statefulset --replicas 3 my-vault
statefulset.apps/my-vault scaled
$ kubectl get pod -n vault -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 18h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 2m9s 10.42.4.25 9952de1bc166 <none> <none>
my-vault-1 0/1 Pending 0 2s <none> <none> <none> <none>
my-vault-2 0/1 Pending 0 2s <none> <none> <none> <none>
my-vault-1 0/1 Pending 0 3s <none> 8239e04d52ef <none> <none>
my-vault-1 0/1 ContainerCreating 0 3s <none> 8239e04d52ef <none> <none>
my-vault-1 0/1 Running 0 4s 10.42.3.10 8239e04d52ef <none> <none>
my-vault-1 0/1 Running 0 4s 10.42.3.10 8239e04d52ef <none> <none>
my-vault-1 0/1 Running 0 9s 10.42.3.10 8239e04d52ef <none> <none>
my-vault-1 0/1 Running 0 9s 10.42.3.10 8239e04d52ef <none> <none>
my-vault-1 1/1 Running 0 9s 10.42.3.10 8239e04d52ef <none> <none>
my-vault-2 0/1 Pending 0 9s <none> b513c8231aad <none> <none>
my-vault-2 0/1 ContainerCreating 0 9s <none> b513c8231aad <none> <none>
my-vault-2 0/1 ContainerCreating 0 17s <none> b513c8231aad <none> <none>
my-vault-2 0/1 Running 0 18s 10.42.5.14 b513c8231aad <none> <none>
my-vault-2 0/1 Running 0 28s 10.42.5.14 b513c8231aad <none> <none>
my-vault-2 0/1 Running 0 28s 10.42.5.14 b513c8231aad <none> <none>
my-vault-2 1/1 Running 0 28s 10.42.5.14 b513c8231aad <none> <none>
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
この通り、自動でクラスターへ追加とunsealが行われるようになりました。
auto remove設定
この状態ならスケーリングの際の運用上の支障はほぼなくなったと思いますが、ついでなのでスケールインの際のクラスターからの除外も自動化してみます。
これはraftのautopilotを使います。
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state
Healthy: true
Failure Tolerance: 1
Leader: my-vault-0
Voters:
my-vault-0
my-vault-1
my-vault-2
Servers:
my-vault-0
:
:
現在3台構成のため、耐障害性は1(1台だけならダウンしてもサービス可能)の状態。
ここにautopilotの設定を追加していきます。(ただ、コマンドかAPIで入れるしか例がないんだけど、設定ファイルでは不可?)
現在の設定
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot get-config
Key Value
--- -----
Cleanup Dead Servers false
Last Contact Threshold 10s
Dead Server Last Contact Threshold 24h0m0s
Server Stabilization Time 10s
Min Quorum 0
Max Trailing Logs 1000
Disable Upgrade Migration false
ここに「停止サーバーの削除判定閾値を1分」「停止サーバーを自動削除」「最小台数を3」「正常状態とみなすまでの時間」をセットします。
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot set-config \
-dead-server-last-contact-threshold=1m \
-cleanup-dead-servers=true \
-min-quorum=3 \
-server-stabilization-time=60
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot get-config
Key Value
--- -----
Cleanup Dead Servers true
Last Contact Threshold 10s
Dead Server Last Contact Threshold 1m0s
Server Stabilization Time 1m0s
Min Quorum 3
Max Trailing Logs 1000
Disable Upgrade Migration false
ではまずレプリカを5にセット。
$ kubectl scale -n vault statefulset --replicas 5 my-vault
statefulset.apps/my-vault scaled
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 18h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 28m 10.42.4.25 9952de1bc166 <none> <none>
my-vault-1 1/1 Running 0 26m 10.42.3.10 8239e04d52ef <none> <none>
my-vault-2 1/1 Running 0 26m 10.42.5.14 b513c8231aad <none> <none>
my-vault-3 1/1 Running 0 33s 10.42.1.12 412b213f3d10 <none> <none>
my-vault-4 1/1 Running 0 33s 10.42.2.14 4c1f8de42bfe <none> <none>
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
my-vault-3 my-vault-3.my-vault-internal:8201 follower true
my-vault-4 my-vault-4.my-vault-internal:8201 follower true
$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state
Healthy: true
Failure Tolerance: 2
Leader: my-vault-0
Voters:
my-vault-0
my-vault-1
my-vault-2
my-vault-3
my-vault-4
5台構成(耐障害は2台まで)になったので、次は手動のクラスターから除外せずにレプリカを3に設定
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-9284p 1/1 Running 0 18h 10.42.5.12 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 30m 10.42.4.25 9952de1bc166 <none> <none>
my-vault-1 1/1 Running 0 28m 10.42.3.10 8239e04d52ef <none> <none>
my-vault-2 1/1 Running 0 28m 10.42.5.14 b513c8231aad <none> <none>
my-vault-4 1/1 Terminating 0 3m5s 10.42.2.14 4c1f8de42bfe <none> <none>
my-vault-3 1/1 Terminating 0 3m5s 10.42.1.12 412b213f3d10 <none> <none>
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
my-vault-3 my-vault-3.my-vault-internal:8201 follower true
my-vault-4 my-vault-4.my-vault-internal:8201 follower true
# 約60秒後
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
my-vault-0 my-vault-0.my-vault-internal:8201 leader true
my-vault-1 my-vault-1.my-vault-internal:8201 follower true
my-vault-2 my-vault-2.my-vault-internal:8201 follower true
自動削除も動作確認できました。
なおこの機能は、最小ノード数が3のようなので、1台に戻すときには使えない模様(3台未満の時点でHAではなくなるから関係ないけどね)
初期構築で自動unseal設定にする
前述の手順は非auto unsealのHashicorp Vaultをauto unsealにする-migrate
を行うものでしたが、初期構築からauto unseal状態でデプロイももちろん可能です。
$ helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-0 0/1 Running 0 23s 10.42.4.28 9952de1bc166 <none> <none>
my-vault-agent-injector-6b66d76649-7vq86 1/1 Running 0 23s 10.42.5.16 b513c8231aad <none> <none>
$ kubectl exec -n vault my-vault-0 -- vault status
Key Value
--- -----
Recovery Seal Type azurekeyvault
Initialized false
Sealed true
Total Recovery Shares 0
:
初期デプロイ時は未初期化のため最初の初期化を行います。
$ kubectl exec -n vault my-vault-0 -- vault operator init
Recovery Key 1: ck667PevKK5uImgLyxWIuo5Gjnbtegi+m64koc5jC1GC
Recovery Key 2: Vz3NMGw7C2EiTMqcicF0oHbcTZrGXvvYBM3cT7ElITo6
Recovery Key 3: /HhvNK2UuQEAtYBSqbcbavEZ5b4SSB51gJA08jpPk6Vt
Recovery Key 4: GQz/MJfpysvc/cokvq1ynMFV65nId6JS+JD84DGnExE8
Recovery Key 5: yDilNc0RQpEa1B+Fyle35fMQvkJ4s1GTmKaAmlBwXRHP
Initial Root Token: hvs.a1CBYXlcb0t7nq5eMGZdLxqC
Success! Vault is initialized
Recovery key initialized with 5 key shares and a key threshold of 3. Please
securely distribute the key shares printed above.
すると、この時点でunsealとなります。
$ kubectl get pod -n vault -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-vault-agent-injector-6b66d76649-7vq86 1/1 Running 0 97s 10.42.5.16 b513c8231aad <none> <none>
my-vault-0 1/1 Running 0 97s 10.42.4.28 9952de1bc166 <none> <none>
まとめ
以上、Hashicorp VaultとAzure Key Vaultを使った自動unsealや自動joinについて動作確認してみました。
auto unsealはクラウドサービスと連携、クラスターへのauto joinはretry_join
設定、クラスターかrのauto removeはautopilot機能で実現できることを確認しました。
最後に繰り返しになりますが、本文中はunseal keyやroot tokenを全部書き出してますが、これは記事用のためで、実際は機密情報なので厳重に管理してください。
参考