HashiCorp Vault — Kubernetes Exercises

3 minute read

Practice Vault + Kubernetes integration with these hands-on exercises.


Exercise 1 — Deploy Vault with Helm

Task: Deploy a dev-mode Vault on Kubernetes.

  1. Add the HashiCorp Helm repo
  2. Install Vault in dev mode
  3. Verify it’s running
  4. Port-forward and access the UI
Show Answer
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

helm install vault hashicorp/vault \
    --set "server.dev.enabled=true" \
    --namespace vault --create-namespace

# Wait for pods
kubectl -n vault get pods -w

# Port forward
kubectl -n vault port-forward svc/vault 8200:8200 &

# Access
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=root
vault status

Exercise 2 — Configure Kubernetes Auth

Task: Set up K8s auth and allow a service account to read secrets.

  1. Enable Kubernetes auth in Vault
  2. Create a policy for reading secret/data/myapp/*
  3. Create a role bound to SA myapp in namespace default
  4. Test authentication from inside a pod
Show Answer
# Enable K8s auth
kubectl -n vault exec vault-0 -- vault auth enable kubernetes

kubectl -n vault exec vault-0 -- vault write auth/kubernetes/config \
    kubernetes_host="https://kubernetes.default.svc"

# Create policy
kubectl -n vault exec vault-0 -- vault policy write myapp - <<EOF
path "secret/data/myapp/*" {
  capabilities = ["read"]
}
EOF

# Create role
kubectl -n vault exec vault-0 -- vault write auth/kubernetes/role/myapp \
    bound_service_account_names=myapp \
    bound_service_account_namespaces=default \
    policies=myapp \
    ttl=1h

# Create SA and test pod
kubectl create serviceaccount myapp

# Store a test secret
kubectl -n vault exec vault-0 -- vault kv put secret/myapp/config \
    username=admin password=s3cret

# Test from a pod
kubectl run test-auth --rm -it \
    --image=curlimages/curl \
    --serviceaccount=myapp \
    --restart=Never -- sh -c '
JWT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -s --request POST \
    --data "{\"jwt\": \"$JWT\", \"role\": \"myapp\"}" \
    http://vault.vault.svc.cluster.local:8200/v1/auth/kubernetes/login | head -c 200
'

Exercise 3 — Agent Injector Annotations

Task: Deploy an app with injected secrets using annotations.

  1. Store credentials at secret/myapp/db
  2. Create a Deployment with Vault annotations
  3. Verify secrets appear at /vault/secrets/
Show Answer
# myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "myapp"
        vault.hashicorp.com/agent-inject-secret-db.env: "secret/data/myapp/db"
        vault.hashicorp.com/agent-inject-template-db.env: |
          {{ with secret "secret/data/myapp/db" -}}
          DB_USER={{ .Data.data.username }}
          DB_PASS={{ .Data.data.password }}
          {{- end }}
    spec:
      serviceAccountName: myapp
      containers:
        - name: myapp
          image: busybox
          command: ["sh", "-c", "cat /vault/secrets/db.env && sleep 3600"]
kubectl apply -f myapp-deployment.yaml
kubectl logs -f deployment/myapp -c myapp
# DB_USER=admin
# DB_PASS=s3cret

Exercise 4 — CSI Provider

Task: Mount Vault secrets using the CSI Provider.

  1. Install the Secrets Store CSI Driver
  2. Create a SecretProviderClass for secret/myapp/db
  3. Deploy a pod that mounts the secrets as a volume
Show Answer
# secret-provider.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-db
spec:
  provider: vault
  parameters:
    vaultAddress: "http://vault.vault.svc.cluster.local:8200"
    roleName: "myapp"
    objects: |
      - objectName: "db-password"
        secretPath: "secret/data/myapp/db"
        secretKey: "password"
      - objectName: "db-username"
        secretPath: "secret/data/myapp/db"
        secretKey: "username"
---
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: csi-test
spec:
  serviceAccountName: myapp
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "cat /mnt/secrets/db-password && sleep 3600"]
      volumeMounts:
        - name: vault-secrets
          mountPath: /mnt/secrets
          readOnly: true
  volumes:
    - name: vault-secrets
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: vault-db

Exercise 5 — External Secrets Operator

Task: Sync a Vault secret to a Kubernetes Secret using ESO.

Show Answer
# secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault
spec:
  provider:
    vault:
      server: "http://vault.vault.svc.cluster.local:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "myapp"
          serviceAccountRef:
            name: myapp
---
# external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-db
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault
    kind: SecretStore
  target:
    name: myapp-db-k8s-secret
  data:
    - secretKey: username
      remoteRef:
        key: myapp/db
        property: username
    - secretKey: password
      remoteRef:
        key: myapp/db
        property: password
kubectl apply -f secret-store.yaml
kubectl apply -f external-secret.yaml

# Verify the K8s Secret was created
kubectl get secret myapp-db-k8s-secret -o jsonpath='{.data.password}' | base64 -d
# s3cret

What’s Next?

With Kubernetes integration covered, let’s look at running Vault in High Availability — Raft consensus, auto-unseal, and disaster recovery.

Next Chapter: High Availability