Pulumi Best Practices - AWS
This page lists all 55 policies in the Pulumi Best Practices pack for AWS, as published in pulumi-best-practices-aws version 1.4.1.
Policies by control
1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
- iam-group-policy-least-privilege
- iam-policy-least-privilege
- iam-role-least-privilege
- iam-role-policy-least-privilege
- iam-user-policy-least-privilege
- pubsub-least-privilege-iam
- s3-bucket-least-privilege
2. Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
- ec2-instance-disallow-unencrypted-block-device
- ec2-instance-disallow-unencrypted-root-block-device
- ec2-launch-configuration-disallow-unencrypted-block-device
- ec2-launch-template-disallow-unencrypted-block-device
2. Resource Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
- dynamodb-kms-encryption-enabled
- ebs-volume-encryption-required
- rds-cluster-disallow-unencrypted-storage
- rds-encryption-enabled
- s3-bucket-encryption
3. Transport Layer Encryption — Require secure protocols (e.g., TLS) for all data in transit to prevent interception or tampering.
- api-gateway-ssl-certificate-required
- elb-disallow-unencrypted-traffic
- rds-clusterinstance-ssl-encryption
- rds-instance-ssl-encryption
- rds-ssl-encryption
4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.
- dms-no-public-access
- ec2-instance-disallow-public-ip
- neptune-clusterinstance-no-public-access
- rds-cluster-instance-disallow-public-access
- rds-private-subnet-validation
- s3-bucket-public-access-block
5. Tagging — Enforce standardized resource tags for ownership, environment, and compliance tracking.
6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
- api-gateway-access-logging
- api-gateway-v2-access-logging
- cloudtrail-enabled
- cloudwatch-log-retention
- elb-access-logging-enabled
- s3-bucket-access-logging
- vpc-flow-logs
7. High Availability — Deploy resources in redundant, fault-tolerant configurations to ensure service continuity.
8. Require DLQ — Ensure all asynchronous messaging systems are configured with a dead-letter queue to handle failures.
9. Resource Availability — Define and enforce timeouts, quotas, and capacity limits to prevent resource exhaustion.
- dynamodb-auto-scaling-enabled
- dynamodb-streams-enabled
- lambda-concurrent-execution-limits-required
- limit-lambda-execution-time
- resource-name-length
11. Networking — Only allow required inbound and outbound traffic through network security groups, firewalls, or ACLs.
12. Documentation — Maintain up-to-date documentation of architectures, configurations, policies, and procedures to ensure clarity, consistency, and auditability.
13. Data Backup and Recovery — Regularly back up critical data and systems, store backups securely, and test recovery procedures to ensure timely restoration after failures or disasters.
14. Key Management & Rotation — Manage encryption keys securely and enforce periodic key rotation to reduce the risk of compromise.
Policy details
api-gateway-access-logging
Severity: medium · Enforcement: advisory
Ensures API Gateway stages have access logging enabled
- 6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
Remediation
Fix: Enable Access Logging for API Gateway Stage
Configure the accessLogSettings property with a CloudWatch Log Group ARN to enable comprehensive audit logging:
const logGroup = new aws.cloudwatch.LogGroup("api-logs", {
retentionInDays: 30,
});
const stage = new aws.apigateway.Stage("my-stage", {
restApi: api.id,
stageName: "prod",
deployment: deployment.id,
accessLogSettings: { // Enable access logging
destinationArn: logGroup.arn, // Specify CloudWatch Log Group
format: "$context.requestId",
},
});
api-gateway-ssl-certificate-required
Severity: high · Enforcement: advisory
Ensures API Gateway REST API stages have client certificates configured for SSL/TLS authentication to protect data in transit.
- 3. Transport Layer Encryption — Require secure protocols (e.g., TLS) for all data in transit to prevent interception or tampering.
Remediation
Fix: Configure Client Certificate for API Gateway Stage
First, create a client certificate:
import * as aws from "@pulumi/aws";
// Create a client certificate for SSL/TLS authentication
const clientCert = new aws.apigateway.ClientCertificate("api-client-cert", {
description: "Client certificate for API Gateway SSL authentication",
});
Then, associate the certificate with your API Gateway stage:
const stage = new aws.apigateway.Stage("api-stage", {
restApi: restApi.id,
deployment: deployment.id,
stageName: "production",
clientCertificateId: clientCert.id, // Add this property to enable SSL certificate validation
});
api-gateway-v2-access-logging
Severity: medium · Enforcement: advisory
Ensures API Gateway V2 stages have access logging enabled
- 6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
Remediation
Fix: Enable Access Logging for API Gateway V2 Stage
Configure the accessLogSettings property with a CloudWatch Log Group ARN to enable comprehensive audit logging:
const logGroup = new aws.cloudwatch.LogGroup("api-v2-logs", {
retentionInDays: 30,
});
const stage = new aws.apigatewayv2.Stage("my-stage", {
apiId: api.id,
name: "prod",
accessLogSettings: { // Enable access logging
destinationArn: logGroup.arn, // Specify CloudWatch Log Group
format: "$context.requestId",
},
});
cloudtrail-enabled
Severity: critical · Enforcement: advisory
Ensures CloudTrail is enabled with at least one active trail for audit logging.
- 6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
Remediation
Fix: Enable CloudTrail for audit logging
Create a CloudTrail trail to capture API activity and management events:
import * as aws from "@pulumi/aws";
// Create an S3 bucket for CloudTrail logs
const trailBucket = new aws.s3.Bucket("cloudtrail-logs", {
forceDestroy: true, // Only for demo - remove in production
});
// Create CloudTrail trail
const trail = new aws.cloudtrail.Trail("main-trail", {
s3BucketName: trailBucket.bucket,
isMultiRegionTrail: true, // Enable for all regions
includeGlobalServiceEvents: true,
enableLogFileValidation: true, // Ensure log integrity
});
Key configuration:
- Set
isMultiRegionTrail: trueto capture events across all AWS regions - Enable
includeGlobalServiceEventsto capture IAM and STS events - Use
enableLogFileValidationto ensure log integrity
cloudwatch-log-retention
Severity: medium · Enforcement: advisory
Ensures CloudWatch log groups have appropriate retention periods for compliance.
- 6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
Remediation
Fix: Configure CloudWatch Log Group Retention Period
Set the retentionInDays property to at least 365 days to meet audit record retention requirements.
import * as aws from "@pulumi/aws";
const logGroup = new aws.cloudwatch.LogGroup("my-log-group", {
name: "/aws/lambda/my-function",
retentionInDays: 365, // Set retention to at least 365 days for compliance
});
dms-no-public-access
Severity: high · Enforcement: advisory
Ensures DMS replication instances are not publicly accessible to maintain security.
- 4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.
Remediation
Fix: Disable Public Accessibility
const replicationInstance = new aws.dms.ReplicationInstance("my-replication-instance", {
replicationInstanceClass: "dms.t3.micro",
publiclyAccessible: false, // Set to false to prevent public access
// ... other config
});
dynamodb-auto-scaling-enabled
Severity: medium · Enforcement: advisory
Ensures DynamoDB tables have auto-scaling or on-demand mode enabled for capacity management.
- 9. Resource Availability — Define and enforce timeouts, quotas, and capacity limits to prevent resource exhaustion.
Remediation
Fix: Enable DynamoDB Auto-Scaling or On-Demand Mode
Configure auto-scaling targets and policies for your DynamoDB table’s read and write capacity, or switch to on-demand billing mode:
import * as aws from "@pulumi/aws";
const table = new aws.dynamodb.Table("myTable", {
name: "my-table",
billingMode: "PROVISIONED", // Use provisioned mode with auto-scaling
readCapacity: 5,
writeCapacity: 5,
attributes: [{ name: "id", type: "S" }],
hashKey: "id",
});
// Configure read capacity auto-scaling
const readTarget = new aws.appautoscaling.Target("readTarget", {
resourceId: pulumi.interpolate`table/${table.name}`,
scalableDimension: "dynamodb:table:ReadCapacityUnits",
serviceNamespace: "dynamodb",
minCapacity: 5,
maxCapacity: 100, // Set appropriate scaling limits
});
new aws.appautoscaling.Policy("readPolicy", {
resourceId: readTarget.resourceId,
scalableDimension: readTarget.scalableDimension,
serviceNamespace: readTarget.serviceNamespace,
policyType: "TargetTrackingScaling",
targetTrackingScalingPolicyConfiguration: {
targetValue: 70.0, // Target utilization percentage
predefinedMetricSpecification: {
predefinedMetricType: "DynamoDBReadCapacityUtilization",
},
},
});
// Configure write capacity auto-scaling
const writeTarget = new aws.appautoscaling.Target("writeTarget", {
resourceId: pulumi.interpolate`table/${table.name}`,
scalableDimension: "dynamodb:table:WriteCapacityUnits",
serviceNamespace: "dynamodb",
minCapacity: 5,
maxCapacity: 100,
});
new aws.appautoscaling.Policy("writePolicy", {
resourceId: writeTarget.resourceId,
scalableDimension: writeTarget.scalableDimension,
serviceNamespace: writeTarget.serviceNamespace,
policyType: "TargetTrackingScaling",
targetTrackingScalingPolicyConfiguration: {
targetValue: 70.0,
predefinedMetricSpecification: {
predefinedMetricType: "DynamoDBWriteCapacityUtilization",
},
},
});
// Alternative: Use on-demand billing mode (no auto-scaling needed)
// const table = new aws.dynamodb.Table("myTable", {
// billingMode: "PAY_PER_REQUEST", // On-demand mode handles scaling automatically
// attributes: [{ name: "id", type: "S" }],
// hashKey: "id",
// });
dynamodb-kms-encryption-enabled
Severity: high · Enforcement: advisory
Ensures DynamoDB tables have encryption enabled using KMS keys.
- 2. Resource Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable Customer-Managed KMS Encryption for DynamoDB Table
Configure the DynamoDB table with a customer-managed KMS key for encryption at rest:
import * as aws from "@pulumi/aws";
// Create or reference a customer-managed KMS key
const kmsKey = new aws.kms.Key("table-encryption-key", {
description: "KMS key for DynamoDB table encryption",
deletionWindowInDays: 10,
});
const table = new aws.dynamodb.Table("my-table", {
// ... other configuration ...
serverSideEncryption: {
enabled: true, // Enable encryption
kmsKeyArn: kmsKey.arn, // Use customer-managed KMS key (not AWS-managed)
},
});
dynamodb-streams-enabled
Severity: medium · Enforcement: advisory
Enforces that all DynamoDB tables have Stream settings enabled to capture all changes
- 9. Resource Availability — Define and enforce timeouts, quotas, and capacity limits to prevent resource exhaustion.
Remediation
Fix: Enable DynamoDB Streams
Set the streamEnabled property to true on your DynamoDB table:
const table = new aws.dynamodb.Table("my-table", {
name: "my-table",
attributes: [
{ name: "id", type: "S" },
],
hashKey: "id",
streamEnabled: true, // Enable DynamoDB Streams
billingMode: "PAY_PER_REQUEST",
});
ebs-volume-encryption-required
Severity: high · Enforcement: advisory
Checks that EBS volumes are encrypted.
- 2. Resource Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable EBS Volume Encryption
Set the encrypted property to true on your EBS volume resource.
import * as aws from "@pulumi/aws";
const volume = new aws.ebs.Volume("my-volume", {
availabilityZone: "us-west-2a",
size: 100,
encrypted: true, // Enable encryption
});
ec2-instance-disallow-public-ip
Severity: high · Enforcement: advisory
Checks that EC2 instances do not have a public IP address.
- 4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.
Remediation
Fix: Disable Public IP Assignment
const instance = new aws.ec2.Instance("app-server", {
ami: "ami-12345678",
instanceType: "t3.medium",
associatePublicIpAddress: false, // Disable public IP to prevent direct internet access
subnetId: privateSubnet.id,
});
ec2-instance-disallow-unencrypted-block-device
Severity: high · Enforcement: advisory
Checks that EC2 instances do not have unencrypted block devices.
- 2. Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable Encryption for EBS Block Devices
Set the encrypted property to true for all EBS block devices attached to EC2 instances:
const instance = new aws.ec2.Instance("my-instance", {
ami: "ami-12345678",
instanceType: "t3.micro",
ebsBlockDevices: [{
deviceName: "/dev/sdf",
volumeSize: 20,
encrypted: true, // Enable encryption for the EBS volume
}],
});
ec2-instance-disallow-unencrypted-root-block-device
Severity: high · Enforcement: advisory
Checks that EC2 instances does not have unencrypted root volumes.
- 2. Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable Encryption for Root Block Device
Set the encrypted property to true in the rootBlockDevice configuration:
const instance = new aws.ec2.Instance("my-instance", {
instanceType: "t3.micro",
ami: "ami-12345678",
rootBlockDevice: {
encrypted: true, // Enable encryption for root volume
},
});
ec2-launch-configuration-disallow-unencrypted-block-device
Severity: high · Enforcement: advisory
Checks that EC2 Launch Configurations do not have unencrypted block devices.
- 2. Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable EBS Block Device Encryption
Set the encrypted property to true for all EBS block devices in the Launch Configuration:
const launchConfig = new aws.ec2.LaunchConfiguration("my-launch-config", {
imageId: "ami-12345678",
instanceType: "t3.medium",
ebsBlockDevices: [{
deviceName: "/dev/sdf",
volumeSize: 100,
encrypted: true, // Enable encryption for all EBS block devices
}],
});
ec2-launch-template-disallow-unencrypted-block-device
Severity: high · Enforcement: advisory
Checks that EC2 Launch Templates do not have unencrypted block device.
- 2. Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable EBS Volume Encryption in Launch Template
Set the encrypted property to “true” for all block device mappings in the Launch Template:
const launchTemplate = new aws.ec2.LaunchTemplate("my-template", {
imageId: "ami-12345678",
instanceType: "t3.micro",
blockDeviceMappings: [{
deviceName: "/dev/sda1",
ebs: {
encrypted: "true", // Enable encryption for the EBS volume
volumeSize: 20,
volumeType: "gp3",
},
}],
});
elb-access-logging-enabled
Severity: medium · Enforcement: advisory
Check that ELB Load Balancers uses access logging.
- 6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
Remediation
Fix: Enable Access Logging for ELB Load Balancer
Configure the accessLogs property with an S3 bucket to store access logs:
const loadBalancer = new aws.elb.LoadBalancer("my-lb", {
availabilityZones: ["us-west-2a", "us-west-2b"],
listeners: [{ /* ... */ }],
accessLogs: {
enabled: true, // Enable access logging
bucket: "my-elb-logs-bucket", // S3 bucket for logs
bucketPrefix: "my-app", // Optional prefix for log files
},
});
elb-cross-zone-load-balancing-enabled
Severity: medium · Enforcement: advisory
Classic Load Balancers must have cross-zone load balancing enabled
- 7. High Availability — Deploy resources in redundant, fault-tolerant configurations to ensure service continuity.
Remediation
Fix: Enable cross-zone load balancing on Classic Load Balancer
Set the crossZoneLoadBalancing property to true to distribute traffic evenly across all availability zones.
const loadBalancer = new aws.elb.LoadBalancer("my-load-balancer", {
availabilityZones: ["us-west-2a", "us-west-2b"],
listeners: [{
instancePort: 80,
instanceProtocol: "http",
lbPort: 80,
lbProtocol: "http",
}],
crossZoneLoadBalancing: true, // Enable cross-zone load balancing
});
elb-disallow-unencrypted-traffic
Severity: critical · Enforcement: advisory
Check that ELB Load Balancers do not allow unencrypted (HTTP) traffic.
- 3. Transport Layer Encryption — Require secure protocols (e.g., TLS) for all data in transit to prevent interception or tampering.
Remediation
Fix: Configure HTTPS/SSL listeners instead of HTTP
Update your ELB Load Balancer to use HTTPS or SSL protocol instead of HTTP:
const loadBalancer = new aws.elb.LoadBalancer("my-lb", {
listeners: [
{
instancePort: 443,
instanceProtocol: "https",
lbPort: 443,
lbProtocol: "https", // Use "https" or "ssl" instead of "http"
sslCertificateId: "arn:aws:iam::123456789012:server-certificate/my-cert", // Required for HTTPS/SSL
},
],
});
environment-separation-tagging
Severity: low · Enforcement: advisory
Ensures that resources are tagged to distinguish between production and non-production environments
- 5. Tagging — Enforce standardized resource tags for ownership, environment, and compliance tracking.
Remediation
Fix: Add Environment Tag to Resource
Add an “Environment” tag to the resource with a valid environment value (e.g., production, development, staging, testing):
const instance = new aws.ec2.Instance("web-server", {
instanceType: "t3.micro",
ami: "ami-12345678",
tags: {
"Environment": "production", // Add environment tag for proper separation
"Name": "web-server",
},
});
iam-group-policy-least-privilege
Severity: high · Enforcement: advisory
Ensures IAM group policies follow least privilege principles
- 1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
Remediation
Fix: Replace Wildcard Permissions with Specific Actions and Resources
Replace wildcard actions and resources with explicit, scoped permissions:
const groupPolicy = new aws.iam.GroupPolicy("my-group-policy", {
group: myGroup.name,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: [
"cloudwatch:PutMetricData", // Specify exact actions needed
],
Resource: "*", // Some AWS services require "*" for resource
Condition: {
StringEquals: {
"cloudwatch:namespace": "MyApp", // Use conditions to scope access
},
},
}],
}),
});
iam-policy-least-privilege
Severity: high · Enforcement: advisory
Ensures IAM policies follow least privilege principles
- 1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
Remediation
Fix: Replace Wildcard Permissions with Specific Actions and Resources
Replace wildcard actions and resources with explicit, scoped permissions that grant only the minimum required access:
const policy = new aws.iam.Policy("my-policy", {
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: [
"s3:GetObject", // Specify exact actions needed
"s3:PutObject",
],
Resource: [
"arn:aws:s3:::my-bucket/*", // Scope to specific resources
],
}],
}),
});
iam-role-least-privilege
Severity: high · Enforcement: advisory
Ensures IAM roles follow least privilege principles
- 1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
Remediation
Fix: Replace Wildcard Permissions with Specific Actions and Resources
Replace wildcard actions and resources in inline policies with explicit, scoped permissions:
const role = new aws.iam.Role("my-role", {
assumeRolePolicy: assumePolicyDoc,
inlinePolicies: [{
name: "my-inline-policy",
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: [
"dynamodb:GetItem", // Specify exact actions needed
"dynamodb:PutItem",
],
Resource: [
"arn:aws:dynamodb:us-east-1:123456789012:table/MyTable", // Scope to specific resources
],
}],
}),
}],
});
iam-role-policy-least-privilege
Severity: high · Enforcement: advisory
Ensures IAM role policies follow least privilege principles
- 1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
Remediation
Fix: Replace Wildcard Permissions with Specific Actions and Resources
Replace wildcard actions and resources with explicit, scoped permissions:
const rolePolicy = new aws.iam.RolePolicy("my-role-policy", {
role: myRole.id,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: [
"ec2:DescribeInstances", // Specify exact actions needed
"ec2:StartInstances",
"ec2:StopInstances",
],
Resource: [
"arn:aws:ec2:us-east-1:123456789012:instance/*", // Scope to specific resources
],
}],
}),
});
iam-user-policy-least-privilege
Severity: high · Enforcement: advisory
Ensures IAM user policies follow least privilege principles
- 1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
Remediation
Fix: Replace Wildcard Permissions with Specific Actions and Resources
Replace wildcard actions and resources with explicit, scoped permissions:
const userPolicy = new aws.iam.UserPolicy("my-user-policy", {
user: myUser.name,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: [
"s3:ListBucket", // Specify exact actions needed
],
Resource: [
"arn:aws:s3:::my-bucket", // Scope to specific resources
],
}],
}),
});
kms-key-creation
Severity: medium · Enforcement: advisory
Validates KMS key creation with appropriate specifications and origins
- 14. Key Management & Rotation — Manage encryption keys securely and enforce periodic key rotation to reduce the risk of compromise.
Remediation
Fix: Configure KMS Key with Proper Specifications
Add a description, configure an appropriate deletion window (7-30 days), and ensure key specifications match your security requirements:
const key = new aws.kms.Key("my-key", {
description: "Key for encrypting application data", // Add clear description
customerMasterKeySpec: "SYMMETRIC_DEFAULT", // Use approved key spec
keyUsage: "ENCRYPT_DECRYPT", // Specify key usage
deletionWindowInDays: 30, // Set deletion window between 7-30 days
enableKeyRotation: true,
});
kms-key-deletion-lifecycle
Severity: medium · Enforcement: advisory
Validates KMS key deletion windows and lifecycle management
- 14. Key Management & Rotation — Manage encryption keys securely and enforce periodic key rotation to reduce the risk of compromise.
Remediation
Fix: Configure KMS Key Deletion Window and Lifecycle Management
Set a deletionWindowInDays between 7 and 30 days, add descriptive tags, and provide a clear description for proper lifecycle management:
const kmsKey = new aws.kms.Key("my-key", {
description: "Encryption key for application data",
deletionWindowInDays: 30, // Set deletion window between 7-30 days
tags: {
Environment: "production",
Owner: "security-team",
Purpose: "data-encryption",
},
});
kms-key-rotation-enabled
Severity: medium · Enforcement: advisory
Checks that KMS Keys have key rotation enabled.
- 14. Key Management & Rotation — Manage encryption keys securely and enforce periodic key rotation to reduce the risk of compromise.
Remediation
Fix: Enable automatic key rotation for KMS keys
Set the enableKeyRotation property to true on your KMS key resource to enable automatic annual rotation of the key material.
const key = new aws.kms.Key("my-key", {
description: "My KMS key",
enableKeyRotation: true, // Enable automatic key rotation
});
lambda-concurrent-execution-limits-required
Severity: medium · Enforcement: advisory
Lambda functions must have concurrent execution limits configured to protect resource availability
- 9. Resource Availability — Define and enforce timeouts, quotas, and capacity limits to prevent resource exhaustion.
Remediation
Fix: Configure Reserved Concurrent Executions
Set the reservedConcurrentExecutions property on your Lambda function to limit concurrent executions and prevent resource exhaustion.
import * as aws from "@pulumi/aws";
const myFunction = new aws.lambda.Function("myFunction", {
runtime: "nodejs18.x",
handler: "index.handler",
role: lambdaRole.arn,
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./function"),
}),
// Set reserved concurrent executions to limit resource consumption
reservedConcurrentExecutions: 10,
});
Choose a value based on your function’s expected load and account limits. Setting this prevents the function from consuming all available concurrency and impacting other functions in your account.
lambda-dead-letter-queue-required
Severity: medium · Enforcement: advisory
Lambda functions must have dead letter queues configured for error handling and incident response
- 8. Require DLQ — Ensure all asynchronous messaging systems are configured with a dead-letter queue to handle failures.
Remediation
Fix: Configure Dead Letter Queue for Lambda Function
Add a deadLetterConfig property to your Lambda function with a target SQS queue or SNS topic ARN to capture failed invocations for incident analysis.
import * as aws from "@pulumi/aws";
// Create a dead letter queue
const dlq = new aws.sqs.Queue("my-function-dlq", {
messageRetentionSeconds: 1209600, // 14 days
});
const lambdaFunction = new aws.lambda.Function("my-function", {
runtime: "nodejs18.x",
handler: "index.handler",
role: lambdaRole.arn,
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./function"),
}),
// Add dead letter configuration to capture failed invocations
deadLetterConfig: {
targetArn: dlq.arn, // ARN of SQS queue or SNS topic
},
});
lambda-function-documentation
Severity: low · Enforcement: advisory
Ensures all AWS Lambda functions have a documented description attribute
- 12. Documentation — Maintain up-to-date documentation of architectures, configurations, policies, and procedures to ensure clarity, consistency, and auditability.
Remediation
Fix: Add Description to Lambda Function
Add a meaningful description attribute to your Lambda function that is at least 10 characters long:
const myFunction = new aws.lambda.Function("my-function", {
code: new pulumi.asset.FileArchive("./function"),
role: lambdaRole.arn,
handler: "index.handler",
runtime: "nodejs20.x",
description: "Processes user authentication requests and validates credentials", // Add descriptive documentation
});
limit-lambda-execution-time
Severity: low · Enforcement: advisory
Ensures that AWS Lambda functions are configured to time out after a specified duration to prevent extended access
- 9. Resource Availability — Define and enforce timeouts, quotas, and capacity limits to prevent resource exhaustion.
Remediation
Fix: Configure Lambda Function Timeout
Set the timeout property to limit function execution time (default maximum: 300 seconds):
const myFunction = new aws.lambda.Function("my-function", {
runtime: "nodejs20.x",
handler: "index.handler",
role: lambdaRole.arn,
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./app"),
}),
timeout: 300, // Set timeout to prevent extended access (max 300 seconds by default)
});
neptune-clusterinstance-no-public-access
Severity: high · Enforcement: advisory
Checks that Neptune Cluster Instances public access is not enabled.
- 4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.
Remediation
Fix: Disable Public Access for Neptune Cluster Instance
Set the publiclyAccessible property to false:
const neptuneInstance = new aws.neptune.ClusterInstance("neptune-instance", {
clusterIdentifier: neptuneCluster.id,
instanceClass: "db.r5.large",
engine: "neptune",
publiclyAccessible: false, // Disable public access
});
pubsub-least-privilege-iam
Severity: medium · Enforcement: advisory
Ensures IAM policies follow least privilege principles for Pub/Sub services (SNS, SQS, Kinesis)
- 1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
Remediation
Fix: Use Specific Pub/Sub IAM Actions and Resource ARNs
Replace wildcard permissions with specific actions and resource ARNs for Pub/Sub services:
const queuePolicy = new aws.iam.Policy("queue-policy", {
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: [
"sqs:SendMessage", // Use specific actions instead of sqs:*
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
],
Resource: "arn:aws:sqs:us-east-1:123456789012:my-queue", // Use specific ARN instead of *
}],
}),
});
rds-cluster-disallow-unencrypted-storage
Severity: high · Enforcement: advisory
Checks that RDS Clusters storage is encrypted.
- 2. Resource Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable RDS Cluster Storage Encryption
Set the storageEncrypted property to true to enable encryption at rest for the RDS cluster:
const cluster = new aws.rds.Cluster("my-cluster", {
engine: "aurora-mysql",
engineVersion: "8.0.mysql_aurora.3.02.0",
masterUsername: "admin",
masterPassword: dbPassword,
storageEncrypted: true, // Enable storage encryption
databaseName: "mydb",
skipFinalSnapshot: true,
});
rds-cluster-instance-disallow-public-access
Severity: high · Enforcement: advisory
Checks that RDS Cluster Instances public access is not enabled.
- 4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.
Remediation
Fix: Disable Public Access for RDS Cluster Instance
Set the publiclyAccessible property to false to ensure the RDS Cluster Instance is not accessible from the public internet:
const clusterInstance = new aws.rds.ClusterInstance("my-cluster-instance", {
clusterIdentifier: cluster.id,
instanceClass: "db.r5.large",
engine: "aurora-postgresql",
publiclyAccessible: false, // Disable public access
});
rds-clusterinstance-ssl-encryption
Severity: high · Enforcement: advisory
Ensures RDS cluster instances have SSL/TLS encryption enabled through parameter group configuration
- 3. Transport Layer Encryption — Require secure protocols (e.g., TLS) for all data in transit to prevent interception or tampering.
Remediation
Fix: Configure SSL/TLS Encryption for Aurora Cluster Instance
Configure a parameter group to enforce SSL/TLS connections for your Aurora cluster instance:
const clusterParamGroup = new aws.rds.ParameterGroup("cluster-params", {
family: "aurora-postgresql14",
parameters: [
{
name: "rds.force_ssl",
value: "1", // Enforce SSL/TLS for all connections
},
],
});
const clusterInstance = new aws.rds.ClusterInstance("my-cluster-instance", {
clusterIdentifier: cluster.id,
instanceClass: "db.r5.large",
engine: cluster.engine,
dbParameterGroupName: clusterParamGroup.name, // Attach parameter group with SSL enforcement
});
rds-encryption-enabled
Severity: high · Enforcement: advisory
Checks that RDS instance storage is encrypted.
- 2. Resource Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Enable RDS Storage Encryption
Enable the storageEncrypted property on your RDS instance:
const myRdsInstance = new aws.rds.Instance("my-db", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
storageEncrypted: true, // Enable encryption at rest
});
rds-instance-enable-backup-retention
Severity: medium · Enforcement: advisory
Checks that RDS Instances backup retention policy is enabled.
- 13. Data Backup and Recovery — Regularly back up critical data and systems, store backups securely, and test recovery procedures to ensure timely restoration after failures or disasters.
Remediation
Fix: Enable automated backup retention for RDS instance
Set the backupRetentionPeriod property to specify the number of days to retain automated backups (1-35 days):
const db = new aws.rds.Instance("my-db", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
backupRetentionPeriod: 7, // Enable automated backups with 7-day retention
});
rds-instance-high-availability
Severity: medium · Enforcement: advisory
Ensures RDS instances have Multi-AZ deployment enabled for high availability
- 7. High Availability — Deploy resources in redundant, fault-tolerant configurations to ensure service continuity.
Remediation
Fix: Enable Multi-AZ Deployment for RDS Instance
Set the multiAz property to true to enable automatic failover to a standby replica in a different Availability Zone:
const dbInstance = new aws.rds.Instance("my-database", {
allocatedStorage: 20,
engine: "mysql",
engineVersion: "8.0",
instanceClass: "db.t3.micro",
username: "admin",
password: dbPassword,
multiAz: true, // Enable Multi-AZ deployment for high availability
skipFinalSnapshot: true,
});
rds-instance-ssl-encryption
Severity: high · Enforcement: advisory
Ensures RDS instances have SSL/TLS encryption enabled through parameter group configuration
- 3. Transport Layer Encryption — Require secure protocols (e.g., TLS) for all data in transit to prevent interception or tampering.
Remediation
Fix: Configure SSL/TLS Encryption for RDS Instance
Configure a parameter group to enforce SSL/TLS connections for your RDS instance:
const dbParamGroup = new aws.rds.ParameterGroup("db-params", {
family: "mysql8.0",
parameters: [
{
name: "require_secure_transport",
value: "1", // Enforce SSL/TLS for all connections
},
],
});
const db = new aws.rds.Instance("my-database", {
engine: "mysql",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
parameterGroupName: dbParamGroup.name, // Attach parameter group with SSL enforcement
username: "admin",
password: dbPassword,
});
rds-private-subnet-validation
Severity: critical · Enforcement: advisory
Validates that RDS DB subnet groups contain only private subnets
- 4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.
Remediation
Fix: Deploy RDS DB Subnet Group in Private Subnets
Ensure the DB subnet group only contains subnets that do not have routes to an Internet Gateway. Private subnets should route internet-bound traffic through a NAT Gateway instead:
const privateSubnetGroup = new aws.rds.SubnetGroup("db-subnet-group", {
subnetIds: [
privateSubnet1.id, // Private subnet without Internet Gateway route
privateSubnet2.id, // Private subnet without Internet Gateway route
],
tags: {
Name: "Private DB Subnet Group",
},
});
const db = new aws.rds.Instance("database", {
dbSubnetGroupName: privateSubnetGroup.name,
publiclyAccessible: false,
// ... other configuration
});
rds-ssl-encryption
Severity: high · Enforcement: advisory
Ensures RDS instances have SSL/TLS encryption enabled
- 3. Transport Layer Encryption — Require secure protocols (e.g., TLS) for all data in transit to prevent interception or tampering.
Remediation
Fix: Configure SSL/TLS Encryption for RDS Instance
Set the caCertIdentifier and configure a parameter group to enforce SSL/TLS connections for your RDS instance:
const dbParamGroup = new aws.rds.ParameterGroup("db-params", {
family: "mysql8.0",
parameters: [
{
name: "require_secure_transport",
value: "1", // Enforce SSL/TLS for all connections
},
],
});
const db = new aws.rds.Instance("my-database", {
engine: "mysql",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
caCertIdentifier: "rds-ca-rsa2048-g1", // Specify CA certificate for SSL/TLS
parameterGroupName: dbParamGroup.name, // Attach parameter group with SSL enforcement
username: "admin",
password: dbPassword,
});
resource-name-length
Severity: high · Enforcement: mandatory
Ensures AWS resource names do not exceed the service-specific character limits imposed by AWS
- 9. Resource Availability — Define and enforce timeouts, quotas, and capacity limits to prevent resource exhaustion.
Remediation
Fix: Shorten the Resource Name
AWS enforces hard character limits on resource names. The most constrained services are:
| Service | Property | Max length | Notes |
|---|---|---|---|
| Elastic Beanstalk environment | name | 23 chars | Forms a DNS hostname |
| ElastiCache cluster | clusterId | 20 chars | Alphanumeric + hyphens only |
| Lambda function | name | 64 chars | |
| IAM role / user | name | 64 chars | |
| S3 bucket | bucket | 63 chars | Must be globally unique |
| RDS instance | identifier | 63 chars | Forms a DNS endpoint |
| SQS queue | name | 80 chars | FIFO queues: 80 chars incl. .fifo |
| EKS cluster | name | 100 chars | |
| CloudFormation stack | name | 128 chars | |
| EC2 Auto Scaling group | name | 255 chars | |
| SNS topic | name | 256 chars |
Shorten the name before running pulumi up. Do not rely on truncation helpers — truncated names can collide silently.
resource-tagging
Severity: low · Enforcement: advisory
Ensures all AWS resources must include tags for proper change tracking
- 5. Tagging — Enforce standardized resource tags for ownership, environment, and compliance tracking.
Remediation
Fix: Add Required Tags to AWS Resources
Add a tags property with meaningful values to enable proper change tracking and documentation:
const instance = new aws.ec2.Instance("my-instance", {
ami: "ami-0c55b159cbfafe1f0",
instanceType: "t3.micro",
tags: {
Environment: "production", // Add meaningful tags for change tracking
Owner: "team-name",
Application: "web-app",
},
});
s3-bucket-access-logging
Severity: medium · Enforcement: advisory
Ensures each S3 bucket has access logging enabled
- 6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
Remediation
Fix: Enable S3 Bucket Access Logging
Create a BucketLogging resource with a target bucket and prefix to enable access logging:
const myBucket = new aws.s3.Bucket("my-bucket", {
// Bucket configuration
});
const logBucket = new aws.s3.Bucket("log-bucket", {
// Configure log bucket settings
});
const bucketLogging = new aws.s3.BucketLogging("my-bucket-logging", {
bucket: myBucket.id,
targetBucket: logBucket.id, // Specify target bucket for logs
targetPrefix: "logs/my-bucket/", // Specify prefix for organization
});
s3-bucket-encryption
Severity: high · Enforcement: advisory
S3 buckets must have server-side encryption configured using BucketServerSideEncryptionConfiguration resource
- 2. Resource Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.
Remediation
Fix: Configure S3 Bucket Server-Side Encryption
Create a separate BucketServerSideEncryptionConfigurationV2 resource for your S3 bucket with encryption rules. Use AES256 or KMS encryption:
const bucket = new aws.s3.BucketV2("my-bucket", {
bucket: "my-secure-bucket",
});
// Add encryption configuration resource
const bucketEncryption = new aws.s3.BucketServerSideEncryptionConfiguration("my-bucket-encryption", {
bucket: bucket.id,
rules: [{
applyServerSideEncryptionByDefault: {
sseAlgorithm: "aws:kms", // Enable KMS encryption
kmsMasterKeyId: kmsKey.arn, // Specify customer-managed key
},
bucketKeyEnabled: true, // Enable bucket key for cost optimization
}],
});
s3-bucket-least-privilege
Severity: critical · Enforcement: advisory
Prevents overly permissive S3 bucket policies
- 1. Least Privilege — Ensure all identities and services have only the minimum permissions required to perform their tasks.
Remediation
Fix: Use Specific Actions, Resources, and Principals
Replace wildcard (*) values in S3 bucket policy statements with specific, scoped permissions:
const bucketPolicy = new aws.s3.BucketPolicy("policy", {
bucket: bucket.id,
policy: bucket.arn.apply(arn => JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: {
AWS: "arn:aws:iam::123456789012:role/specific-role" // Specify exact principal ARN
},
Action: ["s3:GetObject", "s3:PutObject"], // Use specific S3 actions
Resource: `${arn}/*` // Scope to specific bucket
}]
}))
});
s3-bucket-public-access-block
Severity: critical · Enforcement: advisory
Ensures each S3 bucket has a public access block with all settings enabled
- 4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.
Remediation
Fix: Enable All Public Access Block Settings
Create a BucketPublicAccessBlock resource with all four settings set to true:
const myBucket = new aws.s3.Bucket("my-bucket", {
// Bucket configuration
});
const publicAccessBlock = new aws.s3.BucketPublicAccessBlock("my-bucket-public-access-block", {
bucket: myBucket.id,
blockPublicAcls: true, // Blocks new public ACLs
blockPublicPolicy: true, // Blocks new public bucket policies
ignorePublicAcls: true, // Ignores existing public ACLs
restrictPublicBuckets: true, // Restricts public access to buckets with public policies
});
s3-bucket-replication
Enforcement: advisory
Ensures S3 buckets have replication configured for enhanced availability
- 13. Data Backup and Recovery — Regularly back up critical data and systems, store backups securely, and test recovery procedures to ensure timely restoration after failures or disasters.
s3-bucket-versioning
Severity: medium · Enforcement: advisory
S3 buckets must have versioning enabled using BucketVersioning resource
- 13. Data Backup and Recovery — Regularly back up critical data and systems, store backups securely, and test recovery procedures to ensure timely restoration after failures or disasters.
Remediation
Fix: Enable S3 Bucket Versioning
Create a separate BucketVersioning resource with status set to “Enabled” for each S3 bucket:
const bucket = new aws.s3.BucketV2("my-bucket", {
bucket: "my-data-bucket",
});
const versioning = new aws.s3.BucketVersioning("my-bucket-versioning", {
bucket: bucket.id,
versioningConfiguration: {
status: "Enabled", // Enable versioning for data integrity and recovery
},
});
security-group-default-deny
Severity: high · Enforcement: advisory
Ensures Security Groups follow default deny with explicit allow principle
- 11. Networking — Only allow required inbound and outbound traffic through network security groups, firewalls, or ACLs.
Remediation
Fix: Replace Unrestricted CIDR Blocks with Specific IP Ranges
Replace 0.0.0.0/0 in ingress and egress rules with specific CIDR blocks or security group references to follow the default deny with explicit allow principle:
const webSecurityGroup = new aws.ec2.SecurityGroup("web-sg", {
vpcId: vpc.id,
ingress: [{
protocol: "tcp",
fromPort: 443,
toPort: 443,
cidrBlocks: ["10.0.0.0/16"], // Use specific CIDR block instead of 0.0.0.0/0
}],
egress: [{
protocol: "tcp",
fromPort: 443,
toPort: 443,
cidrBlocks: ["10.0.0.0/16"], // Use specific CIDR block instead of 0.0.0.0/0
}],
});
security-group-ssh-rdp
Severity: critical · Enforcement: advisory
Ensures security groups do not allow SSH/RDP from the internet
- 11. Networking — Only allow required inbound and outbound traffic through network security groups, firewalls, or ACLs.
Remediation
Fix: Remove Public SSH and RDP Access
Remove or restrict ingress rules that allow SSH (port 22) or RDP (port 3389) from 0.0.0.0/0. Use specific CIDR blocks for trusted networks or implement alternative secure access methods:
const securityGroup = new aws.ec2.SecurityGroup("my-sg", {
vpcId: vpc.id,
ingress: [
{
fromPort: 22,
toPort: 22,
protocol: "tcp",
cidrBlocks: ["10.0.0.0/16"], // Restrict to specific network, not 0.0.0.0/0
},
{
fromPort: 3389,
toPort: 3389,
protocol: "tcp",
cidrBlocks: ["10.0.0.0/16"], // Restrict to specific network, not 0.0.0.0/0
},
],
});
security-group-strict
Severity: high · Enforcement: advisory
Ensures security groups follow strict firewall rules with default deny
- 11. Networking — Only allow required inbound and outbound traffic through network security groups, firewalls, or ACLs.
Remediation
Fix: Configure Strict Security Group Rules
Restrict ingress rules to specific CIDR blocks instead of allowing access from the entire internet (0.0.0.0/0). Use specific protocols and narrow port ranges:
const securityGroup = new aws.ec2.SecurityGroup("my-sg", {
vpcId: vpc.id,
ingress: [
{
protocol: "tcp", // Use specific protocol, not "-1" (all)
fromPort: 443,
toPort: 443,
cidrBlocks: ["10.0.0.0/8"], // Restrict to internal network, not "0.0.0.0/0"
},
],
egress: [
{
protocol: "-1",
fromPort: 0,
toPort: 0,
cidrBlocks: ["0.0.0.0/0"],
},
],
});
sqs-dead-letter-queue
Severity: medium · Enforcement: advisory
Ensures SQS queues have dead letter queue configuration
- 8. Require DLQ — Ensure all asynchronous messaging systems are configured with a dead-letter queue to handle failures.
Remediation
Fix: Configure Dead Letter Queue for SQS Queue
Set the redrivePolicy with a deadLetterTargetArn pointing to a DLQ and specify maxReceiveCount to capture failed messages:
// Create a dead letter queue first
const dlq = new aws.sqs.Queue("my-dlq", {
messageRetentionSeconds: 1209600, // 14 days
});
// Configure the main queue with redrive policy
const queue = new aws.sqs.Queue("my-queue", {
redrivePolicy: pulumi.interpolate`{
"deadLetterTargetArn": "${dlq.arn}",
"maxReceiveCount": 3
}`, // Configure DLQ to capture messages that fail processing
messageRetentionSeconds: 345600,
});
vpc-flow-logs
Severity: medium · Enforcement: advisory
Ensures VPC flow logs use approved destinations for centralized monitoring
- 6. Enforce Logging — Enable and retain audit logs for all security-relevant actions and events.
Remediation
Fix: Configure VPC Flow Logs with Approved Destination
Configure VPC Flow Logs to use one of the approved log destinations specified in the policy configuration:
const flowLog = new aws.ec2.FlowLog("vpc-flow-log", {
vpcId: vpc.id,
trafficType: "ALL",
logDestination: "arn:aws:s3:::approved-logging-bucket", // Use approved destination
logDestinationType: "s3",
});