Skip to main content
AWSSecurityintermediate

AWS Secrets Manager Guide

Manage secrets, automatic rotation, cross-account sharing, and RDS integration with AWS Secrets Manager.

CloudToolStack Team22 min readPublished Mar 14, 2026

Prerequisites

  • Basic understanding of AWS IAM and KMS
  • Familiarity with database credential management

Why Secrets Management Matters

Hardcoding credentials in application code, environment variables, or configuration files is one of the most common security vulnerabilities in cloud environments. Secrets such as database passwords, API keys, OAuth tokens, and TLS certificates need a dedicated management layer that provides encryption at rest, fine-grained access control, audit logging, and automatic rotation. AWS Secrets Manager is purpose-built for this problem.

Unlike AWS Systems Manager Parameter Store (which stores general configuration), Secrets Manager focuses specifically on secrets with built-in rotation, cross-account sharing, and native integration with AWS services like RDS, Redshift, and DocumentDB. It encrypts every secret with a KMS key, logs all access in CloudTrail, and provides SDKs that make retrieval straightforward from any application runtime.

This guide covers creating and managing secrets, setting up automatic rotation, enabling cross-account access, integrating with RDS and Lambda, and implementing secrets management best practices in production.

Pricing Overview

Secrets Manager charges $0.40 per secret per month and $0.05 per 10,000 API calls. There is no free tier for Secrets Manager. If you only need simple key-value configuration (non-sensitive), consider SSM Parameter Store, which offers a free tier for standard parameters. Use Secrets Manager when you need automatic rotation, cross-account sharing, or native RDS integration.

Creating and Retrieving Secrets

A secret in Secrets Manager can store up to 65,536 bytes of data. You can store plain text (a single password) or structured JSON (multiple key-value pairs like a database connection object). Secrets Manager encrypts the value using an AWS KMS key, either the default service key or a customer-managed key you specify.

bash
# Create a simple secret (plain text)
aws secretsmanager create-secret \
  --name "prod/api/stripe-key" \
  --description "Stripe API key for production" \
  --secret-string "sk_live_abc123def456"

# Create a structured secret (JSON)
aws secretsmanager create-secret \
  --name "prod/db/primary" \
  --description "Production database credentials" \
  --secret-string '{"username":"app_user","password":"S3cur3P@ss!","host":"prod-db.cluster-abc123.us-east-1.rds.amazonaws.com","port":"5432","dbname":"myapp"}'

# Create a secret with a custom KMS key
aws secretsmanager create-secret \
  --name "prod/api/internal-service" \
  --kms-key-id "alias/secrets-key" \
  --secret-string '{"api_key":"key-abc123","api_secret":"secret-xyz789"}'

# Retrieve a secret value
aws secretsmanager get-secret-value \
  --secret-id "prod/db/primary" \
  --query 'SecretString' \
  --output text

# List all secrets
aws secretsmanager list-secrets \
  --query 'SecretList[].{Name:Name, LastChanged:LastChangedDate}' \
  --output table

Retrieving Secrets in Application Code

The AWS SDKs provide a straightforward interface for retrieving secrets at runtime. You should cache the secret value and refresh periodically rather than calling the API on every request. Most SDKs provide built-in caching clients that handle this automatically.

python
import json
import boto3
from botocore.exceptions import ClientError

def get_secret(secret_name: str, region: str = "us-east-1") -> dict:
    """Retrieve a secret from AWS Secrets Manager."""
    client = boto3.client("secretsmanager", region_name=region)

    try:
        response = client.get_secret_value(SecretId=secret_name)
        secret = response["SecretString"]
        return json.loads(secret)
    except ClientError as e:
        if e.response["Error"]["Code"] == "ResourceNotFoundException":
            raise ValueError(f"Secret {secret_name} not found")
        elif e.response["Error"]["Code"] == "AccessDeniedException":
            raise PermissionError(f"No permission to access {secret_name}")
        raise

