"Secret engines" are Vault's pluggable backends for managing secrets. Some store data you give them (static); others generate fresh credentials on demand (dynamic). Both are central to a real Vault deployment.
The KV v2 Engine (Static Secrets)
The Key/Value version 2 engine is the simplest — Vault as an encrypted key-value store with versioning. It's enabled by default at secret/ on new Vault installations.
vault kv put secret/database/master \
username=app \
password='Tr0ub4dor&3'
vault kv get secret/database/master # latest version
vault kv get -version=1 secret/database/master # specific version
vault kv get -field=password secret/database/master # just one field
vault kv metadata get secret/database/master # see all versions
KV v2 features
- Versioning: Every write creates a new version; old versions are retrievable until purged
- Soft delete:
vault kv deletemarks a version as deleted but recoverable - Destroy:
vault kv destroypermanently removes a version - Check-and-set (CAS): Prevent racy overwrites by requiring the current version number
- Metadata: Per-secret max versions, delete-after, custom metadata
The data/metadata path split
KV v2 uses two API paths:
| Path | Purpose |
|---|---|
secret/data/foo | The actual data — used by the API and policies |
secret/metadata/foo | Versions, deletion, config — used by the API and policies |
Critical: in policies, write secret/data/foo for reading the value. Forgetting /data/ is the #1 reason new operators see "403 Forbidden" on a KV v2 mount.
Dynamic Secrets: The Big Idea
Static secrets work, but every static secret is a long-lived liability. Dynamic secrets flip the model: rather than store one shared password, Vault generates a unique credential per consumer at the moment they need it, with a short lease.
Benefits:
- No long-lived passwords to rotate or distribute
- Per-consumer credentials → per-consumer audit
- Lease expiration automatically revokes — no orphaned access
- Compromise window measured in minutes, not months
Database Secrets Engine
Vault connects to your database with an admin credential it uses only to create/drop users. Each consumer asks Vault for a credential, Vault creates a fresh DB user, returns the credentials, and tracks the lease.
# 1) Enable the engine
vault secrets enable database
# 2) Configure the connection
vault write database/config/orders-db \
plugin_name=postgresql-database-plugin \
allowed_roles=app-role,readonly-role \
connection_url="postgresql://{{username}}:{{password}}@orders.db.acme:5432/orders?sslmode=require" \
username=vault-admin \
password='<rotate-after-init>'
# 3) Define a role
vault write database/roles/app-role \
db_name=orders-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl=1h \
max_ttl=4h
# 4) Consumer reads creds
vault read database/creds/app-role
# Returns: username=v-app-role-aXyZ..., password=..., lease_id=..., lease_duration=1h
One hour later, Vault drops the role from Postgres and the credential becomes useless. The application either renewed the lease (extending its TTL) or fetched a new credential.
The same plugin model supports MySQL, MariaDB, MongoDB, MS SQL Server, Cassandra, Elastic, Snowflake, Redis, and ~30 other databases.
AWS Secrets Engine
Vault generates AWS credentials on demand:
| Mode | What Vault returns |
|---|---|
iam_user | A freshly created IAM user + access keys (cleaned up on lease expiry) |
assumed_role | STS-assumed-role credentials (short-lived by STS design) |
federation_token | STS GetFederationToken credentials |
vault write aws/roles/s3-reader \
credential_type=assumed_role \
role_arns=arn:aws:iam::123:role/s3-reader
vault read aws/sts/s3-reader ttl=15m
# Returns: access_key, secret_key, session_token, lease_id, ttl=15m
Same pattern works for Azure (azure engine), GCP (gcp engine), Alibaba, OCI.
PKI Secrets Engine: Vault as a CA
The PKI engine turns Vault into a Certificate Authority. You configure a root or intermediate CA, define roles that constrain CN/SAN/key length/lifetime, then consumers request certs:
vault write pki_int/roles/internal-app \
allowed_domains="svc.cluster.local" \
allow_subdomains=true \
max_ttl=24h
vault write pki_int/issue/internal-app \
common_name="orders.svc.cluster.local" \
ttl=24h
# Returns: certificate, private_key, ca_chain, serial_number, lease_id
Pair this with Vault Agent or cert-manager and you get fully automated, short-lived TLS certificates across your fleet — no more manual cert rotations.
Other Notable Secret Engines
- SSH: Issues SSH OTPs or signed SSH certificates (no more static SSH keys)
- Kubernetes: Generates short-lived K8s service-account tokens
- Consul: Generates Consul ACL tokens dynamically
- RabbitMQ, Nomad, MongoDB Atlas, GitHub: Each generates dynamic creds for its target
- TOTP: Stores and generates TOTP codes (passes the codes through, doesn't store the secret long-term)
Leases and Revocation
Every dynamic secret is paired with a lease:
vault list sys/leases/lookup/database/creds/app-role
vault read sys/leases/lookup -format=json lease_id=database/creds/app-role/abc...
vault lease renew database/creds/app-role/abc...
vault lease revoke database/creds/app-role/abc...
vault lease revoke -prefix database/creds/app-role # revoke all
The lease is the auditable record of "Vault gave X to Y at time T, valid until T+TTL". When you suspect compromise, vault lease revoke -prefix is your incident-response tool.
Migrating from Static to Dynamic
Most teams adopt Vault in phases:
- Move existing static secrets out of code into KV v2 (instant security win)
- Add dynamic credentials for new services, side-by-side with the static
- Retire the static credentials as services migrate
- Eventually, no service holds a long-lived database password
This pragmatic migration matters — going dynamic-everywhere on day one is a recipe for an outage. Build the muscle gradually.