[Kubernetes/Vault]PodへのSecret注入をkv-v1でもやってみた


はじめに

以下のWebセミナーに参加してKubernetesとValutの連携について方法を色々試しているところ。
https://www.youtube.com/watch?v=b2iiZ16eI7A

まずはVaultに登録した認証情報(ユーザー/パスワード情報)をKubernetesのPodに注入する、といったチュートリアルを実施してみた。その中で自分が疑問に思ったことの検証メモ。
※Vaultは触り始めたばかりなので、用語などよくわかってないですが

pathの間に"/data"が入るのはなぜ

チュートリアルを実施するうえで少し疑問だったのが、

vault kv put internal/database/config username="db-readonly-username" password="db-secret-password"

上記ではinternal/database/configにユーザー名/パスワードなどの情報を登録していたのに、

vault policy write internal-app - <<EOF
path "internal/data/database/config" {
  capabilities = ["read"]
}
EOF

ポリシーの作成や、

      annotations:
        vault.hashicorp.com/agent-inject: 'true'
        vault.hashicorp.com/role: 'internal-app'
        vault.hashicorp.com/agent-inject-secret-database-config.txt: 'internal/data/database/config'

Podのyaml定義に記載するアノテーションに記載されているpathがinternal/data/database/configとなっている。さて/dataはどこから出てきたのか?

kvのv1とv2の違い

どうやら、この/dataはkvのversion1とversion2でのAPI利用方法の違いが影響しているみたい。
チュートリアルではversion2のKVで実施している。

version1では/secret/:pathだったのが、version2では/secret/data/:path?version=:version-numberに変更になっている。

検証①:v1でチュートリアルをやってみる

実際の動作を確認するために、以下のチュートリアルをkv-v1で実施してみる。基本的な流れは同じだが一部のコマンドを修正して実施する。
https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar

internalというpathでsecretを有効化する箇所は以下のコマンドに置き換える。

/ $ vault secrets enable -path=internal kv-v1
Success! Enabled the kv-v1 secrets engine at: internal/

念のため、以下コマンドでinternal/がversion1で有効化されていることを確認しておく。

/ $ vault secrets list -detailed | grep internal
internal/     kv           kv_7122e2b5           system         system     false             replicated     false        false
       map[version:1]    n/a                                                        da1cd7bd-bd8c-97a5-97b8-07f8500abf2b

policyは、/dataが含まれていないpathに置き換えて登録する。

/ $ vault policy write internal-app - <<EOF
> path "internal/database/config" {
>   capabilities = ["read"]
> }
> EOF
Success! Uploaded policy: internal-app

Kubernetes側としては、yamlファイルのannotaionのうち、vault.hashicorp.com/agent-inject-secret-database-config.txtの値も同様に修正しておく。
チュートリアルの手順ではgit上から取得した検証用のyamlファイルを利用しているが、今回は以下の定義ファイルで実施してみる。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: alpine-test
  name: alpine-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: alpine-test
  template:
    metadata:
      labels:
        app: alpine-test
      annotations:
        vault.hashicorp.com/agent-inject: 'true'
        vault.hashicorp.com/role: 'internal-app'
        vault.hashicorp.com/agent-inject-secret-database-config.txt: 'internal/database/config'
    spec:
      containers:
      - image: alpine
        name: alpine
        command:
        - "sleep"
        - "3600"
      serviceAccountName: internal-app

kubectl applyでPodを起動させてから、kubectl execコマンドで/vault/secrets/database-config.txtの中身を確認してみる。

$ kubectl get pod alpine-test-59b7456c6d-vsbqt
NAME                           READY   STATUS    RESTARTS   AGE
alpine-test-59b7456c6d-vsbqt   2/2     Running   0          19s
$
$ kubectl exec alpine-test-59b7456c6d-vsbqt -c alpine  -- cat /vault/secrets/database-config.txt
password: db-secret-password
username: db-readonly-username

internal/database/configに登録されているユーザー/パスワードが出力されていることが確認できた。

検証②:v1のまま、pathに"/date"を追加してみる

kv-v1の状態のまま、pathに/dataを付ける形にするとどうなるか試してみる。
vault-0のコンソールにログインした状態で以下コマンドを実施してinternal-appをアップデートする

vault policy write internal-app - <<EOF
path "internal/data/database/config" {
  capabilities = ["read"]
}
EOF

またyamlファイルも修正する。以下コマンドでannotaitonを更新する。

kubectl patch deployment alpine-test -p metadata":{"annotations":{"vault.hashicorp.com/agent-inject-secret-database-config.txt": "internal/data/database/config"}}}}}'

annotationを更新すると新しくPodが作成されるが、新規作成されたPodの状態を確認してもInitが完了せず、Runステータスにはならない。

$ kubectl get pod -w
NAME                           READY   STATUS     RESTARTS   AGE
alpine-test-59b7456c6d-vsbqt   2/2     Running    0          3m24s
alpine-test-6554846995-tpb9v   0/2     Init:0/1   0          5s

kubectl logsコマンドでInitコンテナのログを確認してみると、internal/data/database/configというpathのシークレットは無いといったメッセージが出力されている。
どうやら、version1では/dataはpathの一部として認識されているみたい。

$ kubectl logs alpine-test-6554846995-tpb9v -c vault-agent-init
==> Vault agent started! Log data will stream in below:

==> Vault agent configuration:

                     Cgo: disabled
               Log Level: info
                 Version: Vault v1.9.2
             Version Sha: f4c6d873e2767c0d6853b5d9ffc77b0d297bfbdf

