Skip to content
intermediate Phase 6 · AWS Security & Compliance

Secrets Manager & Parameter Store

Manage secrets, API keys, and configuration with AWS Secrets Manager.

45m
0 problems
Topic Progress 0%

Secrets Manager Fundamentals

Secrets Manager Fundamentals

AWS Secrets Manager helps you protect secrets needed to access applications, services, and IT resources.

Features

  • Automatic secret rotation
  • Centralized secret management
  • Audit with CloudTrail
  • Encryption with KMS
  • Cross-account access

Create a Secret

# Create a secret for database credentials
aws secretsmanager create-secret \
  --name myapp/prod/database \
  --description "Production database credentials" \
  --secret-string '{
    "username": "admin",
    "password": "SecurePass123!",
    "host": "mydb.xxxx.us-east-1.rds.amazonaws.com",
    "port": 3306,
    "dbname": "myapp"
  }'

# Create a secret from JSON file
aws secretsmanager create-secret \
  --name myapp/prod/api-keys \
  --secret-string file://secrets.json

# Create a secret with binary data
aws secretsmanager create-secret \
  --name myapp/prod/certificate \
  --secret-binary fileb://cert.pfx

# List secrets
aws secretsmanager list-secrets

# Get secret value
aws secretsmanager get-secret-value --secret-id myapp/prod/database

Secret Structure

{
  "username": "admin",
  "password": "SecurePass123!",
  "host": "mydb.xxxx.us-east-1.rds.amazonaws.com",
  "port": 3306,
  "dbname": "myapp",
  "engine": "mysql",
  "engineVersion": "8.0"
}

Secret Rotation

Secret Rotation

Automatic Rotation for RDS

# Create rotation function
aws lambda create-function \
  --function-name secret-rotation \
  --runtime python3.12 \
  --role arn:aws:iam::xxx:role/rotation-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://rotation.zip

# Enable rotation
aws secretsmanager rotate-secret \
  --secret-id myapp/prod/database \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:xxx:function:secret-rotation \
  --rotation-rules '{
    "AutomaticallyAfterDays": 30
  }'

# Check rotation status
aws secretsmanager describe-secret --secret-id myapp/prod/database

Rotation Lambda Function

import boto3
import json
import pymysql

def lambda_handler(event, context):
    secret_arn = event['SecretId']
    token = event['ClientRequestToken']
    step = event['Step']
    
    service_client = boto3.client('secretsmanager')
    
    if step == 'createSecret':
        # Generate new secret
        current = json.loads(service_client.get_secret_value(SecretId=secret_arn)['SecretString'])
        new_password = generate_password()
        new_secret = {**current, 'password': new_password}
        service_client.put_secret_value(
            SecretId=secret_arn,
            SecretString=json.dumps(new_secret),
            VersionStages=['AWSPENDING'],
            VersionId=token
        )
    
    elif step == 'setSecret':
        # Set the new password on the database
        pending = json.loads(service_client.get_secret_value(
            SecretId=secret_arn, VersionStage='AWSPENDING')['SecretString'])
        conn = pymysql.connect(host=pending['host'], user=pending['username'], password=pending['password'])
        cursor = conn.cursor()
        cursor.execute(f"ALTER USER '{pending['username']}'@'%' IDENTIFIED BY '{new_password}'")
        conn.commit()
    
    elif step == 'testSecret':
        # Test the new secret
        pending = json.loads(service_client.get_secret_value(
            SecretId=secret_arn, VersionStage='AWSPENDING')['SecretString'])
        conn = pymysql.connect(host=pending['host'], user=pending['username'], password=pending['password'])
        conn.close()
    
    elif step == 'finishSecret':
        # Mark new secret as current
        service_client.update_secret_version_stage(
            SecretId=secret_arn,
            VersionStage='AWSCURRENT',
            MoveToVersionId=token
        )

Using Secrets in AWS Services

Using Secrets in AWS Services

Lambda

import boto3
import json

def lambda_handler(event, context):
    # Get secret
    secrets = boto3.client('secretsmanager')
    secret = json.loads(secrets.get_secret_value(SecretId='myapp/prod/database')['SecretString'])
    
    # Use secret
    connection = pymysql.connect(
        host=secret['host'],
        user=secret['username'],
        password=secret['password'],
        database=secret['dbname']
    )

ECS Task Definition

{
  "containerDefinitions": [{
    "name": "my-app",
    "image": "my-app:latest",
    "secrets": [{
      "name": "DB_PASSWORD",
      "valueFrom": "arn:aws:secretsmanager:us-east-1:xxx:secret:myapp/prod/database:password::"
    }],
    "environment": [{
      "name": "DB_HOST",
      "value": "mydb.xxxx.us-east-1.rds.amazonaws.com"
    }]
  }]
}

EC2 with SSM

# Retrieve secret on EC2
aws secretsmanager get-secret-value --secret-id myapp/prod/database --query 'SecretString' --output text | jq -r '.password'

# Or use SSM Parameter Store with hierarchy
aws ssm get-parameters-by-path \
  --path /myapp/prod/ \
  --recursive \
  --with-decryption

CloudFormation

Resources:
  MySecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: myapp/prod/database
      Description: Database credentials
      SecretString: !Sub '{"username":"admin","password":"${RandomPassword}"}'
      KmsKeyId: alias/aws/secretsmanager

  MyRotation:
    Type: AWS::SecretsManager::RotationSchedule
    Properties:
      SecretId: !Ref MySecret
      RotationLambdaARN: !GetAtt RotationFunction.Arn
      RotationRules:
        AutomaticallyAfterDays: 30

Secret Policies and Access Control

Secret Policies and Access Control

Resource Policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAccountAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
      },
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "*"
    },
    {
      "Sid": "DenyExternalAccess",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "secretsmanager:*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": "us-east-1"
        }
      }
    }
  ]
}

IAM Policy

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

Cross-Account Access

# Share secret with another account
aws secretsmanager put-resource-policy \
  --secret-id myapp/prod/database \
  --resource-policy '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::OTHER_ACCOUNT:root"
      },
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "*"
    }]
  }'

Secrets Manager Best Practices

Secrets Manager Best Practices

Cost Optimization

Feature Cost
Secret storage $0.40/secret/month
API calls $0.05/10,000 calls
Rotation $0.05/10,000 calls

Best Practices

  1. Use naming conventions: env/service/secret-name
  2. Enable rotation for all database credentials
  3. Use resource policies to limit access
  4. Monitor access with CloudTrail
  5. Use encryption context for additional security
  6. Tag secrets for cost allocation and organization
  7. Use replication for disaster recovery
  8. Never hardcode secrets in code or environment variables

Secrets Manager vs SSM Parameter Store

Feature Secrets Manager SSM Parameter Store
Cost $0.40/secret/month Free tier available
Rotation Built-in Manual
Versioning Yes Yes
Encryption KMS KMS or Standard
Cross-region Replication No

Monitoring

# Monitor secret access
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
  --max-results 10

# Check for failed rotation
aws cloudwatch get-metric-statistics \
  --namespace AWS/SecretsManager \
  --metric-name RotationsFailed \
  --dimensions Name=SecretName,Value=myapp/prod/database \
  --start-time $(date -u -d '24 hours ago') \
  --end-time $(date -u) \
  --period 86400 \
  --statistics Sum