How to Set Up Secrets Management with HashiCorp Vault
Integrate Kubernetes workloads with HashiCorp Vault using scoped authentication, safe initialization, agent injection, rotation, and audit controls.
Kubernetes Secret data is base64-encoded in the API representation, not protected by encoding itself. Kubernetes supports encryption at rest when the cluster is configured for it, plus RBAC and audit controls. Vault adds a separate trust boundary, short-lived credentials, dynamic secrets, scoped policies, and detailed auditing, but also becomes critical infrastructure that must be secured and recovered.
Step 1: deploy Vault
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm upgrade --install vault hashicorp/vault \
--namespace vault --create-namespace \
--version CHART_VERSION \
--values vault-values.yaml
Pin a reviewed chart and Vault version. The chart’s default development modes are not a production architecture. Configure integrated storage or another supported backend, TLS, multiple Vault servers across failure domains, resource controls, a PodDisruptionBudget, and backups. Prefer a cloud KMS or HSM-backed auto-unseal design when it fits the threat model and recovery requirements.
Step 2: initialize and unseal Vault
kubectl exec -n vault vault-0 -- vault operator init
kubectl exec -n vault vault-0 -- vault operator unseal <unseal-key-1>
kubectl exec -n vault vault-0 -- vault operator unseal <unseal-key-2>
kubectl exec -n vault vault-0 -- vault operator unseal <unseal-key-3>
Vault starts sealed — encrypted at rest with no way to decrypt anything until enough unseal keys (a threshold, typically 3 of 5 generated at init) are provided. Store these keys and the initial root token somewhere genuinely secure and separate from Vault itself — losing them means losing access to everything Vault protects.
Initialize exactly once. Run the operation through a controlled ceremony, never paste key material into shell history or CI logs, distribute shares to separate custodians, and revoke the initial root token after bootstrap. Test snapshot restoration and unseal recovery before applications depend on the service.
Step 3: enable the Kubernetes authentication method
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443"
This lets pods authenticate to Vault using their own Kubernetes ServiceAccount token, rather than needing a separate Vault-specific credential distributed out of band.
Run these commands from an authenticated administrative context with VAULT_ADDR pointing at the TLS endpoint. Modern Kubernetes uses projected, bounded service-account tokens; configure audiences and token review according to the official Vault integration guidance and cluster version. A successful auth-method enable does not prove token review, issuer, or network connectivity works.
Step 4: write a secret
vault secrets enable -path=secret kv-v2
vault kv put secret/myapp/database username="appuser" password="REDACTED_VALUE"
Step 5: create a policy scoping exactly what can be read
# myapp-policy.hcl
path "secret/data/myapp/*" {
capabilities = ["read"]
}
vault policy write myapp-policy myapp-policy.hcl
Scoping the policy to exactly the path a specific application needs — not a blanket allow — limits the blast radius if that application’s own credentials are ever compromised.
Step 6: bind the policy to a Kubernetes ServiceAccount
vault write auth/kubernetes/role/myapp \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=default \
policies=myapp-policy \
ttl=1h
Step 7: retrieve the secret from within a pod
# from inside the pod, using its own ServiceAccount token:
VAULT_TOKEN=$(vault write -field=token auth/kubernetes/login role=myapp jwt=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token))
vault kv get -field=password secret/myapp/database
In practice, this is usually handled by the Vault Agent injector, which automates fetching secrets and writing them to a file inside the pod, rather than requiring application code to call Vault’s API directly.
Avoid assigning the login token to a shell variable in real workloads: command tracing, environment inspection, crashes, and logs can expose it. Let a reviewed integration manage token renewal and render secrets into an in-memory volume with restrictive permissions. Ensure the pod actually uses serviceAccountName: myapp-sa; creating a role with that name does not bind a pod automatically.
Step 8: enable the Vault Agent injector for automatic secret injection
helm upgrade vault hashicorp/vault \
--namespace vault --reuse-values --set injector.enabled=true
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "myapp"
vault.hashicorp.com/agent-inject-secret-db: "secret/myapp/database"
spec:
serviceAccountName: myapp-sa
With these annotations on a pod spec, Vault’s injector adds a sidecar that authenticates and writes the secret to a local file the application container can read — no application code changes needed to integrate with Vault directly.
The fragment is illustrative; annotations belong under the pod template metadata, while serviceAccountName belongs under its spec. Render and inspect the complete Deployment before applying it. Decide whether the agent should run as an init container only or continue as a sidecar to renew tokens and refresh templates. Confirm how the application reloads rotated values; updating a file is useless if the process reads it only at startup.
Audit access and test revocation
Enable at least one Vault audit device before serving applications and protect the audit destination from unauthorized modification. Audit records can contain sensitive metadata even when values are hashed, so control access and retention. Monitor sealed state, leadership, storage health, replication, token and lease renewal failures, injector errors, certificate expiry, and audit-device failure.
Rotate the example secret and confirm the workload receives and uses the new value. Then revoke its Vault role or Kubernetes service account and verify that new authentication fails while unrelated workloads continue. This proves boundaries and recovery behavior, not just the happy-path read.
Why dynamic, scoped access beats a static Kubernetes Secret
A Kubernetes Secret is a static value, readable by anything with RBAC permission to view Secrets in that namespace, with no built-in expiration or fine-grained audit trail of who actually read it. Vault’s model — short-lived tokens, scoped policies per application, and a full audit log of every access — is a meaningfully stronger security posture for credentials that actually matter, at the cost of the additional operational complexity of running and unsealing Vault itself.
Related:
- How to Set Up Centralized Logging for Kubernetes with the EFK Stack
- How to Set Up Kubernetes NetworkPolicies
Sources: