Secrets Manager & SSM
AWS Secrets Manager stores rotatable secrets; SSM Parameter Store holds configuration and secure strings. Python apps fetch values at startup or on cache miss with boto3 instead of baking credentials into images.
Recipe
import boto3
sm = boto3.client("secretsmanager")
resp = sm.get_secret_value(SecretId="prod/db/password")
password = resp["SecretString"]When to reach for this:
- Database passwords and API keys with rotation
- Non-secret config in Parameter Store (
String) - Secure parameters (
SecureString) with KMS encryption - Injecting into ECS/Lambda via task definition references
Working Example
Fetch secret JSON, parse fields, and read SSM parameters with caching.
import json
import time
from typing import Any
import boto3
class SecretCache:
def __init__(self, ttl_seconds: int = 300) -> None:
self._ttl = ttl_seconds
self._cache: dict[str, tuple[float, Any]] = {}
self._sm = boto3.client("secretsmanager")
self._ssm = boto3.client("ssm")
def get_secret_json(self, secret_id: str) -> dict:
now = time.monotonic()
if secret_id in self._cache:
expires, value = self._cache[secret_id]
if now < expires:
return value
resp = self._sm.get_secret_value(SecretId=secret_id)
data = json.loads(resp["SecretString"])
self._cache[secret_id] = (now + self._ttl, data)
return data
def get_parameter(self, name: str, decrypt: bool = True) -> str:
resp = self._ssm.get_parameter(Name=name, WithDecryption=decrypt)
return resp["Parameter"]["Value"]
if __name__ == "__main__":
cache = SecretCache()
creds = cache.get_secret_json("app/database")
print(creds.get("username"))
region = cache.get_parameter("/app/region")
print(region)What this demonstrates:
SecretStringoften contains JSON - parse once and cache in memory- TTL cache avoids hitting API on every request while allowing rotation pickup
- SSM path-style names (
/app/region) organize parameters by service
Deep Dive
Secrets Manager vs SSM
| Feature | Secrets Manager | SSM Parameter Store |
|---|---|---|
| Rotation | Built-in lambdas | Manual/custom |
| Cost | Higher per secret | Cheaper for plain config |
| Use case | DB passwords, API keys | Feature flags, URLs |
How It Works
- IAM policy grants
secretsmanager:GetSecretValueorssm:GetParameteron specific ARNs - KMS decrypt permissions required for
SecureStringand many secrets - Rotation updates secret value; apps must refresh cache or restart on schedule
Python Notes
from botocore.exceptions import ClientError
def get_secret_or_none(secret_id: str) -> str | None:
try:
return boto3.client("secretsmanager").get_secret_value(SecretId=secret_id)["SecretString"]
except ClientError as exc:
if exc.response["Error"]["Code"] == "ResourceNotFoundException":
return None
raiseGotchas
- Fetching secret every request - latency and API throttling. Fix: process-level cache with TTL.
- Logging secret values - credential leak in log aggregator. Fix: log secret ARN only.
- Hardcoding secret names without env prefix - prod script hits dev secret. Fix:
/app/${ENV}/dbpaths. - No IAM resource scope -
secretsmanager:*on*. Fix: ARN-level policies per secret. - Ignoring rotation - stale password after rotate. Fix: shorter TTL or SSM/Secrets rotation webhook.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| HashiCorp Vault | Multi-cloud secret platform | Simple AWS-only stack |
| K8s secrets | Kubernetes-native deploy | Plain EC2/Lambda without sealed secrets |
| Env vars from CI | Ephemeral deploy-time injection | Apps need runtime rotation |
FAQs
Secrets Manager vs SSM SecureString?
Secrets Manager for credentials needing rotation; SSM SecureString for static secrets and config with KMS.
How do I pass secrets to Lambda?
Reference secret ARN in env var; Lambda resolves at init, or fetch in code with cache.
Can I use pydantic-settings?
Custom settings source that calls boto3 on first access, then caches - keeps validation in one place.
How do I test without AWS?
Inject fake SecretCache in tests; moto supports basic secretsmanager mocks.
What about local dev?
.env excluded from git for laptops; production uses Secrets Manager/SSM only.
How do hierarchies work in SSM?
get_parameters_by_path loads /app/prod/* in one call for bootstrap config.
Who can decrypt?
IAM must allow both ssm:GetParameter and kms:Decrypt on the CMK used by the parameter.
How fast is rotation?
Plan brief dual-password window in DB or app retry on auth failure during rotation event.
Should secrets be in environment variables?
Acceptable when injected at container start from secret reference - not when baked into image layers.
How do I audit access?
CloudTrail logs GetSecretValue calls - alert on unusual principals or regions.
Related
- Credential & Session Patterns - IAM for secret access
- Configuration Management - render without secrets in git
- Configuration & Secrets in Prod - 12-factor injection
- boto3 Basics - sessions and clients
- Cloud SDK Best Practices - least privilege
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.