HashiCorp Vault
Run a secrets management platform on Kubernetes and learn how applications authenticate to it.
Time: ~20 minutes Difficulty: Intermediate
What You Will Learn
Section titled “What You Will Learn”- Installing Vault on minikube via Helm (dev mode)
- The KV v2 secrets engine for key-value storage
- Vault policies for access control
- Kubernetes auth method: pods authenticate using their ServiceAccount token
- Reading secrets from inside a pod
- The Vault web UI
Why Vault
Section titled “Why Vault”The previous demo showed that Kubernetes Secrets are base64 encoded, not encrypted. Anyone with namespace access can read them. Vault solves this with:
- Encryption at rest and in transit
- Fine-grained access policies
- Audit logging (who read what, when)
- Dynamic secrets (database credentials generated on demand)
- Automatic rotation and lease management
Install Vault
Section titled “Install Vault”Navigate to the demo directory:
cd demos/vaultkubectl apply -f manifests/namespace.yaml
helm repo add hashicorp https://helm.releases.hashicorp.comhelm repo update
helm install vault hashicorp/vault \ --namespace vault-demo \ --set "server.dev.enabled=true" \ --set "server.dev.devRootToken=root" \ --set "injector.enabled=false"Dev mode runs Vault unsealed with an in-memory backend. Never use this in production.
Wait for Vault to be ready:
kubectl get pods -n vault-demo -wAccess the Vault UI
Section titled “Access the Vault UI”kubectl port-forward svc/vault 8200:8200 -n vault-demoOpen http://localhost:8200. Log in with token: root.
Store Secrets
Section titled “Store Secrets”From the CLI
Section titled “From the CLI”# Exec into the Vault podkubectl exec -it vault-0 -n vault-demo -- /bin/sh
# Inside the pod:export VAULT_ADDR=http://127.0.0.1:8200export VAULT_TOKEN=root
# Store database credentialsvault kv put secret/demo/database \ username=appuser \ password=s3cret-passw0rd \ host=postgres.example.com \ port=5432
# Store an API keyvault kv put secret/demo/api \ key=sk-live-abc123def456 \ provider=stripe
# Read them backvault kv get secret/demo/databasevault kv get -field=password secret/demo/database
# List all secrets under demo/vault kv list secret/demo/
exitCreate Policies
Section titled “Create Policies”Policies control who can access which secrets.
kubectl exec -it vault-0 -n vault-demo -- /bin/sh
export VAULT_ADDR=http://127.0.0.1:8200export VAULT_TOKEN=root
# Create a read-only policyvault policy write app-readonly - <<'EOF'path "secret/data/demo/*" { capabilities = ["read", "list"]}EOF
# Create an admin policyvault policy write app-admin - <<'EOF'path "secret/data/demo/*" { capabilities = ["create", "read", "update", "delete", "list"]}path "secret/metadata/demo/*" { capabilities = ["read", "list", "delete"]}EOF
# Verifyvault policy listvault policy read app-readonly
exitEnable Kubernetes Auth
Section titled “Enable Kubernetes Auth”This lets pods authenticate to Vault using their ServiceAccount token.
kubectl exec -it vault-0 -n vault-demo -- /bin/sh
export VAULT_ADDR=http://127.0.0.1:8200export VAULT_TOKEN=root
# Enable the Kubernetes auth methodvault auth enable kubernetes
# Configure it to talk to the K8s APIvault write auth/kubernetes/config \ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
# Create a role that maps ServiceAccount "demo-app" to the "app-readonly" policyvault write auth/kubernetes/role/demo-app \ bound_service_account_names=demo-app \ bound_service_account_namespaces=vault-demo \ policies=app-readonly \ ttl=1h
exitDeploy a Test App
Section titled “Deploy a Test App”kubectl apply -f manifests/test-app.yamlRead Secrets from Inside the Pod
Section titled “Read Secrets from Inside the Pod”kubectl exec -it deploy/demo-app -n vault-demo -- /bin/sh
# Install curl (for Vault API calls)wget -qO /tmp/vault.zip https://releases.hashicorp.com/vault/1.15.4/vault_1.15.4_linux_amd64.zip 2>/dev/null
# Alternative: use the Vault API directly with wget# Authenticate using the pod's ServiceAccount tokenSA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# Login to VaultVAULT_TOKEN=$(wget -qO- --post-data "{\"jwt\": \"$SA_TOKEN\", \"role\": \"demo-app\"}" \ http://vault.vault-demo.svc:8200/v1/auth/kubernetes/login 2>/dev/null | \ sed 's/.*"client_token":"\([^"]*\)".*/\1/')
echo "Got Vault token: ${VAULT_TOKEN:0:10}..."
# Read the database secretwget -qO- --header "X-Vault-Token: $VAULT_TOKEN" \ http://vault.vault-demo.svc:8200/v1/secret/data/demo/database 2>/dev/null
exitThe pod authenticates to Vault using its ServiceAccount token and receives a Vault token scoped to the app-readonly policy. It can read secrets but not modify them.
What is Happening
Section titled “What is Happening”manifests/ namespace.yaml # vault-demo namespace vault-policy.yaml # ConfigMap with policy HCL files (reference) test-app.yaml # ServiceAccount + Deployment for testing
Installed via Helm: vault-0 # Vault server pod (dev mode)Authentication flow:
Pod (ServiceAccount: demo-app) | v sends SA JWT tokenVault K8s Auth Method | v validates with K8s APIRole mapping (demo-app -> app-readonly policy) | v returns Vault tokenPod reads secret/demo/* with Vault tokenExperiment
Section titled “Experiment”-
Try writing a secret with the read-only token (should fail):
Terminal window # From inside the demo-app pod, after authenticating:wget -qO- --method=POST \--header "X-Vault-Token: $VAULT_TOKEN" \--body-data '{"data":{"key":"value"}}' \http://vault.vault-demo.svc:8200/v1/secret/data/demo/hack# 403 Forbidden -
Check secret versioning in the Vault UI. Update a secret and see previous versions.
-
Create a second role with
app-adminpolicy and test write access.
Cleanup
Section titled “Cleanup”helm uninstall vault -n vault-demokubectl delete namespace vault-demoFurther Reading
Section titled “Further Reading”See docs/deep-dive.md for a detailed explanation of Vault architecture, unsealing, transit secrets engine, dynamic database credentials, PKI engine, and production deployment patterns.
Next Step
Section titled “Next Step”Move on to External Secrets to learn how ESO syncs Vault secrets into native Kubernetes Secrets automatically.