Files
gh-zxkane-aws-skills-server…/skills/aws-serverless-eda/references/security-best-practices.md
2025-11-30 09:08:46 +08:00

626 lines
15 KiB
Markdown

# 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