aws-secrets-manager · bagelhole — Claude Code skill: AWS secrets and rotation
A skill for AWS Secrets Manager: create secrets, set up automatic rotation and wire them into applications.
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill aws-secrets-manager -a claude-codeWhat aws-secrets-manager does
The skill walks through the whole secret lifecycle in AWS: creating a secret with aws secretsmanager create-secret, reading its value, updating it, deleting it with a recovery window and tagging. A separate section covers automatic rotation — enabling it with rotate-secret, checking status and forcing an immediate change.
It ships a Python Lambda example for custom database password rotation. The function runs four steps: generate a new value, apply it to the target database with psycopg2, test it, then mark the version current. You need AWS CLI v2, an IAM policy allowing secretsmanager:* and Python 3.9+ with boto3. Rotation also expects a Lambda execution role and network access to the target services.
That secretsmanager:* policy grants access to every secret in the account, so scope it down to the resources you actually use before handing it out. The skill fits teams moving hardcoded passwords and keys into managed storage, and those wiring secrets into ECS, EKS or Lambda workloads.
What it helps with
- create a secret with a JSON structure and tags
- enable automatic rotation for an RDS password
- strip hardcoded keys out of config files
- inject secrets into ECS, EKS or Lambda workloads
- restore a deleted secret within the recovery window
Example prompts for the agent
- create a secret myapp/production/database with postgres credentials
- enable rotation for this secret every 30 days with a Lambda function
- show the rotation status and last rotated date for the secret
How to install aws-secrets-manager
1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill aws-secrets-manager -a claude-code2Manually: put the files in place
~/.claude/skills/aws-secrets-manager/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/aws-secrets-manager/
1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill aws-secrets-manager -a codex2Manually: put the files in place
.agents/skills/aws-secrets-manager/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill aws-secrets-manager -a cursor2Manually: put the files in place
.cursor/skills/aws-secrets-manager/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install BagelHole/DevOps-Security-Agent-Skills aws-secrets-manager2Manually: put the files in place
.github/skills/aws-secrets-manager/SKILL.md1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill aws-secrets-manager -a gemini-cli2Platform CLI
gemini skills install https://github.com/BagelHole/DevOps-Security-Agent-Skills --path security/secrets/aws-secrets-managerManually: put the files in place
.devin/skills/aws-secrets-manager/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill aws-secrets-manager -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about aws-secrets-manager
How do I install aws-secrets-manager in Claude?
Run npx skills add BagelHole/DevOps-Security-Agent-Skills --skill aws-secrets-manager -a claude-code. The skill file then lands at ~/.claude/skills/aws-secrets-manager/SKILL.md.
What does aws-secrets-manager do?
It covers storing, reading and rotating secrets in AWS Secrets Manager, including a Lambda example for database password rotation and access policy setup.
What do I need before using it?
An AWS account with IAM permissions for secretsmanager actions and AWS CLI v2 installed and configured. SDK examples need Python 3.9+ with boto3; rotation needs a Lambda execution role and access to the target services.
Source text
SKILL.md · MIT
---
name: aws-secrets-manager
description: Store and rotate secrets in AWS Secrets Manager. Configure automatic rotation, access policies, and application integration. Use when managing secrets in AWS environments or requiring automatic credential rotation.
license: MIT
metadata:
author: devops-skills
version: "1.0"
---
# AWS Secrets Manager
Securely store, manage, and rotate secrets in AWS.
## When to Use This Skill
Use this skill when:
- Storing database credentials, API keys, or tokens in AWS
- Implementing automatic credential rotation for RDS or other services
- Replacing hardcoded secrets in application code or config files
- Integrating secrets into ECS, EKS, or Lambda workloads
- Meeting compliance requirements for secret management and rotation
## Prerequisites
- AWS account with appropriate IAM permissions
- AWS CLI v2 installed and configured
- IAM policy allowing `secretsmanager:*` actions (or scoped permissions)
- For rotation: Lambda execution role and VPC access to target services
- Python 3.9+ with `boto3` for SDK examples
## Secret Creation and Management
```bash
# Create a secret with JSON structure
aws secretsmanager create-secret \
--name myapp/production/database \
--description "Production database credentials" \
--secret-string '{"username":"dbadmin","password":"S3cur3P@ssw0rd!","engine":"postgres","host":"db.internal.example.com","port":5432,"dbname":"myapp"}' \
--tags '[{"Key":"Environment","Value":"production"},{"Key":"Team","Value":"platform"}]'
# Create a secret with KMS encryption (custom key)
aws secretsmanager create-secret \
--name myapp/production/api-key \
--description "Third-party API key" \
--secret-string "ak_live_xxxxxxxxxxxx" \
--kms-key-id alias/secrets-key
# Create a binary secret (certificates, keys)
aws secretsmanager create-secret \
--name myapp/production/tls-cert \
--secret-binary fileb://server.pfx
# Get secret value
aws secretsmanager get-secret-value \
--secret-id myapp/production/database \
--query 'SecretString' --output text | jq .
# Get a specific version
aws secretsmanager get-secret-value \
--secret-id myapp/production/database \
--version-stage AWSPREVIOUS
# Update secret value
aws secretsmanager put-secret-value \
--secret-id myapp/production/database \
--secret-string '{"username":"dbadmin","password":"N3wS3cur3P@ss!","engine":"postgres","host":"db.internal.example.com","port":5432,"dbname":"myapp"}'
# List all secrets
aws secretsmanager list-secrets \
--filters Key=name,Values=myapp/production
# Delete secret (with recovery window)
aws secretsmanager delete-secret \
--secret-id myapp/production/old-key \
--recovery-window-in-days 7
# Restore a deleted secret
aws secretsmanager restore-secret \
--secret-id myapp/production/old-key
# Tag a secret
aws secretsmanager tag-resource \
--secret-id myapp/production/database \
--tags '[{"Key":"RotationEnabled","Value":"true"}]'
```
## Automatic Rotation
### Enable Rotation
```bash
# Enable rotation with an existing Lambda function
aws secretsmanager rotate-secret \
--secret-id myapp/production/database \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSPostgreSQLRotation \
--rotation-rules '{"AutomaticallyAfterDays":30,"ScheduleExpression":"rate(30 days)"}'
# Trigger immediate rotation
aws secretsmanager rotate-secret \
--secret-id myapp/production/database
# Check rotation status
aws secretsmanager describe-secret \
--secret-id myapp/production/database \
--query '{RotationEnabled:RotationEnabled,RotationLambdaARN:RotationLambdaARN,RotationRules:RotationRules,LastRotatedDate:LastRotatedDate}'
```
### Lambda Rotation Function
```python
"""rotation_function.py - Custom rotation Lambda for database credentials."""
import boto3
import json
import logging
import psycopg2
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
"""Secrets Manager rotation handler.
The rotation process has four steps:
1. createSecret - Generate new secret value
2. setSecret - Apply the new secret to the target service
3. testSecret - Verify the new secret works
4. finishSecret - Mark rotation complete
"""
secret_arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
client = boto3.client('secretsmanager')
metadata = client.describe_secret(SecretId=secret_arn)
if not metadata.get('RotationEnabled'):
raise ValueError(f"Secret {secret_arn} does not have rotation enabled")
versions = metadata.get('VersionIdsToStages', {})
if token not in versions:
raise ValueError(f"Secret version {token} has no stage for rotation")
if step == "createSecret":
create_secret(client, secret_arn, token)
elif step == "setSecret":
set_secret(client, secret_arn, token)
elif step == "testSecret":
test_secret(client, secret_arn, token)
elif step == "finishSecret":
finish_secret(client, secret_arn, token)
else:
raise ValueError(f"Invalid step: {step}")
def create_secret(client, secret_arn, token):
"""Generate a new secret value."""
current = client.get_secret_value(
SecretId=secret_arn, VersionStage="AWSCURRENT"
)
current_dict = json.loads(current['SecretString'])
new_password = client.get_random_password(
PasswordLength=32,
ExcludeCharacters='/@"\\',
RequireEachIncludedType=True,
)['RandomPassword']
current_dict['password'] = new_password
client.put_secret_value(
SecretId=secret_arn,
ClientRequestToken=token,
SecretString=json.dumps(current_dict),
VersionStages=['AWSPENDING'],
)
logger.info(f"createSecret: New secret version created for {secret_arn}")
def set_secret(client, secret_arn, token):
"""Apply the new secret to the target database."""
pending = client.get_secret_value(
SecretId=secret_arn, VersionId=token, VersionStage="AWSPENDING"
)
pending_dict = json.loads(pending['SecretString'])
current = client.get_secret_value(
SecretId=secret_arn, VersionStage="AWSCURRENT"
)
current_dict = json.loads(current['SecretString'])
conn = psycopg2.connect(
host=current_dict['host'],
port=current_dict.get('port', 5432),
user=current_dict['username'],
password=current_dict['password'],
dbname=current_dict.get('dbname', 'postgres'),
)
conn.autocommit = True
with conn.cursor() as cur:
cur.execute(
"ALTER USER %s WITH PASSWORD %s",
(pending_dict['username'], pending_dict['password']),
)
conn.close()
logger.info(f"setSecret: Password updated in database for {secret_arn}")
def test_secret(client, secret_arn, token):
"""Verify the new secret works."""
pending = client.get_secret_value(
SecretId=secret_arn, VersionId=token, VersionStage="AWSPENDING"
)
pending_dict = json.loads(pending['SecretString'])
conn = psycopg2.connect(
host=pending_dict['host'],
port=pending_dict.get('port', 5432),
user=pending_dict['username'],
password=pending_dict['password'],
dbname=pending_dict.get('dbname', 'postgres'),
)
conn.close()
logger.info(f"testSecret: New credentials verified for {secret_arn}")
def finish_secret(client, secret_arn, token):
"""Finalize the rotation by updating version stages."""
metadata = client.describe_secret(SecretId=secret_arn)
versions = metadata.get('VersionIdsToStages', {})
current_version = None
for version_id, stages in versions.items():
if "AWSCURRENT" in stages:
if version_id == token:
logger.info("finishSecret: Version already marked AWSCURRENT")
return
current_version = version_id
break
client.update_secret_version_stage(
SecretId=secret_arn,
VersionStage="AWSCURRENT",
MoveToVersionId=token,
RemoveFromVersionId=current_version,
)
logger.info(f"finishSecret: Rotation complete for {secret_arn}")
```
### Rotation Lambda Terraform
```hcl
resource "aws_lambda_function" "rotation" {
filename = "rotation_function.zip"
function_name = "secrets-rotation-postgresql"
role = aws_iam_role.rotation.arn
handler = "rotation_function.lambda_handler"
runtime = "python3.11"
timeout = 60
vpc_config {
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.rotation.id]
}
environment {
variables = {
SECRETS_MANAGER_ENDPOINT = "https://secretsmanager.${var.region}.amazonaws.com"
}
}
}
resource "aws_lambda_permission" "secrets_manager" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.rotation.function_name
principal = "secretsmanager.amazonaws.com"
statement_id = "AllowSecretsManager"
}
resource "aws_secretsmanager_secret_rotation" "db" {
secret_id = aws_secretsmanager_secret.db.id
rotation_lambda_arn = aws_lambda_function.rotation.arn
rotation_rules {
automatically_after_days = 30
}
}
```
## Application Integration
### Python SDK
```python
import boto3
import json
from functools import lru_cache
def get_secret(secret_name: str, region: str = "us-east-1") -> dict:
"""Retrieve and parse a secret from AWS Secrets Manager."""
client = boto3.client("secretsmanager", region_name=region)
response = client.get_secret_value(SecretId=secret_name)
if "SecretString" in response:
return json.loads(response["SecretString"])
else:
import base64
return base64.b64decode(response["SecretBinary"])
@lru_cache(maxsize=32)
def get_cached_secret(secret_name: str) -> dict:
"""Cached secret retrieval. Clear cache on rotation events."""
return get_secret(secret_name)
# Usage
creds = get_secret("myapp/production/database")
connection_string = (
f"postgresql://{creds['username']}:{creds['password']}"
f"@{creds['host']}:{creds['port']}/{creds['dbname']}"
)
```
### ECS Task Definition
```json
{
"containerDefinitions": [
{
"name": "myapp",
"image": "ghcr.io/acme/myapp:v1.0.0",
"secrets": [
{
"name": "DB_USERNAME",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/production/database:username::"
},
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/production/database:password::"
},
{
"name": "API_KEY",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/production/api-key"
}
]
}
],
"executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole"
}
```
### EKS with External Secrets Operator
```yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: myapp/production/database
property: username
- secretKey: password
remoteRef:
key: myapp/production/database
property: password
```
## Resource-Based Policy
```bash
# Restrict secret access to specific roles
aws secretsmanager put-resource-policy \
--secret-id myapp/production/database \
--resource-policy '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789:role/myapp-ecs-task-role",
"arn:aws:iam::123456789:role/myapp-lambda-role"
]
},
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "secretsmanager:GetSecretValue",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalAccount": "123456789012"
}
}
}
]
}'
```
## Troubleshooting
| Problem | Cause | Solution |
|---------|-------|----------|
| `AccessDeniedException` on GetSecretValue | IAM policy missing permission | Add `secretsmanager:GetSecretValue` to the role; check resource-based policy |
| Rotation fails with Lambda timeout | Lambda cannot reach database | Ensure Lambda is in same VPC with route to DB; check security groups |
| Secret value is empty after rotation | createSecret step failed | Check Lambda CloudWatch logs; verify random password generation works |
| ECS container fails to start | Secret ARN format incorrect | Use full ARN with `::` for JSON key extraction; verify secret exists |
| Application uses old credentials after rotation | Client caching stale values | Implement cache invalidation on rotation; reduce cache TTL |
| Rotation Lambda permission error | Missing `lambda:InvokeFunction` permission | Add `aws_lambda_permission` for secretsmanager.amazonaws.com principal |
| KMS decrypt fails | Secret KMS key policy missing role | Add the accessing role to the KMS key policy's `kms:Decrypt` principals |
## Best Practices
- Enable automatic rotation with 30-day intervals minimum
- Use resource-based policies in addition to IAM policies (defense in depth)
- Encrypt secrets with customer-managed KMS keys (not default)
- Implement least-privilege access (only the roles that need each secret)
- Use secret versioning for safe rollback during rotation issues
- Monitor secret access with CloudTrail and alert on unusual patterns
- Structure secret names hierarchically: `{app}/{env}/{secret-type}`
- Never log secret values; log only secret ARNs and access metadata
- Test rotation in staging before enabling in production
- Set up CloudWatch alarms for rotation failures
## Related Skills
- [hashicorp-vault](../hashicorp-vault/) - Multi-cloud secrets
- [aws-iam](../../../infrastructure/cloud-aws/aws-iam/) - IAM policies
- [azure-keyvault](../azure-keyvault/) - Azure secret management
- [gcp-secret-manager](../gcp-secret-manager/) - GCP secret management
© devops-skills · MIT · BagelHole/DevOps-Security-Agent-Skills
Similar skills
Everything for Security →aws-secrets-manager by other authors comes first.