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

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

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

https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar

## 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](https://www.vaultproject.io/api-docs/secret/kv/kv-v1#read-secret)では`/secret/:path`だったのが、[version2](https://www.vaultproject.io/api-docs/secret/kv/kv-v2#read-secret-version)では`/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ファイルを利用しているが、今回は以下の定義ファイルで実施してみる。

```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ではバージョン管理されており過去バージョンの値も参照可能らしい。

https://www.vaultproject.io/docs/secrets/kv

確かに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]
```