HashiCorp Vault — Practice Projects Exercises
Combine what you’ve built across all projects with these challenges.
Exercise 1 — Secure Microservice Pipeline
Task: Build a complete secret pipeline for a microservice.
- Create a KV path for the service config
- Set up dynamic PostgreSQL credentials
- Configure AppRole for the service
- Create a Vault Agent config that renders a
.envfile
Show Answer
# 1. KV secrets
vault secrets enable -path=microservices kv-v2
vault kv put microservices/payment-svc/config \
redis_url="redis://cache:6379" \
log_level="info" \
rate_limit="1000"
# 2. Dynamic DB creds
vault write database/roles/payment-svc \
db_name=webapp-db \
creation_statements="CREATE ROLE \"\" WITH LOGIN PASSWORD '' VALID UNTIL ''; GRANT SELECT, INSERT, UPDATE ON payment_transactions TO \"\";" \
default_ttl=1h max_ttl=4h
# 3. AppRole
vault policy write payment-svc - <<EOF
path "microservices/data/payment-svc/*" { capabilities = ["read"] }
path "database/creds/payment-svc" { capabilities = ["read"] }
path "sys/leases/renew" { capabilities = ["update"] }
EOF
vault write auth/approle/role/payment-svc \
policies=payment-svc token_ttl=15m token_max_ttl=1h
# 4. Vault Agent config
template {
contents = <<-EOT
{{- with secret "microservices/data/payment-svc/config" }}
REDIS_URL={{ .Data.data.redis_url }}
LOG_LEVEL={{ .Data.data.log_level }}
RATE_LIMIT={{ .Data.data.rate_limit }}
{{- end }}
{{- with secret "database/creds/payment-svc" }}
DB_USER={{ .Data.username }}
DB_PASS={{ .Data.password }}
{{- end }}
EOT
destination = "/app/.env"
command = "kill -HUP $(pidof payment-svc)"
}
Exercise 2 — Certificate-Authenticated API
Task: Use Vault’s PKI to create a client certificate for API authentication.
- Create a PKI role for API clients
- Issue a client certificate
- Configure a server to verify client certs
- Test the mTLS connection
Show Answer
# 1. Create PKI role for API clients
vault write pki_int/roles/api-client \
allowed_domains="api.example.com" \
allow_subdomains=true \
client_flag=true \
server_flag=false \
max_ttl=720h
# 2. Issue client certificate
vault write pki_int/issue/api-client \
common_name="service-x.api.example.com" \
ttl=720h
# Save: certificate → client.pem, private_key → client-key.pem
# 3. Server Nginx config
# ssl_client_certificate /etc/nginx/tls/ca-chain.pem;
# ssl_verify_client on;
# 4. Test
curl --cert client.pem --key client-key.pem \
--cacert ca-chain.pem \
https://api.example.com/v1/data
Exercise 3 — Multi-Environment Secret Management
Task: Organize secrets for dev, staging, and production environments.
Show Answer
# Create per-environment paths
for ENV in dev staging production; do
vault kv put apps/${ENV}/webapp/config \
environment="${ENV}" \
debug="$([ "$ENV" = "dev" ] && echo true || echo false)" \
replicas="$([ "$ENV" = "production" ] && echo 3 || echo 1)"
vault kv put apps/${ENV}/webapp/database \
host="db-${ENV}.example.com" \
port="5432" \
name="webapp_${ENV}"
done
# Create per-environment policies
for ENV in dev staging production; do
vault policy write webapp-${ENV} - <<EOF
path "apps/data/${ENV}/webapp/*" {
capabilities = ["read"]
}
EOF
done
# Dev team gets dev + staging
vault write auth/approle/role/webapp-dev \
policies="webapp-dev,webapp-staging" \
token_ttl=4h
# Production deploys get production only
vault write auth/approle/role/webapp-prod \
policies="webapp-production" \
token_ttl=15m
Exercise 4 — Transit Key Rotation Workflow
Task: Implement a complete key rotation workflow for the Transit engine.
Show Answer
# Create a key
vault write -f transit/keys/app-encryption
# Encrypt some data
CIPHER1=$(vault write -field=ciphertext transit/encrypt/app-encryption \
plaintext=$(echo -n "secret-data-1" | base64))
CIPHER2=$(vault write -field=ciphertext transit/encrypt/app-encryption \
plaintext=$(echo -n "secret-data-2" | base64))
echo "Before rotation:"
echo " $CIPHER1" # vault:v1:...
echo " $CIPHER2" # vault:v1:...
# Rotate the key
vault write -f transit/keys/app-encryption/rotate
# Rewrap existing ciphertexts
NEW_CIPHER1=$(vault write -field=ciphertext transit/rewrap/app-encryption \
ciphertext="$CIPHER1")
NEW_CIPHER2=$(vault write -field=ciphertext transit/rewrap/app-encryption \
ciphertext="$CIPHER2")
echo "After rotation + rewrap:"
echo " $NEW_CIPHER1" # vault:v2:...
echo " $NEW_CIPHER2" # vault:v2:...
# Both old and new ciphertexts can be decrypted
vault write transit/decrypt/app-encryption ciphertext="$CIPHER1"
vault write transit/decrypt/app-encryption ciphertext="$NEW_CIPHER1"
# Set minimum version (disable old key)
vault write transit/keys/app-encryption min_decryption_version=2
# Old ciphertexts can no longer be decrypted
vault write transit/decrypt/app-encryption ciphertext="$CIPHER1"
# Error! ❌
Exercise 5 — Disaster Recovery Drill
Task: Simulate a disaster and recover Vault from a backup.
Show Answer
# 1. Create baseline data
vault kv put secret/critical/api-key value=production-key-123
vault kv put secret/critical/db-pass value=super-secret-db-pass
# 2. Take a snapshot
vault operator raft snapshot save dr-drill-$(date +%Y%m%d).snap
# 3. Simulate "disaster" — add data that shouldn't exist after restore
vault kv put secret/critical/corrupted value=bad-data
vault kv delete secret/critical/api-key
# Verify bad state
vault kv get secret/critical/corrupted # ✅ exists
vault kv get secret/critical/api-key # ❌ deleted
# 4. Restore from snapshot
vault operator raft snapshot restore -force dr-drill-*.snap
# 5. Verify recovery
vault kv get secret/critical/api-key # ✅ restored
vault kv get secret/critical/db-pass # ✅ restored
vault kv get secret/critical/corrupted # ❌ gone (pre-snapshot state)
echo "✅ DR drill successful — data restored to backup state"
What’s Next?
You’ve built complete projects covering all major Vault features. Now let’s look at where to go from here — certifications, related tools, and learning resources.
Next Chapter: Next Steps