Safeguard
Cloud Security

How to set up AWS Secrets Manager with automatic rotation

A practical guide to setting up AWS Secrets Manager with automatic rotation via Lambda, including verification steps and a comparison to Parameter Store.

Karan Patel
Cloud Security Engineer
7 min read

Hardcoded database passwords and long-lived API keys are still one of the most common findings in cloud security audits — and one of the easiest to fix. If your team is rotating credentials manually (or not rotating them at all), you're carrying risk that a single leaked .env file or a compromised CI runner can turn into a full breach. This guide walks through how to set up AWS Secrets Manager with automatic rotation from scratch: creating your first secret, wiring up least-privilege IAM policies, deploying a rotation Lambda, and verifying that credentials actually rotate on schedule. By the end, you'll have a working rotation pipeline for a database credential, a clear picture of how Secrets Manager compares to Parameter Store, and a checklist for integrating secrets retrieval into application code without ever committing a plaintext credential again.

Step 1: Plan Your Secret Types and Access Patterns

Before touching the console or CLI, decide what actually needs to live in Secrets Manager versus a cheaper alternative. This matters because AWS bills Secrets Manager per secret per month plus API calls, while Systems Manager Parameter Store's standard tier is free.

A quick framing for the AWS Secrets Manager vs Parameter Store decision:

  • Use Secrets Manager for anything that needs automatic rotation — database credentials, third-party API keys, OAuth client secrets — and anything requiring cross-account access or fine-grained resource policies.
  • Use Parameter Store for configuration values, feature flags, and static secrets that don't need scheduled rotation, especially if cost is a concern at scale.
  • Use both together in larger environments: Parameter Store for app configuration, Secrets Manager for anything with a rotation lifecycle.

For this walkthrough, assume you're securing an RDS PostgreSQL master credential — a textbook case for rotation.

Step 2: Set Up AWS Secrets Manager and Store Your First Secret

Start by creating the secret itself. You can do this in the console under Secrets Manager > Store a new secret, or via the CLI, which is easier to reproduce in infrastructure-as-code pipelines:

aws secretsmanager create-secret \
  --name prod/rds/app-db \
  --description "RDS master credentials for app-db" \
  --secret-string '{"username":"app_admin","password":"CHANGE_ME_INITIAL","engine":"postgres","host":"app-db.abc123.us-east-1.rds.amazonaws.com","port":5432,"dbname":"appdb"}'

Secrets Manager encrypts the secret value at rest with a KMS key (the default aws/secretsmanager key, or a customer-managed key if you specify --kms-key-id). For production workloads, use a customer-managed CMK so you control the key policy and can audit decrypt calls independently in CloudTrail.

Verify the secret was created:

aws secretsmanager describe-secret --secret-id prod/rds/app-db

Step 3: Configure IAM Permissions for Least-Privilege Access

Every consumer of this secret — your application role, the rotation Lambda, and any operators — should get the narrowest policy that works. Avoid secretsmanager:* on *. A typical read-only policy for an application role looks like:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/rds/app-db-*"
    }
  ]
}

Note the trailing -* — Secrets Manager appends a random 6-character suffix to the ARN, so wildcarding the suffix (not the whole path) keeps the policy scoped to this one secret rather than every secret under prod/rds/.

If you're using a customer-managed KMS key, the role also needs kms:Decrypt on that key's policy, scoped with a condition on the kms:ViaService key matching secretsmanager.us-east-1.amazonaws.com.

Step 4: Enable Automatic Rotation with a Rotation Lambda

This is the core of the setup. AWS provides open-source rotation function templates for RDS, Redshift, and DocumentDB in the aws-secretsmanager-rotation-lambdas repo — you rarely need to write one from scratch for common databases.

Deploy the template through the Serverless Application Repository, or package it yourself:

sam deploy \
  --template-file rotation-function/SAR-Template.yaml \
  --stack-name secrets-rotation-rds-postgres \
  --capabilities CAPABILITY_IAM \
  --parameter-overrides \
      endpoint=https://secretsmanager.us-east-1.amazonaws.com \
      functionName=SecretsManagerRDSPostgresRotation \
      vpcSubnetIds=subnet-0123abcd,subnet-0456efgh \
      vpcSecurityGroupIds=sg-0789ijkl

