Vaultでk8s secret作るCSI Driver試したけど全然思った動きじゃなかった話

# 諦めた記事です
Vaultでk8s secret作るCSI Driver試したけど全然思った動きじゃなかった。残念ながら導入を見送った話。

GUI と k8s secret(できればselaedsecret) を連動させたいです。GUIは高機能なのがいいです。何かいい方法があれば教えて〜

# 残念なところ
vaultで新しいパスワードに更新してもSecretProviderClassが k8s secretを更新してくれない。
pod/deploymentを "削除" しないと更新しない。downtime必須。そういうものとしてissueもcloseされてた😭

> https://github.com/kubernetes-sigs/secrets-store-csi-driver/issues/389



https://x.com/uturned0/status/1829160844032389345



# 悲しいけどテストした結果をメモっておく
## ref
rancher-desktop でこの通り試しました
> https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-secret-store-driver

install vault with vault-csi-provider

```rb
helm install vault hashicorp/vault \
   --set "server.dev.enabled=true" \
   --set "injector.enabled=false" \
   --set "csi.enabled=true" \
   -n default
```

できた

```rb
$ k get pods
NAME                       READY   STATUS    RESTARTS   AGE
vault-csi-provider-k7rh8   2/2     Running   0          3m1s
vault-0                    1/1     Running   0          3m1s
```

vaultのroot tokenを探しておく。 `root` でした。GUIで確認しながら作業するため。

```rb
$ k logs  vault-0  | grep "Root Token"
Root Token: root
```

port forwardして http://localhost:8200 でGUIにアクセスできるようになる。loginはtoken選んで `root` といれる.
```rb
 k port-forward  vault-0 8200:8200 &
```

![Screenshot 2024-08-29 at 22.00.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/e624cbe0-e793-10a3-73f8-0a985abcce17.png)

vaultのsetupをしていく。まずvault podに入る

```rb
kubectl exec -it vault-0 -- /bin/sh
```

試すkey value secretを作る(vaultでは KV secret engineと呼ぶ)

```rb
vault kv put secret/db-pass password="db-secret-password"
vault kv get secret/db-pass
```

GUIでも作成されたのが確認できた
![Screenshot 2024-08-29 at 22.00.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/977791b4-704c-3879-9838-aa06c5c38251.png)
![Screenshot 2024-08-29 at 22.00.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/293371e7-4eff-7bec-4e31-09369685f4ae.png)

k8sとの認証？をセットアップする。ここがよくわからない。vault podの中からk8s apiを叩いて情報を取れるようにするためのrole, policyを作ってるみたい。これは `webapp-sa` という service accountに紐づくみたいだけど、ものすごーくあとで作るserviceaccountなのでずっと意味わからなかった

まず、最初はauthentication methodはこう
![Screenshot 2024-08-29 at 22.01.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/13a6473b-bc5e-4c4b-cf38-e71a25c97084.png)

```rb
vault auth enable kubernetes
vault write auth/kubernetes/config \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
```
結果、こうなる 2行目のコマンドはGUIに何の変化も見えなかった
![Screenshot 2024-08-29 at 22.02.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/c31883df-950d-1160-a0d8-3bd377c37a3b.png)
![Screenshot 2024-08-29 at 22.02.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/149b3c20-7eeb-b147-ae63-fc388f5223db.png)

policy作る

```rb
vault policy write internal-app - <<EOF
path "secret/data/db-pass" {
  capabilities = ["read"]
}
EOF
```
![Screenshot 2024-08-29 at 22.03.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/848a81fa-cbb9-390a-0f26-8ffceee42de3.png)

```rb
vault write auth/kubernetes/role/database \
    bound_service_account_names=webapp-sa \
    bound_service_account_namespaces=default \
    policies=internal-app \
    ttl=20m
```
![Screenshot 2024-08-29 at 22.03.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/b3a45498-c3cc-f217-6070-6919acbe4045.png)



secretを作ってくれるやつをinstallする準備

```rb
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
```
```rb

install. 時間かかる.
```
```rb
helm install csi secrets-store-csi-driver/secrets-store-csi-driver \
    --set syncSecret.enabled=true

できた

kubectl  get pods -l "app=secrets-store-csi-driver"
NAME                                 READY   STATUS              RESTARTS   AGE
csi-secrets-store-csi-driver-fng9n   0/3     ContainerCreating   0          19s
```

