Advanced Cloud

Secrets in the cloud — Secrets Manager, KMS, automatic rotation

Credentials in source code, unencrypted environment variables, or committed .env files are the starting point of a large share of cloud incidents. Managing secrets correctly requires dedicated tools, granular access control, and continuous rotation.

What not to do

# Bad: hardcoded password in code
DB_PASSWORD = "myPassword123!"

# Bad: .env with credentials committed to git
echo "AWS_SECRET_ACCESS_KEY=abc123" >> .env
git add .env && git commit -m "config"

# Bad: plain-text environment variable in ECS task definition
"environment": [{ "name": "DB_PASS", "value": "myPassword123!" }]

If credentials have already been committed, consider the repository compromised. Rotate immediately and use git filter-repo to scrub the history.

AWS Secrets Manager

Stores, rotates, and serves secrets in a controlled way. Access is audited via CloudTrail. Natively integrated with RDS for automatic rotation.

# Create a secret
aws secretsmanager create-secret \
  --name prod/app/database \
  --description "Production database credentials" \
  --secret-string '{"username":"app_user","password":"S3cret!@#"}'

# Retrieve the secret (from the application)
aws secretsmanager get-secret-value \
  --secret-id prod/app/database \
  --query SecretString \
  --output text

Consuming in code — never cache the value permanently:

import boto3, json

def get_db_credentials(secret_name: str) -> dict:
    client = boto3.client("secretsmanager", region_name="us-east-1")
    response = client.get_secret_value(SecretId=secret_name)
    return json.loads(response["SecretString"])

creds = get_db_credentials("prod/app/database")
# creds["username"], creds["password"]

Automatic rotation

Secrets Manager can automatically rotate RDS passwords via Lambda:

# Enable rotation every 30 days (native RDS)
aws secretsmanager rotate-secret \
  --secret-id prod/app/database \
  --rotation-rules AutomaticallyAfterDays=30 \
  --rotate-immediately

For custom secrets, implement a Lambda with the four rotation steps: createSecretsetSecrettestSecretfinishSecret.

AWS KMS — key management

KMS manages cryptographic keys. Secrets Manager uses KMS under the hood. Use KMS directly when you need to encrypt your own data:

# Encrypt a value with KMS
aws kms encrypt \
  --key-id alias/my-app-key \
  --plaintext fileb://sensitive-data.txt \
  --output text \
  --query CiphertextBlob | base64 --decode > data.enc

# Decrypt
aws kms decrypt \
  --ciphertext-blob fileb://data.enc \
  --output text \
  --query Plaintext | base64 --decode

KMS key policy — control who can use and who can administer:

{
  "Statement": [
    {
      "Sid": "AdminKey",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789:role/kms-admin" },
      "Action": ["kms:Create*", "kms:Delete*", "kms:Put*"],
      "Resource": "*"
    },
    {
      "Sid": "AppUsage",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789:role/app-role" },
      "Action": ["kms:Decrypt", "kms:GenerateDataKey"],
      "Resource": "*"
    }
  ]
}

Reference secrets instead of copying them

Instead of fetching a secret and injecting it as an env var (which shows up in process logs), use a direct reference:

// ECS Task Definition — Secrets Manager reference
"secrets": [
  {
    "name": "DB_PASSWORD",
    "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:prod/app/database:password::"
  }
]
# Kubernetes — External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-secret
spec:
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-credentials
  data:
  - secretKey: password
    remoteRef:
      key: prod/app/database
      property: password

Secrets checklist

- Never commit secrets to a repository (use pre-commit + gitleaks)
- Secrets Manager or Parameter Store (SecureString) for all secrets
- KMS with customer-managed key (CMK) for sensitive data
- Automatic rotation enabled (30-90 days)
- Access audited via CloudTrail
- No secret in plain-text environment variables
- IAM: only the application role can read the specific secret