Initial commit
This commit is contained in:
625
skills/aws-serverless-eda/references/security-best-practices.md
Normal file
625
skills/aws-serverless-eda/references/security-best-practices.md
Normal file
@@ -0,0 +1,625 @@
|
||||
# Serverless Security Best Practices
|
||||
|
||||
Security best practices for serverless applications based on AWS Well-Architected Framework.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Shared Responsibility Model](#shared-responsibility-model)
|
||||
- [Identity and Access Management](#identity-and-access-management)
|
||||
- [Function Security](#function-security)
|
||||
- [API Security](#api-security)
|
||||
- [Data Protection](#data-protection)
|
||||
- [Network Security](#network-security)
|
||||
|
||||
## Shared Responsibility Model
|
||||
|
||||
### Serverless Shifts Responsibility to AWS
|
||||
|
||||
With serverless, AWS takes on more security responsibilities:
|
||||
|
||||
**AWS Responsibilities**:
|
||||
- Compute infrastructure
|
||||
- Execution environment
|
||||
- Runtime language and patches
|
||||
- Networking infrastructure
|
||||
- Server software and OS
|
||||
- Physical hardware and facilities
|
||||
- Automatic security patches (like Log4Shell mitigation)
|
||||
|
||||
**Customer Responsibilities**:
|
||||
- Function code and dependencies
|
||||
- Resource configuration
|
||||
- Identity and Access Management (IAM)
|
||||
- Data encryption (at rest and in transit)
|
||||
- Application-level security
|
||||
- Secure coding practices
|
||||
|
||||
### Benefits of Shifted Responsibility
|
||||
|
||||
- **Automatic Patching**: AWS applies security patches automatically (e.g., Log4Shell fixed within 3 days)
|
||||
- **Infrastructure Security**: No OS patching, server hardening, or vulnerability scanning
|
||||
- **Operational Agility**: Quick security response at scale
|
||||
- **Focus on Code**: Spend time on business logic, not infrastructure security
|
||||
|
||||
## Identity and Access Management
|
||||
|
||||
### Least Privilege Principle
|
||||
|
||||
**Always use least privilege IAM policies**:
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD - Specific grant
|
||||
const table = new dynamodb.Table(this, 'Table', {});
|
||||
const function = new lambda.Function(this, 'Function', {});
|
||||
|
||||
table.grantReadData(function); // Only read access
|
||||
|
||||
// ❌ BAD - Overly broad
|
||||
function.addToRolePolicy(new iam.PolicyStatement({
|
||||
actions: ['dynamodb:*'],
|
||||
resources: ['*'],
|
||||
}));
|
||||
```
|
||||
|
||||
### Function Execution Role
|
||||
|
||||
**Separate roles per function**:
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD - Each function has its own role
|
||||
const readFunction = new NodejsFunction(this, 'ReadFunction', {
|
||||
entry: 'src/read.ts',
|
||||
// Gets its own execution role
|
||||
});
|
||||
|
||||
const writeFunction = new NodejsFunction(this, 'WriteFunction', {
|
||||
entry: 'src/write.ts',
|
||||
// Gets its own execution role
|
||||
});
|
||||
|
||||
table.grantReadData(readFunction);
|
||||
table.grantReadWriteData(writeFunction);
|
||||
|
||||
// ❌ BAD - Shared role with excessive permissions
|
||||
const sharedRole = new iam.Role(this, 'SharedRole', {
|
||||
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
||||
managedPolicies: [
|
||||
iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'), // Too broad!
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Resource-Based Policies
|
||||
|
||||
Control who can invoke functions:
|
||||
|
||||
```typescript
|
||||
// Allow API Gateway to invoke function
|
||||
myFunction.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com'));
|
||||
|
||||
// Allow specific account
|
||||
myFunction.addPermission('AllowAccountInvoke', {
|
||||
principal: new iam.AccountPrincipal('123456789012'),
|
||||
action: 'lambda:InvokeFunction',
|
||||
});
|
||||
|
||||
// Conditional invoke (only from specific VPC endpoint)
|
||||
myFunction.addPermission('AllowVPCInvoke', {
|
||||
principal: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
||||
action: 'lambda:InvokeFunction',
|
||||
sourceArn: vpcEndpoint.vpcEndpointId,
|
||||
});
|
||||
```
|
||||
|
||||
### IAM Policies Best Practices
|
||||
|
||||
1. **Use grant methods**: Prefer `.grantXxx()` over manual policies
|
||||
2. **Condition keys**: Use IAM conditions for fine-grained control
|
||||
3. **Resource ARNs**: Always specify resource ARNs, avoid wildcards
|
||||
4. **Session policies**: Use for temporary elevated permissions
|
||||
5. **Service Control Policies (SCPs)**: Enforce organization-wide guardrails
|
||||
|
||||
## Function Security
|
||||
|
||||
### Lambda Isolation Model
|
||||
|
||||
**Each function runs in isolated sandbox**:
|
||||
- Built on Firecracker microVMs
|
||||
- Dedicated execution environment per function
|
||||
- No shared memory between functions
|
||||
- Isolated file system and network namespace
|
||||
- Strong workload isolation
|
||||
|
||||
**Execution Environment Security**:
|
||||
- One concurrent invocation per environment
|
||||
- Environment may be reused (warm starts)
|
||||
- `/tmp` storage persists between invocations
|
||||
- Sensitive data in memory may persist
|
||||
|
||||
### Secure Coding Practices
|
||||
|
||||
**Handle sensitive data securely**:
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD - Clean up sensitive data
|
||||
export const handler = async (event: any) => {
|
||||
const apiKey = process.env.API_KEY;
|
||||
|
||||
try {
|
||||
const result = await callApi(apiKey);
|
||||
return result;
|
||||
} finally {
|
||||
// Clear sensitive data from memory
|
||||
delete process.env.API_KEY;
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ GOOD - Use Secrets Manager
|
||||
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
|
||||
|
||||
const secretsClient = new SecretsManagerClient({});
|
||||
|
||||
export const handler = async (event: any) => {
|
||||
const secret = await secretsClient.send(
|
||||
new GetSecretValueCommand({ SecretId: process.env.SECRET_ARN })
|
||||
);
|
||||
|
||||
const apiKey = secret.SecretString;
|
||||
// Use apiKey
|
||||
};
|
||||
```
|
||||
|
||||
### Dependency Management
|
||||
|
||||
**Scan dependencies for vulnerabilities**:
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"scripts": {
|
||||
"audit": "npm audit",
|
||||
"audit:fix": "npm audit fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"snyk": "^1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Keep dependencies updated**:
|
||||
- Run `npm audit` or `pip-audit` regularly
|
||||
- Use Dependabot or Snyk for automated scanning
|
||||
- Update dependencies promptly when vulnerabilities found
|
||||
- Use minimal dependency sets
|
||||
|
||||
### Environment Variable Security
|
||||
|
||||
**Never store secrets in environment variables**:
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - Secret in environment variable
|
||||
new NodejsFunction(this, 'Function', {
|
||||
environment: {
|
||||
API_KEY: 'sk-1234567890abcdef', // Never do this!
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ GOOD - Reference to secret
|
||||
new NodejsFunction(this, 'Function', {
|
||||
environment: {
|
||||
SECRET_ARN: secret.secretArn,
|
||||
},
|
||||
});
|
||||
|
||||
secret.grantRead(myFunction);
|
||||
```
|
||||
|
||||
## API Security
|
||||
|
||||
### API Gateway Security
|
||||
|
||||
**Authentication and Authorization**:
|
||||
|
||||
```typescript
|
||||
// Cognito User Pool authorizer
|
||||
const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'Authorizer', {
|
||||
cognitoUserPools: [userPool],
|
||||
});
|
||||
|
||||
api.root.addMethod('GET', integration, {
|
||||
authorizer,
|
||||
authorizationType: apigateway.AuthorizationType.COGNITO,
|
||||
});
|
||||
|
||||
// Lambda authorizer for custom auth
|
||||
const customAuthorizer = new apigateway.TokenAuthorizer(this, 'CustomAuth', {
|
||||
handler: authorizerFunction,
|
||||
resultsCacheTtl: Duration.minutes(5),
|
||||
});
|
||||
|
||||
// IAM authorization for service-to-service
|
||||
api.root.addMethod('POST', integration, {
|
||||
authorizationType: apigateway.AuthorizationType.IAM,
|
||||
});
|
||||
```
|
||||
|
||||
### Request Validation
|
||||
|
||||
**Validate requests at API Gateway**:
|
||||
|
||||
```typescript
|
||||
const validator = new apigateway.RequestValidator(this, 'Validator', {
|
||||
api,
|
||||
validateRequestBody: true,
|
||||
validateRequestParameters: true,
|
||||
});
|
||||
|
||||
const model = api.addModel('Model', {
|
||||
schema: {
|
||||
type: apigateway.JsonSchemaType.OBJECT,
|
||||
required: ['email', 'name'],
|
||||
properties: {
|
||||
email: {
|
||||
type: apigateway.JsonSchemaType.STRING,
|
||||
format: 'email',
|
||||
},
|
||||
name: {
|
||||
type: apigateway.JsonSchemaType.STRING,
|
||||
minLength: 1,
|
||||
maxLength: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
resource.addMethod('POST', integration, {
|
||||
requestValidator: validator,
|
||||
requestModels: {
|
||||
'application/json': model,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Rate Limiting and Throttling
|
||||
|
||||
```typescript
|
||||
const api = new apigateway.RestApi(this, 'Api', {
|
||||
deployOptions: {
|
||||
throttlingRateLimit: 1000, // requests per second
|
||||
throttlingBurstLimit: 2000, // burst capacity
|
||||
},
|
||||
});
|
||||
|
||||
// Per-method throttling
|
||||
resource.addMethod('POST', integration, {
|
||||
methodResponses: [{ statusCode: '200' }],
|
||||
requestParameters: {
|
||||
'method.request.header.Authorization': true,
|
||||
},
|
||||
throttling: {
|
||||
rateLimit: 100,
|
||||
burstLimit: 200,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### API Keys and Usage Plans
|
||||
|
||||
```typescript
|
||||
const apiKey = api.addApiKey('ApiKey', {
|
||||
apiKeyName: 'customer-key',
|
||||
});
|
||||
|
||||
const plan = api.addUsagePlan('UsagePlan', {
|
||||
name: 'Standard',
|
||||
throttle: {
|
||||
rateLimit: 100,
|
||||
burstLimit: 200,
|
||||
},
|
||||
quota: {
|
||||
limit: 10000,
|
||||
period: apigateway.Period.MONTH,
|
||||
},
|
||||
});
|
||||
|
||||
plan.addApiKey(apiKey);
|
||||
plan.addApiStage({
|
||||
stage: api.deploymentStage,
|
||||
});
|
||||
```
|
||||
|
||||
## Data Protection
|
||||
|
||||
### Encryption at Rest
|
||||
|
||||
**DynamoDB encryption**:
|
||||
|
||||
```typescript
|
||||
// Default: AWS-owned CMK (no additional cost)
|
||||
const table = new dynamodb.Table(this, 'Table', {
|
||||
encryption: dynamodb.TableEncryption.AWS_MANAGED, // AWS managed CMK
|
||||
});
|
||||
|
||||
// Customer-managed CMK (for compliance)
|
||||
const kmsKey = new kms.Key(this, 'Key', {
|
||||
enableKeyRotation: true,
|
||||
});
|
||||
|
||||
const table = new dynamodb.Table(this, 'Table', {
|
||||
encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
|
||||
encryptionKey: kmsKey,
|
||||
});
|
||||
```
|
||||
|
||||
**S3 encryption**:
|
||||
|
||||
```typescript
|
||||
// SSE-S3 (default, no additional cost)
|
||||
const bucket = new s3.Bucket(this, 'Bucket', {
|
||||
encryption: s3.BucketEncryption.S3_MANAGED,
|
||||
});
|
||||
|
||||
// SSE-KMS (for fine-grained access control)
|
||||
const bucket = new s3.Bucket(this, 'Bucket', {
|
||||
encryption: s3.BucketEncryption.KMS,
|
||||
encryptionKey: kmsKey,
|
||||
});
|
||||
```
|
||||
|
||||
**SQS/SNS encryption**:
|
||||
|
||||
```typescript
|
||||
const queue = new sqs.Queue(this, 'Queue', {
|
||||
encryption: sqs.QueueEncryption.KMS,
|
||||
encryptionMasterKey: kmsKey,
|
||||
});
|
||||
|
||||
const topic = new sns.Topic(this, 'Topic', {
|
||||
masterKey: kmsKey,
|
||||
});
|
||||
```
|
||||
|
||||
### Encryption in Transit
|
||||
|
||||
**All AWS service APIs use TLS**:
|
||||
- API Gateway endpoints use HTTPS by default
|
||||
- Lambda to AWS service communication encrypted
|
||||
- EventBridge, SQS, SNS use TLS
|
||||
- Custom domains can use ACM certificates
|
||||
|
||||
```typescript
|
||||
// API Gateway with custom domain
|
||||
const certificate = new acm.Certificate(this, 'Certificate', {
|
||||
domainName: 'api.example.com',
|
||||
validation: acm.CertificateValidation.fromDns(hostedZone),
|
||||
});
|
||||
|
||||
const api = new apigateway.RestApi(this, 'Api', {
|
||||
domainName: {
|
||||
domainName: 'api.example.com',
|
||||
certificate,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Data Sanitization
|
||||
|
||||
**Validate and sanitize inputs**:
|
||||
|
||||
```typescript
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Schema validation
|
||||
const OrderSchema = z.object({
|
||||
orderId: z.string().uuid(),
|
||||
amount: z.number().positive(),
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export const handler = async (event: any) => {
|
||||
const body = JSON.parse(event.body);
|
||||
|
||||
// Validate schema
|
||||
const result = OrderSchema.safeParse(body);
|
||||
if (!result.success) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: result.error }),
|
||||
};
|
||||
}
|
||||
|
||||
// Sanitize HTML inputs
|
||||
const sanitized = {
|
||||
...result.data,
|
||||
description: DOMPurify.sanitize(result.data.description),
|
||||
};
|
||||
|
||||
await processOrder(sanitized);
|
||||
};
|
||||
```
|
||||
|
||||
## Network Security
|
||||
|
||||
### VPC Configuration
|
||||
|
||||
**Lambda in VPC for private resources**:
|
||||
|
||||
```typescript
|
||||
const vpc = new ec2.Vpc(this, 'Vpc', {
|
||||
maxAzs: 2,
|
||||
natGateways: 1,
|
||||
});
|
||||
|
||||
// Lambda in private subnet
|
||||
const vpcFunction = new NodejsFunction(this, 'VpcFunction', {
|
||||
entry: 'src/handler.ts',
|
||||
vpc,
|
||||
vpcSubnets: {
|
||||
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
|
||||
},
|
||||
securityGroups: [securityGroup],
|
||||
});
|
||||
|
||||
// Security group for Lambda
|
||||
const securityGroup = new ec2.SecurityGroup(this, 'LambdaSG', {
|
||||
vpc,
|
||||
description: 'Security group for Lambda function',
|
||||
allowAllOutbound: false, // Restrict outbound
|
||||
});
|
||||
|
||||
// Only allow access to RDS
|
||||
securityGroup.addEgressRule(
|
||||
ec2.Peer.securityGroupId(rdsSecurityGroup.securityGroupId),
|
||||
ec2.Port.tcp(3306),
|
||||
'Allow MySQL access'
|
||||
);
|
||||
```
|
||||
|
||||
### VPC Endpoints
|
||||
|
||||
**Use VPC endpoints for AWS services**:
|
||||
|
||||
```typescript
|
||||
// S3 VPC endpoint (gateway endpoint, no cost)
|
||||
vpc.addGatewayEndpoint('S3Endpoint', {
|
||||
service: ec2.GatewayVpcEndpointAwsService.S3,
|
||||
});
|
||||
|
||||
// DynamoDB VPC endpoint (gateway endpoint, no cost)
|
||||
vpc.addGatewayEndpoint('DynamoDBEndpoint', {
|
||||
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
|
||||
});
|
||||
|
||||
// Secrets Manager VPC endpoint (interface endpoint, cost applies)
|
||||
vpc.addInterfaceEndpoint('SecretsManagerEndpoint', {
|
||||
service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
|
||||
privateDnsEnabled: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Security Groups
|
||||
|
||||
**Principle of least privilege for network access**:
|
||||
|
||||
```typescript
|
||||
// Lambda security group
|
||||
const lambdaSG = new ec2.SecurityGroup(this, 'LambdaSG', {
|
||||
vpc,
|
||||
allowAllOutbound: false,
|
||||
});
|
||||
|
||||
// RDS security group
|
||||
const rdsSG = new ec2.SecurityGroup(this, 'RDSSG', {
|
||||
vpc,
|
||||
allowAllOutbound: false,
|
||||
});
|
||||
|
||||
// Allow Lambda to access RDS only
|
||||
rdsSG.addIngressRule(
|
||||
ec2.Peer.securityGroupId(lambdaSG.securityGroupId),
|
||||
ec2.Port.tcp(3306),
|
||||
'Allow Lambda access'
|
||||
);
|
||||
|
||||
lambdaSG.addEgressRule(
|
||||
ec2.Peer.securityGroupId(rdsSG.securityGroupId),
|
||||
ec2.Port.tcp(3306),
|
||||
'Allow RDS access'
|
||||
);
|
||||
```
|
||||
|
||||
## Security Monitoring
|
||||
|
||||
### CloudWatch Logs
|
||||
|
||||
**Enable and encrypt logs**:
|
||||
|
||||
```typescript
|
||||
new NodejsFunction(this, 'Function', {
|
||||
entry: 'src/handler.ts',
|
||||
logRetention: logs.RetentionDays.ONE_WEEK,
|
||||
logGroup: new logs.LogGroup(this, 'LogGroup', {
|
||||
encryptionKey: kmsKey, // Encrypt logs
|
||||
retention: logs.RetentionDays.ONE_WEEK,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
### CloudTrail
|
||||
|
||||
**Enable CloudTrail for audit**:
|
||||
|
||||
```typescript
|
||||
const trail = new cloudtrail.Trail(this, 'Trail', {
|
||||
isMultiRegionTrail: true,
|
||||
includeGlobalServiceEvents: true,
|
||||
managementEvents: cloudtrail.ReadWriteType.ALL,
|
||||
});
|
||||
|
||||
// Log Lambda invocations
|
||||
trail.addLambdaEventSelector([{
|
||||
includeManagementEvents: true,
|
||||
readWriteType: cloudtrail.ReadWriteType.ALL,
|
||||
}]);
|
||||
```
|
||||
|
||||
### GuardDuty
|
||||
|
||||
**Enable GuardDuty for threat detection**:
|
||||
- Analyzes VPC Flow Logs, DNS logs, CloudTrail events
|
||||
- Detects unusual API activity
|
||||
- Identifies compromised credentials
|
||||
- Monitors for cryptocurrency mining
|
||||
|
||||
## Security Best Practices Checklist
|
||||
|
||||
### Development
|
||||
|
||||
- [ ] Validate and sanitize all inputs
|
||||
- [ ] Scan dependencies for vulnerabilities
|
||||
- [ ] Use least privilege IAM permissions
|
||||
- [ ] Store secrets in Secrets Manager or Parameter Store
|
||||
- [ ] Never log sensitive data
|
||||
- [ ] Enable encryption for all data stores
|
||||
- [ ] Use environment variables for configuration, not secrets
|
||||
|
||||
### Deployment
|
||||
|
||||
- [ ] Enable CloudTrail in all regions
|
||||
- [ ] Configure VPC for sensitive workloads
|
||||
- [ ] Use VPC endpoints for AWS service access
|
||||
- [ ] Enable GuardDuty for threat detection
|
||||
- [ ] Implement resource-based policies
|
||||
- [ ] Use AWS WAF for API protection
|
||||
- [ ] Enable access logging for API Gateway
|
||||
|
||||
### Operations
|
||||
|
||||
- [ ] Monitor CloudTrail for unusual activity
|
||||
- [ ] Set up alarms for security events
|
||||
- [ ] Rotate secrets regularly
|
||||
- [ ] Review IAM policies periodically
|
||||
- [ ] Audit function permissions
|
||||
- [ ] Monitor GuardDuty findings
|
||||
- [ ] Implement automated security responses
|
||||
|
||||
### Testing
|
||||
|
||||
- [ ] Test with least privilege policies
|
||||
- [ ] Validate error handling for security failures
|
||||
- [ ] Test input validation and sanitization
|
||||
- [ ] Verify encryption configurations
|
||||
- [ ] Test with malicious payloads
|
||||
- [ ] Audit logs for security events
|
||||
|
||||
## Summary
|
||||
|
||||
- **Shared Responsibility**: AWS handles infrastructure, you handle application security
|
||||
- **Least Privilege**: Use IAM grant methods, avoid wildcards
|
||||
- **Encryption**: Enable encryption at rest and in transit
|
||||
- **Input Validation**: Validate and sanitize all inputs
|
||||
- **Dependency Security**: Scan and update dependencies regularly
|
||||
- **Monitoring**: Enable CloudTrail, GuardDuty, and CloudWatch
|
||||
- **Secrets Management**: Use Secrets Manager, never environment variables
|
||||
- **Network Security**: Use VPC, security groups, and VPC endpoints appropriately
|
||||
Reference in New Issue
Block a user