# Usage
db_creds = get_secret("prod/db/primary")
connection_string = (
    f"postgresql://{db_creds['username']}:{db_creds['password']}"
    f"@{db_creds['host']}:{db_creds['port']}/{db_creds['dbname']}"
)

Use the Caching Client

AWS provides official caching clients for Python, Java, Go, and .NET. The Python caching client (aws-secretsmanager-caching) caches secrets in memory and refreshes them automatically. This reduces API calls (and costs) and improves latency. Install it with pip install aws-secretsmanager-caching.

Automatic Secret Rotation

Secret rotation is the process of periodically updating a secret value. Secrets Manager can rotate secrets automatically using a Lambda function. AWS provides managed rotation Lambda functions for RDS (MySQL, PostgreSQL, Oracle, SQL Server, MariaDB), Redshift, and DocumentDB. For other services, you write a custom rotation Lambda function.

Rotation follows a four-step process: createSecret (generate a new secret version), setSecret (update the external service with the new value),testSecret (validate the new credentials work), and finishSecret(mark the new version as current). If any step fails, the rotation rolls back automatically.

bash
# Enable rotation for an RDS secret (managed Lambda)
aws secretsmanager rotate-secret \
  --secret-id "prod/db/primary" \
  --rotation-lambda-arn "arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSPostgreSQLRotation" \
  --rotation-rules '{"AutomaticallyAfterDays": 30}'

# Immediately trigger rotation
aws secretsmanager rotate-secret \
  --secret-id "prod/db/primary"

# Check rotation status
aws secretsmanager describe-secret \
  --secret-id "prod/db/primary" \
  --query '{RotationEnabled:RotationEnabled, RotationLambdaARN:RotationLambdaARN, RotationRules:RotationRules, LastRotatedDate:LastRotatedDate}'

Terraform Configuration for Rotation

hcl
resource "aws_secretsmanager_secret" "db_credentials" {
  name        = "prod/db/primary"
  description = "Production database credentials"
  kms_key_id  = aws_kms_key.secrets.arn

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

resource "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = aws_secretsmanager_secret.db_credentials.id
  secret_string = jsonencode({
    username = "app_user"
    password = random_password.db.result
    host     = aws_rds_cluster.main.endpoint
    port     = "5432"
    dbname   = "myapp"
  })
}

resource "aws_secretsmanager_secret_rotation" "db_credentials" {
  secret_id           = aws_secretsmanager_secret.db_credentials.id
  rotation_lambda_arn = aws_lambda_function.secret_rotation.arn

  rotation_rules {
    automatically_after_days = 30
  }
}

resource "random_password" "db" {
  length  = 32
  special = true
}

Rotation Requires VPC Access

The rotation Lambda function must be able to reach the database. If your RDS instance is in a private subnet (which it should be), the Lambda function needs VPC configuration with subnets and security groups that allow connectivity to the database port. You also need a Secrets Manager VPC endpoint so the Lambda can call the Secrets Manager API without traversing the internet.

Cross-Account Secret Sharing

In multi-account AWS environments (common with AWS Organizations), you often need to share secrets across accounts. For example, a shared-services account may hold database credentials that application accounts need to access. Secrets Manager supports cross-account access through resource-based policies.

bash
# Attach a resource policy allowing cross-account access
aws secretsmanager put-resource-policy \
  --secret-id "prod/db/primary" \
  --resource-policy '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "AWS": "arn:aws:iam::111222333444:role/AppRole"
        },
        "Action": [
          "secretsmanager:GetSecretValue",
          "secretsmanager:DescribeSecret"
        ],
        "Resource": "*"
      }
    ]
  }'

# From the other account, retrieve the secret using its ARN
aws secretsmanager get-secret-value \
  --secret-id "arn:aws:secretsmanager:us-east-1:999888777666:secret:prod/db/primary-AbCdEf"

# Validate the resource policy
aws secretsmanager validate-resource-policy \
  --resource-policy file://policy.json

