Enterprise Secrets Management & Rotation

A configuration value can sit in a .env file; a secret cannot. Secrets must be fetched from a managed store at runtime, held in memory as SecretStr, refreshed on a rotation schedule, and never serialized to logs or disk. This section covers the three managers Python teams actually deploy — Vault, AWS Secrets Manager, and Doppler — and the rotation patterns that keep credentials short-lived.

Secret injection and rotation lifecycle A secret manager issues a short-lived credential to the application, which caches it in memory as SecretStr; a rotation job replaces it on a TTL before expiry. Secret manager Vault / ASM / Doppler Application SecretStr cache in-memory, TTL-bound Backing service DB / API / queue Rotation job (TTL) issue connect
Secrets are issued on demand, cached in memory only, and rotated on a TTL before they expire.

What this section covers

Topic Why it matters Go deeper
AWS Secrets Manager Native AWS secret storage with IAM-scoped access and rotation AWS Secrets Manager Integration
HashiCorp Vault Cloud-agnostic secrets with dynamic, short-lived credentials HashiCorp Vault Python SDK
Doppler Developer-friendly multi-cloud secret sync Doppler for Multi-Cloud Secrets
Rotation patterns Replacing credentials with zero downtime on a schedule Automated Secret Rotation Patterns

Fetch at runtime, hold as SecretStr

The defining rule of secrets management: the credential is never in the source tree, the image, or an environment variable baked into a manifest. It is fetched at startup and wrapped so it cannot leak through a stray print.

# secrets/aws.py
import boto3
from pydantic import SecretStr

def fetch_secret(name: str) -> SecretStr:
    client = boto3.client("secretsmanager")
    value = client.get_secret_value(SecretId=name)["SecretString"]
    return SecretStr(value)   # masked in repr(), logs, and model_dump()

db_password = fetch_secret("prod/db/password")

Key rule: a secret only ever exists as a SecretStr in application memory. Native AWS integration is covered in AWS Secrets Manager Integration.

Prefer dynamic, short-lived credentials

A static password that lives for a year is a liability. Vault can mint a database credential that exists for an hour, scoped to one role, then expires automatically.

# secrets/vault.py
import hvac

client = hvac.Client(url="https://vault.internal:8200")
client.auth.approle.login(role_id=ROLE_ID, secret_id=SECRET_ID.get_secret_value())
lease = client.secrets.database.generate_credentials(name="app-readonly")
# lease["data"]["username"], lease["data"]["password"] — valid for the lease TTL only

Key rule: the shorter the lifetime, the smaller the blast radius. AppRole and dynamic credentials are detailed in HashiCorp Vault Python SDK.

Sync secrets across clouds with Doppler

When a team runs across providers, Doppler centralizes secret definition and injects them into each environment without per-cloud glue — see Doppler for Multi-Cloud Secrets.

Rotate on a schedule, not after a breach

Rotation must be routine and automated. The pattern is dual-write: provision the new credential, let both old and new work during an overlap window, switch traffic, then revoke the old — described in Automated Secret Rotation Patterns.

Anti-patterns & common mistakes

  • Secrets in environment variables on the manifest — readable by anyone with cluster access and baked into image history.
  • Caching a secret to disk — survives the process and ends up in backups.
  • Logging the config object — leaks any credential not wrapped in SecretStr.
  • Static, never-expiring credentials — one leak compromises the system indefinitely.
  • Per-service copies of the same secret — rotation has to find every copy; centralize instead.
  • Fetching the secret on every request — hammers the manager’s API and rate limits; cache with a TTL.

Decision flow: which secret manager?

Are you all-in on AWS?
├── Yes → AWS Secrets Manager: IAM-native, built-in RDS rotation.
└── No → Do you need dynamic, short-lived credentials across many backends?
        ├── Yes → HashiCorp Vault: cloud-agnostic, dynamic secrets engine.
        └── No → Is developer ergonomics and multi-cloud sync the priority?
                ├── Yes → Doppler: simple, fast, good local-dev story.
                └── No → start with your cloud's native store and revisit at scale.

CI/CD integration checklist

  1. Grant the pipeline a short-lived, narrowly-scoped identity (OIDC, AppRole) — never a long-lived key.
  2. Inject secrets at deploy time from the manager; never store them in CI variables in plaintext.
  3. Scan the repo and image layers for committed secrets on every build.
  4. Verify rotation works in staging by forcing a rotation and asserting the app reconnects.
  5. Alert on secrets approaching their TTL so rotation never runs late.

Bringing it together

Secrets management is configuration with a sharper edge: fetched from AWS Secrets Manager, Vault, or Doppler at runtime, held as SecretStr inside a validated settings model, and rotated automatically before expiry. Combine that with the configuration patterns and the credential never touches disk, git, or a log line.