こいつはいろんなものをinstallしていく。clusterrole, clusterrolebinding, csidriver etc
```rb
k get clusterrolebinding | grep secretp
secretprovidersyncing-rolebinding                      ClusterRole/secretprovidersyncing-role                             27s
secretproviderclasses-rolebinding                      ClusterRole/secretproviderclasses-role                             27s

k get clusterrolebinding | grep secret
secretprovidersyncing-rolebinding                      ClusterRole/secretprovidersyncing-role                             35s
secretproviderclasses-rolebinding                      ClusterRole/secretproviderclasses-role                             35s

k get clusterrole | grep secret
secretproviderclasses-admin-role                                       2024-08-29T13:24:24Z
secretproviderclasses-viewer-role                                      2024-08-29T13:24:24Z
secretproviderclasses-role                                             2024-08-29T13:24:24Z
secretprovidersyncing-role                                             2024-08-29T13:24:24Z
secretproviderclasspodstatuses-viewer-role                             2024-08-29T13:24:24Z

```

```rb
k get csidriver
NAME                       ATTACHREQUIRED   PODINFOONMOUNT   STORAGECAPACITY   TOKENREQUESTS   REQUIRESREPUBLISH   MODES       AGE
secrets-store.csi.k8s.io   false            true             false             <unset>         false               Ephemeral   54s
```

podもできた

```rb
$ kubectl get pods
NAME                                 READY   STATUS    RESTARTS   AGE
vault-csi-provider-w6kqk             2/2     Running   0          4m56s
vault-0                              1/1     Running   0          4m56s
csi-secrets-store-csi-driver-hsjtg   3/3     Running   0          101s
```


SecretProviderClassを作る。これはsecretをマウントさせるためのもの。あとでk8s secretにするやつに書き換えら得るが、ここではとりあえずマウントだけするためのやつ

```rb
cat > spc-vault-database.yaml <<EOF
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-database
spec:
  provider: vault
  parameters:
    vaultAddress: "http://vault.default:8200"
    roleName: "database"
    objects: |
      - objectName: "db-password"
        secretPath: "secret/data/db-pass"
        secretKey: "password"
EOF
```

```rb
kubectl apply --filename spc-vault-database.yaml
```
```rb
$ kubectl get secretproviderclass
NAME             AGE
vault-database   39s
```

```rb
kubectl describe SecretProviderClass vault-database
Name:         vault-database
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  secrets-store.csi.x-k8s.io/v1
Kind:         SecretProviderClass
Metadata:
  Creation Timestamp:  2024-08-29T13:26:23Z
  Generation:          1
  Managed Fields:
    API Version:  secrets-store.csi.x-k8s.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:parameters:
          .:
          f:objects:
          f:roleName:
          f:vaultAddress:
        f:provider:
    Manager:         kubectl-client-side-apply
    Operation:       Update
    Time:            2024-08-29T13:26:23Z
  Resource Version:  978604
  UID:               f652fd46-4a2f-4ab8-8669-f9eb94f77e99
Spec:
  Parameters:
    Objects:  - objectName: "db-password"
  secretPath: "secret/data/db-pass"
  secretKey: "password"

    Role Name:      database
    Vault Address:  http://vault.default:8200
  Provider:         vault
Events:             <none>
```

ここでやっとserviceaccountを作る。vaultのk8s authで作ったやつ。

```rb
kubectl create serviceaccount webapp-sa
```

```rb
$ k get sa
NAME                       SECRETS   AGE
default                    0         16m
vault-csi-provider         0         13m
vault                      0         13m
secrets-store-csi-driver   0         5m17s
webapp-sa                  0         39s     <---------------
```

pod を作る。

```rb
cat > webapp-pod.yaml <<EOF
kind: Pod
apiVersion: v1
metadata:
  name: webapp
spec:
  serviceAccountName: webapp-sa
  containers:
  - image: jweissig/app:0.0.1
    name: webapp
    volumeMounts:
    - name: secrets-store-inline
      mountPath: "/mnt/secrets-store"
      readOnly: true
  volumes:
    - name: secrets-store-inline
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: "vault-database"
EOF
```
```rb
kubectl apply --filename webapp-pod.yaml
```

secretが/mntにマウントされてるのがわかる

```rb
$ k describe pod webapp
Name:         webapp
Namespace:    default
Priority:     0
Node:         lima-rancher-desktop/192.168.5.15
Start Time:   Thu, 29 Aug 2024 22:27:07 +0900
Labels:       <none>
Annotations:  <none>
Status:       Pending
IP:
IPs:          <none>
Containers:
  webapp:
    Container ID:
    Image:          jweissig/app:0.0.1
    Image ID:
    Port:           <none>
    Host Port:      <none>
    State:          Waiting
      Reason:       ContainerCreating
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /mnt/secrets-store from secrets-store-inline (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-8bzml (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
Volumes:
  secrets-store-inline:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            secrets-store.csi.k8s.io
    FSType:
    ReadOnly:          true
    VolumeAttributes:      secretProviderClass=vault-database
  kube-api-access-8bzml:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  3s    default-scheduler  Successfully assigned default/webapp to lima-rancher-desktop
  Normal  Pulling    3s    kubelet            Pulling image "jweissig/app:0.0.1"
```


