Vault Agent InjectorでKubernetes認証を試す

前回構築したvault でkubernetes認証を試してみました。
前回構築した時は意識してませんでしたが
Vault Agent Injector がデプロイされたのでそれを使います。

vault側の準備

Vault Agent Injectorの使い方は こちら を参考にさせていただきました。

まずvaultにsecretを作成します。

ubuntu@k8s1:~$ vault secrets enable -path=secret -description="my-secret" kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/

ubuntu@k8s1:~$ vault kv put secret/my-app password=supersecret!
=== Secret Path ===
secret/data/my-app

======= Metadata =======
Key                Value
---                -----
created_time       2025-05-31T09:14:45.104709193Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

ubuntu@k8s1:~$ vault kv get secret/my-app
=== Secret Path ===
secret/data/my-app

======= Metadata =======
Key                Value
---                -----
created_time       2025-05-31T09:14:45.104709193Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    supersecret!

このpasswordをアノテーションでPodにマウントする感じです。

Kubernetes認証を設定します。

ubuntu@k8s1:~$ vault auth enable -description="my-cluster" kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

ubuntu@k8s1:~$ vault auth list
Path           Type          Accessor                    Description                Version
----           ----          --------                    -----------                -------
kubernetes/    kubernetes    auth_kubernetes_c14da5d7    my-cluster                 n/a
token/         token         auth_token_f5c2071f         token based credentials    n/a

ubuntu@k8s1:~$ vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc.cluster.local:443"
Success! Data written to: auth/kubernetes/config

ubuntu@k8s1:~$ vault read auth/kubernetes/config
Key                                  Value
---                                  -----
disable_iss_validation               true
disable_local_ca_jwt                 false
issuer                               n/a
kubernetes_ca_cert                   n/a
kubernetes_host                      https://kubernetes.default.svc.cluster.local:443
pem_keys                             []
token_reviewer_jwt_set               false
use_annotations_as_alias_metadata    false

ubuntu@k8s1:~$ vault policy write my-policy - <<EOF
path "secret/data/my-app" {
  capabilities = ["read"]
}
EOF
Success! Uploaded policy: my-policy

ubuntu@k8s1:~$ vault policy read my-policy
path "secret/data/my-app" {
  capabilities = ["read"]
}

ubuntu@k8s1:~$ vault write auth/kubernetes/role/my-role bound_service_account_names=my-sa bound_service_account_namespaces=default policies=my-policy ttl=20m
Success! Data written to: auth/kubernetes/role/my-role

ubuntu@k8s1:~$ vault read auth/kubernetes/role/my-role
Key                                         Value
---                                         -----
alias_name_source                           serviceaccount_uid
bound_service_account_names                 [my-sa]
bound_service_account_namespace_selector    n/a
bound_service_account_namespaces            [default]
policies                                    [my-policy]
token_bound_cidrs                           []
token_explicit_max_ttl                      0s
token_max_ttl                               0s
token_no_default_policy                     false
token_num_uses                              0
token_period                                0s
token_policies                              [my-policy]
token_ttl                                   20m
token_type                                  default
ttl                                         20m

試しにpodを起動してみます。
まずロール作成時に指定した名前とnamespaceでサービスアカウントを作成します。

ubuntu@k8s1:~$ kubectl create sa my-sa
serviceaccount/my-sa created

マニフェストは以下にしました。
こうすると/vault/secrets/my-appにマウントされます。

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  annotations:
    vault.hashicorp.com/agent-inject: 'true'
    vault.hashicorp.com/role: 'my-role'
    vault.hashicorp.com/agent-inject-secret-my-app: 'secret/my-app'
  labels:
    run: vault-test
  name: vault-test
spec:
  serviceAccountName: my-sa
  containers:
  - image: nginx
    name: vault-test
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

無事起動しました。

ubuntu@k8s1:~$ k get pod -w
NAME         READY   STATUS     RESTARTS   AGE
vault-test   0/2     Init:0/1   0          7s
vault-test   0/2     PodInitializing   0          9s
vault-test   2/2     Running           0          13s

無事マウントされました。

