Master Kubernetes secrets management using HashiCorp Vault. Learn about Vault Kubernetes Auth, IaC secrets, and building a secure, scalable solution for your applications.
Managing secrets in a Kubernetes environment can feel like walking a tightrope. You need to keep your sensitive data — API keys, database credentials, TLS certificates — safe, but also make them accessible to your applications. Traditional methods, like storing secrets directly in Kubernetes Secrets objects, often fall short, especially as your infrastructure scales and your security requirements grow. That's where HashiCorp Vault comes in.
In this post, I'll walk you through how I've implemented a secure and scalable secrets management strategy using HashiCorp Vault in a Kubernetes cluster. We'll cover the core concepts, the setup process, and how to leverage Vault's Kubernetes Auth method to securely inject secrets into your pods.
Kubernetes Secrets are a good starting point, but they have limitations:
Secrets objects.Secrets are only encrypted at rest if the etcd datastore is configured for encryption. This is often an afterthought.HashiCorp Vault addresses these shortcomings by providing a centralized, secure, and feature-rich platform for managing secrets. It offers:
There are a few ways to run Vault in Kubernetes:
For production, I strongly recommend the HA setup. HashiCorp provides an excellent Helm chart for deploying Vault in HA mode.
kubectl configured to communicate with your cluster.First, add the HashiCorp Helm repository:
Bashhelm repo add hashicorp https://helm.releases.hashicorp.com helm repo update
Next, create a values.yaml file for your Vault deployment. This is where you'll configure Vault for HA, storage, and other settings.
YAML# values.yaml server: # Number of Vault instances for HA ha: enabled: true replicas: 3 # Configure storage backend. For production, use a reliable, # shared backend like Consul, etcd, or an external database. # This example uses the default `inmem` for simplicity during testing, # but you'll want to change this for production. # # For production, consider Consul: # backend: "consul" # consul: # address: "consul.service.consul:8500" # Replace with your Consul service address # enabled: true # # Or etcd: # backend: "etcd" # etcd: # enabled: true # address: "http://etcd.service.etcd:2379" # Replace with your etcd service address # haEnabled: true # peerURLs: ["http://vault-0.vault-internal.default.svc:8201", "http://vault-1.vault-internal.default.svc:8201", "http://vault-2.vault-internal.default.svc:8201"] # Adjust as needed # Enable the Kubernetes auth method kubernetes: enabled: true # Configure audit logging audit: enabled: true logPath: "/vault/logs/audit.log" # You can configure outputs like syslog or file # output: "file" # Ensure you have a Kubernetes ServiceAccount for Vault ui: enabled: true serviceType: "ClusterIP" # Set this to your Kubernetes namespace where Vault will be deployed # namespaceOverride: "vault" # If you are using a specific namespace for Vault # namespace: vault
Now, deploy Vault using Helm:
Bash# Create a namespace for Vault if it doesn't exist kubectl create namespace vault # Deploy Vault helm install vault hashicorp/vault \ --namespace vault \ -f values.yaml
After deployment, you'll need to initialize Vault and unseal it. The first time, you'll need to unseal it manually. Subsequent unseals can be automated using auto-unseal features (e.g., with KMS or a Shamir secret sharing auto-unseal key).
Initialize and Unseal (First Time)
Bashkubectl get pods -n vault -l app=vault -o jsonpath='{.items[0].metadata.name}'
This will output several unseal keys and a root token. Store these securely! You'll need 3 out of 5 keys to unseal.Bashkubectl exec <vault-pod-name> -n vault -- vault operator init -key-shares=5 -key-threshold=3
Bashkubectl exec <vault-pod-name> -n vault -- vault operator unseal <key1> kubectl exec <vault-pod-name> -n vault -- vault operator unseal <key2> kubectl exec <vault-pod-name> -n vault -- vault operator unseal <key3>
Bash# Get the root token from the init output export VAULT_TOKEN="<your-root-token>" kubectl exec <vault-pod-name> -n vault -- vault status
Configuring Vault for Kubernetes Auth
Once Vault is running and accessible, you need to configure the Kubernetes authentication method. This allows pods to authenticate with Vault using their Kubernetes Service Account tokens.
Enable the Kubernetes Auth Method:
Bashvault auth enable kubernetes
Configure the Kubernetes Auth Method: You need to tell Vault how to communicate with your Kubernetes API. This involves providing the Kubernetes CA certificate and the host URL. Vault can usually discover this automatically if it's running within the cluster.
Bash# Get the Kubernetes API server address and CA certificate KUBE_HOST=$(kubectl config view --raw -o jsonpath='{.clusters[].cluster.server}') KUBE_CA_CERT=$(kubectl config view --raw -o jsonpath='{.clusters[].cluster.certificate-authority-data}') vault write auth/kubernetes/config \ token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_host="${KUBE_HOST}" \ kubernetes_ca_cert="${KUBE_CA_CERT}" \ issuer="https://kubernetes.default.svc.cluster.local"
Note: The token_reviewer_jwt is automatically mounted inside the Vault pods.
Create a Vault Role: A Vault role defines which Kubernetes Service Accounts are allowed to authenticate and what policies they will have.
Bash# Example: Allow the 'default' service account in the 'default' namespace # to authenticate and receive a Vault token with the 'my-app-policy' vault write auth/kubernetes/role/my-app-role \ bound_service_account_names=default \ bound_service_account_namespaces=default \ policies=my-app-policy \ ttl=24h
bound_service_account_names: The specific Service Account(s) allowed.bound_service_account_namespaces: The namespace(s) where the Service Account must exist.policies: The Vault policies to attach to the generated token.ttl: The Time-To-Live for the Vault token.Create a Vault Policy: Define the permissions your application will have within Vault.
Bashvault policy write my-app-policy - <<EOF # Allow reading secrets from a specific path path "secret/data/my-app/*" { capabilities = ["read"] } EOF
Now that Vault is set up and configured for Kubernetes auth, let