Rotate Database Credentials with HashiCorp Vault Dynamic Secrets

Hardcoding database passwords in configuration files or CI/CD variables creates a massive security debt. If a developer leaves the company or a build server is compromised, those static credentials remain valid until someone manually changes them—a task that often breaks applications. Static secrets are the primary fuel for lateral movement during a security breach.

You can solve this by implementing HashiCorp Vault Dynamic Secrets. Instead of storing a pre-existing password, Vault connects to your database and generates a unique, short-lived set of credentials on the fly for every requesting application. When the application is done or the lease expires, Vault automatically deletes the user from the database. This tutorial covers the implementation of dynamic secrets for HashiCorp Vault v1.15+ to achieve a zero-trust security posture.

TL;DR — Use the Vault Database Secrets Engine to generate ephemeral SQL users with time-to-live (TTL) bounds. This eliminates the risk of stale credential leaks and automates the entire rotation lifecycle.

Understanding the Dynamic Secrets Concept

💡 Analogy: Think of static credentials like a physical master key to a building that anyone can copy. Dynamic secrets are like a smart lock that generates a unique code for a specific visitor. That code only works for two hours and automatically expires once the visitor leaves. Even if the visitor loses the code later, it is useless to an intruder.

In a traditional setup, your application retrieves a username and password from an environment variable. These credentials often have "admin" or "owner" level permissions and no expiration date. If your logs accidentally leak these strings, your database is exposed indefinitely. Because rotating these passwords manually causes downtime, most teams simply never do it.

HashiCorp Vault changes the flow. Your application authenticates with Vault (using an IAM role, Kubernetes Service Account, or AppRole) and asks for database access. Vault, acting as a privileged broker, executes a CREATE USER command on the database, assigns specific grants, and returns the new username and password to the app. This credential is tied to a Lease. When the lease expires, Vault executes a DROP USER command, ensuring the credential has a finite lifespan.

When to Transition to Dynamic Rotation

You should adopt dynamic database credentials if you operate in a distributed microservices environment where tracking "who has access to what" becomes impossible. It is a mandatory requirement for compliance frameworks like SOC2, PCI-DSS, and HIPAA, which demand regular rotation of sensitive keys and audit logs of every access request.

Another critical scenario is ephemeral infrastructure. If you are running jobs on Kubernetes or AWS Lambda, those processes only exist for minutes. Giving an ephemeral process a permanent password is a security mismatch. Dynamic secrets align the credential's lifecycle with the compute lifecycle. If your application scales horizontally, each instance gets its own unique identity in the database logs, making it much easier to debug which specific pod is running a slow query or locking a table.

Step-by-Step Implementation Guide

This guide uses PostgreSQL as the target database, but the logic applies to MySQL, MongoDB, MSSQL, and Oracle. Ensure you have a running Vault server and administrative access to your database.

Step 1: Enable the Database Secrets Engine

First, you must enable the engine path in Vault. This is a one-time setup per environment.

# Enable the database engine at a specific path
vault secrets enable -path=db-prod database

Step 2: Configure Vault Connection to the Database

Vault needs a "root" credential to manage other users. This root user should have the permissions to create and drop roles. In this example, we use the postgresql-database-plugin.

vault write db-prod/config/my-postgres-db \
    plugin_name=postgresql-database-plugin \
    allowed_roles="readonly-app" \
    connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" \
    username="vault_admin" \
    password="supersecretpassword"

Step 3: Create a Dynamic Role

This is where you define the SQL template Vault will execute. Use the {{name}}, {{password}}, and {{expiration}} tokens which Vault populates automatically.

vault write db-prod/roles/readonly-app \
    db_name=my-postgres-db \
    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"

Step 4: Request Credentials

Now, any authorized entity can request credentials. Notice that every call returns a different set of credentials.

# Fetch the dynamic credentials
vault read db-prod/creds/readonly-app

# Example Output:
# username: v-token-readonly-app-7b8x...
# password: hvs.mt9...
# lease_id: db-prod/creds/readonly-app/7b8x...
# lease_duration: 1h

Common Pitfalls and How to Fix Them

⚠️ Common Mistake: Short TTLs vs. Connection Pooling If your application uses a connection pool (like HikariCP or PgBouncer) and the Vault TTL is shorter than the pool's connection age, your app will encounter "Authentication Failed" errors when it tries to reuse an expired connection.

To fix connection pool issues, ensure your application logic handles Lease Renewal. If you are using the Vault SDK, you should periodically call the renew endpoint for your lease_id. Alternatively, use Vault Agent as a sidecar. The Agent handles the renewal and writes the fresh credentials to a shared file, which your application can watch for changes.

Another issue is "Lease Churn." If you have thousands of microservices requesting new credentials every minute, your database's system catalog (e.g., pg_authid in Postgres) will bloat with metadata from deleted users. Always set a reasonable default_ttl (e.g., 1 hour to 12 hours) to balance security with database performance. Monitor your database's internal tables to ensure dropped users are being vacuumed correctly.

Expert Tips for Production Hardening

When running this in production, never use the "postgres" or "sa" superuser as the Vault root credential. Instead, create a dedicated vault_manager user with limited CREATEROLE permissions. This limits the "blast radius" if the Vault server itself is compromised. You can further restrict this user's network access to only allow connections from the Vault cluster IP addresses.

Use Vault Sentinel (for Enterprise) or standard ACL policies to enforce TTL limits. You don't want a developer accidentally creating a role with a 10-year TTL, which effectively turns a dynamic secret back into a static one. Additionally, always enable Vault's audit logging to a secure, external destination like Splunk or AWS CloudWatch. This allows you to trace every database action back to a specific Vault token and, by extension, a specific human or service identity.

📌 Key Takeaways

  • Dynamic secrets provide unique, time-bound credentials for every request.
  • Lease durations (TTL) must be synchronized with application connection pool settings.
  • Vault requires a management user in the database to automate CREATE/DROP actions.
  • Centralizing secrets in Vault provides a single audit trail for compliance.

Frequently Asked Questions

Q. How does Vault rotate the actual root database password?

A. Vault can manage its own configuration password using the /database/rotate-root/:name endpoint. When triggered, Vault generates a new random password, updates the database, and updates its internal configuration. This ensures even the master management password is rotated regularly without human intervention.

Q. What happens if the Vault server goes down?

A. Existing leases remain valid until their TTL expires, so your applications won't immediately disconnect. However, new pods won't be able to start, and existing ones cannot renew leases. For high availability, always run Vault in a clustered mode with at least three nodes across different availability zones.

Q. Does Vault support NoSQL databases like MongoDB?

A. Yes, Vault supports a wide array of databases including MongoDB, Cassandra, and Couchbase. The implementation is similar: you enable the engine, provide a management connection, and define a role that specifies the JSON-based creation and revocation logic required by that specific NoSQL engine.

Post a Comment