ubuntu@k8s1:~$ k exec -it vault-test -c vault-test -- cat /vault/secrets/my-app
data: map[password:supersecret!]
metadata: map[created_time:2025-05-31T09:14:45.104709193Z custom_metadata:<nil> deletion_time: destroyed:false version:1]

これでsecretを使わなくても機密情報をPodにマウントできますね。
環境変数とかでマウントしたい場合は サンプル をご確認ください。

外部のvault serverからVault Agent InjectorでKubernetes認証を試す

上記は同じクラスタ内で試したのですが、
vaultとアプリは別のクラスタの場合が多いのではないかと思います。

今手元にある別のクラスタはk3sで構築したrancher用クラスタがあるのでそれを使いました。

root@rancher:~# k get node -o wide
NAME      STATUS   ROLES                       AGE    VERSION         INTERNAL-IP     EXTERNAL-IP   OS-IMAGE           KERNEL-VERSION     CONTAINER-RUNTIME
rancher   Ready    control-plane,etcd,master   322d   v1.27.10+k3s2   192.168.0.208   <none>        Ubuntu 24.04 LTS   6.8.0-51-generic   containerd://1.7.11-k3s2.27

構築方法は こちら にドキュメントがありました。
これを参考にrancherクラスタにVault Agent Injectorのみをインストールします。

root@rancher:~# helm repo add hashicorp https://helm.releases.hashicorp.com
root@rancher:~# helm repo update

設定は以下にしました。

root@rancher:~# cat override-vault-values.yaml 
global:
  externalVaultAddr: "http://vault.tsuchinokometal.com"

injector:
  authPath: "auth/rancher-cluster"
  logFormat: "json"
  metrics:
    enabled: true

externalVaultAddrには 前回vault serverを構築したとき
serverをingressで外部公開したのでそのホストを指定しています。
また、authpathはわかりやすいように変えておきます。

インストールします。

root@rancher:~# helm install vault hashicorp/vault --create-namespace -n vault --values override-vault-values.yaml
NAME: vault
LAST DEPLOYED: Sun Jun  1 12:00:02 2025
NAMESPACE: vault
STATUS: deployed
REVISION: 1
TEST SUITE: None
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 vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault

root@rancher:~# k get pod -n vault
NAME                                    READY   STATUS    RESTARTS   AGE
vault-agent-injector-56dd76fc4f-9rzqp   1/1     Running   0          22m

ドキュメントの通りhelmでサービスアカウントが作成されているのでトークンを作成します。

root@rancher:~# cat > vault-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: vault-token-g955r
  namespace: vault
  annotations:
    kubernetes.io/service-account.name: vault
type: kubernetes.io/service-account-token
EOF

root@rancher:~# kubectl apply -f vault-secret.yaml
secret/vault-token-g955r created

root@rancher:~# kubectl describe serviceaccount vault -n vault
Name:                vault
Namespace:           vault
Labels:              app.kubernetes.io/instance=vault
                     app.kubernetes.io/managed-by=Helm
                     app.kubernetes.io/name=vault
                     helm.sh/chart=vault-0.30.0
Annotations:         meta.helm.sh/release-name: vault
                     meta.helm.sh/release-namespace: vault
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              vault-token-g955r
Events:              <none>

root@rancher:~# VAULT_HELM_SECRET_NAME=$(kubectl get secrets --output=json -n vault | jq -r '.items[].metadata | select(.name|startswith("vault-token-")).name')

root@rancher:~# kubectl describe secret $VAULT_HELM_SECRET_NAME -n vault
Name:         vault-token-g955r
Namespace:    vault
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: vault
              kubernetes.io/service-account.uid: 91c16dad-0908-4c62-94b8-689312750828

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     570 bytes
namespace:  5 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IjB4bFcta0NmR0FveXpJc3h1eGpHb2drNF9kQWcxek1lc1JvaXcyLTdRTFUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJ2YXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJ2YXVsdC10b2tlbi1nOTU1ciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJ2YXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjkxYzE2ZGFkLTA5MDgtNGM2Mi05NGI4LTY4OTMxMjc1MDgyOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDp2YXVsdDp2YXVsdCJ9.vykuTChl_qGA7kTEAFkVPkmOMdsWvoi4ht6p-YnWZ6Wx6fWeZmLAkCtdMlbeub03E9d9cmw1K7apQdgiUXFTtyS_AnfdfltwIe-ZmcKFZwDJh4VKjSFb5g85ZkUtVwrxhy_1OlL0E38uC0HAY5FEHd5znv10fa1nGEkpI2PXZtQu5rTrVQM5WBJEZCd_GyAC6nLYcblDaNvvD8tf3tYGvfp8Z2Z1-qO-9lMOwchGzR81wHDYQJIGP7nw82VWZrQMkeo5pmrv2zFmpIIeAa8UC-12B3vfmOdxbiCHqYkn_fNmoDsKvk0P2Xa2cPzbRffWtFC4v544OctILcJaUWLZsg