KMS Key Policy Required

If the secret uses a customer-managed KMS key, you must also grant the cross-account principal kms:Decrypt permission on the KMS key. The default AWS-managed KMS key cannot be shared across accounts. Always use a customer-managed key for secrets that need cross-account access.

RDS Integration Patterns

Secrets Manager integrates natively with Amazon RDS, Aurora, and Redshift. When you create an RDS instance, you can have AWS manage the master user password in Secrets Manager automatically. This eliminates the need to ever see or store the database password yourself.

bash
# Create an RDS instance with Secrets Manager-managed password
aws rds create-db-instance \
  --db-instance-identifier myapp-prod \
  --db-instance-class db.t3.medium \
  --engine postgres \
  --master-username admin \
  --manage-master-user-password \
  --master-user-secret-kms-key-id alias/rds-secrets-key \
  --allocated-storage 100

# Retrieve the auto-managed secret ARN
aws rds describe-db-instances \
  --db-instance-identifier myapp-prod \
  --query 'DBInstances[0].MasterUserSecret.SecretArn' \
  --output text

Lambda Function Accessing RDS via Secrets Manager

python
import json
import os
import boto3
import psycopg2

secrets_client = boto3.client("secretsmanager")

def get_db_connection():
    """Get database connection using Secrets Manager credentials."""
    secret_name = os.environ["DB_SECRET_ARN"]
    response = secrets_client.get_secret_value(SecretId=secret_name)
    creds = json.loads(response["SecretString"])

    return psycopg2.connect(
        host=creds["host"],
        port=int(creds["port"]),
        user=creds["username"],
        password=creds["password"],
        dbname=creds["dbname"],
        connect_timeout=5,
        sslmode="require",
    )

def handler(event, context):
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            cur.execute("SELECT COUNT(*) FROM users")
            count = cur.fetchone()[0]
            return {"statusCode": 200, "body": json.dumps({"user_count": count})}
    finally:
        conn.close()

ECS and Kubernetes Integration

Container workloads on ECS and EKS can access secrets directly from Secrets Manager without embedding credentials in container images or task definitions.

json
{
  "containerDefinitions": [
    {
      "name": "myapp",
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest",
      "secrets": [
        {
          "name": "DB_USERNAME",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db/primary:username::"
        },
        {
          "name": "DB_PASSWORD",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db/primary:password::"
        }
      ]
    }
  ]
}
yaml
# Kubernetes ExternalSecret (using External Secrets Operator)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: myapp
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: prod/db/primary
        property: username
    - secretKey: password
      remoteRef:
        key: prod/db/primary
        property: password

Monitoring and Auditing

Every API call to Secrets Manager is logged in AWS CloudTrail, providing a complete audit trail of who accessed which secret and when. You should set up CloudWatch alarms to detect suspicious activity such as unauthorized access attempts, unusual access patterns, or secrets accessed from unexpected IP addresses.

bash
# Query CloudTrail for secret access events
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
  --start-time "2026-03-01T00:00:00Z" \
  --end-time "2026-03-14T23:59:59Z" \
  --query 'Events[].{User:Username, Time:EventTime, Secret:Resources[0].ResourceName}' \
  --output table

# Create a CloudWatch metric filter for failed access
aws logs put-metric-filter \
  --log-group-name "/aws/cloudtrail/management" \
  --filter-name "SecretsManagerAccessDenied" \
  --filter-pattern '{ ($.eventName = "GetSecretValue") && ($.errorCode = "AccessDeniedException") }' \
  --metric-transformations \
    metricName=SecretsManagerAccessDenied,metricNamespace=Security,metricValue=1

# Alarm on failed access attempts
aws cloudwatch put-metric-alarm \
  --alarm-name "secrets-access-denied" \
  --metric-name SecretsManagerAccessDenied \
  --namespace Security \
  --statistic Sum \
  --period 300 \
  --evaluation-periods 1 \
  --threshold 5 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions "arn:aws:sns:us-east-1:123456789012:security-alerts"

