Rotate RDS credentials with AWS Secrets Manager

AWS Secrets Manager has turnkey rotation for RDS — it provisions a new password, updates the database, and stores the new value. Your Python app’s only job is to pick up the change without a failed query. This page handles the app side, extending AWS Secrets Manager Integration.

Problem 1: caching the password forever

# ANTI-PATTERN: connection built once with a password that will rotate
ENGINE = create_engine(get_secret("prod/rds")["password"])   # stale after rotation

When Secrets Manager rotates the password, every connection in this pool fails.

Problem 2: fetching on every query

# ANTI-PATTERN: an ASM call per query — throttled and slow
def query():
    pw = get_secret("prod/rds")["password"]    # API call per query

This hammers the Secrets Manager API and adds latency to every database call.

Secure implementation

# db/rds.py
import json, time, boto3
from pydantic import SecretStr
from sqlalchemy import create_engine

_client = boto3.client("secretsmanager")
_cache: dict | None = None
_loaded = 0.0
TTL = 300                                   # below the rotation interval

def _creds() -> dict:
    global _cache, _loaded
    if _cache is None or time.monotonic() - _loaded > TTL:
        raw = _client.get_secret_value(SecretId="prod/rds")["SecretString"]
        _cache, _loaded = json.loads(raw), time.monotonic()
    return _cache

def make_engine():
    c = _creds()
    pw = SecretStr(c["password"]).get_secret_value()
    return create_engine(f"postgresql://{c['username']}:{pw}@{c['host']}/{c['dbname']}",
                         pool_pre_ping=True)   # drops dead connections after rotation

The TTL cache picks up the rotated password within the window; pool_pre_ping=True discards connections invalidated by the rotation so SQLAlchemy reconnects with the new credential.

Gotchas & version-specific behaviour

  • Secrets Manager RDS rotation uses two AWS-managed users alternately — both work during the overlap, so a short TTL never sees a hard cutover.
  • Set the cache TTL below the rotation interval.
  • pool_pre_ping=True (SQLAlchemy) is the cheapest way to drop connections killed by rotation.
  • Grant the rotation Lambda its own role; the app only needs GetSecretValue.

Production parity checklist

  • Rotation is enabled on the RDS secret with an AWS-managed schedule.
  • The app caches credentials with a TTL below the rotation interval.
  • The connection pool drops stale connections (pool_pre_ping).
  • App IAM is scoped to GetSecretValue on the secret ARN.
  • A staging test forces rotation and asserts no failed queries.

Conclusion

Let Secrets Manager rotate the RDS password; on the app side a short-TTL cache plus pool_pre_ping makes the change invisible. For the general pattern, see Automated Secret Rotation Patterns.