Vault Kubernetes auth: secrets without static tokens
Let pods authenticate to Vault with their service account token and get short-lived secrets — no bootstrap secret to leak.
Every secrets manager has a bootstrap problem: the workload needs a credential to fetch its credentials. Hard-coding a Vault token in a Kubernetes Secret just moves the leak one hop left. Vault's Kubernetes auth method breaks the loop — the pod presents the service account token it already has (preferably a short-lived projected token), Vault verifies it against the cluster's TokenReview API, maps the caller to a role with bound service account name and namespace, and returns a Vault token scoped by policy with a TTL measured in minutes.
The trust chain runs pod → API server TokenReview → Vault role → policy → secret path. Tighten any link and the whole chain holds. A wildcard on bound_service_account_namespaces means any pod in any namespace with a matching SA name can authenticate — that is how a compromised dev namespace reads production database credentials. The Vault from dev to production track covers auth methods, policies, and Kubernetes integration hands-on.
The SA JWT is identity. The Vault role is permission. TokenReview sits in the middle.
The Vault role is the security boundary. Copy-pasting an admin policy onto every workload role defeats the entire design.
Enable and configure the auth method
Vault needs three things to talk to the API server: the Kubernetes API URL, the cluster CA certificate, and a JWT with permission to call TokenReview. On Vault running inside the cluster, the in-cluster config works. External Vault needs explicit values and a dedicated reviewer service account — never reuse a cluster-admin token for this.
vault auth enable kubernetesvault write auth/kubernetes/config \kubernetes_host="https://kubernetes.default.svc:443" \kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
Bind a service account to a least-privilege policy
The role ties a Kubernetes identity to a Vault policy. Set bound_service_account_names and bound_service_account_namespaces to exact values — not *. Keep TTL short; a 20-minute token limits blast radius if a pod is compromised. Use dynamic database credentials where possible so the fetched secret is also short-lived.
vault policy write payments-read - <<EOFpath "database/creds/payments" {capabilities = ["read"]}EOFvault write auth/kubernetes/role/payments \bound_service_account_names=payments \bound_service_account_namespaces=shop \policies=payments-read \ttl=20m
Consume secrets from the pod
The Vault Agent Injector adds a sidecar that logs in via Kubernetes auth and writes secrets to a shared in-memory volume. Your application reads a file — no Vault SDK required, no static token in a Secret. For GitOps-heavy teams, External Secrets Operator uses the same auth backend to materialize native Kubernetes Secrets on a refresh interval.
template:metadata:annotations:vault.hashicorp.com/agent-inject: "true"vault.hashicorp.com/role: "payments"vault.hashicorp.com/agent-inject-secret-db: "database/creds/payments"vault.hashicorp.com/agent-inject-template-db: |{{- with secret "database/creds/payments" -}}username={{ .Data.username }}password={{ .Data.password }}{{- end -}}
kubectl exec -n shop payments-0 -- cat /vault/secrets/dbusername=v-kubernetes-shop-payments-x9f2password=A1a-3f7c9d2e...vault token lookup -format=json | jq .data.ttlfresh DB user, leased — revoked when pod or TTL endsWhere this goes next
Static KV secrets synced via Kubernetes auth are a stepping stone. Next: dynamic credentials from Vault's database and cloud secrets engines so each pod gets a unique lease, and External Secrets or CSI drivers for GitOps-native delivery. The Vault from dev to production path covers auth methods, dynamic secrets, HA, and rotation without midnight restarts.
Go deeper in a courseVault from dev to productionAuth methods, dynamic secrets, Kubernetes integration, and HA.View course