PCI DSS v4.0.1 - AWS
This page lists all 160 policies in the PCI DSS v4.0.1 pack for AWS, as published in pci-dss-aws version 2.0.4.
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
- database-strict-network-access
- ec2-instance-disallow-public-ip
- ec2-launch-configuration-disallow-public-ip
- ec2-launch-template-disallow-public-ip
- ec2-security-group-disallow-inbound-http-traffic
- ec2-vpc-placement-required
- eks-cluster-disallow-api-endpoint-public-access
- elasticsearch-vpc-required
- emr-no-public-ip
- lambda-vpc-placement-required
- rds-private-subnet-validation
- redshift-enhanced-vpc-routing-enabled
- sagemaker-notebook-internet-access-disabled
- security-group-default-deny
- security-group-default-deny
- security-group-ssh-rdp-ingress-restricted
- vpc-endpoint-security-policy
- vpc-route-table-internet-gateway-restricted
- vpc-subnet-auto-assign-public-ip-disabled
1.3.2 — 1.3.2: Outbound traffic from the CDE is restricted
- database-strict-egress
- redshift-enhanced-vpc-routing-enabled
- security-group-default-deny
- security-group-egress-restriction
- security-group-ssh-rdp-egress-restricted
1.4.1 — 1.4.1: NSCs are implemented between trusted and untrusted networks *
- dms-no-public-access
- ec2-vpc-placement-required
- elasticsearch-vpc-required
- lambda-vpc-placement-required
- neptune-clusterinstance-no-public-access
- rds-cluster-instance-disallow-public-access
- rds-instance-disallow-public-access
- rds-private-subnet-validation
- redshift-public-access-prohibited
1.4.2 — 1.4.2: Inbound traffic from untrusted networks to trusted networks is restricted
- dms-no-public-access
- ec2-instance-disallow-public-ip
- ec2-launch-configuration-disallow-public-ip
- ec2-launch-template-disallow-public-ip
- eks-cluster-disallow-api-endpoint-public-access
- emr-no-public-ip
- lambda-public-access-restricted
- neptune-clusterinstance-no-public-access
- rds-cluster-instance-disallow-public-access
- rds-instance-disallow-public-access
- redshift-public-access-prohibited
- s3-bucket-disallow-public-read
- s3-bucket-public-access-block-required
- sagemaker-notebook-internet-access-disabled
- security-group-default-deny
- vpc-route-table-internet-gateway-restricted
- vpc-subnet-auto-assign-public-ip-disabled
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 *
- api-gateway-domain-name-configure-security-policy
- api-gateway-ssl-certificate-required
- api-gateway-v2-domain-name-configure-domain-name-security-policy
- cloudfront-distribution-configure-secure-tls-to-origin
- cloudfront-distribution-enable-tls-to-origin
- elasticsearch-node-to-node-encryption-enabled
- redshift-ssl-required
- s3-bucket-ssl-enforcement-required
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
- api-gateway-cache-encryption-enabled
- appflow-connector-profile-configure-customer-managed-key
- appflow-flow-configure-customer-managed-key
- athena-database-configure-customer-managed-key
- athena-database-disallow-unencrypted-database
- athena-workgroup-configure-customer-managed-key
- athena-workgroup-disallow-unencrypted-workgroup
- cloudtrail-kms-encryption-enabled
- cloudwatch-log-group-kms-encryption-enabled
- dynamodb-kms-encryption-enabled
- ebs-volume-configure-customer-managed-key
- ebs-volume-disallow-unencrypted-volume
- ec2-instance-disallow-unencrypted-block-device
- ec2-instance-disallow-unencrypted-root-block-device
- ec2-launch-configuration-disallow-unencrypted-block-device
- ec2-launch-configuration-disallow-unencrypted-root-block-device
- ec2-launch-template-configure-customer-managed-key
- ec2-launch-template-disallow-unencrypted-block-device
- ecr-repository-configure-customer-managed-key
- ecr-repository-disallow-unencrypted-repository
- efs-file-system-configure-customer-managed-key
- efs-file-system-disallow-unencrypted-file-system
- eks-cluster-enable-cluster-encryption-config
- elasticsearch-encryption-enabled
- lambda-environment-variables-encryption
- rds-cluster-configure-customer-managed-key
- rds-cluster-disallow-unencrypted-storage
- rds-instance-configure-customer-managed-key
- rds-instance-disallow-unencrypted-storage
- redshift-kms-encryption-enabled
- s3-bucket-encryption
- sagemaker-endpoint-kms-encryption-enabled
- sagemaker-notebook-kms-encryption-enabled
- secrets-manager-secret-configure-customer-managed-key
- sns-kms-encryption-enabled
- sqs-encryption
3.6.1 — 3.6.1: Procedures are defined and implemented to protect cryptographic keys used to protect stored account data against disclosure
- api-gateway-cache-encryption-enabled
- appflow-connector-profile-configure-customer-managed-key
- appflow-flow-configure-customer-managed-key
- athena-database-configure-customer-managed-key
- athena-database-disallow-unencrypted-database
- athena-workgroup-configure-customer-managed-key
- athena-workgroup-disallow-unencrypted-workgroup
- cloudtrail-kms-encryption-enabled
- cloudwatch-log-group-kms-encryption-enabled
- dynamodb-kms-encryption-enabled
- ebs-volume-configure-customer-managed-key
- ebs-volume-disallow-unencrypted-volume
- ec2-instance-disallow-unencrypted-block-device
- ec2-instance-disallow-unencrypted-root-block-device
- ec2-launch-configuration-disallow-unencrypted-block-device
- ec2-launch-configuration-disallow-unencrypted-root-block-device
- ec2-launch-template-configure-customer-managed-key
- ec2-launch-template-disallow-unencrypted-block-device
- ecr-repository-configure-customer-managed-key
- ecr-repository-disallow-unencrypted-repository
- efs-file-system-configure-customer-managed-key
- efs-file-system-disallow-unencrypted-file-system
- eks-cluster-enable-cluster-encryption-config
- elasticsearch-encryption-enabled
- lambda-environment-variables-encryption
- rds-cluster-configure-customer-managed-key
- rds-cluster-disallow-unencrypted-storage
- rds-instance-configure-customer-managed-key
- rds-instance-disallow-unencrypted-storage
- redshift-kms-encryption-enabled
- s3-bucket-encryption
- sagemaker-endpoint-kms-encryption-enabled
- sagemaker-notebook-kms-encryption-enabled
- secrets-manager-secret-configure-customer-managed-key
- sns-kms-encryption-enabled
- sqs-encryption
3.6.1.2 — 3.6.1.2: Secret and private keys used to protect stored account data
- appflow-connector-profile-configure-customer-managed-key
- appflow-flow-configure-customer-managed-key
- athena-database-configure-customer-managed-key
- athena-workgroup-configure-customer-managed-key
- ebs-volume-configure-customer-managed-key
- ec2-launch-template-configure-customer-managed-key
- ecr-repository-configure-customer-managed-key
- efs-file-system-configure-customer-managed-key
- rds-cluster-configure-customer-managed-key
- rds-instance-configure-customer-managed-key
- s3-bucket-encryption
- secrets-manager-secret-configure-customer-managed-key
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 *
- api-gateway-domain-name-configure-security-policy
- api-gateway-ssl-certificate-required
- api-gateway-v2-domain-name-configure-domain-name-security-policy
- cloudfront-distribution-configure-secure-tls
- cloudfront-distribution-configure-secure-tls-to-origin
- cloudfront-distribution-disallow-unencrypted-traffic
- cloudfront-distribution-enable-tls-to-origin
- ec2-security-group-disallow-inbound-http-traffic
- elasticsearch-https-required
- elasticsearch-node-to-node-encryption-enabled
- elb-load-balancer-disallow-unencrypted-traffic
- rds-clusterinstance-ssl-encryption
- rds-instance-ssl-encryption
- redshift-ssl-required
- s3-bucket-ssl-enforcement-required
6.3.3 — 6.3.3: All system components are protected from known vulnerabilities by installing applicable security patches/updates *
- docdb-clusterinstance-managed-service-patching
- elasticbeanstalk-managed-updates-enabled
- lambda-runtime-restrictions
- neptune-clusterinstance-managed-service-patching
- rds-clusterinstance-managed-service-patching
- rds-instance-managed-service-patching
- redshift-maintenance-required
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
- api-gateway-authorization
- iam-user-group-membership-required
- pubsub-least-privilege-iam
- s3-bucket-least-privilege
7.2.2 — 7.2.2: Access is assigned to users, including privileged users
- api-gateway-authorization
- ec2-iam-profile-required
- ecs-task-non-privileged-required
- iam-user-group-membership-required
- iam-user-policy-least-privilege
- kms-grant-access-control
- kms-key-policy-access-control
- lambda-permission-configure-source-arn
- no-direct-user-access-keys
- pubsub-least-privilege-iam
- s3-bucket-least-privilege
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
- iam-user-policy-least-privilege
- lambda-permission-configure-source-arn
- lambda-public-access-restricted
- pubsub-least-privilege-iam
- s3-bucket-disallow-public-read
- s3-bucket-least-privilege
- s3-bucket-public-access-block-required
- vpc-endpoint-security-policy
8.2.1 — 8.2.1: All users are assigned a unique ID before access to system components or cardholder data is allowed *
- ec2-iam-profile-required
- no-direct-user-access-keys
- rds-iam-authentication
- restrict-default-iam-user-creation
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
- api-gateway-access-logging-enabled
- api-gateway-v2-access-logging
- cloudfront-distribution-configure-access-logging
- cloudtrail-enabled
- cloudtrail-multi-region-enabled
- cloudtrail-s3-data-events-enabled
- config-recorder-enabled
- ec2-monitoring-enabled
- elasticbeanstalk-health-reporting-enabled
- elasticsearch-cloudwatch-logging-enabled
- elb-load-balancer-configure-access-logging
- lambda-function-logging
- rds-audit-logging
- rds-clusterinstance-enhanced-monitoring
- rds-instance-enhanced-monitoring
- redshift-logging-enabled
- s3-bucket-access-logging
- vpc-flow-logs
- vpc-subnet-flow-logs
- wafv2-logging-enabled
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
- cloudtrail-cloudwatch-logs-integration
- elasticsearch-cloudwatch-logging-enabled
- s3-bucket-access-logging
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 caching is enabled for an API Gateway method, ensure that cache data encryption is also enabled to protect sensitive data at rest.
import * as aws from "@pulumi/aws";
const methodSettings = new aws.apigateway.MethodSettings("exampleMethodSettings", {
restApi: restApi.id,
stageName: stage.stageName,
methodPath: "*/*",
settings: {
cachingEnabled: true,
cacheDataEncrypted: true, // Enable cache encryption
cacheTtlInSeconds: 300,
},
});
Key change: Set cacheDataEncrypted: true in the method settings when cachingEnabled is true.
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 API Gateway Domain Name to use TLS 1.2 security policy
Update the API Gateway Domain Name to use the TLS 1.2 security policy for secure encrypted communication.
new apigateway.DomainName("api-domain", {
domainName: "api.example.com",
certificateArn: certificateArn,
// Set security policy to TLS 1.2 for secure encryption
securityPolicy: "TLS_1_2",
});
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 SSL Client Certificate for API Gateway Stage
To remediate this violation, add a client certificate to your API Gateway stage:
import * as aws from "@pulumi/aws";
// First, create a client certificate
const clientCert = new aws.apigateway.ClientCertificate("apiClientCert", {
description: "Client certificate for API Gateway stage",
});
// Then reference it in your stage configuration
const stage = new aws.apigateway.Stage("myApiStage", {
restApi: myRestApi.id,
deployment: myDeployment.id,
stageName: "production",
clientCertificateId: clientCert.id, // Add this property to enable SSL client authentication
});
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
To fix this policy violation, configure access logging for your API Gateway V2 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 V2 logs
const apiLogGroup = new aws.cloudwatch.LogGroup("api-gateway-v2-logs", {
retentionInDays: 30,
});
// Create an API Gateway V2 stage with access logging enabled
const stage = new aws.apigatewayv2.Stage("my-v2-stage", {
apiId: myApi.id,
name: "prod",
// Enable access logging with destination ARN
accessLogSettings: {
destinationArn: apiLogGroup.arn, // Specify CloudWatch log group ARN
},
});
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: Configure API Gateway V2 Domain Name to use TLS 1.2
Update the domain name configuration to use the TLS 1.2 security policy:
import * as apigatewayv2 from "@pulumi/aws/apigatewayv2";
const domainName = new apigatewayv2.DomainName("customDomain", {
domainName: "api.example.com",
domainNameConfiguration: {
certificateArn: "arn:aws:acm:...",
endpointType: "REGIONAL",
securityPolicy: "TLS_1_2", // Set to 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 WAF Web ACL with API Gateway Stage
Add a WAF v2 Web ACL association to protect your API Gateway stage from web-based attacks:
import * as aws from "@pulumi/aws";
// Create or reference an existing WAF Web ACL
const webAcl = new aws.wafv2.WebAcl("api-protection", {
scope: "REGIONAL",
defaultAction: { allow: {} },
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "api-waf-metrics",
sampledRequestsEnabled: true,
},
rules: [/* your WAF rules */],
});
// Associate the Web ACL with your API Gateway stage
const wafAssociation = new aws.wafv2.WebAclAssociation("api-stage-waf", {
resourceArn: apiStage.arn, // Reference your API Gateway stage ARN
webAclArn: webAcl.arn, // Associate the WAF Web ACL
});
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
import * as aws from "@pulumi/aws";
const connectorProfile = new aws.appflow.ConnectorProfile("my-connector-profile", {
kmsArn: myKmsKey.arn, // Add customer-managed KMS key ARN
});
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
Configure a customer-managed KMS key for your AppFlow flow to ensure encrypted data storage.
import * as appflow from "@pulumi/aws/appflow";
import * as kms from "@pulumi/aws/kms";
// Create a customer-managed KMS key
const flowKey = new kms.Key("flowKey", {
description: "KMS key for AppFlow flow encryption",
enableKeyRotation: true, // Enable automatic key rotation
});
const flow = new appflow.Flow("flow", {
kmsArn: flowKey.arn, // Specify the customer-managed KMS key
// ... other flow configuration
});
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 Athena databases to use SSE-KMS encryption with customer-managed keys.
Example - Create Athena database with KMS encryption:
const kmsKey = new aws.kms.Key("athena-key", {
description: "KMS key for Athena database encryption"
});
const athenaDatabase = new aws.athena.Database("encrypted-database", {
name: "my_database",
bucket: outputBucket.id,
encryptionConfiguration: {
encryptionOption: "SSE_KMS", // Use KMS encryption
kmsKey: kmsKey.arn // Customer-managed key
}
});
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
const database = new aws.athena.Database("my-database", {
name: "my_database",
bucket: myBucket.id,
encryptionConfiguration: {
encryptionOption: "SSE_S3",
},
});
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 Athena Workgroup with Customer-Managed KMS Key
const workgroup = new aws.athena.Workgroup("my-workgroup", {
name: "my-workgroup",
configuration: {
resultConfiguration: {
encryptionConfiguration: {
encryptionOption: "SSE_KMS",
kmsKeyArn: kmsKey.arn,
},
},
},
});
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: Configure Athena workgroups with encryption
const workgroup = new aws.athena.Workgroup("encrypted-workgroup", {
name: "my-workgroup",
configuration: {
resultConfiguration: {
encryptionConfiguration: {
encryptionOption: "SSE_S3"
}
}
}
});
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 Configuration Enforcement for Athena Workgroup
const workgroup = new aws.athena.Workgroup("my-workgroup", {
configuration: {
enforceWorkgroupConfiguration: true,
},
});
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.
const trail = new aws.cloudtrail.Trail("audit-trail", {
s3BucketName: bucket.id,
eventSelectors: [{
readWriteType: "All",
includeManagementEvents: true,
dataResources: [{
type: "AWS::S3::Object",
// Enable data events for all S3 buckets to track audit log access
values: ["arn:aws:s3:::*/*"],
}],
}],
});
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: Enable access logging for CloudFront distribution
const distribution = new cloudfront.Distribution("distribution", {
loggingConfig: {
bucket: logsBucket.bucketDomainName,
},
});
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 CloudFront Distribution to Use TLSv1.2_2021
const distribution = new aws.cloudfront.Distribution("my-distribution", {
viewerCertificate: {
minimumProtocolVersion: "TLSv1.2_2021",
},
});
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 Secure TLS for CloudFront to Origin
const distribution = new cloudfront.Distribution("distribution", {
origins: [{
domainName: "example.com",
originId: "myCustomOrigin",
customOriginConfig: {
originSslProtocols: ["TLSv1.2"],
},
}],
});
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 a WAF Web ACL with CloudFront Distribution
const distribution = new aws.cloudfront.Distribution("myDistribution", {
webAclId: webAcl.arn,
});
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: Configure CloudFront to require HTTPS
const distribution = new aws.cloudfront.Distribution("secure-distribution", {
defaultCacheBehavior: {
viewerProtocolPolicy: "redirect-to-https",
},
orderedCacheBehaviors: [{
viewerProtocolPolicy: "https-only",
}]
});
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 for CloudFront to Origin
const distribution = new cloudfront.Distribution("distribution", {
origins: [{
domainName: "example.com",
originId: "myCustomOrigin",
customOriginConfig: {
originProtocolPolicy: "https-only",
},
}],
});
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: Enable CloudWatch Logs Integration for CloudTrail
Configure your CloudTrail trail to send logs to a CloudWatch Logs group by setting both the log group ARN and the IAM role ARN:
import * as aws from "@pulumi/aws";
// Create a CloudWatch Logs group for CloudTrail
const cloudtrailLogGroup = new aws.cloudwatch.LogGroup("cloudtrail-logs");
// Create an IAM role for CloudTrail to write to CloudWatch Logs
const cloudtrailRole = new aws.iam.Role("cloudtrail-cloudwatch-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: { Service: "cloudtrail.amazonaws.com" },
Action: "sts:AssumeRole",
}],
}),
});
// Attach policy to allow CloudTrail to write logs
const cloudtrailPolicy = new aws.iam.RolePolicy("cloudtrail-cloudwatch-policy", {
role: cloudtrailRole.id,
policy: pulumi.all([cloudtrailLogGroup.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: trailBucket.id,
cloudWatchLogsGroupArn: cloudtrailLogGroup.arn, // Set the log group ARN
cloudWatchLogsRoleArn: cloudtrailRole.arn, // Set the IAM role ARN
});
cloudtrail-enabled
Severity: critical · Enforcement: advisory
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 at least one CloudTrail trail to capture all API activity and management events in your AWS account.
import * as aws from "@pulumi/aws";
const trailBucket = new aws.s3.Bucket("cloudtrail-logs");
const trail = new aws.cloudtrail.Trail("main-trail", {
s3BucketName: trailBucket.bucket,
});
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
Configure your CloudTrail trail to use a customer-managed KMS key for encrypting log files:
import * as aws from "@pulumi/aws";
const cloudtrailKey = new aws.kms.Key("cloudtrail-key");
const trail = new aws.cloudtrail.Trail("my-trail", {
s3BucketName: bucket.id,
kmsKeyId: cloudtrailKey.arn,
});
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
Set the enableLogFileValidation property to true on your CloudTrail trail resource to enable cryptographic verification of log files.
const trail = new aws.cloudtrail.Trail("audit-trail", {
s3BucketName: bucket.id,
enableLogFileValidation: true,
});
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
Set the isMultiRegionTrail property to true to enable logging across all AWS regions:
const trail = new aws.cloudtrail.Trail("my-trail", {
s3BucketName: bucket.id,
isMultiRegionTrail: true,
});
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: Enable S3 Public Access Block for CloudTrail Buckets
Configure an S3 BucketPublicAccessBlock resource to deny all public access to your CloudTrail logging bucket. All four public access block settings must be enabled to protect audit logs from unauthorized access.
import * as aws from "@pulumi/aws";
// Create CloudTrail S3 bucket
const cloudtrailBucket = new aws.s3.Bucket("cloudtrail-logs", {
bucket: "my-cloudtrail-logs-bucket",
});
// Enable public access block with all settings enabled
const publicAccessBlock = new aws.s3.BucketPublicAccessBlock("cloudtrail-bucket-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.
Using Event Selectors:
const trail = new aws.cloudtrail.Trail("myTrail", {
s3BucketName: trailBucket.id,
eventSelectors: [{
readWriteType: "All", // Capture both read and write events
includeManagementEvents: true,
dataResources: [{
type: "AWS::S3::Object",
values: ["arn:aws:s3:::*/"], // Log all S3 buckets, or specify specific buckets
}],
}],
});
Using Advanced Event Selectors (Recommended):
const trail = new aws.cloudtrail.Trail("myTrail", {
s3BucketName: trailBucket.id,
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
},
],
}],
});
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 actions on CloudWatch alarms
Configure CloudWatch alarms to have actions enabled and specify at least one action target (such as an SNS topic) for the ALARM state to ensure proper incident response.
import * as aws from "@pulumi/aws";
const alarmTopic = new aws.sns.Topic("alarm-notifications");
const alarm = new aws.cloudwatch.MetricAlarm("example-alarm", {
metricName: "CPUUtilization",
namespace: "AWS/EC2",
statistic: "Average",
period: 300,
evaluationPeriods: 2,
threshold: 80,
comparisonOperator: "GreaterThanThreshold",
actionsEnabled: true,
alarmActions: [alarmTopic.arn],
});
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
import * as aws from "@pulumi/aws";
const logGroup = new aws.cloudwatch.LogGroup("my-log-group", {
kmsKeyId: logEncryptionKey.arn, // Add KMS key ARN
});
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
To comply with PCI DSS 10.5.1 audit log retention requirements, set a retention period of at least 365 days on your CloudWatch log group.
import * as aws from "@pulumi/aws";
const logGroup = new aws.cloudwatch.LogGroup("example-log-group", {
name: "/aws/lambda/my-function",
retentionInDays: 365, // Set retention to at least 365 days for PCI DSS compliance
});
Valid retention values (in days): 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653, or 0 (never expire).
config-recorder-enabled
Severity: critical · Enforcement: advisory
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
const recorder = new aws.cfg.Recorder("recorder", {
recordingGroup: {
allSupported: true,
includeGlobalResourceTypes: true,
},
});
const recorderStatus = new aws.cfg.RecorderStatus("recorderStatus", {
name: recorder.name,
isEnabled: true,
});
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: Configure AWS Config retention
const configRetention = new aws.cfg.RetentionConfiguration("config-retention", {
retentionPeriodInDays: 2555,
});
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: Ensure DMS replication instances are not publicly accessible
const replicationInstance = new aws.dms.ReplicationInstance("private-instance", {
publiclyAccessible: false,
});
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 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 with Customer Managed Key
Configure your DynamoDB table to use a customer managed KMS key for server-side encryption:
import * as aws from "@pulumi/aws";
const kmsKey = new aws.kms.Key("dynamodb-key");
const table = new aws.dynamodb.Table("my-table", {
attributes: [{ name: "id", type: "S" }],
hashKey: "id",
serverSideEncryption: {
enabled: true,
kmsKeyArn: kmsKey.arn,
},
});
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 Volume to Instance
Create a VolumeAttachment resource to attach the volume to an EC2 instance.
const volume = new aws.ebs.Volume("data-volume", {
availabilityZone: "us-west-2a",
size: 100,
});
const attachment = new aws.ec2.VolumeAttachment("data-volume-attachment", {
deviceName: "/dev/sdh",
volumeId: volume.id,
instanceId: instance.id,
});
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: Use Customer-Managed KMS Key for EBS Volume
const volume = new aws.ebs.Volume("my-volume", {
availabilityZone: "us-west-2a",
size: 10,
encrypted: true,
kmsKeyId: kmsKey.id,
});
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 encryption on EBS volumes using the encrypted property.
const volume = new aws.ebs.Volume("encrypted-volume", {
encrypted: true,
});
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 to EC2 Instance
Attach an IAM instance profile to enable role-based access control and ensure proper identity assignment for privileged access.
import * as aws from "@pulumi/aws";
const role = new aws.iam.Role("instance-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: { Service: "ec2.amazonaws.com" },
}],
}),
});
const instanceProfile = new aws.iam.InstanceProfile("instance-profile", {
role: role.name,
});
const instance = new aws.ec2.Instance("my-instance", {
ami: "ami-12345678",
instanceType: "t3.micro",
iamInstanceProfile: instanceProfile.name,
});
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 assignment for EC2 instances
Set the associatePublicIpAddress property to false:
const instance = new ec2.Instance("my-instance", {
associatePublicIpAddress: false,
});
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 EC2 instance block devices
Set the encrypted property to true for all EBS block devices:
const instance = new aws.ec2.Instance("my-instance", {
ebsBlockDevices: [{
encrypted: true,
}],
});
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 EC2 instance root block device
Set the encrypted property to true in the root block device configuration:
const instance = new aws.ec2.Instance("my-instance", {
rootBlockDevice: {
encrypted: true,
},
});
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
Set associatePublicIpAddress to false:
const launchConfiguration = new ec2.LaunchConfiguration("launchConfiguration", {
associatePublicIpAddress: false,
});
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 Volume Encryption
Set the encrypted property to true for all EBS block devices:
const launchConfiguration = new ec2.LaunchConfiguration("launchConfiguration", {
ebsBlockDevices: [{
encrypted: true,
}],
});
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 Root Block Device Encryption
Set the encrypted property to true in the root block device configuration:
const launchConfig = new aws.ec2.LaunchConfiguration("my-launch-config", {
rootBlockDevice: {
encrypted: true,
},
});
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: Configure Customer-Managed KMS Key for Encrypted EBS Volumes
Specify a customer-managed KMS key for encrypted EBS volumes:
const ebs_kms_key = new aws.kms.Key("ebs-key", {});
const launchTemplate = new aws.ec2.LaunchTemplate("example", {
blockDeviceMappings: [{
ebs: {
encrypted: "true",
kmsKeyId: ebs_kms_key.arn,
},
}],
});
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 Auto-Assignment in Launch Template
Set the associatePublicIpAddress property to "false":
const launchTemplate = new aws.ec2.LaunchTemplate("example", {
networkInterfaces: [{
associatePublicIpAddress: "false",
}],
});
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 Encryption on EC2 Launch Template Block Devices
Set the encrypted property to "true" for all EBS block devices:
const launchTemplate = new aws.ec2.LaunchTemplate("example", {
blockDeviceMappings: [{
ebs: {
encrypted: "true",
},
}],
});
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 for EC2 Instance
Enable detailed monitoring on your EC2 instance to provide 1-minute interval metrics for enhanced security monitoring and logging.
const instance = new aws.ec2.Instance("my-instance", {
ami: "ami-12345678",
instanceType: "t3.micro",
monitoring: true,
});
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: Use HTTPS Instead of HTTP
Do not configure ingress rules with TCP protocol on port 80:
const securityGroup = new ec2.SecurityGroup("securityGroup", {
ingress: [
{
protocol: "tcp",
fromPort: 443,
toPort: 443,
},
// DO NOT use port 80:
// {
// protocol: "tcp",
// fromPort: 80,
// toPort: 80,
// },
],
});
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
const myInstance = new aws.ec2.Instance("my-instance", {
instanceType: "t3.micro",
ami: "ami-0abcdef1234567890",
subnetId: mySubnet.id,
});
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 ECR repositories to use customer-managed KMS keys for encryption:
const ecrKmsKey = new aws.kms.Key("ecrKmsKey", {});
const repository = new ecr.Repository("repository", {
encryptionConfigurations: [{
encryptionType: "KMS",
kmsKey: ecrKmsKey.arn,
}],
});
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:
const repository = new aws.ecr.Repository("my-repository", {
imageTagMutability: "IMMUTABLE",
});
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:
const ecrRepo = new aws.ecr.Repository("my-repo", {
encryptionConfigurations: [{
encryptionType: "AES256",
}],
});
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: Configure ECS Task to Run as Non-Privileged User
const taskDefinition = new aws.ecs.TaskDefinition("app-task", {
containerDefinitions: JSON.stringify([{
name: "app-container",
privileged: false,
user: "1000:1000", // For host mode or when checkAllContainers=true
linuxParameters: {
capabilities: {
add: [], // Avoid SYS_ADMIN, NET_ADMIN, 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 your EFS filesystem to use a customer-managed KMS key for encryption:
const efsKmsKey = new aws.kms.Key("efsKmsKey", {});
const fileSystem = new efs.FileSystem("efsFileSystem", {
encrypted: true,
kmsKeyId: efsKmsKey.arn,
});
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 filesystem
Set the encrypted property to true:
const fileSystem = new aws.efs.FileSystem("example", {
encrypted: true,
});
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: Restrict EKS Cluster API Endpoint Access
Either disable public access or restrict publicAccessCidrs:
const cluster = new eks.Cluster("my-cluster", {
vpcConfig: {
endpointPublicAccess: false,
// OR
// endpointPublicAccess: true,
// publicAccessCidrs: ["10.0.0.0/8"], // Do not use 0.0.0.0/0 or ::/0
},
});
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 encryption configuration for your EKS cluster:
const eksKmsKey = new aws.kms.Key("eksKmsKey", {});
const cluster = new eks.Cluster("eksCluster", {
encryptionConfig: {
provider: {
keyArn: eksKmsKey.arn,
},
resources: ["secrets"],
},
});
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 for Elastic Beanstalk Environment
Add a setting to your Elastic Beanstalk environment configuration to enable enhanced health reporting:
const environment = new aws.elasticbeanstalk.Environment("my-environment", {
application: myApp.name,
solutionStackName: "64bit Amazon Linux 2 v5.8.0 running Node.js 18",
settings: [
{
namespace: "aws:elasticbeanstalk:healthreporting:system",
name: "SystemType",
value: "enhanced",
},
],
});
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 for Elastic Beanstalk
Configure managed platform updates to automatically apply security patches and updates to your Elastic Beanstalk environment.
const environment = new aws.elasticbeanstalk.Environment("my-environment", {
application: "my-app",
solutionStackName: "64bit Amazon Linux 2 v5.8.0 running Node.js 18",
settings: [
{
namespace: "aws:elasticbeanstalk:managedactions",
name: "ManagedActionsEnabled",
value: "true",
},
{
namespace: "aws:elasticbeanstalk:managedactions",
name: "PreferredStartTime",
value: "sun:02:00",
},
{
namespace: "aws:elasticbeanstalk:managedactions:platformupdate",
name: "UpdateLevel",
value: "minor",
},
],
});
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
Configure the Elasticsearch domain to send audit logs and other log types to CloudWatch by adding the logPublishingOptions property.
import * as aws from "@pulumi/aws";
const esLogGroup = new aws.cloudwatch.LogGroup("es-audit-logs");
const esDomain = new aws.elasticsearch.Domain("my-domain", {
logPublishingOptions: [
{
logType: "AUDIT_LOGS",
enabled: true,
cloudwatchLogGroupArn: esLogGroup.arn,
},
],
});
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
Add the encryptAtRest configuration to your Elasticsearch domain with enabled set to true:
const domain = new aws.elasticsearch.Domain("my-domain", {
domainName: "my-elasticsearch-domain",
encryptAtRest: {
enabled: true,
},
});
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 Elasticsearch domain to require HTTPS for all client connections by setting the enforceHttps property to true:
const domain = new aws.elasticsearch.Domain("my-domain", {
domainName: "my-elasticsearch-domain",
domainEndpointOptions: {
enforceHttps: true,
},
});
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
Enable node-to-node encryption for the Elasticsearch domain:
const esDomain = new aws.elasticsearch.Domain("my-es-domain", {
elasticsearchVersion: "7.10",
nodeToNodeEncryption: {
enabled: true,
},
});
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
Configure the Elasticsearch domain with VPC options to ensure network isolation and proper access control.
const esDomain = new aws.elasticsearch.Domain("my-domain", {
domainName: "my-elasticsearch-domain",
elasticsearchVersion: "7.10",
vpcOptions: {
subnetIds: [subnet1.id, subnet2.id],
},
});
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
Set the accessLogs.enabled property to true:
const loadBalancer = new aws.elb.LoadBalancer("my-load-balancer", {
accessLogs: {
enabled: true,
bucket: "my-elb-logs-bucket",
},
});
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 ELB listeners
Do not use HTTP as the lbProtocol:
const lb = new aws.elb.LoadBalancer("my-load-balancer", {
listeners: [
{
lbProtocol: "https", // Use HTTPS instead of HTTP
},
],
});
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", {
mapPublicIpOnLaunch: false,
});
const emrCluster = new aws.emr.Cluster("my-emr-cluster", {
ec2Attributes: {
subnetId: privateSubnet.id,
},
});
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: advisory
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
const detector = new aws.guardduty.Detector("guardduty-detector", {
enable: true,
});
const malwareProtection = new aws.guardduty.DetectorFeature("malware-protection", {
detectorId: detector.id,
name: "EBS_MALWARE_PROTECTION",
status: "ENABLED",
});
const s3Protection = new aws.guardduty.DetectorFeature("s3-protection", {
detectorId: detector.id,
name: "S3_DATA_EVENTS",
status: "ENABLED",
});
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 Password Complexity Requirements
Update your IAM Account Password Policy to require all character types (lowercase, uppercase, numbers, and symbols):
new aws.iam.AccountPasswordPolicy("password-policy", {
requireLowercaseCharacters: true, // Require at least one lowercase letter
requireUppercaseCharacters: true, // Require at least one uppercase letter
requireNumbers: true, // Require at least one number
requireSymbols: true, // Require at least one symbol
});
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 IAM Password Expiration
Enable password expiration in your IAM account password policy to ensure users must change their passwords regularly.
const accountPasswordPolicy = new aws.iam.AccountPasswordPolicy("password-policy", {
maxPasswordAge: 90, // Set password expiration period (typically 90 days for PCI DSS compliance)
});
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
Update your IAM Account Password Policy to prevent password reuse by setting the passwordReusePrevention property to at least 24 (or your configured minimum).
const accountPasswordPolicy = new aws.iam.AccountPasswordPolicy("account-password-policy", {
passwordReusePrevention: 24, // Prevent reuse of the last 24 passwords
});
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-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 IAM User to Group
Assign the IAM user to one or more appropriate groups using an aws.iam.UserGroupMembership resource.
// Add the user to the group - this resolves the policy violation
new aws.iam.UserGroupMembership("user-group-membership", {
user: user.name,
groups: [
developerGroup.name,
],
});
Alternatively, if this is a service account or special-purpose user that should not be in a group, add it to the exemptUsers configuration list.
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
userName: user.name, // Associate MFA device with the user
});
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-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
Instead of using wildcards (*) for actions or resources, specify only the permissions needed:
new aws.iam.UserPolicy("example-user-policy", {
user: myUser.name,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
// Use specific actions instead of "*"
Action: [
"s3:ListBucket",
"s3:GetObject"
],
// Use specific resource ARNs instead of "*"
Resource: [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}]
})
});
Avoid using NotAction or NotResource as they can grant unintended permissions.
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: Use UserPolicyAttachment with managed policies
new aws.iam.UserPolicyAttachment("myAttachment", {
user: "my-user-name",
policyArn: managedPolicy.arn,
});
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"
}],
}],
});
// ALB: Avoid tags enabling IP forwarding
new aws.lb.LoadBalancer("my-alb", {
internal: false,
loadBalancerType: "application",
tags: { "Name": "public-alb" }, // Not "forward-internal-ips": "true"
});
// 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 all Lambda functions have their environment variables encrypted using AWS KMS
- 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 KMS
Configure a KMS key for Lambda function environment variable encryption 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 all AWS Lambda functions have logging enabled to track output data processing
- 10.2.1 — 10.2.1: Audit logs are enabled and active for all system components and cardholder data
Remediation
Fix: Enable Lambda Function Logging Configuration
Add the loggingConfig property to your Lambda function with an appropriate applicationLogLevel (DEBUG, INFO, or WARN):
const myFunction = new aws.lambda.Function("my-function", {
runtime: "nodejs18.x",
handler: "index.handler",
role: lambdaRole.arn,
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./app"),
}),
loggingConfig: {
logFormat: "JSON",
applicationLogLevel: "INFO", // Set log level to INFO or DEBUG for adequate logging
},
});
lambda-permission-configure-source-arn
Severity: critical · Enforcement: advisory
Checks that lambda function permissions have a source arn specified.
- 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: Configure Source ARN for Lambda Permissions
Set the sourceArn property on Lambda permissions:
const permission = new lambda.Permission("permission", {
sourceArn: "arn:aws:service:region:account:resource",
});
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
new aws.lambda.Permission("lambdaPermission", {
action: "lambda:InvokeFunction",
function: myLambdaFunction.name,
principal: "apigateway.amazonaws.com",
});
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
Deploy the Lambda function within a VPC to ensure network isolation and controlled access to resources.
const myFunction = new aws.lambda.Function("myFunction", {
vpcConfig: {
subnetIds: [privateSubnet1.id, privateSubnet2.id], // Specify at least 2 subnets
securityGroupIds: [lambdaSecurityGroup.id], // Define security groups
},
});
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: Remove IAM Access Keys and Use IAM Roles or Federated Identity
Remove the aws.iam.AccessKey resource from your Pulumi program. Instead, use one of these secure alternatives:
For human users:
- Use AWS SSO (IAM Identity Center) or federated identity providers
- Configure temporary credentials via
aws sso login
For service-to-service authentication:
// Create an IAM role for your EC2 instance, Lambda, or ECS task
const serviceRole = new aws.iam.Role("serviceRole", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: {
Service: "ec2.amazonaws.com",
},
}],
}),
});
// Remove the IAM Access Key resource entirely
// ❌ const accessKey = new aws.iam.AccessKey("userKey", { user: "myUser" });
no-hardcoded-secrets
Severity: critical · Enforcement: advisory
Ensures EC2 instance userData 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 userData scripts.
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 *
}],
}),
});
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
Set the kmsKeyId property on encrypted RDS clusters:
const kmsKey = new aws.kms.Key("rds-key", {});
const dbCluster = new aws.rds.Cluster("my-cluster", {
storageEncrypted: true,
kmsKeyId: kmsKey.arn,
});
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 storage encryption on RDS clusters using the storageEncrypted property:
const cluster = new aws.rds.Cluster("encrypted-cluster", {
storageEncrypted: 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
Configure a parameter group to enforce SSL/TLS connections for your Aurora cluster instance:
const clusterParamGroup = new aws.rds.ParameterGroup("cluster-params", {
family: "aurora-postgresql14",
parameters: [
{
name: "rds.force_ssl",
value: "1", // Enforce SSL/TLS for all connections
},
],
});
const clusterInstance = new aws.rds.ClusterInstance("my-cluster-instance", {
clusterIdentifier: cluster.id,
instanceClass: "db.r5.large",
engine: cluster.engine,
dbParameterGroupName: clusterParamGroup.name, // Attach parameter group with SSL enforcement
});
rds-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 RDS Instance with Customer-Managed KMS Key
Set the kmsKeyId property on encrypted RDS instances:
const rdsKey = new aws.kms.Key("rds-key", {});
const db = new aws.rds.Instance("my-db", {
storageEncrypted: true,
kmsKeyId: rdsKey.arn,
});
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
const dbInstance = new aws.rds.Instance("my-db", {
storageEncrypted: true,
});
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:
const dbParamGroup = new aws.rds.ParameterGroup("db-params", {
family: "mysql8.0",
parameters: [
{
name: "require_secure_transport",
value: "1", // Enforce SSL/TLS for all connections
},
],
});
const db = new aws.rds.Instance("my-database", {
engine: "mysql",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
parameterGroupName: dbParamGroup.name, // Attach parameter group with SSL enforcement
username: "admin",
password: dbPassword,
});
rds-private-subnet-validation
Severity: critical · Enforcement: advisory
Validates that RDS DB subnet groups contain only private subnets
- 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 use secure credential management instead of hardcoded passwords
- 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 on Redshift Cluster
Set the enhancedVpcRouting property to true on your Redshift cluster to ensure all COPY and UNLOAD traffic flows through your VPC infrastructure.
const redshiftCluster = new aws.redshift.Cluster("my-cluster", {
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 KMS encryption for Redshift cluster
To remediate this violation, enable encryption at rest for your Redshift cluster using a customer-managed KMS key:
const kmsKey = new aws.kms.Key("redshift-key", {
description: "KMS key for Redshift cluster encryption",
enableKeyRotation: true,
});
const redshiftCluster = new aws.redshift.Cluster("my-cluster", {
encrypted: true, // Set to true to enable encryption
kmsKeyId: kmsKey.arn, // Use customer-managed KMS key
});
Note: Encryption must be enabled during cluster creation. To enable encryption on an existing unencrypted cluster, you must create a new encrypted cluster and migrate your data.
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
To enable logging for your Redshift cluster, create a redshift.Logging resource with appropriate log destination configuration.
import * as aws from "@pulumi/aws";
const redshiftLogBucket = new aws.s3.Bucket("redshift-logs");
const cluster = new aws.redshift.Cluster("my-cluster", {
clusterIdentifier: "my-redshift-cluster",
});
const logging = new aws.redshift.Logging("cluster-logging", {
clusterIdentifier: cluster.clusterIdentifier,
logDestinationType: "s3",
bucketName: redshiftLogBucket.bucket,
logExports: ["connectionlog", "useractivitylog"],
});
Alternatively, configure CloudWatch as the log destination:
const logging = new aws.redshift.Logging("cluster-logging", {
clusterIdentifier: cluster.clusterIdentifier,
logDestinationType: "cloudwatch",
logExports: ["connectionlog", "useractivitylog"],
});
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 Maintenance Settings for Redshift Cluster
Configure a maintenance window and enable automatic version upgrades for your Redshift cluster.
import * as aws from "@pulumi/aws";
const redshiftCluster = new aws.redshift.Cluster("my-cluster", {
preferredMaintenanceWindow: "sun:05:00-sun:06:00",
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 on Redshift Cluster
Set the publiclyAccessible property to false to ensure your Redshift cluster is only accessible from within your VPC.
new aws.redshift.Cluster("example", {
publiclyAccessible: false,
});
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 for Redshift Cluster Connections
Configure your Redshift cluster to require SSL connections by creating a parameter group with the require_ssl parameter set to true.
import * as aws from "@pulumi/aws";
const redshiftParamGroup = new aws.redshift.ParameterGroup("my-redshift-params", {
family: "redshift-1.0",
parameters: [
{
name: "require_ssl",
value: "true",
},
],
});
const redshiftCluster = new aws.redshift.Cluster("my-cluster", {
clusterParameterGroupName: redshiftParamGroup.name,
});
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
});
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
const bucket = new aws.s3.Bucket("my-bucket", {
acl: "private",
});
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 with customer-managed KMS key
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.BucketV2("my-bucket", {
bucket: "my-secure-bucket",
});
const bucketEncryption = new aws.s3.BucketServerSideEncryptionConfiguration("bucket-encryption", {
bucket: bucket.id,
rules: [{
applyServerSideEncryptionByDefault: {
sseAlgorithm: "aws:kms",
kmsMasterKeyId: kmsKey.arn, // Customer-managed KMS key required
},
}],
});
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)
},
},
],
});
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 an S3 bucket policy that denies all requests made over insecure HTTP connections.
// 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"
}
}
}]
}))
});
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 a KMS key ARN to the SageMaker endpoint configuration to enable encryption at rest.
import * as aws from "@pulumi/aws";
const kmsKey = new aws.kms.Key("sagemaker-key");
const endpointConfig = new aws.sagemaker.EndpointConfiguration("my-endpoint-config", {
kmsKeyArn: kmsKey.arn,
});
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 for SageMaker Notebook Instance
To comply with PCI DSS requirements, disable direct internet access for your SageMaker notebook instance and use VPC-only mode with appropriate network controls.
import * as aws from "@pulumi/aws";
const notebookInstance = new aws.sagemaker.NotebookInstance("my-notebook", {
directInternetAccess: "Disabled",
});
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 on SageMaker notebook instances for data at rest protection.
const kmsKey = new aws.kms.Key("sagemaker-key");
const notebookInstance = new aws.sagemaker.NotebookInstance("encrypted-notebook", {
kmsKeyId: kmsKey.id,
});
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 a rotation Lambda function and rotation schedule:
import * as aws from "@pulumi/aws";
const rotationLambda = new aws.lambda.Function("secretRotationLambda");
const mySecret = new aws.secretsmanager.Secret("mySecret");
const secretRotation = new aws.secretsmanager.SecretRotation("mySecretRotation", {
secretId: mySecret.id,
rotationLambdaArn: rotationLambda.arn,
rotationRules: {
automaticallyAfterDays: 90,
},
});
Key requirements:
- Attach a
SecretRotationresource to each secret - Configure
rotationRuleswith eitherautomaticallyAfterDays(≤90 days) orscheduleExpression - Provide a valid
rotationLambdaArnfor the rotation function
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
const kmsKey = new aws.kms.Key("secret-key", {
description: "KMS key for encrypting secrets",
});
const secret = new secretsmanager.Secret("my-secret", {
kmsKeyId: kmsKey.id,
});
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.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: advisory
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 stack to enable continuous security monitoring and compliance assessment.
import * as aws from "@pulumi/aws";
// Enable Security Hub in the AWS account
const securityHubAccount = new aws.securityhub.Account("security-hub", {
// Security Hub will be enabled with default settings
});
// Optional: Enable specific security standards
const cisStandard = new aws.securityhub.StandardsSubscription("cis-standard", {
standardsArn: "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0",
});
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
import * as aws from "@pulumi/aws";
const topic = new aws.sns.Topic("myTopic", {
kmsMasterKeyId: kmsKey.arn, // Add KMS key ARN
});
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
Set the kmsMasterKeyId property to enable server-side encryption with AWS KMS:
const queue = new aws.sqs.Queue("my-queue", {
name: "my-secure-queue",
kmsMasterKeyId: "alias/aws/sqs", // Enable encryption with AWS-managed KMS key
// Or use a customer-managed key:
// 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)
});
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 Internet Gateway Access in Route Table
const routeTable = new aws.ec2.RouteTable("example", {
vpcId: vpc.id,
routes: [{
cidrBlock: "10.0.0.0/16",
gatewayId: internetGateway.id,
}],
});
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 on VPC Subnet
Set the mapPublicIpOnLaunch property to false to prevent EC2 instances from automatically receiving public IP addresses when launched in this subnet.
const privateSubnet = new aws.ec2.Subnet("private-subnet", {
mapPublicIpOnLaunch: false, // Disable auto-assign public IP
});
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 Logging Configuration for WAFv2 Web ACL
Create a WebAclLoggingConfiguration resource with resourceArn that references your Web ACL.
import * as aws from "@pulumi/aws";
const myWebAcl = new aws.wafv2.WebAcl("myWebAcl", {
scope: "REGIONAL",
defaultAction: { allow: {} },
rules: [/* your rules */],
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "myWebAcl",
sampledRequestsEnabled: true,
},
});
const webAclLogging = new aws.wafv2.WebAclLoggingConfiguration("webAclLogging", {
resourceArn: myWebAcl.arn,
logDestinationConfigs: ["arn:aws:..."],
});