確かこれでsecretの中身が見れたはず。
```rb
kubectl exec webapp -- cat /mnt/secrets-store/db-password
```

次に SecretProviderClassを k8s secretにするやつに書き換える。

```rb
$ cat > spc-vault-database.yaml <<EOF
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-database
spec:
  provider: vault
  secretObjects:
  - data:
    - key: password
      objectName: db-password
    secretName: dbpass
    type: Opaque
  parameters:
    vaultAddress: "http://vault.default:8200"
    roleName: "database"
    objects: |
      - objectName: "db-password"
        secretPath: "secret/data/db-pass"
        secretKey: "password"
EOF
```
```rb
 kubectl apply --filename spc-vault-database.yaml
```

pod側もk8s secretを使うようにする。

```rb
cat > webapp-pod.yaml <<EOF
kind: Pod
apiVersion: v1
metadata:
  name: webapp
spec:
  serviceAccountName: webapp-sa
  containers:
  - image: jweissig/app:0.0.1
    name: webapp
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: dbpass
          key: password
    volumeMounts:
    - name: secrets-store-inline
      mountPath: "/mnt/secrets-store"
      readOnly: true
  volumes:
    - name: secrets-store-inline
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: "vault-database"
EOF
```

podを `削除` して作り直すと動く
```rb
$ kubectl delete pod webapp && kubectl apply --filename webapp-pod.yaml
pod "webapp" deleted
pod/webapp created
```

secretが作られていて
```rb
kubectl get secret dbpass
NAME     TYPE     DATA   AGE
dbpass   Opaque   1      8s
```
pod から envとして取れる 
```rb
kubectl exec webapp -- env | grep DB_PASSWORD
DB_PASSWORD=db-secret-password
```

k8s secretを直接みると、ちゃんと入ってるね

```rb
$ kubectl get secret dbpass -o jsonpath='{.data.password}' | base64 -d
db-secret-password
```

# ここから残念さに気づく
Vault GUI で KV のpasswordを更新した。

![Screenshot 2024-08-29 at 22.31.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/e84c697a-fda0-4c73-b965-65fd7eb9abe6.png)


しかし、全然k8s secretが更新されない。やっとわかったのは、それを利用しているpodが削除 -> 生成されると更新されるということだ。

podを削除して作り直す
```rb
$ kubectl delete pod webapp && kubectl apply --filename webapp-pod.yaml
pod "webapp" deleted
pod/webapp created
```
更新された！
```rb
kubectl get secret dbpass -o jsonpath='{.data.password}' | base64 -d
new-password
```

しかし、これじゃdowntimeが発生するじゃんか。deploymentでやってみよう。
一回podは消しておく
```rb
kubectl delete pod webapp
```

Deploymentを作る
```rb
$ cat deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 1 # Number of webapp pods to run
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      serviceAccountName: webapp-sa
      containers:
      - image: jweissig/app:0.0.1
        name: webapp
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: dbpass
              key: password
        volumeMounts:
        - name: secrets-store-inline
          mountPath: "/mnt/secrets-store"
          readOnly: true
      volumes:
      - name: secrets-store-inline
        csi:
          driver: secrets-store.csi.k8s.io
          readOnly: true
          volumeAttributes:
            secretProviderClass: "vault-database"
```

このあといろいろ試したんですけどダメでした。

- `k delete deployment` して `apply` すると、secret は更新される
- `k rollout restart` しても `secret` は古いまま
- `k delete pod` しても、`secret` は古いまま。
    - あくまで `SecretProviderClass` を使っているのは `deployment` だから、 `deployment` が削除されないと `secret` を更新する契機にならない様子

ここでissueを見に行って絶望しました

切ない意見
> Not having each pod contain a consistent view of the external secret in a way that can be updated declaratively is a dealbreaker for this driver. 
> 各ポッドに、宣言的に更新できる方法で外部シークレットの一貫したビューが含まれていないことは、このドライバーにとって致命的です。
> https://github.com/kubernetes-sigs/secrets-store-csi-driver/issues/389




諦め！残念！

何かいい方法があれば教えて〜