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

PCI DSS v4.0.1 - AWS

This Pulumi Cloud feature is available in the Enterprise edition.

    This page lists all 171 policies in the PCI DSS v4.0.1 pack for AWS, as published in pci-dss-aws version 3.1.1.

    Policies by control

    A1.1.2 — A1.1.2: Controls are implemented such that each customer only has permission to access its own cardholder data and CDE

    1.2.8 — 1.2.8: Configuration files for NSCs are secured from unauthorized access and are kept consistent with active network configurations

    1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied

    1.3.2 — 1.3.2: Outbound traffic from the CDE is restricted

    1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *

    1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted

    1.4.3 — 1.4.3: Anti-spoofing measures are implemented to detect and block forged source IP addresses from entering the trusted network *

    1.4.5 — 1.4.5: The disclosure of internal IP addresses and routing information is limited to only authorized parties *

    2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *

    3.2.1 — 3.2.1: Account data storage is kept to a minimum through implementation of data retention and disposal policies, procedures, and processes

    3.3.2 — 3.3.2: SAD that is stored electronically prior to completion of authorization is encrypted using strong cryptography

    3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored

    3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure

    3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data

    3.6.1.3 — 3.6.1.3: Access to cleartext cryptographic key components is restricted to the fewest number of custodians necessary

    3.7.1 — 3.7.1: Key-management policies and procedures are implemented to include generation of strong cryptographic keys used to protect stored account data

    3.7.2 — 3.7.2: Key-management policies and procedures are implemented to include secure distribution of cryptographic keys used to protect stored account data

    3.7.4 — 3.7.4: Key management policies and procedures are implemented for cryptographic key changes for keys that have reached the end of their cryptoperiod, as defined by the associated application vendor or key owner

    3.7.5 — 3.7.5: Key management policies procedures are implemented to include the retirement, replacement, or destruction of keys used to protect stored account data *

    4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *

    6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *

    6.4.2 — 6.4.2: For public-facing web applications, an automated technical solution is deployed that continually detects and prevents web-based attacks

    7.2.1 — 7.2.1: An access control model is defined and includes granting access

    7.2.2 — 7.2.2: Access is assigned to users, including privileged users

    7.2.3 — 7.2.3: Required privileges are approved by authorized personnel *

    7.2.4 — 7.2.4: All user accounts and related access privileges, including third-party/vendor accounts, are reviewed *

    7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components

    8.2.1 — 8.2.1: All users are assigned a unique ID before access to system components or cardholder data is allowed *

    8.2.2 — 8.2.2: Group, shared, or generic IDs, or other shared authentication credentials are only used when necessary on an exception basis, and are managed

    8.2.8 — 8.2.8: If a user session has been idle for more than 15 minutes, the user is required to re-authenticate to re-activate the terminal or session

    8.3.2 — 8.3.2: Strong cryptography is used to render all authentication factors unreadable during transmission and storage on all system components

    8.3.6 — 8.3.6: 6 If passwords/passphrases are used as authentication factors to meet Requirement 8.3.6, they meet the minimum level of complexity *

    8.3.7 — 8.3.7: Individuals are not allowed to submit a new password/passphrase that is the same as any of the last four passwords/passphrases used *

    8.3.9 — 8.3.9 If passwords/passphrases are used as the only authentication factor for user access (i.e., in any single-factor authentication implementation) then either: • Passwords/passphrases are changed at least once every 90 days, OR • The security posture of accounts is dynamically analyzed, and real-time access to resources is automatically determined accordingly. *

    8.4.2 — 8.4.2 MFA is implemented for all non-console access into the CDE *

    8.4.3 — 8.4.3 MFA is implemented for all remote access originating from outside the entity’s network that could access or impact the CDE *

    8.6.3 — 8.6.3: 3 Passwords/passphrases for any application and system accounts are protected against misuse

    10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data

    10.2.1.1 — 10.2.1.1: Audit logs capture all individual user access to cardholder data *

    10.2.1.2 — 10.2.1.2: Audit logs capture all actions taken by any individual with administrative access *

    10.2.1.3 — 10.2.1.3: Audit logs capture all access to audit logs *

    10.2.1.4 — 10.2.1.4: Audit logs capture all invalid access attempts *

    10.2.1.5 — 10.2.1.5: Audit logs capture creation and deletion of system-level objects *

    10.2.1.6 — 10.2.1.6: Audit logs capture initialization, stopping, or pausing of the audit logs *

    10.2.1.7 — 10.2.1.7: Audit logs capture creation and deletion of system level objects *

    10.3.2 — 10.3.2: Audit log files are protected to prevent modifications by individuals *

    10.3.3 — 10.3.3: Audit log files, including those for externalfacing technologies, are promptly backed up to a secure, central, internal log server(s) or other media that is difficult to modify

    10.3.4 — 10.3.4: File integrity monitoring or change-detection mechanisms is used on audit logs to ensure that existing log data cannot be changed without generating alerts *

    10.4.1 — 10.4.1: Potentially suspicious or anomalous activities are quickly identified to minimize impact *

    10.4.1.1 — 10.4.1.1: Automated mechanisms are used to perform audit log reviews *

    10.4.3 — 10.4.3: Exceptions and anomalies identified during the review process are addressed *

    10.5.1 — 10.5.1: Retain audit log history for at least 12 months, with at least the most recent three months immediately available for analysis *

    10.7.2 — 10.7.2: Failures in critical security control systems are promptly identified and addressed *

    11.3.1 — 11.3.1: Internal vulnerability scans are performed

    11.5.1 — 11.5.1: Intrusion-detection and/or intrusionprevention techniques are used to detect and/or prevent intrusions into the network *

    11.5.2 — 11.5.2: A change-detection mechanism (for example, file integrity monitoring tools) is deployed

    12.10.5 — 12.10.5: The security incident response plan includes monitoring and responding to alerts from security monitoring systems *

    Policy details

    anti-spoofing-measures

    Severity: high · Enforcement: advisory

    Ensures AWS Network Firewall policies are configured with anti-spoofing measures to detect and block forged source IP addresses

    • 1.4.3 — 1.4.3: Anti-spoofing measures are implemented to detect and block forged source IP addresses from entering the trusted network *
    Remediation
    Fix:

    Configure Network Firewall with secure default actions:

    import * as aws from "@pulumi/aws";
    
    const firewallPolicy = new aws.networkfirewall.FirewallPolicy("firewallPolicy", {
        firewallPolicy: {
            statelessDefaultActions: ["aws:forward_to_sfe"], // Not "aws:pass"
            statelessFragmentDefaultActions: ["aws:forward_to_sfe"], // Not "aws:pass"
            statefulRuleGroupReferences: [{ resourceArn: ruleGroup.arn }], // Required
        },
    });
    
    const statelessRuleGroup = new aws.networkfirewall.RuleGroup("rules", {
        type: "STATELESS",
        ruleGroup: {
            rulesSource: {
                statelessRulesAndCustomActions: {
                    statelessRules: [{ /* at least one rule */ }], // Required
                },
            },
        },
    });
    

    api-gateway-access-logging-enabled

    Severity: medium · Enforcement: advisory

    Ensures API Gateway stages have access logging enabled

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable Access Logging for API Gateway Stage

    To fix this policy violation, configure access logging for your API Gateway stage by adding the accessLogSettings property with a destination ARN pointing to a CloudWatch log group.

    import * as aws from "@pulumi/aws";
    
    // Create a CloudWatch log group for API Gateway logs
    const apiLogGroup = new aws.cloudwatch.LogGroup("api-gateway-logs", {
        retentionInDays: 30,
    });
    
    // Create an API Gateway stage with access logging enabled
    const stage = new aws.apigateway.Stage("my-stage", {
        restApi: myApi.id,
        deployment: myDeployment.id,
        stageName: "prod",
        // Enable access logging with destination ARN
        accessLogSettings: {
            destinationArn: apiLogGroup.arn, // Specify CloudWatch log group ARN
        },
    });
    

    api-gateway-authorization

    Severity: high · Enforcement: advisory

    Ensures API Gateway methods use strong authorization instead of NONE

    • 7.2.1 — 7.2.1: An access control model is defined and includes granting access
    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    Remediation
    Fix: Enable Strong Authorization for API Gateway Method

    Set the authorization property to a strong authorization type (AWS_IAM, COGNITO_USER_POOLS, CUSTOM, or JWT) instead of “NONE”:

    const method = new aws.apigateway.Method("my-method", {
        restApi: api.id,
        resourceId: resource.id,
        httpMethod: "GET",
        authorization: "AWS_IAM", // Use strong authorization instead of "NONE"
        // For CUSTOM authorization, also specify the authorizer:
        // authorizerId: authorizer.id,
    });
    

    api-gateway-cache-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures API Gateway method settings have cache data encryption enabled when caching is configured.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable cache data encryption for API Gateway method settings

    When enabling caching for API Gateway methods, ensure cache data encryption is also enabled to protect sensitive data at rest.

    import * as aws from "@pulumi/aws";
    
    const methodSettings = new aws.apigateway.MethodSettings("example-method-settings", {
        restApi: restApi.id,
        stageName: stage.stageName,
        methodPath: "*/*",
        settings: {
            cachingEnabled: true,
            cacheDataEncrypted: true,  // Enable cache encryption at rest
            cacheTtlInSeconds: 300,
        },
    });
    

    api-gateway-domain-name-configure-security-policy

    Severity: high · Enforcement: advisory

    Checks that ApiGateway Domain Name Security Policy uses secure/modern TLS encryption.

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Configure TLS 1.2 Security Policy

    Set the securityPolicy to “TLS_1_2” to ensure the API Gateway domain name uses secure TLS encryption:

    const domainName = new apigateway.DomainName("my-domain", {
        domainName: "api.example.com",
        certificateArn: cert.arn,
        securityPolicy: "TLS_1_2", // Use TLS 1.2 for secure encryption
    });
    

    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.

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    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

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    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",
        },
    });
    

    api-gateway-v2-domain-name-configure-domain-name-security-policy

    Severity: high · Enforcement: advisory

    Checks that any ApiGatewayV2 Domain Name Security Policy uses secure/modern TLS encryption.

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Set TLS 1.2 Security Policy for API Gateway V2 Domain Name

    Configure the securityPolicy to “TLS_1_2” in the domain name configuration to enforce modern TLS encryption:

    const domainName = new aws.apigatewayv2.DomainName("my-domain", {
        domainName: "api.example.com",
        domainNameConfiguration: {
            certificateArn: certificate.arn,
            endpointType: "REGIONAL",
            securityPolicy: "TLS_1_2", // Use TLS 1.2 for secure encryption
        },
    });
    

    api-gateway-waf-enabled

    Severity: high · Enforcement: advisory

    Ensures API Gateway stages have WAF Web ACL associations for protection against web attacks.

    • 6.4.2 — 6.4.2: For public-facing web applications, an automated technical solution is deployed that continually detects and prevents web-based attacks
    Remediation
    Fix: Associate a WAF Web ACL with your API Gateway stage

    Either set webAclArn directly on the stage, or create a WebAclAssociation resource:

    import * as aws from "@pulumi/aws";
    
    // Create or reference a WAF Web ACL
    const webAcl = new aws.wafv2.WebAcl("web-acl", {
        scope: "REGIONAL",
        defaultAction: { allow: {} },
        rules: [/* your WAF rules */],
        visibilityConfig: {
            cloudwatchMetricsEnabled: true,
            metricName: "webAclMetric",
            sampledRequestsEnabled: true,
        },
    });
    
    // Option 1: attach the Web ACL on the stage itself
    const apiStage = new aws.apigateway.Stage("api-stage", {
        restApi: restApi.id,
        stageName: "prod",
        deployment: deployment.id,
        webAclArn: webAcl.arn, // Protects the stage with no separate association resource
    });
    
    // Option 2: attach it with a separate association resource
    const wafAssociation = new aws.wafv2.WebAclAssociation("stage-waf", {
        resourceArn: apiStage.arn, // Link to the API Gateway stage
        webAclArn: webAcl.arn,
    });
    

    appflow-connector-profile-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Check that AppFlow ConnectorProfile uses a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for AppFlow Connector Profile

    Set the kmsArn property to reference a customer-managed KMS key:

    const connectorProfile = new aws.appflow.ConnectorProfile("my-connector-profile", {
        name: "salesforce-connector",
        connectorType: "Salesforce",
        connectionMode: "Public",
        kmsArn: kmsKey.arn, // Specify customer-managed KMS key ARN
        connectorProfileConfig: {
            connectorProfileProperties: {
                salesforce: {
                    instanceUrl: "https://my-org.my.salesforce.com",
                },
            },
            connectorProfileCredentials: {
                salesforce: {
                    accessToken: salesforceAccessToken,
                    refreshToken: salesforceRefreshToken,
                    oauthRequest: {
                        authCode: authCode,
                        redirectUri: redirectUri,
                    },
                },
            },
        },
    });
    

    appflow-flow-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Check that AppFlow Flow uses a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for AppFlow Flow

    Set the kmsArn property to specify a customer-managed KMS key for encrypting the AppFlow Flow:

    const flow = new aws.appflow.Flow("my-flow", {
        name: "my-appflow",
        kmsArn: kmsKey.arn, // Configure customer-managed KMS key
        sourceFlowConfig: {
            connectorType: "S3",
            sourceConnectorProperties: {
                s3: {
                    bucketName: sourceBucket.bucket,
                    bucketPrefix: "data/",
                },
            },
        },
        destinationFlowConfigs: [{
            connectorType: "S3",
            destinationConnectorProperties: {
                s3: {
                    bucketName: destBucket.bucket,
                },
            },
        }],
        tasks: [{
            taskType: "Map_all",
            sourceFields: [],
            connectorOperators: [{
                s3: "NO_OP",
            }],
        }],
        triggerConfig: {
            triggerType: "OnDemand",
        },
    });
    

    athena-database-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Checks that Athena Databases storage uses a customer-managed-key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed Key Encryption for Athena Database

    Set the encryptionOption to “SSE_KMS” and specify a KMS key ARN in the encryptionConfiguration:

    const athenaDatabase = new aws.athena.Database("my-database", {
        name: "my_database",
        bucket: myBucket.id,
        encryptionConfiguration: {
            encryptionOption: "SSE_KMS", // Use customer-managed KMS key
            kmsKey: myKmsKey.arn, // Specify the KMS key ARN
        },
    });
    

    athena-database-disallow-unencrypted-database

    Severity: high · Enforcement: advisory

    Checks that Athena Databases storage is encrypted.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Encryption for Athena Database

    Configure the encryptionConfiguration property to encrypt query results and data catalog:

    const athenaDatabase = new aws.athena.Database("my-database", {
        name: "my_database",
        bucket: dataBucket.id,
        encryptionConfiguration: { // Enable encryption
            // SSE_S3 and CSE_KMS also satisfy this policy, but the sibling
            // `athena-database-configure-customer-managed-key` runs in the same packs and accepts
            // only SSE_KMS under a key you own.
            encryptionOption: "SSE_KMS",
            kmsKey: kmsKey.arn,
        },
    });
    

    athena-workgroup-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Checks that Athena Workgroups use a customer-managed-key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed Key Encryption for Athena Workgroup

    Set the encryptionOption to “SSE_KMS” in the workgroup’s result configuration to enable customer-managed key encryption:

    const workgroup = new athena.Workgroup("my-workgroup", {
        name: "my-workgroup",
        configuration: {
            resultConfiguration: {
                outputLocation: "s3://my-bucket/query-results/",
                encryptionConfiguration: {
                    encryptionOption: "SSE_KMS", // Enable customer-managed key encryption
                    kmsKeyArn: kmsKey.arn, // ARN of the KMS key you create and control
                },
            },
        },
    });
    

    athena-workgroup-disallow-unencrypted-workgroup

    Severity: high · Enforcement: advisory

    Checks that Athena Workgroups are encrypted.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Encryption for Athena Workgroup Query Results

    Configure encryption for query results by adding an encryptionConfiguration within the workgroup’s resultConfiguration:

    const workgroup = new athena.Workgroup("my-workgroup", {
        name: "example-workgroup",
        configuration: {
            resultConfiguration: {
                outputLocation: "s3://my-query-results-bucket/",
                encryptionConfiguration: { // Add encryption configuration
                    encryptionOption: "SSE_KMS", // or "SSE_S3" or "CSE_KMS"
                    kmsKeyArn: kmsKey.arn, // Required when using SSE_KMS or CSE_KMS
                },
            },
        },
    });
    

    athena-workgroup-enforce-configuration

    Severity: high · Enforcement: advisory

    Checks that Athena Workgroups enforce their configuration to their clients.

    • 1.2.8 — 1.2.8: Configuration files for NSCs are secured from unauthorized access and are kept consistent with active network configurations
    Remediation
    Fix: Enable Workgroup Configuration Enforcement

    Set enforceWorkgroupConfiguration to true in the Athena Workgroup configuration to ensure clients use the workgroup’s settings:

    const workgroup = new athena.Workgroup("my-workgroup", {
        name: "my-workgroup",
        configuration: {
            enforceWorkgroupConfiguration: true, // Enforce workgroup configuration on clients
            resultConfiguration: {
                outputLocation: "s3://my-query-results/",
                // Required by `athena-workgroup-disallow-unencrypted-workgroup`, which runs in the
                // same packs: a result configuration with no encryption block is reported there.
                encryptionConfiguration: {
                    encryptionOption: "SSE_KMS",
                    kmsKeyArn: kmsKey.arn,
                },
            },
        },
    });
    

    audit-admin-actions-logged

    Severity: high · Enforcement: advisory

    Ensures CloudTrail captures all administrative actions

    • 10.2.1.2 — 10.2.1.2: Audit logs capture all actions taken by any individual with administrative access *
    • 10.2.1.5 — 10.2.1.5: Audit logs capture creation and deletion of system-level objects *
    • 10.2.1.6 — 10.2.1.6: Audit logs capture initialization, stopping, or pausing of the audit logs *
    • 10.2.1.7 — 10.2.1.7: Audit logs capture creation and deletion of system level objects *
    Remediation
    Fix: Enable CloudTrail Management Events for Administrative Actions

    Configure CloudTrail to capture management events with write operations to log all administrative actions:

    const trail = new aws.cloudtrail.Trail("audit-trail", {
        s3BucketName: bucket.id,
        eventSelectors: [{
            includeManagementEvents: true,  // Enable management event logging
            readWriteType: "All",            // Capture both read and write operations (or "WriteOnly" for admin actions)
        }],
    });
    

    audit-log-access-logged

    Severity: high · Enforcement: advisory

    Ensures CloudTrail has S3 data events logging enabled for all S3 buckets

    • 10.2.1.3 — 10.2.1.3: Audit logs capture all access to audit logs *
    Remediation
    Fix: Enable S3 Data Events Logging in CloudTrail

    Configure CloudTrail to log S3 data events for all buckets to capture access to audit logs.

    Use the prefix arn:aws:s3 (arn:aws-us-gov:s3 or arn:aws-cn:s3 in those partitions), which every object ARN in every bucket starts with.

    Using Event Selectors:
    const trail = new aws.cloudtrail.Trail("audit-trail", {
        s3BucketName: bucket.id,
        eventSelectors: [{
            readWriteType: "All",
            includeManagementEvents: true,
            dataResources: [{
                type: "AWS::S3::Object",
                // Prefix of every object ARN: logs data events for all S3 buckets, including the audit log bucket
                values: ["arn:aws:s3"],
            }],
        }],
    });
    
    Using Advanced Event Selectors:
    const trail = new aws.cloudtrail.Trail("audit-trail", {
        s3BucketName: bucket.id,
        advancedEventSelectors: [{
            name: "Log all S3 data events",
            fieldSelectors: [
                { field: "eventCategory", equals: ["Data"] },
                { field: "resources.type", equals: ["AWS::S3::Object"] },
                // No resources.ARN filter, so every bucket in the account is covered.
            ],
        }],
    });
    

    A resources.ARN filter that names, includes or excludes particular buckets leaves other buckets unlogged and does not satisfy this policy. Neither does a filter on another field, such as eventName or userIdentity.arn, which logs only some of each bucket’s events.

    cloudfront-distribution-configure-access-logging

    Severity: medium · Enforcement: advisory

    Checks that any CloudFront distributions have access logging configured.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Configure CloudFront Access Logging

    Either configure the legacy loggingConfig block with an S3 bucket:

    const distribution = new cloudfront.Distribution("my-distribution", {
        enabled: true,
        loggingConfig: {
            bucket: logBucket.bucketDomainName, // S3 bucket for access logs
            includeCookies: false,
            prefix: "cloudfront-logs/",
        },
        // ... other distribution configuration
    });
    

    …or use standard logging v2, which delivers access logs through CloudWatch Logs:

    const source = new aws.cloudwatch.LogDeliverySource("cf-access-logs", {
        name: "cf-access-logs",
        logType: "ACCESS_LOGS",
        resourceArn: distribution.arn, // the distribution these logs come from
    });
    
    const destination = new aws.cloudwatch.LogDeliveryDestination("cf-access-logs", {
        name: "cf-access-logs",
        deliveryDestinationConfiguration: {
            destinationResourceArn: logGroup.arn,
        },
    });
    
    new aws.cloudwatch.LogDelivery("cf-access-logs", {
        deliverySourceName: source.name,
        deliveryDestinationArn: destination.arn,
    });
    

    cloudfront-distribution-configure-secure-tls

    Severity: high · Enforcement: advisory

    Checks that CloudFront distributions uses secure/modern TLS encryption.

    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Configure Secure TLS Protocol Version for CloudFront

    minimumProtocolVersion can only be raised on a distribution that serves a custom certificate. The provider documents it as “Can only be set if cloudfrontDefaultCertificate = false”, and “If you have specified cloudfrontDefaultCertificate, TLSv1 must be specified” — so a distribution on the default *.cloudfront.net certificate is pinned to TLSv1 and cannot be fixed by adding the property. Attach an ACM (or IAM) certificate first, then set the security policy:

    const distribution = new cloudfront.Distribution("my-distribution", {
        enabled: true,
        aliases: ["www.example.com"],
        origins: [/* ... */],
        defaultCacheBehavior: {/* ... */},
        viewerCertificate: {
            // A custom certificate is what makes a modern TLS floor settable at all.
            acmCertificateArn: certificate.arn, // must be issued in us-east-1
            sslSupportMethod: "sni-only",
            minimumProtocolVersion: "TLSv1.2_2021", // Use secure TLS 1.2 with modern cipher suites
        },
    });
    

    cloudfront-distribution-configure-secure-tls-to-origin

    Severity: high · Enforcement: advisory

    Checks that CloudFront distributions communicate with custom origins using TLS 1.2 encryption only.

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Configure TLS 1.2 for CloudFront Custom Origins

    Set the originSslProtocols in your custom origin configuration to only allow TLS 1.2:

    const distribution = new cloudfront.Distribution("my-distribution", {
        enabled: true,
        origins: [{
            domainName: "my-origin.example.com",
            originId: "myCustomOrigin",
            customOriginConfig: {
                httpPort: 80,
                httpsPort: 443,
                originProtocolPolicy: "https-only",
                originSslProtocols: ["TLSv1.2"], // Only allow TLS 1.2
            },
        }],
        defaultCacheBehavior: {
            targetOriginId: "myCustomOrigin",
            viewerProtocolPolicy: "redirect-to-https",
            allowedMethods: ["GET", "HEAD"],
            cachedMethods: ["GET", "HEAD"],
            forwardedValues: {
                queryString: false,
                cookies: { forward: "none" },
            },
        },
    });
    

    cloudfront-distribution-configure-waf

    Severity: high · Enforcement: advisory

    Checks that any CloudFront distribution has a WAF ACL associated.

    • 6.4.2 — 6.4.2: For public-facing web applications, an automated technical solution is deployed that continually detects and prevents web-based attacks
    Remediation
    Fix: Associate WAF Web ACL with CloudFront Distribution

    Set the webAclId property to attach a WAF Web ACL to your CloudFront distribution:

    const distribution = new aws.cloudfront.Distribution("my-distribution", {
        enabled: true,
        webAclId: webAcl.arn, // Associate WAF Web ACL for protection
        origins: [{
            domainName: bucket.bucketRegionalDomainName,
            originId: "myS3Origin",
        }],
        defaultCacheBehavior: {
            targetOriginId: "myS3Origin",
            viewerProtocolPolicy: "redirect-to-https",
            allowedMethods: ["GET", "HEAD"],
            cachedMethods: ["GET", "HEAD"],
            forwardedValues: {
                queryString: false,
                cookies: { forward: "none" },
            },
        },
    });
    

    cloudfront-distribution-disallow-unencrypted-traffic

    Severity: critical · Enforcement: advisory

    Checks that CloudFront distributions only allow encypted ingress traffic.

    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Enforce HTTPS for CloudFront Distribution

    Set the viewerProtocolPolicy to “redirect-to-https” or “https-only” for both default and ordered cache behaviors to ensure all traffic is encrypted:

    const distribution = new cloudfront.Distribution("my-distribution", {
        enabled: true,
        defaultCacheBehavior: {
            targetOriginId: origin.originId,
            viewerProtocolPolicy: "redirect-to-https", // Enforce HTTPS for all traffic
            allowedMethods: ["GET", "HEAD"],
            cachedMethods: ["GET", "HEAD"],
            forwardedValues: {
                queryString: false,
                cookies: { forward: "none" },
            },
        },
        orderedCacheBehaviors: [{
            pathPattern: "/api/*",
            targetOriginId: origin.originId,
            viewerProtocolPolicy: "https-only", // Enforce HTTPS-only for API paths
            allowedMethods: ["GET", "HEAD", "OPTIONS"],
            cachedMethods: ["GET", "HEAD"],
            forwardedValues: {
                queryString: true,
                cookies: { forward: "none" },
            },
        }],
        origins: [origin],
        viewerCertificate: {
            cloudfrontDefaultCertificate: true,
        },
    });
    

    cloudfront-distribution-enable-tls-to-origin

    Severity: critical · Enforcement: advisory

    Checks that CloudFront distributions communicate with custom origins using TLS encryption.

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Enable HTTPS-Only Protocol for CloudFront Custom Origins

    Set the originProtocolPolicy to “https-only” in the customOriginConfig for all custom origins to ensure encrypted communication:

    const distribution = new cloudfront.Distribution("my-distribution", {
        enabled: true,
        origins: [{
            domainName: "my-custom-origin.example.com",
            originId: "my-custom-origin",
            customOriginConfig: {
                httpPort: 80,
                httpsPort: 443,
                originProtocolPolicy: "https-only", // Enforce TLS encryption to origin
                originSslProtocols: ["TLSv1.2"],
            },
        }],
        defaultCacheBehavior: {
            targetOriginId: "my-custom-origin",
            viewerProtocolPolicy: "redirect-to-https",
            allowedMethods: ["GET", "HEAD"],
            cachedMethods: ["GET", "HEAD"],
            forwardedValues: {
                queryString: false,
                cookies: { forward: "none" },
            },
        },
    });
    

    cloudtrail-cloudwatch-logs-integration

    Severity: high · Enforcement: advisory

    Ensures CloudTrail trails have CloudWatch Logs integration enabled for real-time monitoring and analysis.

    • 10.3.3 — 10.3.3: Audit log files, including those for externalfacing technologies, are promptly backed up to a secure, central, internal log server(s) or other media that is difficult to modify
    Remediation
    Fix: Configure CloudWatch Logs Integration for CloudTrail

    Add the CloudWatch Logs group ARN and IAM role ARN to your CloudTrail trail configuration, and grant that role permission to write to the log group — a trail that names a destination the role cannot write to is configured but delivers nothing. CloudTrail requires the log stream wildcard (:*) on the log group ARN, and checks the role’s access when the trail is created:

    import * as aws from "@pulumi/aws";
    import * as pulumi from "@pulumi/pulumi";
    
    const account = aws.getCallerIdentityOutput();
    const region = aws.getRegionOutput();
    
    // The key policy must let the regional CloudWatch Logs service encrypt the log group with this key.
    const logsKey = new aws.kms.Key("trail-logs-key", {
        description: "Encrypts the CloudTrail CloudWatch Logs log group",
        enableKeyRotation: true,
        policy: pulumi.all([account.accountId, region.region]).apply(([accountId, regionName]) => JSON.stringify({
            Version: "2012-10-17",
            Statement: [
                {
                    Effect: "Allow",
                    Principal: { AWS: `arn:aws:iam::${accountId}:root` },
                    Action: [
                        "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*",
                        "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:TagResource",
                        "kms:UntagResource", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion",
                    ],
                    Resource: "*",
                },
                {
                    Effect: "Allow",
                    Principal: { Service: `logs.${regionName}.amazonaws.com` },
                    Action: ["kms:Encrypt*", "kms:Decrypt*", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:Describe*"],
                    Resource: "*",
                    Condition: {
                        ArnLike: { "kms:EncryptionContext:aws:logs:arn": `arn:aws:logs:${regionName}:${accountId}:log-group:*` },
                    },
                },
            ],
        })),
    });
    
    const logGroup = new aws.cloudwatch.LogGroup("trail-log-group", {
        retentionInDays: 365,
        kmsKeyId: logsKey.arn,
    });
    
    const trailRole = new aws.iam.Role("trail-cloudwatch-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: { Service: "cloudtrail.amazonaws.com" },
                Action: "sts:AssumeRole",
            }],
        }),
    });
    
    const trailRolePolicy = new aws.iam.RolePolicy("trail-cloudwatch-policy", {
        role: trailRole.id,
        policy: pulumi.all([logGroup.arn]).apply(([logGroupArn]) => JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
                Resource: `${logGroupArn}:*`,
            }],
        })),
    });
    
    const trail = new aws.cloudtrail.Trail("my-trail", {
        s3BucketName: bucket.id,
        cloudWatchLogsGroupArn: pulumi.interpolate`${logGroup.arn}:*`,  // CloudTrail requires the log stream wildcard
        cloudWatchLogsRoleArn: trailRole.arn,  // Add this field
    }, { dependsOn: [trailRolePolicy] });  // The role must be able to write before CloudTrail accepts it
    

    cloudtrail-enabled

    Severity: critical · Enforcement: disabled

    Ensures CloudTrail is enabled with at least one active trail for audit logging.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    • 10.2.1.4 — 10.2.1.4: Audit logs capture all invalid access attempts *
    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

    cloudtrail-kms-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures CloudTrail trails have encryption enabled using KMS keys.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable KMS Encryption for CloudTrail

    Add a customer-managed KMS key to your CloudTrail trail configuration:

    import * as aws from "@pulumi/aws";
    
    // Create or reference a KMS key for CloudTrail encryption
    const trailKey = new aws.kms.Key("trail-key", {
        description: "KMS key for CloudTrail log encryption",
        enableKeyRotation: true,
    });
    
    const trail = new aws.cloudtrail.Trail("my-trail", {
        s3BucketName: bucket.id,
        kmsKeyId: trailKey.arn, // Add the KMS key ARN to enable encryption
    });
    

    cloudtrail-log-file-validation-enabled

    Severity: high · Enforcement: advisory

    Ensures CloudTrail trails have log file validation enabled to protect audit log integrity.

    • 10.3.4 — 10.3.4: File integrity monitoring or change-detection mechanisms is used on audit logs to ensure that existing log data cannot be changed without generating alerts *
    Remediation
    Fix: Enable CloudTrail Log File Validation

    Update your CloudTrail trail configuration to enable log file validation:

    import * as aws from "@pulumi/aws";
    
    const trail = new aws.cloudtrail.Trail("my-trail", {
        s3BucketName: "my-cloudtrail-bucket",
        enableLogFileValidation: true, // Enable log file validation to ensure audit log integrity
    });
    

    This ensures CloudTrail logs are cryptographically signed, allowing you to verify they haven’t been modified after delivery.

    cloudtrail-multi-region-enabled

    Severity: high · Enforcement: advisory

    Ensures CloudTrail trails are configured as multi-region trails for comprehensive audit coverage.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable Multi-Region Trail Configuration

    Set the isMultiRegionTrail property to true to enable comprehensive audit coverage across all AWS regions.

    const trail = new aws.cloudtrail.Trail("audit-trail", {
        s3BucketName: bucket.bucket,
        isMultiRegionTrail: true, // Enable multi-region trail for system-wide audit coverage
    });
    

    cloudtrail-s3-bucket-public-access-denied

    Severity: high · Enforcement: advisory

    Ensures S3 buckets used for CloudTrail logging deny public access to protect audit information.

    • 10.3.2 — 10.3.2: Audit log files are protected to prevent modifications by individuals *
    Remediation
    Fix: Configure S3 Bucket Public Access Block for CloudTrail

    Add a BucketPublicAccessBlock resource to the CloudTrail S3 bucket with all public access settings enabled:

    import * as aws from "@pulumi/aws";
    
    const cloudtrailBucket = new aws.s3.Bucket("cloudtrail-logs", {
        bucket: "my-cloudtrail-logs"
    });
    
    // Enable public access block to protect audit logs
    const publicAccessBlock = new aws.s3.BucketPublicAccessBlock("cloudtrail-public-access-block", {
        bucket: cloudtrailBucket.id,
        blockPublicAcls: true,        // Block public ACLs
        blockPublicPolicy: true,      // Block public bucket policies
        ignorePublicAcls: true,       // Ignore existing public ACLs
        restrictPublicBuckets: true,  // Restrict public bucket access
    });
    

    cloudtrail-s3-data-events-enabled

    Severity: high · Enforcement: advisory

    Ensures CloudTrail trails have S3 data events enabled for comprehensive object-level logging.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    • 10.2.1.1 — 10.2.1.1: Audit logs capture all individual user access to cardholder data *
    Remediation
    Fix: Enable S3 Data Events in CloudTrail

    Configure your CloudTrail trail to log S3 object-level API activity by adding event selectors or advanced event selectors.

    Use arn:aws:s3 for every bucket in the account, or a bucket’s ARN followed by / (for example arn:aws:s3:::my-bucket/) for all objects in one bucket.

    Using Event Selectors:
    import * as aws from "@pulumi/aws";
    
    const trail = new aws.cloudtrail.Trail("my-trail", {
        s3BucketName: trailBucket.bucket,
        eventSelectors: [{
            readWriteType: "All", // Log both read and write operations
            includeManagementEvents: true,
            dataResources: [{
                type: "AWS::S3::Object",
                values: ["arn:aws:s3"], // Prefix of every object ARN: covers every bucket in the account
            }],
        }],
    });
    
    const trail = new aws.cloudtrail.Trail("my-trail", {
        s3BucketName: trailBucket.bucket,
        advancedEventSelectors: [{
            name: "Log all S3 data events",
            fieldSelectors: [
                {
                    field: "eventCategory",
                    equals: ["Data"], // Enable data event logging
                },
                {
                    field: "resources.type",
                    equals: ["AWS::S3::Object"], // Target S3 objects
                },
                // No resources.ARN filter, so every bucket in the account is covered.
            ],
        }],
    });
    

    cloudwatch-alarms-actions-required

    Severity: high · Enforcement: advisory

    Ensures CloudWatch alarms have actions enabled and configured for proper incident response.

    • 10.7.2 — 10.7.2: Failures in critical security control systems are promptly identified and addressed *
    • 12.10.5 — 12.10.5: The security incident response plan includes monitoring and responding to alerts from security monitoring systems *
    Remediation
    Fix: Enable CloudWatch Alarm Actions

    Leave actionsEnabled unset (it defaults to true) or set it to true, and give the alarm at least one action target, such as an SNS topic, for the ALARM state so the alarm reaches your incident response process. If the policy is configured with requireOkActions or requireInsufficientDataActions, set okActions or insufficientDataActions the same way.

    import * as aws from "@pulumi/aws";
    
    // Create SNS topic for alarm notifications
    const alarmTopic = new aws.sns.Topic("alarm-topic", {
        displayName: "CloudWatch Alarm Notifications",
    });
    
    // Create CloudWatch alarm with actions enabled
    const alarm = new aws.cloudwatch.MetricAlarm("my-alarm", {
        comparisonOperator: "GreaterThanThreshold",
        evaluationPeriods: 2,
        metricName: "CPUUtilization",
        namespace: "AWS/EC2",
        period: 120,
        statistic: "Average",
        threshold: 80,
        actionsEnabled: true,  // Enable actions (default: true)
        alarmActions: [alarmTopic.arn],  // Add SNS topic ARN for ALARM state
    });
    

    cloudwatch-log-group-kms-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures CloudWatch log groups have encryption enabled using KMS keys.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable KMS Encryption for CloudWatch Log Group

    Set kmsKeyId on the log group to the ARN of a KMS key to encrypt its log data at rest:

    import * as aws from "@pulumi/aws";
    
    const logGroup = new aws.cloudwatch.LogGroup("my-log-group", {
        name: "/aws/my-application",
        kmsKeyId: kmsKey.arn,  // Add KMS key ARN to enable encryption
        retentionInDays: 365,  // cloudwatch-log-retention requires at least 365 days by default
    });
    

    cloudwatch-log-retention

    Severity: medium · Enforcement: advisory

    Ensures CloudWatch log groups have appropriate retention periods for compliance.

    • 10.5.1 — 10.5.1: Retain audit log history for at least 12 months, with at least the most recent three months immediately available for analysis *
    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
    });
    

    config-recorder-enabled

    Severity: critical · Enforcement: disabled

    Ensures AWS Config configuration recorders are enabled for tracking and auditing resource changes.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable AWS Config Recorder

    Create a Config Recorder, the IAM role it assumes, a delivery channel, and a RecorderStatus to start it. AWS will not start a recorder that has no delivery channel:

    import * as aws from "@pulumi/aws";
    import * as pulumi from "@pulumi/pulumi";
    
    // IAM role AWS Config assumes, with the AWS managed policy for Config attached
    const configRole = new aws.iam.Role("config-role", {
        assumeRolePolicy: {
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: { Service: "config.amazonaws.com" },
                Action: "sts:AssumeRole",
            }],
        },
    });
    
    new aws.iam.RolePolicyAttachment("config-role-managed-policy", {
        role: configRole.name,
        policyArn: "arn:aws:iam::aws:policy/service-role/AWS_ConfigRole",
    });
    
    // S3 bucket the delivery channel writes configuration history and snapshots to
    const configBucket = new aws.s3.Bucket("config-delivery");
    const accountId = aws.getCallerIdentityOutput().accountId;
    
    // Let the role deliver to that bucket
    const configDeliveryPolicy = new aws.iam.RolePolicy("config-delivery-policy", {
        role: configRole.name,
        policy: {
            Version: "2012-10-17",
            Statement: [
                {
                    Effect: "Allow",
                    Action: ["s3:PutObject", "s3:PutObjectAcl"],
                    Resource: pulumi.interpolate`${configBucket.arn}/AWSLogs/${accountId}/*`,
                    Condition: { StringLike: { "s3:x-amz-acl": "bucket-owner-full-control" } },
                },
                {
                    Effect: "Allow",
                    Action: "s3:GetBucketAcl",
                    Resource: configBucket.arn,
                },
            ],
        },
    });
    
    // Create Config Recorder
    const recorder = new aws.cfg.Recorder("config-recorder", {
        recordingGroup: {
            allSupported: true,  // Record all supported resource types
            includeGlobalResourceTypes: true,  // Include global resources (IAM, etc.)
        },
        roleArn: configRole.arn,  // IAM role with Config permissions
    });
    
    // A delivery channel needs the recorder to exist, and the recorder will not start without one
    const deliveryChannel = new aws.cfg.DeliveryChannel("config-delivery-channel", {
        s3BucketName: configBucket.bucket,
    }, { dependsOn: [recorder, configDeliveryPolicy] });
    
    // Activate the recorder
    const recorderStatus = new aws.cfg.RecorderStatus("config-recorder-status", {
        name: recorder.name,
        isEnabled: true,  // Enable the recorder
    }, { dependsOn: [deliveryChannel] });
    

    config-rule-auto-remediation-enabled

    Severity: high · Enforcement: advisory

    Ensures AWS Config rules have automatic remediation configured for integrity violations.

    • 10.4.3 — 10.4.3: Exceptions and anomalies identified during the review process are addressed *
    Remediation
    Fix: Configure Automatic Remediation for AWS Config Rules
    const remediationConfig = new aws.cfg.RemediationConfiguration("remediationConfig", {
        configRuleName: configRule.name,
        automatic: true,
        maximumAutomaticAttempts: 5,
        retryAttemptSeconds: 60,
    });
    

    config-snapshot-retention

    Severity: medium · Enforcement: advisory

    Ensures AWS Config retention configuration meets minimum 7-year requirement for compliance auditing.

    • 10.5.1 — 10.5.1: Retain audit log history for at least 12 months, with at least the most recent three months immediately available for analysis *
    Remediation
    Fix: Set AWS Config retention period to minimum 7 years

    Configure the AWS Config retention configuration to retain snapshots for at least 2555 days (7 years):

    import * as aws from "@pulumi/aws";
    
    const configRetention = new aws.cfg.RetentionConfiguration("config-retention", {
        retentionPeriodInDays: 2555, // Set to 7 years (minimum required)
    });
    

    database-strict-egress

    Severity: critical · Enforcement: advisory

    Ensures database security groups have strict egress controls

    • 1.3.2 — 1.3.2: Outbound traffic from the CDE is restricted
    Remediation
    Fix: Restrict Security Group Egress Rules
    const dbSecurityGroup = new aws.ec2.SecurityGroup("db-sg", {
        egress: [{
            protocol: "tcp",
            fromPort: 443,
            toPort: 443,
            cidrBlocks: ["10.0.0.0/16"], // Not 0.0.0.0/0
        }],
    });
    

    database-strict-network-access

    Severity: critical · Enforcement: advisory

    Ensures RDS instances have strict network access controls

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    Remediation
    Fix: Configure Strict Network Access for Database Resources

    Restrict database access to specific internal sources only. Never allow public access (0.0.0.0/0) to database ports.

    // Create a security group with restricted ingress
    const dbSecurityGroup = new aws.ec2.SecurityGroup("db-sg", {
        vpcId: vpc.id,
        ingress: [{
            protocol: "tcp",
            fromPort: 3306,
            toPort: 3306,
            cidrBlocks: ["10.0.0.0/16"], // Restrict to internal VPC CIDR only
        }],
    });
    
    // Associate security group with RDS instance
    const db = new aws.rds.Instance("my-db", {
        engine: "mysql",
        instanceClass: "db.t3.micro",
        allocatedStorage: 20,
        vpcSecurityGroupIds: [dbSecurityGroup.id], // Attach security group
        dbSubnetGroupName: dbSubnetGroup.name,
    });
    
    // Ensure subnet group has at least 2 subnets
    const dbSubnetGroup = new aws.rds.SubnetGroup("db-subnet-group", {
        subnetIds: [privateSubnet1.id, privateSubnet2.id], // Minimum 2 subnets
    });
    

    dms-no-public-access

    Severity: high · Enforcement: advisory

    Ensures DMS replication instances are not publicly accessible to maintain security.

    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    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
    });
    

    docdb-clusterinstance-managed-service-patching

    Severity: medium · Enforcement: advisory

    Ensures DocumentDB cluster instances have automated minor version upgrades enabled

    • 6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
    Remediation
    Fix: Enable Automatic Minor Version Upgrades for DocumentDB Cluster Instance

    Set the autoMinorVersionUpgrade property to true to enable automated patching for managed service security updates:

    const clusterInstance = new aws.docdb.ClusterInstance("my-cluster-instance", {
        clusterIdentifier: cluster.id,
        instanceClass: "db.r5.large",
        autoMinorVersionUpgrade: true, // Enable automatic minor version upgrades
    });
    

    dynamodb-kms-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures DynamoDB tables are encrypted with a customer managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    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

    • 11.5.2 — 11.5.2: A change-detection mechanism (for example, file integrity monitoring tools) is deployed
    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-unused-volumes-prohibited

    Severity: low · Enforcement: advisory

    EBS volumes must be removed when unused

    • 3.2.1 — 3.2.1: Account data storage is kept to a minimum through implementation of data retention and disposal policies, procedures, and processes
    Remediation
    Fix: Attach EBS volume to an EC2 instance or delete if unused

    Either attach the unattached EBS volume to an EC2 instance, or delete it if no longer needed:

    import * as aws from "@pulumi/aws";
    
    // Option 1: Attach the volume to an EC2 instance
    const volumeAttachment = new aws.ec2.VolumeAttachment("my-volume-attachment", {
        instanceId: instance.id,
        volumeId: volume.id,  // Attach the previously unattached volume
        deviceName: "/dev/sdh",
    });
    
    // Option 2: Delete the unused volume resource from your Pulumi program
    // Simply remove the aws.ebs.Volume resource definition if no longer needed
    

    ebs-volume-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Check that encrypted EBS volumes use a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for EBS Volume

    Specify a customer-managed KMS key using the kmsKeyId property when creating an encrypted EBS volume:

    const volume = new aws.ebs.Volume("my-volume", {
        availabilityZone: "us-west-2a",
        size: 100,
        encrypted: true,
        kmsKeyId: customerKey.arn, // Use customer-managed KMS key instead of default AWS-managed key
    });
    

    ebs-volume-disallow-unencrypted-volume

    Severity: high · Enforcement: advisory

    Checks that EBS volumes are encrypted.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable EBS Volume Encryption

    Set the encrypted property to true on the EBS volume:

    const volume = new aws.ebs.Volume("my-volume", {
        availabilityZone: "us-west-2a",
        size: 100,
        encrypted: true, // Enable encryption for the volume
    });
    

    ec2-iam-profile-required

    Severity: high · Enforcement: advisory

    EC2 instances must have IAM profile attached

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 8.2.1 — 8.2.1: All users are assigned a unique ID before access to system components or cardholder data is allowed *
    Remediation
    Fix: Attach IAM Instance Profile
    const role = new aws.iam.Role("ec2-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Action: "sts:AssumeRole",
                Effect: "Allow",
                Principal: { Service: "ec2.amazonaws.com" },
            }],
        }),
    });
    
    const profile = new aws.iam.InstanceProfile("ec2-profile", {
        role: role.name,
    });
    
    const instance = new aws.ec2.Instance("app-server", {
        ami: "ami-12345678",
        instanceType: "t3.medium",
        iamInstanceProfile: profile.name,  // Attach IAM instance profile for role-based access
        subnetId: subnet.id,
    });
    

    ec2-instance-disallow-public-ip

    Severity: high · Enforcement: advisory

    Checks that EC2 instances do not have a public IP address.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Public IP Address Assignment

    Set associatePublicIpAddress to false to prevent the instance from receiving a public IP address:

    const instance = new ec2.Instance("my-instance", {
        instanceType: "t3.micro",
        ami: "ami-12345678",
        associatePublicIpAddress: false, // Disable public IP assignment
        subnetId: privateSubnet.id,
    });
    

    ec2-instance-disallow-unencrypted-block-device

    Severity: high · Enforcement: advisory

    Checks that EC2 instances do not have unencrypted block devices.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    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.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    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-instance-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that EC2 instances do not launch with an instance profile whose role holds the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Launch with a Role Holding a Scoped Policy

    Attach a policy scoped to the workload’s job to the instance profile’s role, instead of AdministratorAccess, and launch the instance with that profile’s name in iamInstanceProfile:

    const role = new aws.iam.Role("app", {
        assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "ec2.amazonaws.com" }),
    });
    
    new aws.iam.RolePolicyAttachment("app-read-only", {
        role: role.name,
        policyArn: aws.iam.ManagedPolicy.ReadOnlyAccess,
    });
    
    const profile = new aws.iam.InstanceProfile("app", { role: role.name });
    

    ec2-launch-configuration-disallow-public-ip

    Severity: high · Enforcement: advisory

    Checks that EC2 Launch Configurations do not have a public IP address.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Public IP Assignment for Launch Configuration

    Set the associatePublicIpAddress property to false to prevent instances from receiving public IP addresses:

    const launchConfig = new aws.ec2.LaunchConfiguration("my-launch-config", {
        imageId: "ami-12345678",
        instanceType: "t3.micro",
        associatePublicIpAddress: false, // Disable public IP assignment
        securityGroups: [securityGroup.id],
    });
    

    ec2-launch-configuration-disallow-unencrypted-block-device

    Severity: high · Enforcement: advisory

    Checks that EC2 Launch Configurations do not have unencrypted block devices.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    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-configuration-disallow-unencrypted-root-block-device

    Severity: high · Enforcement: advisory

    Checks that EC2 launch configuration do not have unencrypted root block device.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Encryption for Root Block Device

    Set the encrypted property to true in the rootBlockDevice configuration:

    const launchConfig = new ec2.LaunchConfiguration("my-launch-config", {
        imageId: "ami-0c55b159cbfafe1f0",
        instanceType: "t2.micro",
        rootBlockDevice: {
            encrypted: true, // Enable encryption for root block device
        },
    });
    

    ec2-launch-template-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Check that encrypted EBS volume uses a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Specify Customer-Managed KMS Key for Encrypted EBS Volumes

    Add the kmsKeyId property to encrypted EBS block device mappings in your Launch Template:

    const launchTemplate = new aws.ec2.LaunchTemplate("my-template", {
        blockDeviceMappings: [{
            ebs: {
                encrypted: "true",
                kmsKeyId: customerManagedKey.id, // Specify customer-managed KMS key
                volumeSize: 20,
            },
        }],
    });
    

    ec2-launch-template-disallow-public-ip

    Severity: high · Enforcement: advisory

    Checks that EC2 Launch Templates do not have public IP addresses.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Public IP Assignment in Launch Template

    Set associatePublicIpAddress to the string "false" on the network interface. The provider types this field as a string, and omitting it does not disable the address: the instance then takes the subnet’s auto-assign public IP setting.

    const launchTemplate = new aws.ec2.LaunchTemplate("my-template", {
        imageId: "ami-12345678",
        instanceType: "t3.micro",
        networkInterfaces: [{
            associatePublicIpAddress: "false", // Disable public IP assignment
            securityGroups: [securityGroup.id],
            subnetId: privateSubnet.id,
        }],
    });
    

    ec2-launch-template-disallow-unencrypted-block-device

    Severity: high · Enforcement: advisory

    Checks that EC2 Launch Templates do not have unencrypted block device.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    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",
            },
        }],
    });
    

    ec2-monitoring-enabled

    Severity: low · Enforcement: advisory

    EC2 instances must have detailed monitoring enabled

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable Detailed Monitoring on EC2 Instance

    Set the monitoring property to true on your EC2 instance resource:

    const instance = new aws.ec2.Instance("my-instance", {
        ami: "ami-12345678",
        instanceType: "t3.micro",
        monitoring: true, // Enable detailed (1-minute interval) monitoring
    });
    

    ec2-security-group-disallow-inbound-http-traffic

    Severity: critical · Enforcement: advisory

    Check that EC2 Security Groups do not allow inbound HTTP traffic.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Remove HTTP Inbound Traffic Rules

    Remove any ingress rules that allow HTTP traffic on port 80, or use HTTPS (port 443) instead:

    const securityGroup = new ec2.SecurityGroup("my-sg", {
        vpcId: vpc.id,
        ingress: [
            {
                protocol: "tcp",
                fromPort: 443, // Use HTTPS instead of HTTP (port 80)
                toPort: 443,
                cidrBlocks: ["0.0.0.0/0"],
            },
            // Remove any rules whose port range covers 80, and any rule with protocol "-1"/"all",
            // which opens every port.
        ],
    });
    
    // The same applies to standalone rules, which are the provider's current best practice:
    new aws.vpc.SecurityGroupIngressRule("my-sg-https", {
        securityGroupId: securityGroup.id,
        ipProtocol: "tcp",
        fromPort: 443,
        toPort: 443,
        cidrIpv4: "0.0.0.0/0",
    });
    

    ec2-vpc-placement-required

    Severity: high · Enforcement: advisory

    EC2 instances must be placed in VPC for network isolation

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    Remediation
    Fix: Place EC2 Instance in a VPC Subnet

    Add the subnetId property to your EC2 instance resource to ensure it’s deployed within a VPC for proper network isolation.

    new aws.ec2.Instance("my-instance", {
        ami: "ami-12345678",
        instanceType: "t3.micro",
        subnetId: mySubnet.id, // Specify a subnet to place instance in VPC
        // ... other configuration
    });
    

    ecr-repository-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Checks that ECR repositories use a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for ECR Repository

    Set the encryptionConfiguration with encryptionType to “KMS” and provide a kmsKey ARN to use customer-managed encryption:

    const kmsKey = new aws.kms.Key("ecr-key", {
        description: "KMS key for ECR repository encryption",
    });
    
    const repo = new ecr.Repository("my-repo", {
        encryptionConfigurations: [{
            encryptionType: "KMS", // Use KMS encryption instead of AES256
            kmsKey: kmsKey.arn, // Specify customer-managed KMS key
        }],
    });
    

    ecr-repository-disallow-mutable-image

    Severity: high · Enforcement: advisory

    Checks that ECR Repositories have immutable images enabled.

    • 11.5.2 — 11.5.2: A change-detection mechanism (for example, file integrity monitoring tools) is deployed
    Remediation
    Fix: Enable Immutable Image Tags

    Set the imageTagMutability property to “IMMUTABLE” to prevent image tags from being overwritten:

    const repository = new ecr.Repository("my-repository", {
        imageTagMutability: "IMMUTABLE", // Prevent image tags from being overwritten
    });
    

    ecr-repository-disallow-unencrypted-repository

    Severity: high · Enforcement: advisory

    Checks that ECR Repositories are encrypted.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Encryption for ECR Repository

    Configure the encryptionConfigurations property to enable encryption at rest for your ECR repository:

    const repo = new ecr.Repository("my-repo", {
        name: "my-application-repo",
        encryptionConfigurations: [{ // Enable encryption
            encryptionType: "AES256", // Use AWS managed encryption (or "KMS" for customer-managed keys)
        }],
    });
    

    ecs-task-definition-image-scanning

    Severity: medium · Enforcement: advisory

    Ensures ECS task definitions use images from repositories with vulnerability scanning

    • 11.3.1 — 11.3.1: Internal vulnerability scans are performed
    Remediation
    Fix: Use ECR Images with Scanning Enabled

    Update your ECS task definition to use container images from ECR repositories that have vulnerability scanning enabled:

    const taskDefinition = new aws.ecs.TaskDefinition("my-task", {
        family: "my-app",
        containerDefinitions: JSON.stringify([{
            name: "app",
            image: "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest", // Use ECR image with scanning
            memory: 512,
            cpu: 256,
        }]),
        requiresCompatibilities: ["FARGATE"],
        networkMode: "awsvpc",
        cpu: "256",
        memory: "512",
    });
    

    ecs-task-non-privileged-required

    Severity: high · Enforcement: advisory

    ECS task definitions must use non-privileged user for host mode

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    Remediation
    Fix: Remove Privileged Container Settings
    const taskDefinition = new aws.ecs.TaskDefinition("app-task", {
        family: "app-task",
        containerDefinitions: JSON.stringify([{
            name: "app-container",
            image: "nginx:latest",
            privileged: false,  // Set to false to disable privileged mode
            user: "1000:1000",  // For host network mode: specify non-root user (UID:GID)
            linuxParameters: {
                capabilities: {
                    add: ["NET_BIND_SERVICE"],  // Avoid SYS_ADMIN, NET_ADMIN, or ALL
                },
            },
        }]),
    });
    

    efs-file-system-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Check that encrypted EFS File system uses a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for EFS Encryption

    Set the kmsKeyId property to specify a customer-managed KMS key for EFS file system encryption:

    const fileSystem = new aws.efs.FileSystem("my-efs", {
        encrypted: true,
        kmsKeyId: kmsKey.arn, // Use customer-managed KMS key instead of AWS-managed key
        lifecyclePolicy: {
            transitionToIa: "AFTER_30_DAYS",
        },
    });
    

    efs-file-system-disallow-unencrypted-file-system

    Severity: high · Enforcement: advisory

    Checks that EFS File Systems do not have an unencrypted file system.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Encryption for EFS File System

    Set the encrypted property to true when creating an EFS file system:

    const fileSystem = new aws.efs.FileSystem("my-efs", {
        encrypted: true, // Enable encryption at rest
        kmsKeyId: kmsKey.arn, // Optional: specify a custom KMS key
    });
    

    eks-cluster-disallow-api-endpoint-public-access

    Severity: critical · Enforcement: advisory

    Check that EKS Clusters API Endpoint are not publicly accessible.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Public Access to EKS Cluster API Endpoint

    Set endpointPublicAccess to false and endpointPrivateAccess to true in the VPC configuration:

    const cluster = new aws.eks.Cluster("my-cluster", {
        roleArn: clusterRole.arn,
        vpcConfig: {
            subnetIds: privateSubnetIds,
            endpointPublicAccess: false, // Disable public access to API endpoint
            endpointPrivateAccess: true, // Enable private access for internal communication
        },
    });
    

    If the public endpoint must stay enabled, set publicAccessCidrs to the networks that need access. Leaving it unset or empty allows 0.0.0.0/0, and a list containing 0.0.0.0/0 or ::/0 is still reported:

    const cluster = new aws.eks.Cluster("my-cluster", {
        roleArn: clusterRole.arn,
        vpcConfig: {
            subnetIds: privateSubnetIds,
            endpointPublicAccess: true,
            publicAccessCidrs: ["203.0.113.0/24"], // Only the networks that need access
        },
    });
    

    eks-cluster-enable-cluster-encryption-config

    Severity: high · Enforcement: advisory

    Check that EKS Cluster Encryption Config is enabled.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable EKS Cluster Encryption Configuration

    Configure the encryptionConfig property to encrypt Kubernetes secrets using AWS KMS:

    const cluster = new aws.eks.Cluster("my-cluster", {
        roleArn: clusterRole.arn,
        vpcConfig: {
            subnetIds: subnetIds,
        },
        encryptionConfig: { // Enable encryption for Kubernetes secrets
            provider: {
                keyArn: kmsKey.arn, // KMS key for encrypting secrets
            },
            resources: ["secrets"], // Encrypt Kubernetes secrets at rest
        },
    });
    

    elasticbeanstalk-health-reporting-enabled

    Severity: medium · Enforcement: advisory

    Elastic Beanstalk must have enhanced health reporting enabled

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable Enhanced Health Reporting

    Add a setting to your Elastic Beanstalk environment configuration to enable enhanced health reporting:

    const environment = new aws.elasticbeanstalk.Environment("my-env", {
        application: app.name,
        solutionStackName: "64bit Amazon Linux 2 v5.8.0 running Node.js 18",
        settings: [
            {
                namespace: "aws:elasticbeanstalk:healthreporting:system",
                name: "SystemType",
                value: "enhanced", // Enable enhanced health reporting
            },
        ],
    });
    

    elasticbeanstalk-managed-updates-enabled

    Severity: high · Enforcement: advisory

    Elastic Beanstalk environments must have managed platform updates enabled

    • 6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
    Remediation
    Fix: Enable Managed Platform Updates

    Configure managed platform updates for your Elastic Beanstalk environment by adding the required settings:

    import * as aws from "@pulumi/aws";
    
    const environment = new aws.elasticbeanstalk.Environment("my-environment", {
        application: "my-app",
        solutionStackName: "64bit Amazon Linux 2023 v6.0.0 running Node.js 18",
        settings: [
            // Enable managed platform updates
            {
                namespace: "aws:elasticbeanstalk:managedactions",
                name: "ManagedActionsEnabled",
                value: "true",  // Required: Enable automated updates
            },
            {
                namespace: "aws:elasticbeanstalk:managedactions",
                name: "PreferredStartTime",
                value: "sun:02:00",  // Required: Set maintenance window
            },
            // Configure update level
            {
                namespace: "aws:elasticbeanstalk:managedactions:platformupdate",
                name: "UpdateLevel",
                value: "minor",  // Required: Set to "patch", "minor", or "all"
            },
            {
                namespace: "aws:elasticbeanstalk:managedactions:platformupdate",
                name: "InstanceRefreshEnabled",
                value: "true",  // Recommended: Enable for zero-downtime updates
            },
        ],
    });
    

    elasticsearch-cloudwatch-logging-enabled

    Severity: medium · Enforcement: advisory

    Elasticsearch domains must send logs to CloudWatch for audit tracking

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    • 10.3.3 — 10.3.3: Audit log files, including those for externalfacing technologies, are promptly backed up to a secure, central, internal log server(s) or other media that is difficult to modify
    Remediation
    Fix: Enable CloudWatch Logging for Elasticsearch Domain

    Add a logPublishingOptions entry for every required log type (AUDIT_LOGS by default; the requiredLogTypes config can require others), each enabled and directed to a CloudWatch log group:

    const logGroup = new aws.cloudwatch.LogGroup("es-audit-logs", {
        retentionInDays: 365,
        kmsKeyId: logsKey.arn, // a KMS key whose policy lets CloudWatch Logs use it
    });
    
    // CloudWatch Logs must allow the Elasticsearch service to write to the log group.
    const logResourcePolicy = new aws.cloudwatch.LogResourcePolicy("es-audit-logs", {
        policyName: "es-audit-logs",
        policyDocument: pulumi.jsonStringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: { Service: "es.amazonaws.com" },
                Action: ["logs:PutLogEvents", "logs:CreateLogStream"],
                Resource: pulumi.interpolate`${logGroup.arn}:*`,
            }],
        }),
    });
    
    const domain = new aws.elasticsearch.Domain("my-domain", {
        // ... other configuration; AUDIT_LOGS also requires advancedSecurityOptions to be enabled ...
        logPublishingOptions: [
            {
                logType: "AUDIT_LOGS",  // Enable audit logging
                enabled: true,
                cloudwatchLogGroupArn: logGroup.arn,
            },
        ],
    }, { dependsOn: [logResourcePolicy] });
    

    elasticsearch-encryption-enabled

    Severity: high · Enforcement: advisory

    Elasticsearch domains must have encryption at rest enabled

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Encryption at Rest for Elasticsearch Domain

    Enable the encryptAtRest configuration on your Elasticsearch domain to encrypt data stored on disk:

    const domain = new aws.elasticsearch.Domain("my-domain", {
        domainName: "my-elasticsearch-domain",
        encryptAtRest: {
            enabled: true,  // Enable encryption at rest
        },
        // ... other configuration
    });
    

    elasticsearch-https-required

    Severity: high · Enforcement: advisory

    Elasticsearch domains must require HTTPS for client connections

    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Enable HTTPS enforcement for Elasticsearch domain

    Configure the domainEndpointOptions property to enforce HTTPS connections:

    import * as aws from "@pulumi/aws";
    
    const domain = new aws.elasticsearch.Domain("my-domain", {
        domainName: "my-elasticsearch-domain",
        domainEndpointOptions: {
            enforceHttps: true, // Enable HTTPS enforcement
            tlsSecurityPolicy: "Policy-Min-TLS-1-2-PFS-2023-10", // Required TLS security policy
        },
        // ... other configuration
    });
    

    elasticsearch-node-to-node-encryption-enabled

    Severity: high · Enforcement: advisory

    Elasticsearch domains must have node-to-node encryption enabled

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Enable node-to-node encryption for Elasticsearch domain

    Add the nodeToNodeEncryption property to your Elasticsearch domain configuration and set enabled to true:

    const esDomain = new aws.elasticsearch.Domain("my-domain", {
        domainName: "my-elasticsearch-domain",
        elasticsearchVersion: "7.10",
        clusterConfig: {
            instanceType: "r5.large.elasticsearch",
        },
        nodeToNodeEncryption: {
            enabled: true, // Enable encryption for inter-node communication
        },
        // ... other configuration
    });
    

    elasticsearch-vpc-required

    Severity: high · Enforcement: advisory

    Elasticsearch domains must be deployed in VPC for network isolation

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    Remediation
    Fix: Deploy Elasticsearch Domain in VPC

    Add VPC configuration to your Elasticsearch domain to enable network isolation and boundary protection.

    import * as aws from "@pulumi/aws";
    
    const myElasticsearchDomain = new aws.elasticsearch.Domain("my-domain", {
        domainName: "my-domain",
        elasticsearchVersion: "7.10",
        clusterConfig: {
            instanceType: "r5.large.elasticsearch",
            instanceCount: 2,
        },
        // Add VPC configuration for network isolation
        vpcOptions: {
            subnetIds: [subnet1.id, subnet2.id], // Specify subnet IDs from your VPC
            securityGroupIds: [securityGroup.id], // Optional: specify security groups
        },
        ebsOptions: {
            ebsEnabled: true,
            volumeSize: 10,
        },
    });
    

    elb-load-balancer-configure-access-logging

    Severity: medium · Enforcement: advisory

    Check that ELB Load Balancers uses access logging.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable Access Logging for ELB Load Balancer

    Configure the accessLogs property with enabled: true and specify an S3 bucket for storing access logs:

    const lb = new aws.elb.LoadBalancer("my-lb", {
        availabilityZones: ["us-west-2a", "us-west-2b"],
        listeners: [{
            instancePort: 80,
            instanceProtocol: "http",
            lbPort: 80,
            lbProtocol: "http",
        }],
        accessLogs: {
            enabled: true, // Enable access logging
            bucket: "my-logs-bucket", // S3 bucket for logs
        },
    });
    

    elb-load-balancer-disallow-unencrypted-traffic

    Severity: critical · Enforcement: advisory

    Check that ELB Load Balancers do not allow unencrypted (HTTP) traffic.

    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Use HTTPS Instead of HTTP for Load Balancer Listeners

    Configure all listeners to use HTTPS (port 443) instead of HTTP (port 80) to ensure encrypted traffic:

    const lb = new aws.elb.LoadBalancer("my-lb", {
        availabilityZones: ["us-west-2a", "us-west-2b"],
        listeners: [{
            instancePort: 443,
            instanceProtocol: "https",
            lbPort: 443,
            lbProtocol: "https", // Use HTTPS instead of HTTP
            sslCertificateId: "arn:aws:iam::123456789012:server-certificate/my-cert",
        }],
    });
    

    emr-no-public-ip

    Severity: high · Enforcement: advisory

    EMR clusters must not be deployed in public subnets that auto-assign public IP addresses

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Deploy EMR Cluster in Private Subnet
    const privateSubnet = new aws.ec2.Subnet("private-subnet", {
        vpcId: vpc.id,
        cidrBlock: "10.0.1.0/24",
        mapPublicIpOnLaunch: false,  // Disable auto-assign public IP
        availabilityZone: "us-east-1a",
    });
    
    const emrCluster = new aws.emr.Cluster("my-cluster", {
        releaseLabel: "emr-6.10.0",
        serviceRole: emrServiceRole.arn,
        ec2Attributes: {
            subnetId: privateSubnet.id,  // Use private subnet without public IP auto-assignment
            instanceProfile: emrInstanceProfile.arn,
            emrManagedMasterSecurityGroup: masterSecurityGroup.id,
            emrManagedSlaveSecurityGroup: slaveSecurityGroup.id,
        },
        // ... other cluster configuration
    });
    

    If the cluster needs outbound internet access, route the private subnet through a NAT gateway or use VPC endpoints.

    environment-separation-tagging

    Severity: low · Enforcement: advisory

    Ensures that resources are tagged to distinguish between production and non-production environments

    • A1.1.2 — A1.1.2: Controls are implemented such that each customer only has permission to access its own cardholder data and CDE
    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",
        },
    });
    

    guardduty-malware-detection-enabled

    Severity: high · Enforcement: disabled

    Ensures AWS GuardDuty is enabled with malware detection capabilities for threat protection.

    • 11.5.1 — 11.5.1: Intrusion-detection and/or intrusionprevention techniques are used to detect and/or prevent intrusions into the network *
    Remediation
    Fix: Enable GuardDuty with Malware Detection

    Create a GuardDuty Detector with EBS malware protection and S3 data events enabled:

    import * as aws from "@pulumi/aws";
    
    // Enable GuardDuty detector
    const detector = new aws.guardduty.Detector("guardduty-detector", {
        enable: true, // Ensure detector is enabled
    });
    
    // Enable EBS malware protection for EC2 instances
    new aws.guardduty.DetectorFeature("ebs-malware-protection", {
        detectorId: detector.id,
        name: "EBS_MALWARE_PROTECTION",
        status: "ENABLED", // Enable malware scanning for EBS volumes
    });
    
    // Enable S3 data events protection
    new aws.guardduty.DetectorFeature("s3-protection", {
        detectorId: detector.id,
        name: "S3_DATA_EVENTS",
        status: "ENABLED", // Enable S3 threat detection
    });
    

    iam-group-policy-attachment-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that IAM group policy attachments do not attach the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Policy Instead of AdministratorAccess

    Attach an AWS managed policy scoped to the group’s job, or a customer-managed policy granting only the actions it needs:

    new aws.iam.GroupPolicyAttachment("readers-read-only", {
        group: group.name,
        policyArn: aws.iam.ManagedPolicy.ReadOnlyAccess,
    });
    

    iam-group-policy-attachments-exclusive-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that exclusive IAM group policy attachments do not include the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Policy Instead of AdministratorAccess

    Attach an AWS managed policy scoped to the group’s job, or a customer-managed policy granting only the actions it needs:

    new aws.iam.GroupPolicyAttachmentsExclusive("readers-policies", {
        groupName: group.name,
        policyArns: [aws.iam.ManagedPolicy.ReadOnlyAccess],
    });
    

    iam-instance-profile-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that IAM instance profiles do not carry a role holding the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Give the Profile’s Role a Scoped Policy

    Attach a policy scoped to the workload’s job to the role, instead of AdministratorAccess:

    const role = new aws.iam.Role("app", {
        assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "ec2.amazonaws.com" }),
    });
    
    new aws.iam.RolePolicyAttachment("app-read-only", {
        role: role.name,
        policyArn: aws.iam.ManagedPolicy.ReadOnlyAccess,
    });
    
    const profile = new aws.iam.InstanceProfile("app", { role: role.name });
    

    iam-password-complexity

    Severity: medium · Enforcement: advisory

    IAM password policy must require character complexity (lowercase, uppercase, numbers, symbols)

    • 8.3.6 — 8.3.6: 6 If passwords/passphrases are used as authentication factors to meet Requirement 8.3.6, they meet the minimum level of complexity *
    Remediation
    Fix: Enable all character complexity requirements in IAM password policy

    Update your IAM account password policy to require all character types:

    new aws.iam.AccountPasswordPolicy("account-password-policy", {
        requireLowercaseCharacters: true,  // Require lowercase letters
        requireUppercaseCharacters: true,  // Require uppercase letters
        requireNumbers: true,              // Require numbers
        requireSymbols: true,              // Require symbols
        minimumPasswordLength: 14,
    });
    

    iam-password-expiration

    Severity: medium · Enforcement: advisory

    IAM password policy must expire passwords

    • 8.3.9 — 8.3.9 If passwords/passphrases are used as the only authentication factor for user access (i.e., in any single-factor authentication implementation) then either: • Passwords/passphrases are changed at least once every 90 days, OR • The security posture of accounts is dynamically analyzed, and real-time access to resources is automatically determined accordingly. *
    Remediation
    Fix: Configure Password Expiration in IAM Password Policy

    Set the maxPasswordAge property to enforce password expiration. Common security baselines ask for 90 days or fewer; raise maxAllowedPasswordAge if your standard allows longer.

    import * as aws from "@pulumi/aws";
    
    const accountPasswordPolicy = new aws.iam.AccountPasswordPolicy("policy", {
        maxPasswordAge: 90,  // Passwords must be changed every 90 days
        minimumPasswordLength: 14,
        requireNumbers: true,
        requireSymbols: true,
        requireLowercaseCharacters: true,
        requireUppercaseCharacters: true,
    });
    

    iam-password-policy-minimum-length

    Severity: high · Enforcement: advisory

    Ensure IAM password policy requires minimum length of 14 or greater.

    • 8.3.6 — 8.3.6: 6 If passwords/passphrases are used as authentication factors to meet Requirement 8.3.6, they meet the minimum level of complexity *
    Remediation
    Fix: Set IAM Password Minimum Length to 14 Characters

    Update your IAM Account Password Policy to require a minimum password length of at least 14 characters:

    const passwordPolicy = new aws.iam.AccountPasswordPolicy("account-password-policy", {
        minimumPasswordLength: 14, // Ensure minimum length is 14 or greater
    });
    

    iam-password-policy-prevent-reuse

    Severity: high · Enforcement: advisory

    Ensure IAM password policy prevents password reuse.

    • 8.3.7 — 8.3.7: Individuals are not allowed to submit a new password/passphrase that is the same as any of the last four passwords/passphrases used *
    Remediation
    Fix: Configure Password Reuse Prevention

    Set the passwordReusePrevention property to 24 to prevent users from reusing their previous 24 passwords:

    const accountPasswordPolicy = new aws.iam.AccountPasswordPolicy("password-policy", {
        minimumPasswordLength: 14,
        maxPasswordAge: 90,
        requireNumbers: true,
        requireSymbols: true,
        requireUppercaseCharacters: true,
        requireLowercaseCharacters: true,
        passwordReusePrevention: 24, // Prevent reuse of previous 24 passwords
    });
    

    iam-policy-attachment-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that IAM policy attachments do not attach the AWS managed AdministratorAccess policy to users, roles or groups.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Policy Instead of AdministratorAccess

    Attach an AWS managed policy scoped to the job, or a customer-managed policy granting only the actions needed:

    new aws.iam.PolicyAttachment("app-read-only", {
        roles: [role.name],
        policyArn: aws.iam.ManagedPolicy.ReadOnlyAccess,
    });
    

    iam-role-assume-role-mfa-enforcement

    Severity: high · Enforcement: advisory

    Ensures IAM roles require MFA when assumed by human users (not AWS services)

    • 8.4.2 — 8.4.2 MFA is implemented for all non-console access into the CDE *
    • 8.4.3 — 8.4.3 MFA is implemented for all remote access originating from outside the entity’s network that could access or impact the CDE *
    Remediation
    Fix: Add MFA Condition to Role Trust Policy

    Add the aws:MultiFactorAuthPresent condition to any assume role statement that allows human principals to assume the role:

    const adminRole = new aws.iam.Role("admin-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: {
                    AWS: "arn:aws:iam::123456789012:root" // Root account (human)
                },
                Action: "sts:AssumeRole",
                Condition: {
                    Bool: {
                        "aws:MultiFactorAuthPresent": "true" // Require MFA
                    }
                }
            }],
        }),
    });
    
    const userAssumeRole = new aws.iam.Role("user-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: {
                    AWS: "arn:aws:iam::123456789012:user/john.doe" // IAM user (human)
                },
                Action: "sts:AssumeRole",
                Condition: {
                    Bool: {
                        "aws:MultiFactorAuthPresent": "true" // Require MFA
                    }
                }
            }],
        }),
    });
    
    // Service roles are automatically exempt (no MFA condition needed)
    const ecsTaskRole = new aws.iam.Role("ecs-task-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: {
                    Service: "ecs-tasks.amazonaws.com" // Service principal - no MFA needed
                },
                Action: "sts:AssumeRole"
            }],
        }),
    });
    
    // Role principals are never flagged (could be cross-account service roles)
    const crossAccountRole = new aws.iam.Role("cross-account-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: {
                    AWS: "arn:aws:iam::111111111111:role/service-role" // Role principal - not checked
                },
                Action: "sts:AssumeRole"
            }],
        }),
    });
    

    iam-role-managed-policy-arns-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that IAM roles do not list the AWS managed AdministratorAccess policy in managedPolicyArns.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Policy Instead of AdministratorAccess

    List an AWS managed policy scoped to the role’s job, or a customer-managed policy granting only the actions it needs:

    const role = new aws.iam.Role("app", {
        assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "ec2.amazonaws.com" }),
        managedPolicyArns: [aws.iam.ManagedPolicy.ReadOnlyAccess],
    });
    

    iam-role-policy-attachment-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that IAM role policy attachments do not attach the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Policy Instead of AdministratorAccess

    Attach an AWS managed policy scoped to the role’s job, or a customer-managed policy granting only the actions it needs:

    new aws.iam.RolePolicyAttachment("app-read-only", {
        role: role.name,
        policyArn: aws.iam.ManagedPolicy.ReadOnlyAccess,
    });
    

    iam-role-policy-attachments-exclusive-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that exclusive IAM role policy attachments do not include the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Policy Instead of AdministratorAccess

    Attach an AWS managed policy scoped to the role’s job, or a customer-managed policy granting only the actions it needs:

    new aws.iam.RolePolicyAttachmentsExclusive("app-policies", {
        roleName: role.name,
        policyArns: [aws.iam.ManagedPolicy.ReadOnlyAccess],
    });
    

    iam-role-session-duration

    Severity: medium · Enforcement: advisory

    Enforces maximum session duration for IAM roles

    • 8.2.8 — 8.2.8: If a user session has been idle for more than 15 minutes, the user is required to re-authenticate to re-activate the terminal or session
    Remediation
    Fix: Set Appropriate Maximum Session Duration for IAM Role

    Configure the maxSessionDuration property based on the role type to limit credential exposure window:

    const appRole = new aws.iam.Role("app-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: { Service: "ec2.amazonaws.com" },
                Action: "sts:AssumeRole",
            }],
        }),
        maxSessionDuration: 3600, // 1 hour for general roles (default)
    });
    
    const adminRole = new aws.iam.Role("admin-role", {
        name: "AdminRole",
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: { AWS: "arn:aws:iam::123456789012:root" },
                Action: "sts:AssumeRole",
            }],
        }),
        maxSessionDuration: 7200, // 2 hours for administrative roles
    });
    

    iam-user-group-membership-required

    Severity: medium · Enforcement: advisory

    IAM users must be members of groups for proper access management

    • 7.2.1 — 7.2.1: An access control model is defined and includes granting access
    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    Remediation
    Fix: Add User to IAM Group
    const user = new aws.iam.User("developer", {
        name: "developer-user",
    });
    
    const group = new aws.iam.Group("developers", {
        name: "developers",
    });
    
    // Add user to group to satisfy group membership requirement
    new aws.iam.UserGroupMembership("developer-membership", {
        user: user.name,
        groups: [group.name],  // Assign user to appropriate groups
    });
    

    An aws.iam.GroupMembership resource, which lists a group’s members instead, is equally acceptable:

    new aws.iam.GroupMembership("developers-members", {
        group: group.name,
        users: [user.name],
    });
    

    All IAM users should be members of groups, so that the account types in use are defined and documented and permissions are granted per group rather than per user.

    If this user is a service account or another special-purpose identity that is deliberately not in any group, add its name to the exemptUsers configuration list instead.

    iam-user-mfa-console-access

    Severity: high · Enforcement: advisory

    Ensures IAM users with console access have MFA devices

    • 8.4.2 — 8.4.2 MFA is implemented for all non-console access into the CDE *
    • 8.4.3 — 8.4.3 MFA is implemented for all remote access originating from outside the entity’s network that could access or impact the CDE *
    Remediation
    Fix: Enable MFA for IAM User Console Access

    Create a Virtual MFA Device for each IAM user with console access:

    const user = new aws.iam.User("example-user", {
        name: "example-user",
    });
    
    const loginProfile = new aws.iam.UserLoginProfile("example-login", {
        user: user.name,
    });
    
    // Create Virtual MFA Device for the user
    const mfaDevice = new aws.iam.VirtualMfaDevice("example-user-mfa", {
        virtualMfaDeviceName: "example-user-mfa", // Name must match pattern: <username>-mfa or <username>-mfa-device
    });
    

    iam-user-policy-attachment-prohibited

    Severity: medium · Enforcement: advisory

    IAM users must not have directly attached policies

    • 7.2.3 — 7.2.3: Required privileges are approved by authorized personnel *
    • 7.2.4 — 7.2.4: All user accounts and related access privileges, including third-party/vendor accounts, are reviewed *
    Remediation
    Fix: Attach policies to IAM groups instead of users

    Remove the UserPolicyAttachment resource and attach the policy to an IAM group. Then add the user to that group.

    // Attach the policy to the group instead of the user
    const groupPolicyAttachment = new aws.iam.GroupPolicyAttachment("group-policy-attachment", {
        group: userGroup.name,
        policyArn: "arn:aws:iam::aws:policy/ReadOnlyAccess",
    });
    
    // Add the user to the group
    const userGroupMembership = new aws.iam.UserGroupMembership("user-group-membership", {
        user: userName,
        groups: [userGroup.name],
    });
    
    // Remove the direct UserPolicyAttachment resource
    

    iam-user-policy-attachments-exclusive-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that exclusive IAM user policy attachments do not include the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Policy Instead of AdministratorAccess

    Grant the user a scoped policy through a group rather than attaching AdministratorAccess:

    const group = new aws.iam.Group("readers");
    new aws.iam.GroupPolicyAttachment("readers-read-only", {
        group: group.name,
        policyArn: aws.iam.ManagedPolicy.ReadOnlyAccess,
    });
    new aws.iam.UserGroupMembership("user-readers", {
        user: user.name,
        groups: [group.name],
    });
    

    iam-user-policy-least-privilege

    Severity: high · Enforcement: advisory

    Ensures IAM user policies follow least privilege principles

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Replace Wildcard Permissions with Specific Actions and Resources

    Replace wildcard actions and resources with explicit, scoped permissions:

    const listPolicy = new aws.iam.Policy("my-list-policy", {
        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
                ],
            }],
        }),
    });
    
    // Grant it through a group rather than to the user directly
    const readers = new aws.iam.Group("readers");
    new aws.iam.GroupPolicyAttachment("readers-list-policy", {
        group: readers.name,
        policyArn: listPolicy.arn,
    });
    new aws.iam.UserGroupMembership("my-user-readers", {
        user: myUser.name,
        groups: [readers.name],
    });
    

    iam-user-policy-restriction

    Severity: medium · Enforcement: advisory

    IAM user policies (inline policy attachments) should not be used

    • 7.2.3 — 7.2.3: Required privileges are approved by authorized personnel *
    • 7.2.4 — 7.2.4: All user accounts and related access privileges, including third-party/vendor accounts, are reviewed *
    Remediation
    Fix: Grant the User a Managed Policy Through a Group
    // Create a managed policy instead of inline policy
    const customPolicy = new aws.iam.Policy("custom-policy", {
        policy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Action: ["ec2:StartInstances", "ec2:StopInstances"],
                Resource: "arn:aws:ec2:us-east-1:123456789012:instance/*",
            }],
        }),
    });
    
    // Don't use aws.iam.UserPolicy (inline attachment)
    // Instead, attach the managed policy to a group and add the user to it
    const operators = new aws.iam.Group("operators");
    new aws.iam.GroupPolicyAttachment("operators-policy-attachment", {
        group: operators.name,
        policyArn: customPolicy.arn,
    });
    new aws.iam.UserGroupMembership("user-operators", {
        user: myUser.name,
        groups: [operators.name],
    });
    

    internal-ip-disclosure-prevention

    Severity: high · Enforcement: advisory

    Ensures internal IP addresses are not disclosed through public-facing edge services

    • 1.4.5 — 1.4.5: The disclosure of internal IP addresses and routing information is limited to only authorized parties *
    Remediation
    Fix: Remove Internal IP Addresses from Public-Facing Services
    // CloudFront: No private IPs in custom headers
    new aws.cloudfront.Distribution("my-distribution", {
        origins: [{
            customHeaders: [{
                value: "external-service.example.com", // Not "10.0.1.100"
            }],
        }],
    });
    
    // Listener Rules: No private IPs in responses or redirects
    new aws.lb.ListenerRule("my-rule", {
        actions: [{
            type: "fixed-response",
            fixedResponse: {
                messageBody: "Resource not found", // Not "Server at 10.0.1.50"
            },
        }],
    });
    

    kinesis-stream-retention

    Severity: medium · Enforcement: advisory

    Ensures Kinesis streams have retention periods configured

    • 3.2.1 — 3.2.1: Account data storage is kept to a minimum through implementation of data retention and disposal policies, procedures, and processes
    Remediation
    Fix: Configure Kinesis Stream Retention Period

    Set the retentionPeriod property to define how long data records remain accessible in the stream (minimum 24 hours, maximum 8760 hours):

    const stream = new aws.kinesis.Stream("my-stream", {
        shardCount: 1,
        retentionPeriod: 24, // Set retention period in hours (24 hours minimum)
        encryptionType: "KMS",
        kmsKeyId: kmsKey.id,
    });
    

    kms-grant-access-control

    Severity: high · Enforcement: advisory

    Validates KMS grants for least privilege access control

    • 3.6.1.3 — 3.6.1.3: Access to cleartext cryptographic key components is restricted to the fewest number of custodians necessary
    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    Remediation
    Fix: Configure KMS Grant with Least Privilege Access Control

    Specify explicit operations for the grant, add constraints for sensitive operations, and define a specific grantee principal:

    const grant = new aws.kms.Grant("my-kms-grant", {
        keyId: kmsKey.id,
        granteePrincipal: roleArn, // Specify a specific grantee principal
        operations: ["Encrypt", "Decrypt"], // Define specific operations
        constraints: [{
            encryptionContextSubset: {
                "Department": "Finance", // Add constraints for sensitive operations
            },
        }],
    });
    

    kms-key-creation

    Severity: medium · Enforcement: advisory

    Validates KMS key creation with appropriate specifications and origins

    • 3.7.1 — 3.7.1: Key-management policies and procedures are implemented to include generation of strong cryptographic keys used to protect stored account data
    • 3.7.2 — 3.7.2: Key-management policies and procedures are implemented to include secure distribution of cryptographic keys used to protect stored account data
    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-protection

    Severity: medium · Enforcement: advisory

    Validates KMS key deletion windows and lifecycle management

    • 3.7.5 — 3.7.5: Key management policies procedures are implemented to include the retirement, replacement, or destruction of keys used to protect stored account data *
    Remediation
    Fix: Configure KMS Key Deletion Window

    Set the deletionWindowInDays property to a value between 7 and 30 days to ensure secure key lifecycle management with adequate protection against accidental deletion.

    const key = new aws.kms.Key("my-key", {
        deletionWindowInDays: 30, // Set deletion window between 7-30 days
    });
    

    kms-key-policy-access-control

    Severity: high · Enforcement: advisory

    Validates KMS key policies for least privilege and separation of duties

    • 3.6.1.3 — 3.6.1.3: Access to cleartext cryptographic key components is restricted to the fewest number of custodians necessary
    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    Remediation
    Fix: Configure KMS Key Policy with Least Privilege Access Control

    Define an explicit key policy with specific principals and actions, avoiding wildcards:

    const keyPolicy = {
        Version: "2012-10-17",
        Statement: [
            {
                Sid: "Enable IAM User Permissions",
                Effect: "Allow",
                Principal: {
                    AWS: `arn:aws:iam::${accountId}:root`, // Specific account root
                },
                Action: "kms:*",
                Resource: "*",
            },
            {
                Sid: "Allow Key Administrators",
                Effect: "Allow",
                Principal: {
                    AWS: `arn:aws:iam::${accountId}:role/KeyAdminRole`, // Specific role ARN
                },
                Action: [
                    "kms:Create*",
                    "kms:Describe*",
                    "kms:Enable*",
                    "kms:List*",
                    "kms:Put*",
                    "kms:Update*",
                    "kms:Revoke*",
                    "kms:Disable*",
                    "kms:Get*",
                    "kms:Delete*",
                    "kms:ScheduleKeyDeletion",
                    "kms:CancelKeyDeletion", // Specific administrative actions only
                ],
                Resource: "*",
            },
            {
                Sid: "Allow Key Usage",
                Effect: "Allow",
                Principal: {
                    AWS: `arn:aws:iam::${accountId}:role/KeyUserRole`, // Specific user role ARN
                },
                Action: [
                    "kms:Decrypt",
                    "kms:EncryptionContext*",
                    "kms:GenerateDataKey", // Specific usage actions only
                ],
                Resource: "*",
            },
        ],
    };
    
    const key = new aws.kms.Key("my-key", {
        description: "My KMS key with least privilege policy",
        policy: JSON.stringify(keyPolicy), // Apply the policy with specific principals and actions
    });
    

    kms-key-rotation-enabled

    Severity: medium · Enforcement: advisory

    Checks that KMS Keys have key rotation enabled.

    • 3.7.4 — 3.7.4: Key management policies and procedures are implemented for cryptographic key changes for keys that have reached the end of their cryptoperiod, as defined by the associated application vendor or key owner
    Remediation
    Fix: Enable Automatic Key Rotation
    const myKey = new aws.kms.Key("my-key", {
        enableKeyRotation: true,
    });
    

    lambda-environment-variables-encryption

    Severity: high · Enforcement: advisory

    Ensures that Lambda functions with environment variables encrypt them with a customer-managed AWS KMS key (CMK) rather than the AWS-managed default key

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Encrypt Lambda Environment Variables with a Customer-Managed KMS Key

    Lambda encrypts environment variables at rest either way — kmsKeyArn is what selects a customer-managed key instead of the AWS-managed default, keeping key policy, rotation and CloudTrail key-usage records under your control.

    Configure a customer-managed KMS key by setting the kmsKeyArn property:

    const kmsKey = new aws.kms.Key("lambda-env-key", {
        description: "KMS key for Lambda environment variable encryption",
    });
    
    const lambdaFunction = new aws.lambda.Function("my-function", {
        runtime: "nodejs18.x",
        handler: "index.handler",
        role: role.arn,
        code: new pulumi.asset.AssetArchive({
            ".": new pulumi.asset.FileArchive("./function"),
        }),
        environment: {
            variables: {
                DATABASE_URL: "postgres://example.com/db",
            },
        },
        kmsKeyArn: kmsKey.arn, // Encrypt environment variables with KMS
    });
    

    lambda-function-logging

    Severity: medium · Enforcement: advisory

    Ensures that AWS Lambda functions do not filter out application logs below the required level of detail

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Do Not Suppress Lambda Application Logs

    Lambda already forwards application logs to CloudWatch Logs without any loggingConfig block, so there is nothing to switch on. A violation means the function has opted into a log-level filter that discards logs the control requires.

    Either remove applicationLogLevel (all application logs are then forwarded), or raise it to a level at least as verbose as the configured minimum (TRACE > DEBUG > INFO > WARN > ERROR > FATAL, most verbose first). An adequate level is what gives output data validation and troubleshooting sufficient detail:

    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"),
        }),
        loggingConfig: {
            logFormat: "JSON",
            applicationLogLevel: "INFO", // not "ERROR"/"FATAL", which discard INFO and below
        },
    });
    

    lambda-function-url-no-public-access

    Severity: high · Enforcement: advisory

    Ensures Lambda function URLs require IAM authentication

    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Require IAM Authentication on the Function URL
    new aws.lambda.FunctionUrl("function-url", {
        functionName: lambdaFunction.name,
        authorizationType: "AWS_IAM",
    });
    

    lambda-permission-configure-source-arn

    Severity: critical · Enforcement: advisory

    Checks that lambda function permissions bound the invoker, via sourceArn, sourceAccount, principalOrgId, or an identity-scoped principal.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Restrict Who May Invoke the Lambda Permission

    Add the sourceArn property to restrict which AWS service or resource can invoke the Lambda function:

    const lambdaPermission = new aws.lambda.Permission("my-permission", {
        action: "lambda:InvokeFunction",
        function: myFunction.name,
        principal: "apigateway.amazonaws.com",
        sourceArn: apiGateway.executionArn, // Specify the source ARN to restrict invocation
    });
    

    Where the invoking resource’s ARN is not known at deploy time, sourceAccount (single account) or principalOrgId (an AWS Organization) are accepted alternatives. A permission whose principal is already a specific AWS account id or IAM principal ARN is scoped by that principal and needs neither.

    lambda-public-access-restricted

    Severity: high · Enforcement: advisory

    Lambda functions must restrict public access through resource-based policies

    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Restrict Lambda Function Access to Specific Principals

    Replace the wildcard principal with the service or account that invokes the function:

    // Grant access to a specific AWS service, scoped to the invoking resource
    new aws.lambda.Permission("apiGatewayInvoke", {
        action: "lambda:InvokeFunction",
        function: lambdaFunction.name,
        principal: "apigateway.amazonaws.com",
        sourceArn: pulumi.interpolate`${api.executionArn}/*`,
    });
    
    // Or grant access to a specific AWS account
    new aws.lambda.Permission("crossAccountInvoke", {
        action: "lambda:InvokeFunction",
        function: lambdaFunction.name,
        principal: "123456789012",
    });
    

    A wildcard principal restricted with principalOrgId is not reported. A function that must stay publicly invocable can be listed in exemptFunctions.

    lambda-runtime-restrictions

    Severity: low · Enforcement: advisory

    Ensures that AWS Lambda functions are created only with approved runtime versions

    • 6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
    Remediation
    Fix: Use Approved Lambda Runtime

    Set the runtime property to an approved runtime version from the organization’s approved list:

    const myFunction = new aws.lambda.Function("my-function", {
        runtime: "nodejs20.x", // Use an approved runtime version
        handler: "index.handler",
        role: role.arn,
        code: new pulumi.asset.AssetArchive({
            ".": new pulumi.asset.FileArchive("./function"),
        }),
    });
    

    lambda-vpc-placement-required

    Severity: high · Enforcement: advisory

    Lambda functions must be deployed in VPC for network isolation and security

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    Remediation
    Fix: Configure Lambda Function VPC Placement

    Add a vpcConfig to your Lambda function with subnet and security group configurations:

    import * as aws from "@pulumi/aws";
    import * as pulumi from "@pulumi/pulumi";
    
    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"),
        }),
        // Add VPC configuration for network isolation
        vpcConfig: {
            subnetIds: [
                privateSubnet1.id,  // Use at least 2 subnets for high availability
                privateSubnet2.id,
            ],
            securityGroupIds: [lambdaSecurityGroup.id],  // Specify security groups for access control
        },
    });
    

    neptune-clusterinstance-managed-service-patching

    Severity: medium · Enforcement: advisory

    Ensures Neptune cluster instances have automated minor version upgrades enabled

    • 6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
    Remediation
    Fix: Enable Automatic Minor Version Upgrades for Neptune Cluster Instance

    Set the autoMinorVersionUpgrade property to true to enable automated patching for managed service security updates:

    const neptuneInstance = new aws.neptune.ClusterInstance("my-neptune-instance", {
        clusterIdentifier: neptuneCluster.id,
        instanceClass: "db.r5.large",
        engine: "neptune",
        autoMinorVersionUpgrade: true, // Enable automatic minor version upgrades
    });
    

    neptune-clusterinstance-no-public-access

    Severity: critical · Enforcement: advisory

    Checks that Neptune Cluster Instances public access is not enabled.

    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    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
    });
    

    no-direct-user-access-keys

    Severity: high · Enforcement: advisory

    Prevents creation of direct IAM user access keys for human users

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 8.2.1 — 8.2.1: All users are assigned a unique ID before access to system components or cardholder data is allowed *
    Remediation
    Fix: Use IAM Roles Instead of Access Keys

    Remove the IAM access key resource and use IAM roles with temporary credentials instead:

    // Remove this - do not create IAM access keys
    // const accessKey = new aws.iam.AccessKey("user-key", {
    //     user: user.name,
    // });
    
    // Instead, use IAM roles for workloads
    const role = new aws.iam.Role("app-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: { Service: "ec2.amazonaws.com" }, // Use appropriate service
                Action: "sts:AssumeRole",
            }],
        }),
    });
    
    // Attach necessary policies to the role
    new aws.iam.RolePolicyAttachment("app-policy", {
        role: role.name,
        policyArn: "arn:aws:iam::aws:policy/ReadOnlyAccess", // Use least-privilege policy
    });
    
    // For human users, use AWS SSO or federated identity instead of access keys
    

    no-hardcoded-secrets

    Severity: critical · Enforcement: advisory

    Ensures user data on EC2 instances, launch templates, launch configurations, spot requests, spot fleets, Image Builder recipes and Lightsail instances does not contain hardcoded secrets

    • 3.3.2 — 3.3.2: SAD that is stored electronically prior to completion of authorization is encrypted using strong cryptography
    • 8.3.2 — 8.3.2: Strong cryptography is used to render all authentication factors unreadable during transmission and storage on all system components
    Remediation
    Fix: Remove Hardcoded Secrets

    Remove hardcoded secrets from user data and fetch them at boot from AWS Secrets Manager or SSM Parameter Store instead. This applies to EC2 instances, launch templates, launch configurations, spot requests, spot fleets, Image Builder recipes and Lightsail instances.

    Example Violation
    const instance = new aws.ec2.Instance("web-server", {
        instanceType: "t3.micro",
        ami: "ami-12345678",
        userData: `#!/bin/bash
    export DATABASE_PASSWORD="mySecretPassword123"
    export API_KEY="sk_live_abc123def456789"
    mysql -u admin -p"hardcodedPassword" -h db.example.com
    `,
    });
    

    pubsub-least-privilege-iam

    Severity: medium · Enforcement: advisory

    Ensures IAM policies follow least privilege principles for Pub/Sub services (SNS, SQS, Kinesis)

    • 7.2.1 — 7.2.1: An access control model is defined and includes granting access
    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    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 *
            }],
        }),
        tags: {
            Environment: "production",
        },
    });
    

    rds-audit-logging

    Severity: medium · Enforcement: advisory

    Ensures RDS instances have audit logging enabled

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable RDS Audit Logging to CloudWatch

    Configure enabledCloudwatchLogsExports with appropriate audit log types for your database engine:

    // For MySQL/MariaDB
    const db = new aws.rds.Instance("my-db", {
        engine: "mysql",
        instanceClass: "db.t3.micro",
        allocatedStorage: 20,
        enabledCloudwatchLogsExports: ["audit", "error", "general", "slowquery"], // Enable audit logs
        // ... other configuration
    });
    
    // For PostgreSQL
    const pgDb = new aws.rds.Instance("my-pg-db", {
        engine: "postgres",
        instanceClass: "db.t3.micro",
        allocatedStorage: 20,
        enabledCloudwatchLogsExports: ["postgresql"], // Enable PostgreSQL logs
        // ... other configuration
    });
    
    // For Aurora clusters
    const cluster = new aws.rds.Cluster("my-cluster", {
        engine: "aurora-mysql",
        enabledCloudwatchLogsExports: ["audit", "error", "general", "slowquery"], // Enable audit logs
        // ... other configuration
    });
    

    rds-cluster-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Checks that RDS Clusters storage uses a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for RDS Cluster

    Specify a customer-managed KMS key for RDS Cluster storage encryption using the kmsKeyId property:

    const cluster = new rds.Cluster("my-cluster", {
        engine: "aurora-postgresql",
        storageEncrypted: true,
        kmsKeyId: kmsKey.arn, // Use customer-managed KMS key
        masterUsername: "admin",
        masterPassword: dbPassword,
    });
    

    rds-cluster-disallow-unencrypted-storage

    Severity: high · Enforcement: advisory

    Checks that RDS Clusters storage is encrypted.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    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: critical · Enforcement: advisory

    Checks that RDS Cluster Instances public access is not enabled.

    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    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-enhanced-monitoring

    Severity: medium · Enforcement: advisory

    RDS cluster instances must have enhanced monitoring enabled to provide detailed system-level metrics

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable RDS Cluster Instance Enhanced Monitoring

    Configure the RDS cluster instance with monitoringInterval and monitoringRoleArn to enable enhanced monitoring:

    import * as aws from "@pulumi/aws";
    
    // Create IAM role for RDS enhanced monitoring
    const monitoringRole = new aws.iam.Role("rds-monitoring-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Action: "sts:AssumeRole",
                Principal: { Service: "monitoring.rds.amazonaws.com" },
                Effect: "Allow",
            }],
        }),
    });
    
    new aws.iam.RolePolicyAttachment("rds-monitoring-policy", {
        role: monitoringRole.name,
        policyArn: "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole",
    });
    
    const clusterInstance = new aws.rds.ClusterInstance("my-cluster-instance", {
        clusterIdentifier: cluster.id,
        instanceClass: "db.r5.large",
        engine: cluster.engine,
        monitoringInterval: 60, // Valid values: 0, 1, 5, 10, 15, 30, 60
        monitoringRoleArn: monitoringRole.arn, // Required for enhanced monitoring
    });
    

    rds-clusterinstance-managed-service-patching

    Severity: medium · Enforcement: advisory

    Ensures RDS cluster instances have automated minor version upgrades enabled

    • 6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
    Remediation
    Fix: Enable Automatic Minor Version Upgrades for Aurora Cluster Instance

    Set the autoMinorVersionUpgrade property to true to enable automated patching for managed service security updates:

    const clusterInstance = new aws.rds.ClusterInstance("my-cluster-instance", {
        clusterIdentifier: cluster.id,
        instanceClass: "db.r5.large",
        engine: cluster.engine,
        autoMinorVersionUpgrade: true, // Enable automatic minor version upgrades
    });
    

    rds-clusterinstance-ssl-encryption

    Severity: high · Enforcement: advisory

    Ensures RDS cluster instances have SSL/TLS encryption enabled through parameter group configuration

    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Configure SSL/TLS Encryption for Aurora Cluster Instance

    Aurora enforces TLS through the DB cluster parameter group attached to the cluster, not through a flag on the instance. The parameter name differs by engine family: require_secure_transport for MySQL-compatible engines, rds.force_ssl for PostgreSQL-compatible engines.

    const clusterParamGroup = new aws.rds.ClusterParameterGroup("cluster-params", {
        family: "aurora-postgresql14",
        parameters: [
            {
                name: "rds.force_ssl",
                value: "1", // Enforce SSL/TLS for all connections
            },
        ],
    });
    
    const cluster = new aws.rds.Cluster("my-cluster", {
        engine: "aurora-postgresql",
        dbClusterParameterGroupName: clusterParamGroup.name, // Applies to every instance
        // ...
    });
    
    const clusterInstance = new aws.rds.ClusterInstance("my-cluster-instance", {
        clusterIdentifier: cluster.id,
        instanceClass: "db.r5.large",
        engine: cluster.engine,
    });
    

    An instance-level aws.rds.ParameterGroup attached through dbParameterGroupName — on the instance itself or through the cluster’s dbInstanceParameterGroupName — is accepted as well.

    rds-iam-authentication

    Severity: medium · Enforcement: advisory

    Ensures RDS instances have IAM database authentication enabled

    • 8.2.1 — 8.2.1: All users are assigned a unique ID before access to system components or cardholder data is allowed *
    Remediation
    Fix: Enable IAM Database Authentication for RDS Instance

    Set the iamDatabaseAuthenticationEnabled property to true to enable IAM-based authentication for your RDS instance:

    const rdsInstance = new aws.rds.Instance("my-database", {
        engine: "mysql",
        instanceClass: "db.t3.micro",
        allocatedStorage: 20,
        username: "admin",
        iamDatabaseAuthenticationEnabled: true, // Enable IAM database authentication
        skipFinalSnapshot: true,
    });
    

    rds-instance-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Checks that RDS Instance storage uses a customer-managed KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for RDS Instance

    Specify a customer-managed KMS key when enabling storage encryption on your RDS instance:

    const rdsInstance = new aws.rds.Instance("my-db", {
        allocatedStorage: 20,
        engine: "postgres",
        instanceClass: "db.t3.micro",
        storageEncrypted: true,
        kmsKeyId: kmsKey.arn, // Specify customer-managed KMS key ARN
        // ... other configuration
    });
    

    rds-instance-disallow-public-access

    Severity: critical · Enforcement: advisory

    Checks that RDS Instance public access is not enabled.

    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Public Access for RDS Instance

    Set publiclyAccessible to false to prevent the RDS instance from being accessible from the internet:

    const dbInstance = new aws.rds.Instance("my-database", {
        allocatedStorage: 20,
        engine: "mysql",
        engineVersion: "8.0",
        instanceClass: "db.t3.micro",
        dbSubnetGroupName: privateSubnetGroup.name,
        vpcSecurityGroupIds: [dbSecurityGroup.id],
        publiclyAccessible: false, // Disable public access
        username: dbUsername,
        password: dbPassword,
    });
    

    rds-instance-disallow-unencrypted-storage

    Severity: high · Enforcement: advisory

    Checks that RDS instance storage is encrypted.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable RDS Storage Encryption

    Set the storageEncrypted property to true to encrypt the RDS instance storage:

    const dbInstance = new aws.rds.Instance("my-db", {
        engine: "postgres",
        instanceClass: "db.t3.micro",
        allocatedStorage: 20,
        storageEncrypted: true, // Enable storage encryption
        username: "admin",
        password: adminPassword,
    });
    

    rds-instance-enhanced-monitoring

    Severity: medium · Enforcement: advisory

    RDS database instances must have enhanced monitoring enabled to provide detailed system-level metrics

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable RDS Instance Enhanced Monitoring

    Configure the RDS instance with monitoringInterval and monitoringRoleArn to enable enhanced monitoring:

    import * as aws from "@pulumi/aws";
    
    // Create IAM role for RDS enhanced monitoring
    const monitoringRole = new aws.iam.Role("rds-monitoring-role", {
        assumeRolePolicy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Action: "sts:AssumeRole",
                Principal: { Service: "monitoring.rds.amazonaws.com" },
                Effect: "Allow",
            }],
        }),
    });
    
    new aws.iam.RolePolicyAttachment("rds-monitoring-policy", {
        role: monitoringRole.name,
        policyArn: "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole",
    });
    
    const db = new aws.rds.Instance("my-database", {
        engine: "postgres",
        instanceClass: "db.t3.micro",
        allocatedStorage: 20,
        // Enable enhanced monitoring with 60-second interval
        monitoringInterval: 60, // Valid values: 0, 1, 5, 10, 15, 30, 60
        monitoringRoleArn: monitoringRole.arn, // Required for enhanced monitoring
    });
    

    rds-instance-managed-service-patching

    Severity: medium · Enforcement: advisory

    Ensures RDS instances have automated minor version upgrades enabled

    • 6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
    Remediation
    Fix: Enable Automatic Minor Version Upgrades for RDS

    Set the autoMinorVersionUpgrade property to true to enable automated patching for managed service security updates:

    const db = new aws.rds.Instance("my-database", {
        engine: "postgres",
        instanceClass: "db.t3.micro",
        allocatedStorage: 20,
        dbName: "mydb",
        username: "admin",
        password: dbPassword,
        autoMinorVersionUpgrade: true, // Enable automatic minor version upgrades
        skipFinalSnapshot: true,
    });
    

    rds-instance-ssl-encryption

    Severity: high · Enforcement: advisory

    Ensures RDS instances have SSL/TLS encryption enabled through parameter group configuration

    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Configure SSL/TLS Encryption for RDS Instance

    Configure a parameter group to enforce SSL/TLS connections for your RDS instance. The parameter name differs by engine family: require_secure_transport for MySQL and MariaDB, rds.force_ssl for PostgreSQL and SQL Server.

    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",
        manageMasterUserPassword: true,
    });
    

    rds-private-subnet-validation

    Severity: critical · Enforcement: advisory

    Validates that RDS DB subnet groups contain only private subnets

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    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-secure-master-credentials

    Severity: high · Enforcement: advisory

    Ensures RDS instances delegate the master password to AWS Secrets Manager (manageMasterUserPassword) instead of supplying it inline

    • 8.3.2 — 8.3.2: Strong cryptography is used to render all authentication factors unreadable during transmission and storage on all system components
    • 8.3.2 — 8.3.2: Strong cryptography is used to render all authentication factors unreadable during transmission and storage on all system components
    • 8.6.3 — 8.6.3: 3 Passwords/passphrases for any application and system accounts are protected against misuse
    • 8.6.3 — 8.6.3: 3 Passwords/passphrases for any application and system accounts are protected against misuse
    Remediation
    Fix: Use AWS Secrets Manager for RDS Master Credentials

    Enable AWS-managed master password using Secrets Manager instead of hardcoded credentials:

    const dbInstance = new aws.rds.Instance("my-db", {
        allocatedStorage: 20,
        engine: "mysql",
        instanceClass: "db.t3.micro",
        manageMasterUserPassword: true, // Enable AWS Secrets Manager for master password
        username: "admin",
        // Do NOT set the password property - AWS Secrets Manager handles it
        vpcSecurityGroupIds: [securityGroup.id],
        dbSubnetGroupName: subnetGroup.name,
    });
    

    redshift-enhanced-vpc-routing-enabled

    Severity: medium · Enforcement: advisory

    Ensures Redshift clusters have enhanced VPC routing enabled for network isolation.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.3.2 — 1.3.2: Outbound traffic from the CDE is restricted
    Remediation
    Fix: Enable Enhanced VPC Routing

    Set the enhancedVpcRouting property to true on your Redshift cluster to ensure all COPY and UNLOAD traffic flows through your VPC infrastructure.

    const cluster = new aws.redshift.Cluster("my-cluster", {
        clusterIdentifier: "my-cluster",
        // ... other configuration ...
        enhancedVpcRouting: true, // Enable enhanced VPC routing for network isolation
    });
    

    redshift-kms-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures Redshift clusters have encryption enabled using KMS keys.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Encryption at Rest for Redshift Cluster

    Enable encryption on your Redshift cluster by setting the ’encrypted’ property to true and providing a customer-managed KMS key.

    import * as aws from "@pulumi/aws";
    
    const cluster = new aws.redshift.Cluster("my-cluster", {
        clusterIdentifier: "my-redshift-cluster",
        encrypted: true, // Enable encryption at rest
        kmsKeyId: kmsKey.arn, // Use a customer-managed KMS key
        // ... other configuration
    });
    

    If you only need AWS managed encryption (not recommended), you can set ’encrypted’ to true without specifying ‘kmsKeyId’, but you must configure the policy to allow this by setting ‘requireCustomKMSKey’ to false in the policy configuration.

    redshift-logging-enabled

    Severity: high · Enforcement: advisory

    Ensures Redshift clusters have logging configurations enabled for audit and monitoring purposes.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable Redshift cluster logging

    Create a redshift.Logging resource to enable audit logging for your Redshift cluster. Configure either S3 or CloudWatch as the log destination to capture connection logs and user activity logs.

    import * as aws from "@pulumi/aws";
    
    const cluster = new aws.redshift.Cluster("my-cluster", {
        clusterIdentifier: "my-redshift-cluster",
        // ... other cluster configuration
    });
    
    // Add logging configuration for the cluster
    const clusterLogging = new aws.redshift.Logging("my-cluster-logging", {
        clusterIdentifier: cluster.clusterIdentifier,
        logDestinationType: "s3", // or "cloudwatch"
        bucketName: "my-audit-logs-bucket", // Required for S3 destination
        s3KeyPrefix: "redshift-logs/", // Optional: organize logs with prefix
        logExports: ["connectionlog", "useractivitylog"], // Enable audit log types
    });
    

    redshift-maintenance-required

    Severity: medium · Enforcement: advisory

    Ensures Redshift clusters have proper maintenance settings configured for automated updates.

    • 6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
    Remediation
    Fix: Configure Redshift Maintenance Settings

    Add a preferred maintenance window and enable automatic version upgrades to allow automated security updates and system baseline changes.

    const cluster = new aws.redshift.Cluster("my-cluster", {
        clusterIdentifier: "my-redshift-cluster",
        // ... other configuration ...
    
        // Configure maintenance window for automated updates
        preferredMaintenanceWindow: "sun:05:00-sun:06:00", // Weekly maintenance window
    
        // Enable automatic version upgrades for security patches
        allowVersionUpgrade: true,
    });
    

    redshift-public-access-prohibited

    Severity: high · Enforcement: advisory

    Ensures Redshift clusters prohibit public access to prevent unauthorized connections.

    • 1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Public Access for Redshift Cluster
    const cluster = new aws.redshift.Cluster("data-warehouse", {
        clusterIdentifier: "my-cluster",
        nodeType: "dc2.large",
        masterUsername: "admin",
        masterPassword: password.result,
        publiclyAccessible: false,  // Disable public access to prevent internet exposure
        vpcSecurityGroupIds: [securityGroup.id],
        clusterSubnetGroupName: subnetGroup.name,
    });
    

    redshift-ssl-required

    Severity: high · Enforcement: advisory

    Ensures Redshift clusters have encryption in transit enabled through SSL parameter configuration.

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Enable SSL/TLS encryption for Redshift cluster connections

    Create a parameter group with require_ssl set to true and associate it with your Redshift cluster:

    import * as aws from "@pulumi/aws";
    
    // Create parameter group with SSL requirement
    const parameterGroup = new aws.redshift.ParameterGroup("secure-parameter-group", {
        family: "redshift-1.0",
        parameters: [
            {
                name: "require_ssl",
                value: "true", // Enable SSL/TLS encryption in transit
            },
        ],
    });
    
    // Associate parameter group with Redshift cluster
    const cluster = new aws.redshift.Cluster("my-cluster", {
        clusterIdentifier: "my-secure-cluster",
        clusterParameterGroupName: parameterGroup.name, // Reference the secure parameter group
        // ... other cluster configuration
    });
    

    resource-tagging

    Severity: low · Enforcement: advisory

    Ensures all AWS resources must include tags for proper change tracking

    • 11.5.2 — 11.5.2: A change-detection mechanism (for example, file integrity monitoring tools) is deployed
    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",
        },
    });
    

    restrict-default-iam-user-creation

    Severity: medium · Enforcement: advisory

    Ensures that default IAM user accounts are not allowed to be created

    • 8.2.1 — 8.2.1: All users are assigned a unique ID before access to system components or cardholder data is allowed *
    • 8.2.2 — 8.2.2: Group, shared, or generic IDs, or other shared authentication credentials are only used when necessary on an exception basis, and are managed
    Remediation
    Fix: Use Descriptive User Names

    Use specific, descriptive user names that follow your organization’s naming conventions instead of generic default names:

    const iamUser = new aws.iam.User("my-iam-user", {
        name: "john.doe", // Use descriptive, organization-specific user names
        // Avoid generic names like: root, admin, administrator, default, user, guest, test, demo
    });
    

    s3-bucket-access-logging

    Severity: medium · Enforcement: advisory

    Ensures each S3 bucket has access logging enabled

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    • 10.3.3 — 10.3.3: Audit log files, including those for externalfacing technologies, are promptly backed up to a secure, central, internal log server(s) or other media that is difficult to modify
    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
    });
    

    The target bucket must be a separate, dedicated logging bucket, and the target prefix keeps each source bucket’s logs separable. The deprecated inline logging / loggings block on the bucket itself is accepted too.

    s3-bucket-disallow-public-read

    Severity: critical · Enforcement: advisory

    Checks that S3 Bucket ACLs don’t allow ‘public-read’ or ‘public-read-write’ or ‘authenticated-read’.

    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Use Private ACL for S3 Bucket

    Set the acl to “private” or omit it entirely (defaults to private) to prevent public read access, whether it is set on the bucket or on an aws.s3.BucketAcl. Do not grant to the AllUsers or AuthenticatedUsers groups:

    const bucket = new aws.s3.Bucket("my-bucket", {
        acl: "private", // Use private ACL instead of public-read/public-read-write/authenticated-read
        // Or omit the acl property to use the default private setting
    });
    

    s3-bucket-encryption

    Severity: high · Enforcement: advisory

    S3 buckets must have server-side encryption configured using BucketServerSideEncryptionConfiguration resource

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure S3 Bucket Server-Side Encryption

    Create a separate BucketServerSideEncryptionConfiguration resource for your S3 bucket with encryption rules. Use AES256 or KMS encryption:

    const bucket = new aws.s3.Bucket("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
        }],
    });
    

    On provider 6.x, use aws.s3.BucketV2 with aws.s3.BucketServerSideEncryptionConfigurationV2; those and an inline encryption block naming a customer-managed key are still accepted.

    s3-bucket-least-privilege

    Severity: critical · Enforcement: advisory

    Prevents overly permissive S3 bucket policies

    • 7.2.1 — 7.2.1: An access control model is defined and includes granting access
    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    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-lifecycle

    Severity: medium · Enforcement: advisory

    Ensures each S3 bucket has lifecycle rules configured for retention/disposal

    • 3.2.1 — 3.2.1: Account data storage is kept to a minimum through implementation of data retention and disposal policies, procedures, and processes
    Remediation
    Fix: Configure Lifecycle Rules for S3 Bucket

    Create a BucketLifecycleConfiguration resource with at least one enabled rule:

    const myBucket = new aws.s3.Bucket("my-bucket", {
        // Bucket configuration
    });
    
    const lifecycleConfig = new aws.s3.BucketLifecycleConfiguration("my-bucket-lifecycle", {
        bucket: myBucket.id,
        rules: [
            {
                id: "delete-old-objects",
                status: "Enabled", // Rule must be enabled
                expiration: {
                    days: 90, // Define retention period (e.g., expire after 90 days)
                },
            },
        ],
    });
    

    Every rule must be actively enforced and carry at least one retention or disposal action: expiration, transition, noncurrent version management, or abort of incomplete multipart uploads. The deprecated inline lifecycleRules block on the bucket itself is accepted too.

    s3-bucket-public-access-block-required

    Severity: high · Enforcement: advisory

    Ensures each S3 bucket has a public access block with all settings enabled

    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Enable all S3 bucket public access block settings

    Configure the S3 BucketPublicAccessBlock resource with all four public access block settings enabled:

    const publicAccessBlock = new aws.s3.BucketPublicAccessBlock("example-public-access-block", {
        bucket: exampleBucket.id,
        blockPublicAcls: true,        // Block public ACLs
        blockPublicPolicy: true,      // Block public bucket policies
        ignorePublicAcls: true,       // Ignore existing public ACLs
        restrictPublicBuckets: true,  // Restrict public bucket access
    });
    

    All four settings must be set to true to prevent public access to the S3 bucket.

    s3-bucket-ssl-enforcement-required

    Severity: high · Enforcement: advisory

    S3 buckets must enforce SSL/TLS for all requests to ensure encryption in transit

    • 2.2.7 — 2.2.7: All non-console administrative access is encrypted using strong cryptography *
    • 4.2.1 — 4.2.1: Strong cryptography and security protocols are implemented as follows to safeguard PAN during transmission over open, public networks *
    Remediation
    Fix: Add Bucket Policy to Enforce SSL/TLS

    Add a bucket policy that denies all requests made without SSL/TLS encryption:

    import * as aws from "@pulumi/aws";
    
    const bucket = new aws.s3.Bucket("my-bucket", {
        bucket: "my-secure-bucket",
    });
    
    // Add bucket policy to enforce SSL/TLS
    const bucketPolicy = new aws.s3.BucketPolicy("my-bucket-policy", {
        bucket: bucket.id,
        policy: bucket.arn.apply(arn => JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Sid: "DenyInsecureTransport",
                Effect: "Deny",
                Principal: "*",
                Action: "s3:*",
                Resource: [
                    arn,
                    `${arn}/*`,
                ],
                Condition: {
                    Bool: {
                        "aws:SecureTransport": "false"  // Deny when SecureTransport is false
                    }
                }
            }]
        }))
    });
    

    When requireTlsVersion is configured, add a second Deny statement carrying Condition: {"NumericLessThan": {"s3:TlsVersion": "1.2"}} so connections negotiating an older protocol version are refused. The equivalent policy written inline on the bucket resource itself is accepted too. On provider 6.x, use aws.s3.BucketV2; it is still accepted.

    sagemaker-endpoint-kms-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures SageMaker endpoint configurations have encryption enabled using KMS keys.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable KMS encryption for SageMaker endpoint configuration

    Add the kmsKeyArn property to your SageMaker endpoint configuration to enable encryption at rest:

    const endpointConfig = new aws.sagemaker.EndpointConfiguration("example", {
        productionVariants: [{
            modelName: model.name,
            initialInstanceCount: 1,
            instanceType: "ml.t2.medium",
        }],
        kmsKeyArn: kmsKey.arn, // Add KMS key ARN to enable encryption at rest
    });
    

    sagemaker-notebook-internet-access-disabled

    Severity: high · Enforcement: advisory

    Ensures SageMaker notebook instances have direct internet access disabled.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Direct Internet Access
    const notebookInstance = new aws.sagemaker.NotebookInstance("ml-notebook", {
        instanceType: "ml.t3.medium",
        roleArn: role.arn,
        directInternetAccess: "Disabled",  // Disable direct internet access
        subnetId: subnet.id,  // Must specify subnet when internet access is disabled
        securityGroups: [securityGroup.id],  // Control network access via security groups
    });
    

    sagemaker-notebook-kms-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures SageMaker notebook instances have encryption enabled using KMS keys.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable KMS encryption for SageMaker notebook instance

    Add the kmsKeyId property to your SageMaker notebook instance configuration to enable encryption at rest:

    import * as aws from "@pulumi/aws";
    
    const notebookInstance = new aws.sagemaker.NotebookInstance("example", {
        name: "my-notebook",
        instanceType: "ml.t2.medium",
        roleArn: role.arn,
        kmsKeyId: kmsKey.id, // Add KMS key ID to enable encryption at rest
    });
    

    secrets-manager-rotation-required

    Severity: high · Enforcement: advisory

    Ensures Secrets Manager secrets have automatic rotation enabled with proper scheduling and frequency limits.

    • 3.7.4 — 3.7.4: Key management policies and procedures are implemented for cryptographic key changes for keys that have reached the end of their cryptoperiod, as defined by the associated application vendor or key owner
    Remediation
    Fix: Enable Automatic Rotation for Secrets Manager Secret

    Configure a SecretRotation resource with rotation rules to automatically rotate your secret credentials at regular intervals.

    import * as aws from "@pulumi/aws";
    
    const mySecret = new aws.secretsmanager.Secret("mySecret", {
        name: "my-database-credentials",
    });
    
    // Create rotation configuration with Lambda function
    const rotationLambda = new aws.lambda.Function("rotationLambda", {
        // Lambda function configuration for rotation
    });
    
    // Add automatic rotation to the secret
    const secretRotation = new aws.secretsmanager.SecretRotation("mySecretRotation", {
        secretId: mySecret.id,
        rotationLambdaArn: rotationLambda.arn,
        rotationRules: {
            automaticallyAfterDays: 30, // Rotate every 30 days (must be <= configured maximum)
            // Or use scheduleExpression: "cron(0 0 1 * ? *)" for cron-based rotation
        },
    });
    

    secrets-manager-secret-configure-customer-managed-key

    Severity: low · Enforcement: advisory

    Check that Secrets Manager Secrets use a customer-manager KMS key.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    • 3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
    Remediation
    Fix: Configure Customer-Managed KMS Key for Secrets Manager Secret

    Set the kmsKeyId property to reference a customer-managed KMS key for encrypting the secret:

    const secret = new aws.secretsmanager.Secret("my-secret", {
        name: "my-application-secret",
        kmsKeyId: kmsKey.arn, // Use customer-managed KMS key
        description: "Sensitive application credentials",
    });
    

    security-group-default-deny

    Severity: high · Enforcement: advisory

    Ensures security groups follow strict firewall rules with default deny

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.3.2 — 1.3.2: Outbound traffic from the CDE is restricted
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Restrict Security Group Ingress Rules

    Remove overly permissive ingress rules that allow access from 0.0.0.0/0 to restricted ports or broad port ranges. Instead, use specific CIDR blocks and limit access to only necessary ports.

    const securityGroup = new aws.ec2.SecurityGroup("my-security-group", {
        vpcId: vpc.id,
        ingress: [
            {
                protocol: "tcp",
                fromPort: 443,
                toPort: 443,
                cidrBlocks: ["10.0.0.0/8"],
            },
        ],
    });
    

    security-group-egress-restriction

    Severity: high · Enforcement: advisory

    Ensures security groups restrict egress traffic with default deny principles

    • 1.3.2 — 1.3.2: Outbound traffic from the CDE is restricted
    Remediation
    Fix: Restrict Security Group Egress Rules

    Replace overly permissive egress rules with specific, restrictive rules that only allow necessary outbound traffic.

    const securityGroup = new aws.ec2.SecurityGroup("my-sg", {
        vpcId: vpc.id,
        egress: [
            {
                protocol: "tcp",
                fromPort: 443,
                toPort: 443,
                cidrBlocks: ["10.0.0.0/8"],
            },
        ],
    });
    

    security-group-ssh-rdp-egress-restricted

    Severity: critical · Enforcement: advisory

    Enforces strict egress restrictions for SSH and RDP traffic in security groups

    • 1.3.2 — 1.3.2: Outbound traffic from the CDE is restricted
    Remediation
    Fix:

    Restrict SSH and RDP egress traffic to specific destinations instead of allowing unrestricted access:

    import * as aws from "@pulumi/aws";
    
    const securityGroup = new aws.ec2.SecurityGroup("restrictedEgress", {
        egress: [
            {
                protocol: "tcp",
                fromPort: 22,
                toPort: 22,
                cidrBlocks: ["10.0.100.0/24"], // Restrict to management network
            },
            {
                protocol: "tcp",
                fromPort: 3389,
                toPort: 3389,
                cidrBlocks: ["10.0.100.0/24"], // Restrict to management network
            },
        ],
    });
    

    security-group-ssh-rdp-ingress-restricted

    Severity: critical · Enforcement: advisory

    Ensures security groups do not allow SSH/RDP ingress from the internet

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    Remediation
    Fix: Restrict SSH and RDP Access

    Restrict SSH and RDP access to specific IP ranges instead of allowing public internet access.

    import * as aws from "@pulumi/aws";
    
    const securityGroup = new aws.ec2.SecurityGroup("securityGroup", {
        ingress: [
            {
                protocol: "tcp",
                fromPort: 22,
                toPort: 22,
                cidrBlocks: ["10.0.0.0/8"],
            },
            {
                protocol: "tcp",
                fromPort: 3389,
                toPort: 3389,
                cidrBlocks: ["10.0.0.0/8"],
            },
        ],
    });
    

    security-hub-enabled

    Severity: high · Enforcement: disabled

    Ensures AWS Security Hub is enabled for continuous monitoring and security assessment.

    • 10.4.1 — 10.4.1: Potentially suspicious or anomalous activities are quickly identified to minimize impact *
    • 10.4.1.1 — 10.4.1.1: Automated mechanisms are used to perform audit log reviews *
    Remediation
    Fix: Enable AWS Security Hub

    Add an AWS Security Hub Account resource to your Pulumi program to enable continuous monitoring:

    import * as aws from "@pulumi/aws";
    
    // Enable Security Hub for continuous monitoring
    const securityHub = new aws.securityhub.Account("security-hub", {
        // Security Hub will be enabled in the current region
    });
    

    sns-kms-encryption-enabled

    Severity: high · Enforcement: advisory

    Ensures SNS topics have encryption enabled using KMS keys.

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable KMS encryption for SNS topic

    Add the kmsMasterKeyId property to your SNS topic to enable encryption at rest using a KMS key.

    const myTopic = new aws.sns.Topic("myTopic", {
        name: "my-topic",
        kmsMasterKeyId: "alias/aws/sns", // Add KMS key ID or ARN for encryption
    });
    

    sqs-encryption

    Severity: high · Enforcement: advisory

    Ensures SQS queues have server-side encryption enabled

    • 3.5.1 — 3.5.1 PAN is rendered unreadable anywhere it is stored
    • 3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
    Remediation
    Fix: Enable Server-Side Encryption for SQS Queue

    Either remove the explicit sqsManagedSseEnabled: false (SSE-SQS is on by default), or select a KMS key:

    // SSE-SQS with SQS-owned keys - this is also what you get by omitting both properties.
    const queue = new aws.sqs.Queue("my-queue", {
        name: "my-secure-queue",
        sqsManagedSseEnabled: true,
    });
    
    // Or SSE-KMS, with the AWS-managed key or a customer-managed key.
    const kmsQueue = new aws.sqs.Queue("my-kms-queue", {
        name: "my-kms-queue",
        kmsMasterKeyId: "alias/aws/sqs",
        // kmsMasterKeyId: customerKey.arn,
    });
    

    sqs-message-retention

    Severity: medium · Enforcement: advisory

    Ensures SQS queues have message retention periods configured

    • 3.2.1 — 3.2.1: Account data storage is kept to a minimum through implementation of data retention and disposal policies, procedures, and processes
    Remediation
    Fix: Configure SQS Message Retention Period

    Set the messageRetentionSeconds property to define how long messages are retained in the queue (60 seconds to 1,209,600 seconds/14 days):

    const queue = new aws.sqs.Queue("my-queue", {
        messageRetentionSeconds: 345600, // Set retention period (e.g., 4 days)
    });
    

    ssoadmin-managed-policy-attachment-no-admin-access

    Severity: high · Enforcement: advisory

    Checks that IAM Identity Center permission sets do not attach the AWS managed AdministratorAccess policy.

    • 7.2.2 — 7.2.2: Access is assigned to users, including privileged users
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Attach a Scoped Managed Policy to the Permission Set

    Attach an AWS managed policy scoped to the job the permission set grants, instead of AdministratorAccess:

    new aws.ssoadmin.ManagedPolicyAttachment("read-only", {
        instanceArn: permissionSet.instanceArn,
        permissionSetArn: permissionSet.arn,
        managedPolicyArn: aws.iam.ManagedPolicy.ReadOnlyAccess,
    });
    

    vpc-endpoint-security-policy

    Severity: medium · Enforcement: advisory

    Ensures that VPC endpoints are associated with security policies that limit access to specified resources

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 7.3.1 — 7.3.1: An access control system(s) is in place that restricts access based on a user’s need to know and covers all system components
    Remediation
    Fix: Configure Restrictive VPC Endpoint Policy

    Add a policy to your VPC endpoint that specifies explicit principals and resources instead of wildcards:

    const vpcEndpoint = new aws.ec2.VpcEndpoint("my-endpoint", {
        vpcId: vpc.id,
        serviceName: "com.amazonaws.us-west-2.s3",
        policy: JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Principal: {
                    AWS: "arn:aws:iam::123456789012:role/MyRole" // Specify explicit principal ARN
                },
                Action: "s3:GetObject",
                Resource: "arn:aws:s3:::my-bucket/*" // Specify explicit resource ARN
            }]
        }),
    });
    

    vpc-flow-logs

    Severity: medium · Enforcement: advisory

    Ensures VPC flow logs use approved destinations for centralized monitoring

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    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",
    });
    

    vpc-route-table-internet-gateway-restricted

    Severity: high · Enforcement: advisory

    Ensures VPC route tables restrict public access to internet gateways appropriately.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Restrict Route Table Internet Gateway Access

    Send outbound traffic from private subnets through a NAT gateway (IPv4) or an egress-only internet gateway (IPv6) instead of an internet gateway:

    import * as aws from "@pulumi/aws";
    
    const privateRouteTable = new aws.ec2.RouteTable("private", {
        vpcId: vpc.id,
        routes: [
            { cidrBlock: "0.0.0.0/0", natGatewayId: natGateway.id },
            { ipv6CidrBlock: "::/0", egressOnlyGatewayId: egressOnlyGateway.id },
        ],
    });
    

    A public route table that must reach an internet gateway can be listed in exemptRouteTables.

    vpc-subnet-auto-assign-public-ip-disabled

    Severity: high · Enforcement: advisory

    Ensures VPC subnets have auto-assign public IP disabled to prevent unintended internet exposure.

    • 1.3.1 — 1.3.1: Inbound traffic to the CDE is restricted as follows: To only traffic that is necessary, All other traffic is specifically denied
    • 1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
    Remediation
    Fix: Disable Auto-Assign Public IP for VPC Subnets
    const subnet = new aws.ec2.Subnet("private-subnet", {
        vpcId: vpc.id,
        cidrBlock: "10.0.1.0/24",
        mapPublicIpOnLaunch: false,  // Disable auto-assign public IP to prevent unintended internet exposure
    });
    

    vpc-subnet-flow-logs

    Severity: medium · Enforcement: advisory

    Ensures all VPCs and subnets have flow logs enabled

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable VPC Flow Logs for Network Monitoring

    Create a VPC Flow Log resource and associate it with your VPC or subnet to capture network traffic information:

    const vpc = new aws.ec2.Vpc("my-vpc", {
        cidrBlock: "10.0.0.0/16",
    });
    
    // Enable flow logs for the VPC
    const flowLog = new aws.ec2.FlowLog("vpc-flow-log", {
        vpcId: vpc.id, // Associate flow log with VPC
        trafficType: "ALL", // Capture all traffic (ACCEPT, REJECT, ALL)
        logDestinationType: "cloud-watch-logs",
        logDestination: logGroup.arn,
        iamRoleArn: flowLogRole.arn,
    });
    

    waf-association-validation

    Severity: critical · Enforcement: advisory

    Validates WAF Web ACL associations are properly configured

    • 6.4.2 — 6.4.2: For public-facing web applications, an automated technical solution is deployed that continually detects and prevents web-based attacks
    Remediation
    Fix: Configure WAF Web ACL Association Properties

    Ensure both resourceArn and webAclArn are specified in the WAF association:

    const wafAssociation = new aws.wafv2.WebAclAssociation("my-waf-association", {
        resourceArn: resource.arn, // Specify the resource ARN to protect
        webAclArn: webAcl.arn, // Specify the WAF Web ACL ARN
    });
    

    wafv2-logging-enabled

    Severity: high · Enforcement: advisory

    Ensures WAFv2 Web ACLs have logging configurations enabled for audit and monitoring purposes.

    • 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
    Remediation
    Fix: Enable WAFv2 Web ACL Logging

    Create a WebAclLoggingConfiguration resource to enable logging for your WAFv2 Web ACL:

    import * as aws from "@pulumi/aws";
    
    // Create a Kinesis Data Firehose delivery stream for WAF logs
    const wafLogStream = new aws.kinesis.FirehoseDeliveryStream("waf-logs", {
        destination: "extended_s3",
        extendedS3Configuration: {
            roleArn: logRole.arn,
            bucketArn: logBucket.arn,
            prefix: "waf-logs/",
        },
    });
    
    // Enable logging for the Web ACL
    const webAclLogging = new aws.wafv2.WebAclLoggingConfiguration("web-acl-logging", {
        resourceArn: myWebAcl.arn, // Reference to your Web ACL
        logDestinationConfigs: [
            wafLogStream.arn, // Link to Kinesis Firehose stream
        ],
    });
    

    Alternatively, you can log directly to CloudWatch Logs or S3.

      The infrastructure as code platform for any cloud.