諦めた記事です
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
悲しいけどテストした結果をメモっておく
ref
rancher-desktop でこの通り試しました
https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-secret-store-driver
install vault with vault-csi-provider
helm install vault hashicorp/vault \
--set "server.dev.enabled=true" \
--set "injector.enabled=false" \
--set "csi.enabled=true" \
-n default
できた
$ 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で確認しながら作業するため。
$ k logs vault-0 | grep "Root Token"
Root Token: root
port forwardして http://localhost:8200 でGUIにアクセスできるようになる。loginはtoken選んで root
といれる.
k port-forward vault-0 8200:8200 &
vaultのsetupをしていく。まずvault podに入る
kubectl exec -it vault-0 -- /bin/sh
試すkey value secretを作る(vaultでは KV secret engineと呼ぶ)
vault kv put secret/db-pass password="db-secret-password"
vault kv get secret/db-pass
k8sとの認証?をセットアップする。ここがよくわからない。vault podの中からk8s apiを叩いて情報を取れるようにするためのrole, policyを作ってるみたい。これは webapp-sa
という service accountに紐づくみたいだけど、ものすごーくあとで作るserviceaccountなのでずっと意味わからなかった
まず、最初はauthentication methodはこう
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
結果、こうなる 2行目のコマンドはGUIに何の変化も見えなかった
policy作る
vault policy write internal-app - <<EOF
path "secret/data/db-pass" {
capabilities = ["read"]
}
EOF
vault write auth/kubernetes/role/database \
bound_service_account_names=webapp-sa \
bound_service_account_namespaces=default \
policies=internal-app \
ttl=20m
secretを作ってくれるやつをinstallする準備
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
install. 時間かかる.
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
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
k get csidriver
NAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGE
secrets-store.csi.k8s.io false true false <unset> false Ephemeral 54s
podもできた
$ 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にするやつに書き換えら得るが、ここではとりあえずマウントだけするためのやつ
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
kubectl apply --filename spc-vault-database.yaml
$ kubectl get secretproviderclass
NAME AGE
vault-database 39s
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で作ったやつ。
kubectl create serviceaccount webapp-sa
$ 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 を作る。
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
kubectl apply --filename webapp-pod.yaml
secretが/mntにマウントされてるのがわかる
$ 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の中身が見れたはず。
kubectl exec webapp -- cat /mnt/secrets-store/db-password
次に SecretProviderClassを k8s secretにするやつに書き換える。
$ 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
kubectl apply --filename spc-vault-database.yaml
pod側もk8s secretを使うようにする。
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を 削除
して作り直すと動く
$ kubectl delete pod webapp && kubectl apply --filename webapp-pod.yaml
pod "webapp" deleted
pod/webapp created
secretが作られていて
kubectl get secret dbpass
NAME TYPE DATA AGE
dbpass Opaque 1 8s
pod から envとして取れる
kubectl exec webapp -- env | grep DB_PASSWORD
DB_PASSWORD=db-secret-password
k8s secretを直接みると、ちゃんと入ってるね
$ kubectl get secret dbpass -o jsonpath='{.data.password}' | base64 -d
db-secret-password
ここから残念さに気づく
Vault GUI で KV のpasswordを更新した。
しかし、全然k8s secretが更新されない。やっとわかったのは、それを利用しているpodが削除 -> 生成されると更新されるということだ。
podを削除して作り直す
$ kubectl delete pod webapp && kubectl apply --filename webapp-pod.yaml
pod "webapp" deleted
pod/webapp created
更新された!
kubectl get secret dbpass -o jsonpath='{.data.password}' | base64 -d
new-password
しかし、これじゃdowntimeが発生するじゃんか。deploymentでやってみよう。
一回podは消しておく
kubectl delete pod webapp
Deploymentを作る
$ 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
諦め!残念!
何かいい方法があれば教えて〜