Dynamic Secrets
Generate short-lived database, cloud, and PKI credentials on demand - the real reason to run Vault
Dynamic Secrets
Static secrets are the obvious use of Vault. Dynamic secrets are the reason to run it. Instead of storing a long-lived password, you store the recipe to generate a credential, and Vault makes one on demand with a short TTL and an automatic revocation. The credential never outlives its purpose, so a leak is naturally bounded.
The Pattern
App ──► Vault ("give me a DB user")
│
├── Vault opens an admin connection
├── CREATE ROLE temp_xyz123 WITH LOGIN PASSWORD '...'
│ VALID UNTIL now()+1h IN GROUP my_app_role
▼
Returns { username, password, lease_id, lease_duration: 3600 }
│
▼
App connects to the DB with those creds
│
1 hour later (or earlier if revoked)
│
▼
Vault revokes: DROP ROLE temp_xyz123You get four wins for free:
- No long-lived password. Even the database "doesn't know" the credentials before the request.
- Per-app, per-time-window auditing. Logs show which AppRole asked, when, and what lease was issued.
- Revocation is one CLI call.
vault lease revoke -prefix database/creds/my-app. - Forced rotation by design. If the lease is 1 hour, you've rotated 24 times a day without writing a cron.
Database Engine
Vault's database engine works with Postgres, MySQL, MongoDB, MSSQL, Oracle, Cassandra, Elasticsearch, and more.
Set Up
# Enable the database secrets engine
vault secrets enable database
# Configure connection (use a Vault-only admin user with CREATE ROLE rights)
vault write database/config/myapp \
plugin_name=postgresql-database-plugin \
allowed_roles="myapp-read,myapp-write" \
connection_url="postgresql://{{username}}:{{password}}@db:5432/myapp?sslmode=disable" \
username="vault_admin" \
password="vault_admin_password"
# Define what "a credential for myapp-read" means
vault write database/roles/myapp-read \
db_name=myapp \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT; \
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"Use
vault read database/creds/myapp-readKey Value
--- -----
lease_id database/creds/myapp-read/aB3Cd...
lease_duration 1h
lease_renewable true
password A1b2-C3d4-E5f6-...
username v-myapp-read-9b3a4The app connects with username + password. When the lease ends, Vault drops the role. Renew it if you need more time, up to max_ttl.
Rotate the Vault-Only Admin
The admin Vault uses to create roles is itself a secret. Rotate it on a schedule:
vault write -force database/rotate-root/myappNow even Vault doesn't know the admin password — it changed itself.
AWS / Cloud Credentials
Same shape for AWS IAM:
vault secrets enable aws
vault write aws/config/root \
access_key=AKIA... \
secret_key=...
vault write aws/roles/s3-readonly \
credential_type=iam_user \
policy_document=-<<EOF
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }
]
}
EOF
vault read aws/creds/s3-readonlyVault calls AWS, creates an IAM user with that inline policy, returns the access key + secret. Lease expires → user deleted.
For STS-based short-lived creds (preferred), use credential_type=assumed_role with a pre-existing IAM role.
PKI Engine — TLS Certificates on Demand
Vault can act as your internal CA, signing certs that live for hours:
vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki
# Generate the root CA (or import an existing one)
vault write -field=certificate pki/root/generate/internal \
common_name="example.com" \
ttl=87600h > ca.crt
# A role that defines what certs look like
vault write pki/roles/myapp \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl="72h"
# Issue a cert
vault write pki/issue/myapp common_name="api.example.com" ttl="24h"The response includes certificate, private_key, and ca_chain. Used for service-to-service mTLS without ever putting long-lived keys on disk.
Transit Engine — Encrypt-as-a-Service
Sometimes you don't want to store the secret in Vault — you want to encrypt your own data using Vault's keys:
vault secrets enable transit
vault write -f transit/keys/customer-data
# Encrypt
vault write transit/encrypt/customer-data \
plaintext=$(echo -n "alice@example.com" | base64)
# → "ciphertext": "vault:v1:..."
# Decrypt
vault write transit/decrypt/customer-data \
ciphertext="vault:v1:..."
# → "plaintext": "<base64 of alice@example.com>"The plaintext never lands on Vault's disk. Useful for application-layer encryption — your DB stores vault:v1:... blobs, only Vault can decrypt them.
Lease Management
Every dynamic secret comes with a lease:
# See active leases
vault list sys/leases/lookup/database/creds/myapp-read
# Look at one
vault read sys/leases/lookup/database/creds/myapp-read/<lease-id>
# Renew (extend)
vault lease renew database/creds/myapp-read/<lease-id>
# Revoke (instant)
vault lease revoke database/creds/myapp-read/<lease-id>
# Revoke everything from a path (incident response)
vault lease revoke -prefix database/creds/myapp-readThe last one is the panic button. Something compromised? Revoke every credential issued for an app in one call.
A Working Recipe
A common production pattern:
- AppRole (or K8s auth, or AWS IAM auth) authenticates the workload to Vault.
- Policy grants
readondatabase/creds/<role>and any static secrets the app needs. - App fetches DB credentials at startup, opens its connection pool.
- App schedules a renewal loop on the DB lease; refreshes the connection pool when the lease nears expiry.
- Audit log records every credential issuance and renewal for forensics.
The infrastructure cost of doing this right is meaningful — but the security and compliance posture is in a different league.
What's Next
Best Practices — HA topology, unsealing strategies, K8s integration, audit, disaster recovery, and the operational story behind keeping Vault up.