The secrets manager rotation lambda needs network access to both the database (so it typically runs in the same VPC) and the Secrets Manager service endpoint (via a VPC endpoint or NAT gateway). This is the most common deployment mistake — a rotation Lambda stuck in a private subnet with no route to secretsmanager.<region>.amazonaws.com will time out silently.

Once deployed, attach the function to the secret and set a rotation schedule:

aws secretsmanager rotate-secret \
  --secret-id prod/rds/app-db \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSPostgresRotation \
  --rotation-rules AutomaticallyAfterDays=30

Step 5: Test the Rotation Lambda Before Trusting the Schedule

Don't wait 30 days to find out rotation is broken. Trigger it manually:

aws secretsmanager rotate-secret --secret-id prod/rds/app-db --rotate-immediately

Rotation runs through four Lambda steps — createSecret, setSecret, testSecret, and finishSecret — each logged to CloudWatch under /aws/lambda/SecretsManagerRDSPostgresRotation. Watch that log group during the test run; a failure in testSecret (usually a connectivity or password-policy mismatch) will leave the secret in a pending state rather than corrupting the live credential, since Secrets Manager keeps the previous version (AWSPREVIOUS) intact until finishSecret succeeds.

Step 6: Integrate Secrets Retrieval Into Your Application

With rotation working, the last step is making sure your application always reads the current value instead of caching a stale one. This is the part most secrets manager integration guides skip, and it's where outages actually happen — an app that caches a credential for hours will start failing auth right after a rotation completes.

Fetch secrets at startup and on a refresh interval shorter than your rotation window:

import boto3, json

client = boto3.client("secretsmanager", region_name="us-east-1")

def get_db_credentials():
    response = client.get_secret_value(SecretId="prod/rds/app-db")
    return json.loads(response["SecretString"])

For containerized workloads, consider the AWS Secrets and Configuration Provider (ASCP) for the Secrets Store CSI Driver on EKS, or the Secrets Manager agent for EC2/ECS, both of which handle caching and refresh for you rather than requiring custom polling logic in every service.

Verifying Rotation and Troubleshooting Common Issues

After your first scheduled rotation completes, confirm end to end:

aws secretsmanager get-secret-value --secret-id prod/rds/app-db --version-stage AWSCURRENT
aws secretsmanager list-secret-version-ids --secret-id prod/rds/app-db

Common failure patterns:

  • Rotation Lambda times out — almost always a networking issue. Confirm the Lambda's security group allows outbound HTTPS to the Secrets Manager VPC endpoint (or that a NAT gateway path exists) and outbound access to the database port.
  • AccessDeniedException on GetSecretValue — check for a resource policy on the secret itself, not just the IAM role; cross-account access requires both.
  • Rotation succeeds but the application still fails auth — the app is caching AWSPREVIOUS past its validity window. Shorten the cache TTL or move to an SDK-level cache with rotation awareness.
  • testSecret step fails repeatedly — usually the database's password complexity or connection limit policy rejecting the newly generated credential; adjust the Lambda's password generation parameters (ExcludeCharacters, PasswordLength) to match your database's constraints.
  • Multiple services rotating the same secret concurrently — Secrets Manager handles concurrent rotation requests safely, but only one rotation should be configured per secret; duplicate schedules from IaC drift are a frequent cause of unexpected version churn.

How Safeguard Helps

Setting up rotation for one secret is straightforward. Knowing that every secret across every AWS account, repo, and CI pipeline in your organization is actually rotating — and hasn't quietly drifted back to a static, unrotated credential — is a different problem. Safeguard continuously inventories secrets and their handling across your software supply chain, flagging credentials that bypass Secrets Manager entirely (hardcoded in source, embedded in container images, or leaked into build logs) as well as secrets that are stored correctly but never had rotation enabled. Instead of relying on a one-time audit or tribal knowledge about which team configured what, Safeguard gives you a continuously verified view of secret hygiene alongside the rest of your dependency and pipeline risk — so a rotation policy you set up once stays enforced, not just documented.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.