skip to content

How does Spring Cloud Vault's database secret engine integration provide a DataSource, and what are the operational implications of per-instance dynamic credentials?

level: seniorimportance: should knowfreq 30%

answer

  1. database/creds/<role> -> generated user+password with lease
  2. maps to spring.datasource.username/password
  3. database.enabled + role + backend
  4. pool max-lifetime < credential max_ttl
  5. per-instance DB users; leases revocable

basics

~20 s

Vault's database engine generates a unique, short-lived DB username/password per request. Spring Cloud Vault reads database/creds/<role> and exposes them as spring.datasource.username/password, so your DataSource connects with generated credentials that Vault renews or rotates via leases.

solid answer

~40 s

The Vault **database secret engine** is configured (in Vault) with a connection to your DB and a **role** that maps to SQL that creates a temporary user with a TTL. When an app requests `database/creds/<role>`, Vault runs that SQL and returns a fresh `username`/`password` with a lease. Spring Cloud Vault, enabled via `spring.cloud.vault.database.enabled=true`, `role`, and `backend=database`, reads that path and maps the result onto `spring.datasource.username` and `spring.datasource.password`, so an auto-configured `DataSource` uses generated credentials. The `SecretLeaseContainer` then renews the lease and rotates when needed. Operationally: each instance has its *own* DB user, so DB-side connection limits and audit logs multiply per instance; credentials are short-lived so a leaked password self-expires; but connection pools must handle credential changes (event-driven update + connection eviction) and you must ensure the lease TTL comfortably exceeds pool acquisition needs.

code

java · 27 lines
java
// Vault (CLI, one-time setup — not Java):
//   vault secrets enable database
//   vault write database/config/mydb plugin_name=postgresql-database-plugin \
//        connection_url="postgresql://{{username}}:{{password}}@db:5432/app" \
//        allowed_roles=my-app-role username=vault-admin password=... 
//   vault write database/roles/my-app-role db_name=mydb \
//        creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
//        default_ttl=1h max_ttl=24h

// application.yml (Spring side):
// spring:
//   config: { import: "vault://" }
//   cloud:
//     vault:
//       uri: https://vault:8200
//       authentication: KUBERNETES
//       database:
//         enabled: true
//         role: my-app-role
//         backend: database
// -> spring.datasource.username / password are populated with generated creds

@Repository
class OrderRepo {
    private final JdbcTemplate jdbc; // DataSource built from Vault-generated creds
    OrderRepo(JdbcTemplate jdbc) { this.jdbc = jdbc; }
}

go deeper

for a junior

Know Vault can generate short-lived DB credentials that Spring uses as datasource username/password.

for a middle

Explain the role/creation-statements model and the database.enabled/role config.

for a senior

Reason about pool lifetime vs TTL, event-driven credential updates, and rotation handling.

for a principal

Trade off dynamic-credential complexity vs static KV; design least-privilege roles, revocation and audit strategy, and user-proliferation controls.

**What the database engine is:** a *dynamic* Vault secret engine. Instead of storing a static DB password, you configure Vault with (a) a **connection config** telling Vault how to reach the database as an admin, and (b) one or more **roles**, each holding `creation_statements` (SQL that creates a user, e.g. `CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT ...`) and a `default_ttl`/`max_ttl`. When a client reads `database/creds/<role>`, Vault executes the creation SQL, producing a **unique username/password valid only for that lease**, and returns them with a `lease_id` + `lease_duration`. **Spring Cloud Vault integration:** enable it with ```yaml spring: cloud: vault: database: enabled: true role: my-app-role # the Vault DB role backend: database # the mount username-property: spring.datasource.username password-property: spring.datasource.password ``` Spring reads `database/creds/my-app-role` at startup and writes the returned `username`/`password` into the property source under the configured property names (defaults are exactly `spring.datasource.username`/`spring.datasource.password`). Spring Boot's `DataSourceAutoConfiguration` then builds the pool (HikariCP by default) using those generated values. The lease is registered with the `SecretLeaseContainer`, which renews/rotates as covered by the lease-lifecycle mechanism. **Why it's attractive:** - **No long-lived DB password anywhere.** Credentials exist only for the lease; a leak self-heals at expiry. - **Per-instance, per-role identity** improves auditability — DB logs show which Vault-issued user did what. - **Revocation** — Vault can revoke a lease immediately (incident response) and the generated user is dropped. **Operational implications & gotchas:** - **Credential rotation vs the connection pool.** When the lease rotates, `spring.datasource.password` changes, but a running HikariCP pool won't magically use it. You need `@RefreshScope` on the datasource or (more robustly) a lease-event listener that updates the pool credentials and evicts stale connections. Existing open connections usually keep working until closed; *new* connections with a revoked user fail. - **Pool max-lifetime vs TTL.** Set the pool's `max-lifetime` below the credential `max_ttl` so connections recycle onto valid credentials rather than being killed mid-use by user expiry. - **DB user proliferation.** Every instance and every rotation creates a new DB user. Vault cleans them up on lease expiry/revocation, but a broken renewal loop can leak users and hit role limits. - **Startup coupling.** The DB engine and DB must be reachable at startup; `fail-fast` governs whether unreachable Vault aborts boot. - **Renewable vs non-renewable roles.** If the role issues non-renewable creds, you get rotation (new user) rather than renewal — plan the pool update path accordingly. - **Least privilege in creation statements** — the generated role should grant only what the app needs; a permissive `creation_statements` undermines the benefit. **When to use it:** high-value databases where static credential sprawl is a real risk, environments already running Vault, and services that can tolerate the added complexity of credential rotation in the pool. For simpler apps, a static KV password with periodic manual rotation may be enough.

  • Why should the HikariCP max-lifetime be shorter than the credential max TTL?
    So connections are proactively recycled onto currently-valid credentials before the generating user is revoked at TTL. Otherwise a connection can be killed mid-use when its user expires.
  • What security advantage do dynamic DB credentials give over a static KV password?
    They're short-lived and unique per instance/role, so a leaked credential self-expires, can be revoked instantly, and DB audit logs attribute activity to a specific issued user — eliminating a long-lived shared password.

saying these in an interview costs you the question

  • Believing a running Hikari pool auto-adopts rotated DB credentials with no extra work
  • Setting pool max-lifetime longer than the credential TTL
  • Thinking one shared DB user is created rather than per-instance dynamic users

context