Comprehensive Claude Code guidance system with: - 5 agents: tdd-guardian, code-reviewer, security-scanner, refactor-scan, dependency-audit - 18 skills covering languages (Python, TypeScript, Rust, Go, Java, C#), infrastructure (AWS, Azure, GCP, Terraform, Ansible, Docker/K8s, Database, CI/CD), testing (TDD, UI, Browser), and patterns (Monorepo, API Design, Observability) - 3 hooks: secret detection, auto-formatting, TDD git pre-commit - Strict TDD enforcement with 80%+ coverage requirements - Multi-model strategy: Opus for planning, Sonnet for execution (opusplan) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
424 lines
12 KiB
Markdown
424 lines
12 KiB
Markdown
---
|
|
name: aws-services
|
|
description: AWS service patterns, IAM best practices, and common architectures. Use when designing or implementing AWS infrastructure.
|
|
---
|
|
|
|
# AWS Services Skill
|
|
|
|
## Common Architecture Patterns
|
|
|
|
### Web Application (ECS + RDS)
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ VPC │
|
|
│ ┌─────────────────────────────────────────────────────────┐│
|
|
│ │ Public Subnets ││
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ││
|
|
│ │ │ ALB │ │ NAT GW │ ││
|
|
│ │ └──────┬──────┘ └──────┬──────┘ ││
|
|
│ └─────────┼───────────────────────────────────┼───────────┘│
|
|
│ │ │ │
|
|
│ ┌─────────┼───────────────────────────────────┼───────────┐│
|
|
│ │ │ Private Subnets │ ││
|
|
│ │ ┌──────▼──────┐ ┌───────▼─────┐ ││
|
|
│ │ │ ECS Fargate │ │ RDS │ ││
|
|
│ │ │ (Tasks) │───────────────────▶│ PostgreSQL │ ││
|
|
│ │ └─────────────┘ └─────────────┘ ││
|
|
│ └─────────────────────────────────────────────────────────┘│
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Serverless (Lambda + API Gateway)
|
|
```
|
|
┌────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
│ Route53 │────▶│ API Gateway │────▶│ Lambda │
|
|
└────────────┘ └─────────────┘ └──────┬──────┘
|
|
│
|
|
┌──────────────────────────┼──────────────┐
|
|
│ │ │
|
|
┌──────▼─────┐ ┌─────────┐ ┌────▼────┐
|
|
│ DynamoDB │ │ S3 │ │ Secrets │
|
|
└────────────┘ └─────────┘ │ Manager │
|
|
└─────────┘
|
|
```
|
|
|
|
## IAM Best Practices
|
|
|
|
### Least Privilege Policy
|
|
```json
|
|
{
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Sid": "AllowS3ReadSpecificBucket",
|
|
"Effect": "Allow",
|
|
"Action": [
|
|
"s3:GetObject",
|
|
"s3:ListBucket"
|
|
],
|
|
"Resource": [
|
|
"arn:aws:s3:::my-app-data-bucket",
|
|
"arn:aws:s3:::my-app-data-bucket/*"
|
|
]
|
|
},
|
|
{
|
|
"Sid": "AllowSecretsAccess",
|
|
"Effect": "Allow",
|
|
"Action": [
|
|
"secretsmanager:GetSecretValue"
|
|
],
|
|
"Resource": [
|
|
"arn:aws:secretsmanager:eu-west-2:123456789:secret:my-app/*"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Trust Policy (for ECS Tasks)
|
|
```json
|
|
{
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Effect": "Allow",
|
|
"Principal": {
|
|
"Service": "ecs-tasks.amazonaws.com"
|
|
},
|
|
"Action": "sts:AssumeRole"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Cross-Account Access
|
|
```json
|
|
{
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Effect": "Allow",
|
|
"Principal": {
|
|
"AWS": "arn:aws:iam::ACCOUNT_ID:role/CrossAccountRole"
|
|
},
|
|
"Action": "sts:AssumeRole",
|
|
"Condition": {
|
|
"StringEquals": {
|
|
"sts:ExternalId": "unique-external-id"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Secrets Management
|
|
|
|
### Using Secrets Manager
|
|
```python
|
|
# Python - boto3
|
|
import boto3
|
|
import json
|
|
|
|
def get_secret(secret_name: str, region: str = "eu-west-2") -> dict:
|
|
client = boto3.client("secretsmanager", region_name=region)
|
|
response = client.get_secret_value(SecretId=secret_name)
|
|
return json.loads(response["SecretString"])
|
|
|
|
# Usage
|
|
db_creds = get_secret("myapp/prod/database")
|
|
connection_string = f"postgresql://{db_creds['username']}:{db_creds['password']}@{db_creds['host']}/{db_creds['database']}"
|
|
```
|
|
|
|
```typescript
|
|
// TypeScript - AWS SDK v3
|
|
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
|
|
|
async function getSecret(secretName: string): Promise<Record<string, string>> {
|
|
const client = new SecretsManagerClient({ region: "eu-west-2" });
|
|
const command = new GetSecretValueCommand({ SecretId: secretName });
|
|
const response = await client.send(command);
|
|
|
|
if (!response.SecretString) {
|
|
throw new Error("Secret not found");
|
|
}
|
|
|
|
return JSON.parse(response.SecretString);
|
|
}
|
|
```
|
|
|
|
### ECS Task with Secrets
|
|
```json
|
|
// Task definition
|
|
{
|
|
"containerDefinitions": [
|
|
{
|
|
"name": "app",
|
|
"secrets": [
|
|
{
|
|
"name": "DATABASE_PASSWORD",
|
|
"valueFrom": "arn:aws:secretsmanager:eu-west-2:123456789:secret:myapp/database:password::"
|
|
},
|
|
{
|
|
"name": "API_KEY",
|
|
"valueFrom": "arn:aws:secretsmanager:eu-west-2:123456789:secret:myapp/api-key"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## S3 Patterns
|
|
|
|
### Bucket Policy (Least Privilege)
|
|
```json
|
|
{
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Sid": "AllowECSTaskAccess",
|
|
"Effect": "Allow",
|
|
"Principal": {
|
|
"AWS": "arn:aws:iam::123456789:role/ecs-task-role"
|
|
},
|
|
"Action": [
|
|
"s3:GetObject",
|
|
"s3:PutObject"
|
|
],
|
|
"Resource": "arn:aws:s3:::my-bucket/uploads/*"
|
|
},
|
|
{
|
|
"Sid": "DenyUnencryptedUploads",
|
|
"Effect": "Deny",
|
|
"Principal": "*",
|
|
"Action": "s3:PutObject",
|
|
"Resource": "arn:aws:s3:::my-bucket/*",
|
|
"Condition": {
|
|
"StringNotEquals": {
|
|
"s3:x-amz-server-side-encryption": "AES256"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Presigned URLs
|
|
```python
|
|
import boto3
|
|
from botocore.config import Config
|
|
|
|
def generate_presigned_url(bucket: str, key: str, expiration: int = 3600) -> str:
|
|
"""Generate a presigned URL for S3 object access."""
|
|
s3_client = boto3.client(
|
|
"s3",
|
|
config=Config(signature_version="s3v4"),
|
|
region_name="eu-west-2"
|
|
)
|
|
|
|
return s3_client.generate_presigned_url(
|
|
"get_object",
|
|
Params={"Bucket": bucket, "Key": key},
|
|
ExpiresIn=expiration
|
|
)
|
|
```
|
|
|
|
## DynamoDB Patterns
|
|
|
|
### Single Table Design
|
|
```python
|
|
# Entity types in same table
|
|
ENTITY_TYPES = {
|
|
"USER": {"PK": "USER#", "SK": "PROFILE"},
|
|
"ORDER": {"PK": "USER#", "SK": "ORDER#"},
|
|
"PRODUCT": {"PK": "PRODUCT#", "SK": "DETAILS"},
|
|
}
|
|
|
|
# Access patterns
|
|
def get_user(user_id: str) -> dict:
|
|
return table.get_item(
|
|
Key={"PK": f"USER#{user_id}", "SK": "PROFILE"}
|
|
)["Item"]
|
|
|
|
def get_user_orders(user_id: str) -> list:
|
|
response = table.query(
|
|
KeyConditionExpression="PK = :pk AND begins_with(SK, :sk)",
|
|
ExpressionAttributeValues={
|
|
":pk": f"USER#{user_id}",
|
|
":sk": "ORDER#"
|
|
}
|
|
)
|
|
return response["Items"]
|
|
```
|
|
|
|
## Lambda Patterns
|
|
|
|
### Handler with Error Handling
|
|
```python
|
|
import json
|
|
import logging
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger()
|
|
logger.setLevel(logging.INFO)
|
|
|
|
def handler(event: dict, context: Any) -> dict:
|
|
"""Lambda handler with proper error handling."""
|
|
try:
|
|
logger.info("Processing event", extra={"event": event})
|
|
|
|
# Process request
|
|
body = json.loads(event.get("body", "{}"))
|
|
result = process_request(body)
|
|
|
|
return {
|
|
"statusCode": 200,
|
|
"headers": {"Content-Type": "application/json"},
|
|
"body": json.dumps(result)
|
|
}
|
|
|
|
except ValidationError as e:
|
|
logger.warning("Validation error", extra={"error": str(e)})
|
|
return {
|
|
"statusCode": 400,
|
|
"body": json.dumps({"error": str(e)})
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.exception("Unexpected error")
|
|
return {
|
|
"statusCode": 500,
|
|
"body": json.dumps({"error": "Internal server error"})
|
|
}
|
|
```
|
|
|
|
### Cold Start Optimization
|
|
```python
|
|
# Initialize outside handler (runs once per container)
|
|
import boto3
|
|
|
|
# These persist across invocations
|
|
dynamodb = boto3.resource("dynamodb")
|
|
table = dynamodb.Table("my-table")
|
|
secrets_client = boto3.client("secretsmanager")
|
|
|
|
# Cache secrets
|
|
_cached_secrets = {}
|
|
|
|
def get_cached_secret(name: str) -> dict:
|
|
if name not in _cached_secrets:
|
|
response = secrets_client.get_secret_value(SecretId=name)
|
|
_cached_secrets[name] = json.loads(response["SecretString"])
|
|
return _cached_secrets[name]
|
|
|
|
def handler(event, context):
|
|
# Use cached resources
|
|
secret = get_cached_secret("my-secret")
|
|
# ...
|
|
```
|
|
|
|
## CloudWatch Patterns
|
|
|
|
### Structured Logging
|
|
```python
|
|
import json
|
|
import logging
|
|
|
|
class JsonFormatter(logging.Formatter):
|
|
def format(self, record):
|
|
log_record = {
|
|
"timestamp": self.formatTime(record),
|
|
"level": record.levelname,
|
|
"message": record.getMessage(),
|
|
"logger": record.name,
|
|
}
|
|
|
|
# Add extra fields
|
|
if hasattr(record, "extra"):
|
|
log_record.update(record.extra)
|
|
|
|
return json.dumps(log_record)
|
|
|
|
# Setup
|
|
logger = logging.getLogger()
|
|
handler = logging.StreamHandler()
|
|
handler.setFormatter(JsonFormatter())
|
|
logger.addHandler(handler)
|
|
|
|
# Usage
|
|
logger.info("User created", extra={"user_id": "123", "email": "user@example.com"})
|
|
```
|
|
|
|
### Custom Metrics
|
|
```python
|
|
import boto3
|
|
|
|
cloudwatch = boto3.client("cloudwatch")
|
|
|
|
def publish_metric(name: str, value: float, unit: str = "Count"):
|
|
cloudwatch.put_metric_data(
|
|
Namespace="MyApp",
|
|
MetricData=[
|
|
{
|
|
"MetricName": name,
|
|
"Value": value,
|
|
"Unit": unit,
|
|
"Dimensions": [
|
|
{"Name": "Environment", "Value": "prod"},
|
|
{"Name": "Service", "Value": "api"},
|
|
]
|
|
}
|
|
]
|
|
)
|
|
|
|
# Usage
|
|
publish_metric("OrdersProcessed", 1)
|
|
publish_metric("ProcessingTime", 150, "Milliseconds")
|
|
```
|
|
|
|
## CLI Commands
|
|
|
|
```bash
|
|
# IAM
|
|
aws iam get-role --role-name MyRole
|
|
aws iam list-attached-role-policies --role-name MyRole
|
|
aws sts get-caller-identity
|
|
|
|
# S3
|
|
aws s3 ls s3://my-bucket/
|
|
aws s3 cp file.txt s3://my-bucket/
|
|
aws s3 presign s3://my-bucket/file.txt --expires-in 3600
|
|
|
|
# Secrets Manager
|
|
aws secretsmanager get-secret-value --secret-id my-secret
|
|
aws secretsmanager list-secrets
|
|
|
|
# ECS
|
|
aws ecs list-clusters
|
|
aws ecs describe-services --cluster my-cluster --services my-service
|
|
aws ecs update-service --cluster my-cluster --service my-service --force-new-deployment
|
|
|
|
# Lambda
|
|
aws lambda invoke --function-name my-function output.json
|
|
aws lambda list-functions
|
|
aws logs tail /aws/lambda/my-function --follow
|
|
|
|
# CloudWatch
|
|
aws logs filter-log-events --log-group-name /aws/lambda/my-function --filter-pattern "ERROR"
|
|
```
|
|
|
|
## Security Checklist
|
|
|
|
- [ ] All S3 buckets have versioning enabled
|
|
- [ ] All S3 buckets block public access (unless explicitly needed)
|
|
- [ ] Encryption at rest enabled for all data stores
|
|
- [ ] Encryption in transit (TLS) for all connections
|
|
- [ ] IAM roles use least privilege
|
|
- [ ] No long-term credentials (use IAM roles/instance profiles)
|
|
- [ ] Secrets in Secrets Manager (not env vars or code)
|
|
- [ ] VPC endpoints for AWS services (avoid public internet)
|
|
- [ ] Security groups follow principle of least privilege
|
|
- [ ] CloudTrail enabled for auditing
|
|
- [ ] GuardDuty enabled for threat detection
|