Skip to content
SP StackPractices
intermediate By Mathias Paulenko

Dynamic Database Credentials with HashiCorp Vault

How to use HashiCorp Vault to generate short-lived database credentials, eliminating hardcoded passwords and reducing secret sprawl

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Dynamic Database Credentials with HashiCorp Vault

Hardcoded database credentials in configuration files are a persistent security risk. HashiCorp Vault solves this by generating short-lived, dynamically managed credentials that are created on demand and automatically revoked after a configurable TTL.

When to Use This

  • You want to eliminate static database passwords from application configuration
  • Credential rotation must happen without application restarts
  • You need an audit trail of every database access with user attribution

Prerequisites

  • Vault server running (dev mode acceptable for testing)
  • PostgreSQL or MySQL database
  • Vault token with permissions to configure the database secrets engine

Solution

1. Enable the Database Secrets Engine

vault secrets enable database

2. Configure Database Connection

vault write database/config/postgres \
  plugin_name=postgresql-database-plugin \
  allowed_roles="app" \
  connection_url="postgresql://{{username}}:{{password}}@localhost:5432/mydb" \
  username="vaultadmin" \
  password="vaultadmin-password"

3. Create a Dynamic Role

vault write database/roles/app \
  db_name=postgres \
  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"

4. Request Dynamic Credentials

// vault-client.ts
import vault from 'node-vault';

const client = vault({ apiVersion: 'v1', endpoint: 'http://localhost:8200' });

export async function getDatabaseCredentials() {
  const result = await client.read('database/creds/app');
  return {
    username: result.data.username,
    password: result.data.password,
    leaseId: result.lease_id,
    leaseDuration: result.lease_duration,
  };
}

5. Application Integration with Lease Renewal

// db/ConnectionPool.ts
import { getDatabaseCredentials } from './vault-client';
import { Pool } from 'pg';

class ManagedConnectionPool {
  private pool: Pool | null = null;
  private leaseTimer: NodeJS.Timeout | null = null;

  async initialize() {
    const creds = await getDatabaseCredentials();
    
    this.pool = new Pool({
      host: 'localhost',
      database: 'mydb',
      user: creds.username,
      password: creds.password,
      max: 20,
    });

    // Renew or rotate before lease expires
    const renewalMs = (creds.leaseDuration - 60) * 1000;
    this.leaseTimer = setTimeout(() => this.rotate(), renewalMs);
  }

  private async rotate() {
    await this.pool?.end();
    await this.initialize();
  }

  async query(sql: string, params: unknown[]) {
    return this.pool!.query(sql, params);
  }

  async close() {
    if (this.leaseTimer) clearTimeout(this.leaseTimer);
    await this.pool?.end();
  }
}

6. Revoke Credentials on Shutdown

// Graceful shutdown handler
process.on('SIGTERM', async () => {
  await connectionPool.close();
  await vault.revoke({ lease_id: currentLeaseId });
  process.exit(0);
});

How It Works

  1. Database Plugin connects to PostgreSQL with admin credentials
  2. Role Definition specifies creation SQL with templated username and password
  3. Credential Request triggers Vault to create a new role in PostgreSQL
  4. TTL Enforcement automatically drops the role after expiration
  5. Lease Renewal extends or replaces credentials before expiration

Production Considerations

  • Run Vault in HA mode with Raft storage for production environments
  • Use AppRole or Kubernetes auth instead of long-lived tokens
  • Enable audit devices to log every credential generation and access
  • Set max_ttl to enforce maximum session duration regardless of renewal

Common Mistakes

  • Forgetting to revoke leases, leaving orphaned database roles
  • Setting TTL too short, causing excessive credential churn
  • Not handling Vault unavailability gracefully in the application

FAQ

Q: What happens if Vault is down when the app needs credentials? A: The application should fail to start or fall back to a cached connection pool. For critical systems, run Vault in HA mode with multiple replicas.

Q: Can Vault rotate the static admin password too? A: Yes. Use vault write database/rotate-root/postgres to rotate the root credentials Vault uses to manage dynamic roles.

Q: Does this work with connection pooling? A: Yes, but the pool must be recreated when credentials rotate. Use a factory pattern that manages pool lifecycle alongside lease TTL.