Skip to main content
Pulumi logo Pulumi logo
  1. Docs
  2. Reference
  3. Pre-built Policy Packs
  4. Pulumi Best Practices
  5. AWS

Pulumi Best Practices - AWS

This Pulumi Cloud feature is available in the Essentials, Pro, and Enterprise editions.

    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.

    2. Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.

    2. Resource Encryption at Rest — Encrypt all stored data using approved encryption mechanisms to protect against unauthorized access.

    3. Transport Layer Encryption — Require secure protocols (e.g., TLS) for all data in transit to prevent interception or tampering.

    4. No Public Access — Prohibit direct public exposure of resources unless explicitly approved and required.

    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.

    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.

    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: true to capture events across all AWS regions
    • Enable includeGlobalServiceEvents to capture IAM and STS events
    • Use enableLogFileValidation to 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:

    ServicePropertyMax lengthNotes
    Elastic Beanstalk environmentname23 charsForms a DNS hostname
    ElastiCache clusterclusterId20 charsAlphanumeric + hyphens only
    Lambda functionname64 chars
    IAM role / username64 chars
    S3 bucketbucket63 charsMust be globally unique
    RDS instanceidentifier63 charsForms a DNS endpoint
    SQS queuename80 charsFIFO queues: 80 chars incl. .fifo
    EKS clustername100 chars
    CloudFormation stackname128 chars
    EC2 Auto Scaling groupname255 chars
    SNS topicname256 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",
    });
    

      The infrastructure as code platform for any cloud.