2022-03-21T05:38:03.105Z [INFO]  sink.file: creating file sink
2022-03-21T05:38:03.105Z [INFO]  sink.file: file sink configured: path=/home/vault/.vault-token mode=-rw-r-----
2022-03-21T05:38:03.105Z [INFO]  template.server: starting template server
2022-03-21T05:38:03.105Z [INFO]  auth.handler: starting auth handler
2022-03-21T05:38:03.105Z [INFO]  sink.server: starting sink server
2022-03-21T05:38:03.105Z [INFO] (runner) creating new runner (dry: false, once: false)
2022-03-21T05:38:03.105Z [INFO]  auth.handler: authenticating
2022-03-21T05:38:03.106Z [INFO] (runner) creating watcher
2022-03-21T05:38:03.118Z [INFO]  auth.handler: authentication successful, sending token to sinks
2022-03-21T05:38:03.118Z [INFO]  auth.handler: starting renewal process
2022-03-21T05:38:03.118Z [INFO]  template.server: template server received new token
2022-03-21T05:38:03.118Z [INFO] (runner) stopping
2022-03-21T05:38:03.118Z [INFO] (runner) creating new runner (dry: false, once: false)
2022-03-21T05:38:03.118Z [INFO]  sink.file: token written: path=/home/vault/.vault-token
2022-03-21T05:38:03.119Z [INFO]  sink.server: sink server stopped
2022-03-21T05:38:03.119Z [INFO]  sinks finished, exiting
2022-03-21T05:38:03.119Z [INFO] (runner) creating watcher
2022-03-21T05:38:03.119Z [INFO] (runner) starting
2022-03-21T05:38:03.121Z [INFO]  auth.handler: renewed auth token
2022-03-21T05:38:03.124Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 1 after "250ms")
2022-03-21T05:38:03.377Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 2 after "500ms")
2022-03-21T05:38:03.879Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 3 after "1s")
2022-03-21T05:38:04.881Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 4 after "2s")
2022-03-21T05:38:06.884Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 5 after "4s")
2022-03-21T05:38:10.888Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 6 after "8s")
2022-03-21T05:38:18.892Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 7 after "16s")
2022-03-21T05:38:34.895Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 8 after "32s")
2022-03-21T05:39:06.897Z [WARN] (view) vault.read(internal/data/database/config): no secret exists at internal/data/database/config (retry at
tempt 9 after "1m0s")

検証③:kvをバージョンアップしてみる

これまでkv-v1だったものをkv-v2にアップデートしてみる。
vault-0コンテナへログインして、以下コマンドでkv-v2へアップデートする。

/ $ vault kv enable-versioning internal
Success! Tuned the secrets engine at: internal/

internal/がversion2になっていることを確認しておく。

/ $ vault secrets list -detailed | grep internal
internal/     kv           kv_7122e2b5           system         system     false             replicated     false        false
       map[version:2]    n/a        

先ほどのPodが"Running"ステータスになっており、internal/database/configに登録されているユーザー/パスワードが出力されていることが確認できた。

$ kubectl get pod
NAME                           READY   STATUS    RESTARTS   AGE
alpine-test-6554846995-tpb9v   2/2     Running   0          2m58s
$
$ kubectl exec alpine-test-6554846995-tpb9v -c alpine -- cat /vault/secrets/database-config.txt
data: map[password:db-secret-password username:db-readonly-username]
metadata: map[created_time:2022-03-21T05:39:57.3716551Z custom_metadata:<nil> deletion_time: destroyed:false version:1]

v2だとバージョン管理が可能

kv-v1とkv-v2の違いについては、kv-v2ではバージョン管理されており過去バージョンの値も参照可能らしい。

確かにAPIの説明でも末尾に/secret/data/:path?version=:version-numberとバージョンが指定できる。
これも動作検証してみる。

まずはinternal/database/configに登録されている情報を上書きする。vault-0コンテナへログインして以下のようにusername/passwordを更新する。

$ kubectl exec -n vault -it vault-0 -- /bin/sh
/ $ vault kv put internal/database/config username="username!!" password="password!!"
Key                Value
---                -----
created_time       2022-03-21T05:42:27.5826724Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2
/ $ exit

再度、ポッドを削除、再起動させる。
/vault/secrets/database-config.txtの中身に、先ほどアップデートした内容が反映されていることが確認できた。

$ kubectl get pod
NAME                           READY   STATUS    RESTARTS   AGE
alpine-test-6554846995-lnf2r   2/2     Running   0          52s
$
$ kubectl exec alpine-test-6554846995-lnf2r -c alpine -- cat /vault/secrets/database-config.txt
data: map[password:password!! username:username!!]
metadata: map[created_time:2022-03-21T05:42:27.5826724Z custom_metadata:<nil> deletion_time: destroyed:false version:2]

では、annotaionのpathの末尾に?version=1を追記して、修正前のinternal/database/configの内容を参照するようにしてみる。

kubectl patch deployment alpine-test -p '{"spec": {"template":{"metadata":{"annotations":{"vault.hashicorp.com/agent-inject-secret-database-config.txt": "internal/data/database/config?version=1"}}}}}'

Podが自動的に再起動される。/vault/secrets/database-config.txtの中身が修正前のinternal/database/configの内容となっていることが確認できた。

$ kubectl get pod
NAME                          READY   STATUS    RESTARTS   AGE
alpine-test-7f65559cb-srrwf   2/2     Running   0          42s
$
$ kubectl exec alpine-test-7f65559cb-srrwf -c alpine -- cat /vault/secrets/database-config.txt
data: map[password:db-secret-password username:db-readonly-username]
metadata: map[created_time:2022-03-21T05:39:57.3716551Z custom_metadata:<nil> deletion_time: destroyed:false version:1]