Best Practices and Security Hardening

Implementing secrets management properly requires attention to several aspects beyond just storing and retrieving secrets. Follow these best practices to maximize security.

Naming Conventions

Use a hierarchical naming convention that includes environment, service, and purpose. For example: prod/myapp/db-credentials, staging/myapp/api-key,shared/integrations/stripe. This structure enables you to write IAM policies that grant access to specific environments or services using wildcards.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/myapp/*",
      "Condition": {
        "StringEquals": {
          "aws:PrincipalTag/Environment": "production"
        }
      }
    }
  ]
}

Secret Lifecycle Management

PracticeRecommendationImplementation
Rotation frequency30-90 days for database credentialsAutomatic rotation via Lambda
EncryptionUse customer-managed KMS keysSpecify kms-key-id at creation
Access controlLeast privilege per environmentHierarchical naming + IAM conditions
Deletion protection7-30 day recovery windowDefault is 30 days; minimum 7
ReplicationReplicate to DR regionsUse secret replication feature
AuditingAlert on unauthorized accessCloudTrail + CloudWatch alarms

Multi-Region Secret Replication

For disaster recovery and multi-region applications, Secrets Manager supports replicating secrets to other AWS regions. The primary secret is writable, and replicas are read-only copies that stay synchronized automatically.

bash
# Replicate a secret to another region
aws secretsmanager replicate-secret-to-regions \
  --secret-id "prod/db/primary" \
  --add-replica-regions Region=us-west-2,KmsKeyId=alias/secrets-key-west \
  --add-replica-regions Region=eu-west-1,KmsKeyId=alias/secrets-key-eu

# List replica regions
aws secretsmanager describe-secret \
  --secret-id "prod/db/primary" \
  --query 'ReplicationStatus[].{Region:Region, Status:Status}'

# Promote a replica to standalone (during DR failover)
aws secretsmanager stop-replication-to-replica \
  --secret-id "arn:aws:secretsmanager:us-west-2:123456789012:secret:prod/db/primary-AbCdEf" \
  --region us-west-2

Rotation and Replication

When rotation occurs on the primary secret, the new value is automatically replicated to all replica regions. However, rotation Lambda functions run only in the primary region. If you fail over to a replica region, you must promote the replica and set up rotation independently in the new primary region.

IAM Best Practices: Securing Your AWS AccountRDS & Aurora Guide: Managed Databases on AWS

Key Takeaways

  1. 1Secrets Manager encrypts all secrets with KMS and logs access in CloudTrail.
  2. 2Automatic rotation uses Lambda functions following a four-step process: create, set, test, finish.
  3. 3Cross-account sharing requires resource-based policies and customer-managed KMS keys.
  4. 4RDS managed master passwords eliminate the need to ever see database credentials.

Frequently Asked Questions

What is the difference between Secrets Manager and Parameter Store?
Secrets Manager is purpose-built for secrets with built-in rotation, cross-account sharing, and native RDS integration at $0.40/secret/month. Parameter Store is for general configuration with a free tier for standard parameters but no rotation or cross-account features. Use Secrets Manager for credentials; Parameter Store for configuration values.
How does automatic secret rotation work?
Secrets Manager invokes a Lambda function that follows four steps: createSecret (generate new value), setSecret (update the external service), testSecret (validate new credentials), and finishSecret (mark as current). If any step fails, it rolls back automatically. AWS provides managed Lambda functions for RDS, Redshift, and DocumentDB.

Written by CloudToolStack Team

Cloud engineers and architects with hands-on experience across AWS, Azure, and GCP. We write guides based on real-world production patterns, not just documentation rewrites.

Disclaimer: This guide is for educational purposes. Cloud services change frequently; always refer to official documentation for the latest information. AWS, Azure, and GCP are trademarks of their respective owners.