vault CLIをインストールしてまずはsecretを登録します。
インストールの仕方は こちら を参考にしてください。
シークレットもドキュメント通りにしています。

root@rancher:~# vault kv put secret/devwebapp/config username='giraffe' password='salsa'
root@rancher:~# vault kv get -format=json secret/devwebapp/config | jq ".data.data"
{
  "password": "salsa",
  "username": "giraffe"
}
root@rancher:~# vault policy write devwebapp - <<EOF
path "secret/data/devwebapp/config" {
  capabilities = ["read"]
}
EOF
Success! Uploaded policy: devwebapp

kubernetes認証を設定します。
インストール時に指定したauthpathと一致するようにパスを変えています。

root@rancher:~# vault auth enable --path=rancher-cluster kubernetes
Success! Enabled kubernetes auth method at: rancher-cluster/

root@rancher:~# TOKEN_REVIEW_JWT=$(kubectl get secret $VAULT_HELM_SECRET_NAME -n vault --output='go-template={{ .data.token }}' | base64 --decode)
root@rancher:~# KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode)
root@rancher:~# KUBE_HOST=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.server}')

root@rancher:~# vault write auth/rancher-cluster/config token_reviewer_jwt="$TOKEN_REVIEW_JWT" kubernetes_host="$KUBE_HOST" kubernetes_ca_cert="$KUBE_CA_CERT" issuer="https://kubernetes.default.svc.cluster.local"
Success! Data written to: auth/rancher-cluster/config

root@rancher:~# vault read auth/rancher-cluster/config
Key                                  Value
---                                  -----
disable_iss_validation               true
disable_local_ca_jwt                 false
issuer                               https://kubernetes.default.svc.cluster.local
kubernetes_ca_cert                   -----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE3MjA4NDgzOTcwHhcNMjQwNzEzMDUyNjM3WhcNMzQwNzExMDUyNjM3
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3MjA4NDgzOTcwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAASnJQQR/SpCe2mYi9QK32KDFGeuhV/Ee/aa+zqJVeeH
bfxEHji3MSleWILTt3LH5Ah9tge7XDb3TleIyivtRS35o0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOzXSamwjsbkRapEF2k/H
NrIXyj8wCgYIKoZIzj0EAwIDSAAwRQIhALIxIbuBBrcCHi0Cimo3AQZNpURfIXKt
cnS/Zd4h0VXzAiBwkjymuUxSMXEmdfdFh8+YASNT4wysiUT4NC4xFt2NJg==
-----END CERTIFICATE-----
kubernetes_host                      https://127.0.0.1:6443
pem_keys                             []
token_reviewer_jwt_set               true
use_annotations_as_alias_metadata    false

kubernetes_hostが127.0.0.1ですね。
ここはVault Agent Injectorがいるクラスタじゃないといけないようです。

みたところKubernetes APIには外部からアクセスできるようです。

root@rancher:~# lsof -i:6443 | grep LISTEN
k3s-serve 3816535 root    7u  IPv6 886731924      0t0  TCP *:6443 (LISTEN)

なのでコントロールノードのIPに書き換えます。

