Operations¶
Scope
Production deployment, seal/unseal procedures, secret engines, audit logging, and HA patterns for HashiCorp Vault.
Deployment Patterns¶
HA Architectures¶
| Pattern | Storage Backend | Auto-Unseal | Use Case |
|---|---|---|---|
| Integrated Raft | Built-in Raft | AWS KMS / Azure Key Vault | Most deployments |
| Consul Backend | Consul cluster | Cloud KMS | Legacy, transitioning away |
| External PostgreSQL | PostgreSQL | Cloud KMS | Managed DB preference |
# Raft storage (recommended)
storage "raft" {
path = "/vault/data"
node_id = "vault-0"
retry_join {
leader_api_addr = "https://vault-1:8200"
}
}
# Auto-unseal with AWS KMS
seal "awskms" {
region = "us-east-1"
kms_key_id = "alias/vault-unseal"
}
Kubernetes Deployment (Helm)¶
helm install vault hashicorp/vault \
--namespace vault --create-namespace \
--set server.ha.enabled=true \
--set server.ha.replicas=3 \
--set server.ha.raft.enabled=true \
--set server.auditStorage.enabled=true \
--set injector.enabled=true
Secret Engines¶
| Engine | Use Case | Path |
|---|---|---|
| KV v2 | Static secrets | secret/ |
| PKI | TLS certificates | pki/ |
| Transit | Encryption as a Service | transit/ |
| Database | Dynamic DB credentials | database/ |
| AWS | Dynamic IAM credentials | aws/ |
| Kubernetes | Service account tokens | kubernetes/ |
Dynamic Database Secrets¶
# Configure database secret engine
vault write database/config/mydb \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@db:5432/mydb" \
allowed_roles="readonly" \
username="vault" \
password="vault-password"
# Create role
vault write database/roles/readonly \
db_name=mydb \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
# Get dynamic credentials
vault read database/creds/readonly
Seal/Unseal Operations¶
# Initialize (first time only)
vault operator init -key-shares=5 -key-threshold=3
# Manual unseal (without auto-unseal)
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>
# Check seal status
vault status
Recovery Keys
With auto-unseal enabled, the unseal keys become recovery keys. Store them securely (e.g., in separate physical safes or HSMs).
Audit Logging¶
# Enable file audit
vault audit enable file file_path=/vault/audit/audit.log
# Enable syslog
vault audit enable syslog tag="vault" facility="AUTH"
Monitoring¶
# Token creation rate
rate(vault_token_create_count[5m])
# Authentication failures
vault_core_handle_login_request{error!=""}
# Seal status
vault_core_unsealed
# Storage backend latency
vault_barrier_put{quantile="0.99"}
Common Issues¶
| Issue | Root Cause | Resolution |
|---|---|---|
| Vault sealed after restart | No auto-unseal configured | Configure cloud KMS auto-unseal |
| Token expired | TTL misconfigured | Increase default_ttl or use periodic tokens |
| Policy denied | Insufficient permissions | Review policy paths and capabilities |
| Raft leader election failures | Network partition | Check node connectivity, raft peers |
| High memory usage | Too many leases | Review lease TTLs, enable revocation |
Commands & Recipes¶
Setup & Init¶
# Dev mode (testing only!)
vault server -dev
# Production init
vault operator init -key-shares=5 -key-threshold=3
# Unseal (3 of 5 keys)
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>
# Login
export VAULT_ADDR="https://vault.example.com:8200"
vault login <root-token>
KV Secrets¶
# Enable KV v2
vault secrets enable -path=secret kv-v2
# Write secret
vault kv put secret/myapp/db username="admin" password="s3cr3t"
# Read secret
vault kv get secret/myapp/db
vault kv get -field=password secret/myapp/db
# List secrets
vault kv list secret/myapp/
Dynamic Database Secrets¶
# Enable database engine
vault secrets enable database
# Configure PostgreSQL
vault write database/config/mydb \
plugin_name=postgresql-database-plugin \
allowed_roles="readonly" \
connection_url="postgresql://{{username}}:{{password}}@db:5432/mydb?sslmode=disable" \
username="vault_admin" \
password="vault_pass"
# Create role (dynamic creds with 1h TTL)
vault write database/roles/readonly \
db_name=mydb \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
# Get dynamic credentials (a new user every time!)
vault read database/creds/readonly
Kubernetes Auth¶
# Enable K8s auth
vault auth enable kubernetes
# Configure
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc"
# Create role for app
vault write auth/kubernetes/role/myapp \
bound_service_account_names=myapp \
bound_service_account_namespaces=default \
policies=myapp-policy \
ttl=1h
Transit (Encryption-as-a-Service)¶
# Enable Transit
vault secrets enable transit
# Create encryption key
vault write -f transit/keys/mykey
# Encrypt data
vault write transit/encrypt/mykey plaintext=$(echo -n "secret data" | base64)
# Decrypt data
vault write transit/decrypt/mykey ciphertext="vault:v1:..."