Vault Core Concepts
Secrets Engines
Vault's functionality is organized into pluggable backends called secrets engines. Each engine handles a different type of secret:
- KV v2 (Key-Value) — Store arbitrary key-value secrets with versioning. Most commonly used. Secrets are static — you put them in, Vault stores them.
- Database — Dynamically generate short-lived database credentials. Vault creates a DB user on-demand when requested, with a configurable TTL. No static passwords to rotate.
- PKI — Issue X.509 certificates on-demand. Acts as a Certificate Authority. Used for mTLS between services.
- AWS/GCP/Azure — Dynamically generate cloud provider credentials (IAM roles, service account keys) with limited TTL.
- Transit — Encryption-as-a-service. Encrypt/decrypt data without exposing the key. Data never leaves Vault encrypted.
- SSH — Sign SSH public keys or generate OTPs for SSH access. Enables zero-standing-access to servers.
Auth Methods
Before accessing any secret, a client must authenticate. Vault supports many auth methods:
- Kubernetes — Pods authenticate using their service account JWT. Vault verifies with the Kubernetes API. No credentials needed in the Pod spec.
- Token — Root tokens and short-lived tokens. Token auth is always enabled; don't use root tokens in automation.
- AppRole — Machine-to-machine auth: a RoleID (non-secret) + SecretID (secret, short-lived). Good for CI/CD pipelines not on Kubernetes.
- AWS/GCP/Azure IAM — Authenticate using cloud provider identity. Good for VMs, Lambda functions, or GKE Workload Identity.
- OIDC/JWT — Authenticate using OIDC tokens from GitHub Actions, GitLab CI, or any OIDC provider.
Policies
# Vault policies use HCL (HashiCorp Configuration Language)
# They grant or deny capabilities on paths
# grade-api-policy.hcl
path "secret/data/grade-api/*" {
capabilities = ["read", "list"]
}
path "secret/data/grade-api/db" {
capabilities = ["read"]
}
path "database/creds/grade-api-role" {
capabilities = ["read"]
}
# Deny access to other teams' secrets
path "secret/data/payment-api/*" {
capabilities = ["deny"]
}
# Capabilities: create, read, update, delete, list, sudo, deny
# 'deny' overrides any allow in other policies
# Write the policy to Vault
vault policy write grade-api grade-api-policy.hcl
# List policies
vault policy list
# Read a policy
vault policy read grade-api
Install on Kubernetes
Helm Installation
# Add HashiCorp Helm repo
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# Install Vault in dev mode first (no HA, unsealed, for learning)
helm install vault hashicorp/vault \
--namespace vault \
--create-namespace \
--set "server.dev.enabled=true"
# Wait for Vault to be ready
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=vault -n vault --timeout=60s
# Exec into the vault pod
kubectl exec -it vault-0 -n vault -- vault status
# Key Value
# --- -----
# Seal Type shamir
# Initialized true
# Sealed false (dev mode starts unsealed)
HA Configuration
# values.yaml for production HA Vault install
server:
replicas: 3
# Integrated storage (Raft) — no external Consul needed
ha:
enabled: true
raft:
enabled: true
setNodeId: true
config: |
ui = true
listener "tcp" {
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
telemetry {
unauthenticated_metrics_access = "true"
}
}
storage "raft" {
path = "/vault/data"
retry_join {
leader_api_addr = "http://vault-0.vault-internal:8200"
}
retry_join {
leader_api_addr = "http://vault-1.vault-internal:8200"
}
retry_join {
leader_api_addr = "http://vault-2.vault-internal:8200"
}
}
service_registration "kubernetes" {}
dataStorage:
enabled: true
size: 10Gi
storageClass: standard # Use your cluster's storage class
resources:
requests:
memory: 256Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 1000m
ui:
enabled: true
serviceType: ClusterIP # Use Ingress, not LoadBalancer
injector:
enabled: true # Vault Agent Injector (Part 3)
# Install production Vault
helm install vault hashicorp/vault \
--namespace vault \
--create-namespace \
-f values.yaml
# Check all 3 pods come up
kubectl get pods -n vault
# NAME READY STATUS RESTARTS AGE
# vault-0 0/1 Running 0 30s
# vault-1 0/1 Running 0 28s
# vault-2 0/1 Running 0 26s
# All show 0/1 because Vault is not initialized/unsealed yet
Initialize & Unseal
# Initialize Vault (only done once per cluster)
kubectl exec vault-0 -n vault -- vault operator init \
-key-shares=5 \ # Split the master key into 5 shares
-key-threshold=3 \ # Need 3 of 5 to unseal
-format=json > vault-keys.json
# CRITICAL: Save vault-keys.json securely (AWS KMS, encrypted backup, etc.)
# It contains the unseal keys and the root token
# NEVER commit to Git
# Extract keys
VAULT_TOKEN=$(cat vault-keys.json | jq -r ".root_token")
UNSEAL_KEY_1=$(cat vault-keys.json | jq -r ".unseal_keys_b64[0]")
UNSEAL_KEY_2=$(cat vault-keys.json | jq -r ".unseal_keys_b64[1]")
UNSEAL_KEY_3=$(cat vault-keys.json | jq -r ".unseal_keys_b64[2]")
# Unseal vault-0 (must provide 3 of 5 keys)
kubectl exec vault-0 -n vault -- vault operator unseal $UNSEAL_KEY_1
kubectl exec vault-0 -n vault -- vault operator unseal $UNSEAL_KEY_2
kubectl exec vault-0 -n vault -- vault operator unseal $UNSEAL_KEY_3
# Join and unseal vault-1 and vault-2
kubectl exec vault-1 -n vault -- vault operator raft join http://vault-0.vault-internal:8200
kubectl exec vault-1 -n vault -- vault operator unseal $UNSEAL_KEY_1
kubectl exec vault-1 -n vault -- vault operator unseal $UNSEAL_KEY_2
kubectl exec vault-1 -n vault -- vault operator unseal $UNSEAL_KEY_3
kubectl exec vault-2 -n vault -- vault operator raft join http://vault-0.vault-internal:8200
kubectl exec vault-2 -n vault -- vault operator unseal $UNSEAL_KEY_1
kubectl exec vault-2 -n vault -- vault operator unseal $UNSEAL_KEY_2
kubectl exec vault-2 -n vault -- vault operator unseal $UNSEAL_KEY_3
# Verify all pods are 1/1
kubectl get pods -n vault
# NAME READY STATUS RESTARTS AGE
# vault-0 1/1 Running 0 5m
# vault-1 1/1 Running 0 5m
# vault-2 1/1 Running 0 5m
seal "awskms" in the Vault config with your KMS key ARN, and Vault decrypts its unseal key automatically on startup.
Writing & Reading Secrets
# Port-forward to Vault UI/API
kubectl port-forward vault-0 8200:8200 -n vault &
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=$VAULT_TOKEN # From init step
# Enable the KV v2 secrets engine
vault secrets enable -path=secret kv-v2
# Write a secret
vault kv put secret/grade-api/db \
username="grade_api_user" \
password="super-secret-db-password" \
host="postgres.grade-api.svc.cluster.local"
# Read the secret
vault kv get secret/grade-api/db
# == Secret Path ==
# secret/data/grade-api/db
# == Data ==
# Key Value
# --- -----
# host postgres.grade-api.svc.cluster.local
# password super-secret-db-password
# username grade_api_user
# List secrets at a path
vault kv list secret/grade-api/
# Update (creates a new version)
vault kv patch secret/grade-api/db password="new-password-v2"
# Get a specific version
vault kv get -version=1 secret/grade-api/db
# Delete (marks as deleted; version still exists)
vault kv delete secret/grade-api/db
# Destroy a specific version (permanent)
vault kv destroy -versions=1 secret/grade-api/db
Vault CLI Essentials
# Status
vault status
vault operator raft list-peers # HA cluster members
# Auth
vault login $ROOT_TOKEN # Login with root token (avoid in prod)
vault token lookup # Info about current token
vault token create -ttl=1h -policy=grade-api # Create short-lived token
# Secrets
vault kv put secret/path key=value
vault kv get secret/path
vault kv list secret/
vault kv delete secret/path
vault kv metadata get secret/path # Version history
# Policies
vault policy list
vault policy write myapp myapp.hcl
vault policy read myapp
# Auth methods
vault auth list
vault auth enable kubernetes
vault auth enable approle
# Secrets engines
vault secrets list
vault secrets enable -path=database database
vault secrets enable -path=pki pki
# Audit
vault audit enable file file_path=/vault/logs/audit.log
vault audit list
Exercises
secret/data/grade-api/* but denies access to secret/data/grade-api/admin. Create a token with that policy. Verify: reading grade-api/db succeeds, reading grade-api/admin is denied.
vault operator raft list-peers. Kill vault-0 and verify vault-1 becomes the new leader.
Key Takeaways & Next Steps
- Vault organizes functionality into secrets engines (KV, Database, PKI, Transit) and auth methods (Kubernetes, AppRole, OIDC)
- Policies use HCL and control which paths a token/entity can access with which capabilities
- KV v2 provides versioning — deleted secrets can be recovered; destroyed secrets cannot
- HA with Raft integrated storage requires no external Consul — 3-node Raft cluster is production-grade
- Use auto-unseal (AWS KMS, GCP KMS) in production — manual unseal doesn't scale
- The root token should be revoked after initial setup — use AppRole or Kubernetes auth for all automation
Next in This Track
In Part 2: Kubernetes Auth, we configure Vault's Kubernetes auth method so Pods authenticate using their service account JWTs — no static credentials, no Secrets to manage. Vault issues short-lived tokens scoped to policies based on namespace and service account.