root@rancher:~# vault write auth/rancher-cluster/config token_reviewer_jwt="$TOKEN_REVIEW_JWT" kubernetes_host="https://192.168.0.208:6443" kubernetes_ca_cert="$KUBE_CA_CERT" issuer="https://kubernetes.default.svc.cluster.local"
Success! Data written to: auth/rancher-cluster/config
root@rancher:~# vault read auth/rancher-cluster/config
Key                                  Value
---                                  -----
disable_iss_validation               true
disable_local_ca_jwt                 false
issuer                               https://kubernetes.default.svc.cluster.local
kubernetes_ca_cert                   -----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE3MjA4NDgzOTcwHhcNMjQwNzEzMDUyNjM3WhcNMzQwNzExMDUyNjM3
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3MjA4NDgzOTcwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAASnJQQR/SpCe2mYi9QK32KDFGeuhV/Ee/aa+zqJVeeH
bfxEHji3MSleWILTt3LH5Ah9tge7XDb3TleIyivtRS35o0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOzXSamwjsbkRapEF2k/H
NrIXyj8wCgYIKoZIzj0EAwIDSAAwRQIhALIxIbuBBrcCHi0Cimo3AQZNpURfIXKt
cnS/Zd4h0VXzAiBwkjymuUxSMXEmdfdFh8+YASNT4wysiUT4NC4xFt2NJg==
-----END CERTIFICATE-----
kubernetes_host                      https://192.168.0.208:6443
pem_keys                             []
token_reviewer_jwt_set               true
use_annotations_as_alias_metadata    false

ロールを作成します。

root@rancher:~# vault write auth/rancher-cluster/role/devweb-app bound_service_account_names=internal-app bound_service_account_namespaces=default policies=devwebapp ttl=24h
Success! Data written to: auth/rancher-cluster/role/devweb-app
root@rancher:~# vault read auth/rancher-cluster/role/devweb-app
Key                                         Value
---                                         -----
alias_name_source                           serviceaccount_uid
bound_service_account_names                 [internal-app]
bound_service_account_namespace_selector    n/a
bound_service_account_namespaces            [default]
policies                                    [devwebapp]
token_bound_cidrs                           []
token_explicit_max_ttl                      0s
token_max_ttl                               0s
token_no_default_policy                     false
token_num_uses                              0
token_period                                0s
token_policies                              [devwebapp]
token_ttl                                   24h
token_type                                  default
ttl                                         24h

ではpodにマウントしてみます。
サービスアカウントを作成します。

root@rancher:~# kubectl create sa internal-app
serviceaccount/internal-app created

マニフェストはドキュメントと同じ内容にしました。

root@rancher:~# cat pod-devwebapp-with-annotations.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: devwebapp-with-annotations
  labels:
    app: devwebapp-with-annotations
  annotations:
    vault.hashicorp.com/agent-inject: 'true'
    vault.hashicorp.com/role: 'devweb-app'
    vault.hashicorp.com/agent-inject-secret-credentials.txt: 'secret/data/devwebapp/config'
spec:
  serviceAccountName: internal-app
  containers:
    - name: app
      image: burtlo/devwebapp-ruby:k8s

root@rancher:~# kubectl apply --filename pod-devwebapp-with-annotations.yaml
pod/devwebapp-with-annotations created

無事マウントできました。

root@rancher:~# k get pod
NAME                         READY   STATUS    RESTARTS   AGE
devwebapp-with-annotations   2/2     Running   0          70s
root@rancher:~# kubectl exec -it devwebapp-with-annotations -c app -- cat /vault/secrets/credentials.txt
data: map[password:salsa username:giraffe]
metadata: map[created_time:2025-05-31T12:18:54.282592425Z custom_metadata:<nil> deletion_time: destroyed:false version:1]

ちなみにvault serverのingressのホスト名を、
Vault Agent InjectorのPodが名前解決できないといけないのでご注意を。
クラスタ内のPodが名前解決できるかどうかは こちら を参考に確認するとよいと思います。

cat <<EOF | kubectl -n vault apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
spec:
  containers:
  - name: dnsutils
    image: registry.k8s.io/e2e-test-images/agnhost:2.39
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
EOF

root@rancher:~# kubectl exec -i -t dnsutils -n vault -- nslookup vault.tsuchinokometal.com
Server:		10.43.0.10
Address:	10.43.0.10#53

Name:	vault.tsuchinokometal.com
Address: 192.168.0.52