AWS Secrets Manager Integration
Hard-coding a database password into a Kubernetes manifest puts it in cluster state, image history, and every backup. AWS Secrets Manager keeps the credential in a managed store, scoped by IAM, and rotates it on a schedule. This page integrates it into a Python service so the secret only ever exists in memory as a SecretStr.
AWS Secrets Manager is the native option in the enterprise secrets section for teams on AWS, feeding credentials into the same pydantic-settings model that validates the rest of the configuration.
Secure implementation
# secrets/asm.py
import json
import time
import boto3
from pydantic import SecretStr
_client = boto3.client("secretsmanager")
_cache: dict[str, tuple[float, dict]] = {}
TTL_SECONDS = 600 # shorter than the rotation interval
def get_secret(secret_id: str) -> dict[str, SecretStr]:
now = time.monotonic()
cached = _cache.get(secret_id)
if cached and now - cached[0] < TTL_SECONDS:
return cached[1] # serve from in-memory cache
raw = _client.get_secret_value(SecretId=secret_id)["SecretString"]
parsed = {k: SecretStr(v) for k, v in json.loads(raw).items()} # mask every field
_cache[secret_id] = (now, parsed)
return parsed
creds = get_secret("prod/db")
password = creds["password"].get_secret_value() # unwrap only at the point of use
The cache keeps API calls within rate limits; SecretStr keeps the value out of logs; the credential is unwrapped only at the moment it is handed to the database driver.
Configuration reference
| Parameter | Type | Default | Security implication |
|---|---|---|---|
SecretId |
ARN/name | — | Scope IAM to this exact ARN |
TTL_SECONDS |
int |
600 | Keep below rotation interval |
SecretStr wrap |
masked | — | Prevents leakage in logs/tracebacks |
| IAM action | GetSecretValue |
— | Least privilege; no wildcard |
VersionStage |
AWSCURRENT |
current | Pin to current after rotation |
Deployment parity: local to production
- Local dev — developers assume a read-only role via SSO and fetch a non-prod secret; no plaintext on disk.
- CI — the pipeline uses an OIDC role scoped to test secrets only.
- Staging/Production — the pod’s IAM role grants
GetSecretValueon its own secret ARNs; rotation runs on the Secrets Manager schedule.
Security boundaries & guardrails
- IAM policy lists explicit secret ARNs — never
Resource: "*". - Cache in memory only; never write the fetched secret to disk.
- Wrap every field in
SecretStrand unwrap at the call site only. - Set the cache TTL below the rotation interval so stale credentials expire quickly.
- Enable CloudTrail on
GetSecretValueto audit access.
Troubleshooting
AccessDeniedException— the role lacksGetSecretValueon that ARN; scope the policy to it.- Stale credential after rotation — the cache TTL is longer than the rotation interval; shorten it. See Caching AWS Secrets in Memory.
- Throttling /
ThrottlingException— too manyGetSecretValuecalls; the in-memory cache is missing or disabled. - Secret value in logs — a field is a plain string; wrap in
SecretStr.
Frequently asked questions
How do I avoid hitting the AWS Secrets Manager API on every request?
Cache the fetched secret in memory with a short TTL and refresh on expiry. Secrets Manager has rate limits and per-call cost, so a 5–15 minute in-memory cache wrapped in SecretStr is the standard pattern.
What IAM permissions does the application need?
Grant only secretsmanager:GetSecretValue on the specific secret ARNs the service uses, scoped by resource. Never attach a wildcard secretsmanager:* policy to an application role.
How does rotation work without redeploying?
Secrets Manager rotates the underlying credential and updates the stored value; your app picks it up when its cache TTL expires and it re-fetches. Keep the TTL shorter than the rotation interval so stale credentials are never used for long.
Conclusion
The invariant: the credential lives in Secrets Manager, reaches the app only as a TTL-cached SecretStr, and is accessed through an IAM role scoped to its exact ARN. Rotation is the store’s job, not a redeploy.