- Add date_added to all 950+ skills for complete tracking - Update version to 6.5.0 in package.json and README - Regenerate all indexes and catalog - Sync all generated files Features from merged PR #150: - Stars/Upvotes system for community-driven discovery - Auto-update mechanism via START_APP.bat - Interactive Prompt Builder - Date tracking badges - Smart auto-categorization All skills validated and indexed. Made-with: Cursor
467 lines
12 KiB
Markdown
467 lines
12 KiB
Markdown
---
|
|
name: aws-secrets-rotation
|
|
description: "Automate AWS secrets rotation for RDS, API keys, and credentials"
|
|
category: security
|
|
risk: safe
|
|
source: community
|
|
tags: "[aws, secrets-manager, security, automation, kiro-cli, credentials]"
|
|
date_added: "2026-02-27"
|
|
---
|
|
|
|
# AWS Secrets Rotation
|
|
|
|
Automate rotation of secrets, credentials, and API keys using AWS Secrets Manager and Lambda.
|
|
|
|
## When to Use
|
|
|
|
Use this skill when you need to implement automated secrets rotation, manage credentials securely, or comply with security policies requiring regular key rotation.
|
|
|
|
## Supported Secret Types
|
|
|
|
**AWS Services**
|
|
- RDS database credentials
|
|
- DocumentDB credentials
|
|
- Redshift credentials
|
|
- ElastiCache credentials
|
|
|
|
**Third-Party Services**
|
|
- API keys
|
|
- OAuth tokens
|
|
- SSH keys
|
|
- Custom credentials
|
|
|
|
## Secrets Manager Setup
|
|
|
|
### Create a Secret
|
|
|
|
```bash
|
|
# Create RDS secret
|
|
aws secretsmanager create-secret \
|
|
--name prod/db/mysql \
|
|
--description "Production MySQL credentials" \
|
|
--secret-string '{
|
|
"username": "admin",
|
|
"password": "CHANGE_ME",
|
|
"engine": "mysql",
|
|
"host": "mydb.cluster-abc.us-east-1.rds.amazonaws.com",
|
|
"port": 3306,
|
|
"dbname": "myapp"
|
|
}'
|
|
|
|
# Create API key secret
|
|
aws secretsmanager create-secret \
|
|
--name prod/api/stripe \
|
|
--secret-string '{
|
|
"api_key": "sk_live_xxxxx",
|
|
"webhook_secret": "whsec_xxxxx"
|
|
}'
|
|
|
|
# Create secret from file
|
|
aws secretsmanager create-secret \
|
|
--name prod/ssh/private-key \
|
|
--secret-binary fileb://~/.ssh/id_rsa
|
|
```
|
|
|
|
### Retrieve Secrets
|
|
|
|
```bash
|
|
# Get secret value
|
|
aws secretsmanager get-secret-value \
|
|
--secret-id prod/db/mysql \
|
|
--query 'SecretString' --output text
|
|
|
|
# Get specific field
|
|
aws secretsmanager get-secret-value \
|
|
--secret-id prod/db/mysql \
|
|
--query 'SecretString' --output text | \
|
|
jq -r '.password'
|
|
|
|
# Get binary secret
|
|
aws secretsmanager get-secret-value \
|
|
--secret-id prod/ssh/private-key \
|
|
--query 'SecretBinary' --output text | \
|
|
base64 -d > private-key.pem
|
|
```
|
|
|
|
## Automatic Rotation Setup
|
|
|
|
### Enable RDS Rotation
|
|
|
|
```bash
|
|
# Enable automatic rotation (30 days)
|
|
aws secretsmanager rotate-secret \
|
|
--secret-id prod/db/mysql \
|
|
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSMySQLRotation \
|
|
--rotation-rules AutomaticallyAfterDays=30
|
|
|
|
# Rotate immediately
|
|
aws secretsmanager rotate-secret \
|
|
--secret-id prod/db/mysql
|
|
|
|
# Check rotation status
|
|
aws secretsmanager describe-secret \
|
|
--secret-id prod/db/mysql \
|
|
--query 'RotationEnabled'
|
|
```
|
|
|
|
### Lambda Rotation Function
|
|
|
|
```python
|
|
# lambda_rotation.py
|
|
import boto3
|
|
import json
|
|
import os
|
|
|
|
secrets_client = boto3.client('secretsmanager')
|
|
rds_client = boto3.client('rds')
|
|
|
|
def lambda_handler(event, context):
|
|
"""Rotate RDS MySQL password"""
|
|
|
|
secret_arn = event['SecretId']
|
|
token = event['ClientRequestToken']
|
|
step = event['Step']
|
|
|
|
# Get current secret
|
|
current = secrets_client.get_secret_value(SecretId=secret_arn)
|
|
secret = json.loads(current['SecretString'])
|
|
|
|
if step == "createSecret":
|
|
# Generate new password
|
|
new_password = generate_password()
|
|
secret['password'] = new_password
|
|
|
|
# Store as pending
|
|
secrets_client.put_secret_value(
|
|
SecretId=secret_arn,
|
|
ClientRequestToken=token,
|
|
SecretString=json.dumps(secret),
|
|
VersionStages=['AWSPENDING']
|
|
)
|
|
|
|
elif step == "setSecret":
|
|
# Update RDS password
|
|
rds_client.modify_db_instance(
|
|
DBInstanceIdentifier=secret['dbInstanceIdentifier'],
|
|
MasterUserPassword=secret['password'],
|
|
ApplyImmediately=True
|
|
)
|
|
|
|
elif step == "testSecret":
|
|
# Test new credentials
|
|
import pymysql
|
|
conn = pymysql.connect(
|
|
host=secret['host'],
|
|
user=secret['username'],
|
|
password=secret['password'],
|
|
database=secret['dbname']
|
|
)
|
|
conn.close()
|
|
|
|
elif step == "finishSecret":
|
|
# Mark as current
|
|
secrets_client.update_secret_version_stage(
|
|
SecretId=secret_arn,
|
|
VersionStage='AWSCURRENT',
|
|
MoveToVersionId=token,
|
|
RemoveFromVersionId=current['VersionId']
|
|
)
|
|
|
|
return {'statusCode': 200}
|
|
|
|
def generate_password(length=32):
|
|
import secrets
|
|
import string
|
|
alphabet = string.ascii_letters + string.digits + "!@#$%^&*()"
|
|
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
|
```
|
|
|
|
### Custom Rotation for API Keys
|
|
|
|
```python
|
|
# api_key_rotation.py
|
|
import boto3
|
|
import requests
|
|
import json
|
|
|
|
secrets_client = boto3.client('secretsmanager')
|
|
|
|
def rotate_stripe_key(secret_arn, token, step):
|
|
"""Rotate Stripe API key"""
|
|
|
|
current = secrets_client.get_secret_value(SecretId=secret_arn)
|
|
secret = json.loads(current['SecretString'])
|
|
|
|
if step == "createSecret":
|
|
# Create new Stripe key via API
|
|
response = requests.post(
|
|
'https://api.stripe.com/v1/api_keys',
|
|
auth=(secret['api_key'], ''),
|
|
data={'name': f'rotated-{token[:8]}'}
|
|
)
|
|
new_key = response.json()['secret']
|
|
|
|
secret['api_key'] = new_key
|
|
secrets_client.put_secret_value(
|
|
SecretId=secret_arn,
|
|
ClientRequestToken=token,
|
|
SecretString=json.dumps(secret),
|
|
VersionStages=['AWSPENDING']
|
|
)
|
|
|
|
elif step == "testSecret":
|
|
# Test new key
|
|
response = requests.get(
|
|
'https://api.stripe.com/v1/balance',
|
|
auth=(secret['api_key'], '')
|
|
)
|
|
if response.status_code != 200:
|
|
raise Exception("New key failed validation")
|
|
|
|
elif step == "finishSecret":
|
|
# Revoke old key
|
|
old_key = json.loads(current['SecretString'])['api_key']
|
|
requests.delete(
|
|
f'https://api.stripe.com/v1/api_keys/{old_key}',
|
|
auth=(secret['api_key'], '')
|
|
)
|
|
|
|
# Promote to current
|
|
secrets_client.update_secret_version_stage(
|
|
SecretId=secret_arn,
|
|
VersionStage='AWSCURRENT',
|
|
MoveToVersionId=token
|
|
)
|
|
```
|
|
|
|
## Rotation Monitoring
|
|
|
|
### CloudWatch Alarms
|
|
|
|
```bash
|
|
# Create alarm for rotation failures
|
|
aws cloudwatch put-metric-alarm \
|
|
--alarm-name secrets-rotation-failures \
|
|
--alarm-description "Alert on secrets rotation failures" \
|
|
--metric-name RotationFailed \
|
|
--namespace AWS/SecretsManager \
|
|
--statistic Sum \
|
|
--period 300 \
|
|
--evaluation-periods 1 \
|
|
--threshold 1 \
|
|
--comparison-operator GreaterThanThreshold \
|
|
--alarm-actions arn:aws:sns:us-east-1:123456789012:alerts
|
|
```
|
|
|
|
### Rotation Audit Script
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# audit-rotations.sh
|
|
|
|
echo "Secrets Rotation Audit"
|
|
echo "====================="
|
|
|
|
aws secretsmanager list-secrets --query 'SecretList[*].[Name,RotationEnabled,LastRotatedDate]' \
|
|
--output text | \
|
|
while read name enabled last_rotated; do
|
|
echo ""
|
|
echo "Secret: $name"
|
|
echo " Rotation Enabled: $enabled"
|
|
echo " Last Rotated: $last_rotated"
|
|
|
|
if [ "$enabled" = "True" ]; then
|
|
# Check rotation schedule
|
|
rules=$(aws secretsmanager describe-secret --secret-id "$name" \
|
|
--query 'RotationRules.AutomaticallyAfterDays' --output text)
|
|
echo " Rotation Schedule: Every $rules days"
|
|
|
|
# Calculate days since last rotation
|
|
if [ "$last_rotated" != "None" ]; then
|
|
days_ago=$(( ($(date +%s) - $(date -d "$last_rotated" +%s)) / 86400 ))
|
|
echo " Days Since Rotation: $days_ago"
|
|
|
|
if [ $days_ago -gt $rules ]; then
|
|
echo " ⚠️ OVERDUE for rotation!"
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
```
|
|
|
|
## Application Integration
|
|
|
|
### Python SDK
|
|
|
|
```python
|
|
import boto3
|
|
import json
|
|
|
|
def get_secret(secret_name):
|
|
"""Retrieve secret from Secrets Manager"""
|
|
client = boto3.client('secretsmanager')
|
|
|
|
try:
|
|
response = client.get_secret_value(SecretId=secret_name)
|
|
return json.loads(response['SecretString'])
|
|
except Exception as e:
|
|
print(f"Error retrieving secret: {e}")
|
|
raise
|
|
|
|
# Usage
|
|
db_creds = get_secret('prod/db/mysql')
|
|
connection = pymysql.connect(
|
|
host=db_creds['host'],
|
|
user=db_creds['username'],
|
|
password=db_creds['password'],
|
|
database=db_creds['dbname']
|
|
)
|
|
```
|
|
|
|
### Node.js SDK
|
|
|
|
```javascript
|
|
const AWS = require('aws-sdk');
|
|
const secretsManager = new AWS.SecretsManager();
|
|
|
|
async function getSecret(secretName) {
|
|
try {
|
|
const data = await secretsManager.getSecretValue({
|
|
SecretId: secretName
|
|
}).promise();
|
|
|
|
return JSON.parse(data.SecretString);
|
|
} catch (err) {
|
|
console.error('Error retrieving secret:', err);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const dbCreds = await getSecret('prod/db/mysql');
|
|
const connection = mysql.createConnection({
|
|
host: dbCreds.host,
|
|
user: dbCreds.username,
|
|
password: dbCreds.password,
|
|
database: dbCreds.dbname
|
|
});
|
|
```
|
|
|
|
## Rotation Best Practices
|
|
|
|
**Planning**
|
|
- [ ] Identify all secrets requiring rotation
|
|
- [ ] Define rotation schedules (30, 60, 90 days)
|
|
- [ ] Test rotation in non-production first
|
|
- [ ] Document rotation procedures
|
|
- [ ] Plan for emergency rotation
|
|
|
|
**Implementation**
|
|
- [ ] Use AWS managed rotation when possible
|
|
- [ ] Implement proper error handling
|
|
- [ ] Add CloudWatch monitoring
|
|
- [ ] Test application compatibility
|
|
- [ ] Implement gradual rollout
|
|
|
|
**Operations**
|
|
- [ ] Monitor rotation success/failure
|
|
- [ ] Set up alerts for failures
|
|
- [ ] Regular rotation audits
|
|
- [ ] Document troubleshooting steps
|
|
- [ ] Maintain rotation runbooks
|
|
|
|
## Emergency Rotation
|
|
|
|
```bash
|
|
# Immediate rotation (compromise detected)
|
|
aws secretsmanager rotate-secret \
|
|
--secret-id prod/db/mysql \
|
|
--rotate-immediately
|
|
|
|
# Force rotation even if recently rotated
|
|
aws secretsmanager rotate-secret \
|
|
--secret-id prod/api/stripe \
|
|
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:RotateStripeKey \
|
|
--rotate-immediately
|
|
|
|
# Verify rotation completed
|
|
aws secretsmanager describe-secret \
|
|
--secret-id prod/db/mysql \
|
|
--query 'LastRotatedDate'
|
|
```
|
|
|
|
## Compliance Tracking
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
# compliance-report.py
|
|
|
|
import boto3
|
|
from datetime import datetime, timedelta
|
|
|
|
client = boto3.client('secretsmanager')
|
|
|
|
def generate_compliance_report():
|
|
secrets = client.list_secrets()['SecretList']
|
|
|
|
compliant = []
|
|
non_compliant = []
|
|
|
|
for secret in secrets:
|
|
name = secret['Name']
|
|
rotation_enabled = secret.get('RotationEnabled', False)
|
|
last_rotated = secret.get('LastRotatedDate')
|
|
|
|
if not rotation_enabled:
|
|
non_compliant.append({
|
|
'name': name,
|
|
'issue': 'Rotation not enabled'
|
|
})
|
|
continue
|
|
|
|
if last_rotated:
|
|
days_ago = (datetime.now(last_rotated.tzinfo) - last_rotated).days
|
|
if days_ago > 90:
|
|
non_compliant.append({
|
|
'name': name,
|
|
'issue': f'Not rotated in {days_ago} days'
|
|
})
|
|
else:
|
|
compliant.append(name)
|
|
else:
|
|
non_compliant.append({
|
|
'name': name,
|
|
'issue': 'Never rotated'
|
|
})
|
|
|
|
print(f"Compliant Secrets: {len(compliant)}")
|
|
print(f"Non-Compliant Secrets: {len(non_compliant)}")
|
|
print("\nNon-Compliant Details:")
|
|
for item in non_compliant:
|
|
print(f" - {item['name']}: {item['issue']}")
|
|
|
|
if __name__ == "__main__":
|
|
generate_compliance_report()
|
|
```
|
|
|
|
## Example Prompts
|
|
|
|
- "Set up automatic rotation for my RDS credentials"
|
|
- "Create a Lambda function to rotate API keys"
|
|
- "Audit all secrets for rotation compliance"
|
|
- "Implement emergency rotation for compromised credentials"
|
|
- "Generate a secrets rotation report"
|
|
|
|
## Kiro CLI Integration
|
|
|
|
```bash
|
|
kiro-cli chat "Use aws-secrets-rotation to set up RDS credential rotation"
|
|
kiro-cli chat "Create a rotation audit report with aws-secrets-rotation"
|
|
```
|
|
|
|
## Additional Resources
|
|
|
|
- [AWS Secrets Manager Rotation](https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets.html)
|
|
- [Rotation Lambda Templates](https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas)
|
|
- [Best Practices for Secrets](https://docs.aws.amazon.com/secretsmanager/latest/userguide/best-practices.html)
|