cloudproof — AWS Compliance Audit Report

Generated 2026-05-31T00:00:00Z · version demo · scope: single

Compliance posture — automated + attested

FrameworkTypeAutomatedAttestedCombined
CIS Amazon Web Services Foundations Benchmark TECHNICAL 27% (9/33)
27% · 33 controls
AWS Foundational Security Best Practices TECHNICAL 37% (35/93)
37% · 93 controls
Payment Card Industry Data Security Standard TECHNICAL 33% (26/78)
33% · 78 controls
NIST SP 800-53 TECHNICAL 35% (37/104)
35% · 104 controls

Combined = (automated passes + attested “yes”) ÷ (all evaluated automated + attested controls). A dash means that dimension was not assessed (e.g. no attestation file supplied, or no automated checks for that framework yet).

Account 123456789012 — demo-prod

Account ID: 123456789012 Alias: demo-prod
FrameworkTypeScorePass / FailCoverage
CIS Amazon Web Services Foundations Benchmark v3.0.0
About & recommendation
Prescriptive, technical baseline for securing AWS accounts.
Authority: Center for Internet Security
Applies to: Universal AWS security baseline; the common starting point for any cloud compliance program.
Recommendation: Treat as the minimum bar. Remediate every failing technical check — most map directly onto PCI, NIST, and ISO controls.
TECHNICAL
27%
9 24 Automated technical checks only; some controls may still require manual review.
AWS Foundational Security Best Practices v1.0.0
About & recommendation
AWS's own broad set of foundational security controls.
Authority: Amazon Web Services
Applies to: Any AWS workload; maps natively to AWS Security Hub control IDs.
Recommendation: Run alongside CIS for broad service coverage; pair with Security Hub for continuous monitoring.
TECHNICAL
37%
35 58 Automated technical checks only; some controls may still require manual review.
Payment Card Industry Data Security Standard v4.0
About & recommendation
Mandatory controls for organizations handling cardholder data.
Authority: PCI Security Standards Council
Applies to: Required for anyone that stores, processes, or transmits payment card data.
Recommendation: Scope to your cardholder data environment (CDE). Technical AWS controls cover parts of requirements 1–10; network segmentation, policies, and a QSA assessment are still required.
TECHNICAL
33%
26 52 Automated technical checks only; some controls may still require manual review.
NIST SP 800-53 vRev. 5
About & recommendation
Federal control catalog; the backbone many frameworks map onto.
Authority: U.S. National Institute of Standards and Technology
Applies to: U.S. federal systems and contractors; widely reused by the regulated private sector.
Recommendation: Use the control IDs (AC-, AU-, IA-, SC-) as the canonical mapping. Select a Low/Moderate/High baseline matching system impact level.
TECHNICAL
35%
37 67 Automated technical checks only; some controls may still require manual review.
SOC 2 (Trust Services Criteria)
About & recommendation
Trust principles for service providers; largely process and policy controls audited by a CPA firm.
Authority: AICPA — Trust Services Criteria
Applies to: B2B SaaS/service providers whose customers demand assurance over security, availability, confidentiality, processing integrity, or privacy.
Recommendation: This tool evidences the technical common-criteria controls (CC6 logical access, CC7 monitoring). Automate those, attest the policy/process controls, and engage an auditor for a Type II report over an observation period.
ORGANIZATIONAL
0%
0 0 Organizational framework — controls require manual attestation; automated checks cover only the technical subset.
HIPAA Security Rule
About & recommendation
Safeguards for protected health information (PHI/ePHI).
Authority: U.S. Dept. of Health & Human Services
Applies to: Covered entities and business associates handling PHI/ePHI.
Recommendation: Sign a BAA with AWS and stay within HIPAA-eligible services. Technical safeguards (encryption, access control, audit controls) are auditable here; administrative and physical safeguards need documented policies and attestation.
ORGANIZATIONAL
0%
0 0 Organizational framework — controls require manual attestation; automated checks cover only the technical subset.
ISO/IEC 27001 v2022
About & recommendation
International standard for a certifiable Information Security Management System (ISMS).
Authority: ISO/IEC
Applies to: Organizations seeking certifiable security governance; common for international and enterprise B2B sales.
Recommendation: Certification needs a documented ISMS, risk assessment, and Statement of Applicability audited by an accredited body. This tool supports the Annex A technical controls; manage management-system clauses 4–10 via attestation.
ORGANIZATIONAL
0%
0 0 Organizational framework — controls require manual attestation; automated checks cover only the technical subset.
ISO/IEC 27017
About & recommendation
Cloud-specific extension of ISO 27001.
Authority: ISO/IEC
Applies to: Relevant once you hold or pursue ISO 27001 and operate in the cloud.
Recommendation: Layer onto an existing ISO 27001 program; it addresses shared responsibility, VM hardening, and cloud admin operational security.
ORGANIZATIONAL
0%
0 0 Organizational framework — controls require manual attestation; automated checks cover only the technical subset.
EU General Data Protection Regulation
About & recommendation
EU regulation governing processing of personal data. Not a certification.
Authority: European Union
Applies to: Any organization processing personal data of individuals in the EU/EEA, wherever the org is based.
Recommendation: Primarily legal/process (lawful basis, DPAs, data-subject rights, breach notification). Article 32 technical measures (encryption, resilience, access control) are auditable; the rest needs legal and process attestation.
ORGANIZATIONAL
0%
0 0 Organizational framework — controls require manual attestation; automated checks cover only the technical subset.
California Consumer Privacy Act
About & recommendation
California consumer data protection and privacy rights.
Authority: State of California
Applies to: For-profit businesses meeting CCPA thresholds that handle California residents' personal information.
Recommendation: Process-driven (consumer rights, opt-out, disclosures). 'Reasonable security' measures map to the same AWS controls; attest the consumer-rights and disclosure obligations.
ORGANIZATIONAL
0%
0 0 Organizational framework — controls require manual attestation; automated checks cover only the technical subset.
NIST Cybersecurity Framework v2.0
About & recommendation
Voluntary, risk-based framework for organizing a security program (Govern, Identify, Protect, Detect, Respond, Recover).
Authority: U.S. National Institute of Standards and Technology
Applies to: Any organization that wants a maturity-based structure for its security program rather than a pass/fail audit.
Recommendation: Use as the organizing structure for your program. Map technical findings to the Protect/Detect functions and assess maturity per function.
ORGANIZATIONAL
0%
0 0 Organizational framework — controls require manual attestation; automated checks cover only the technical subset.
CheckSeverityStatusMapped controls
ACM certificates are not near expiry
acm.cert-expiry · acm
1 finding(s)
[PASS] eu-central-1 acm-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP ACM.1NIST_800_53 SC-12
API Gateway REST stages have logging enabled
apigateway.logging-enabled · apigateway
1 finding(s)
[FAIL] eu-central-1 apigateway-demo-resource — Sample finding — API Gateway REST stages have logging enabled not satisfied.
LOW FAIL AWS_FSBP APIGateway.1PCI_DSS 10.2.1NIST_800_53 AU-2
Recommended actions
Enable access logging (to a CloudWatch log group) or execution logging (method settings LoggingLevel=INFO/ERROR) on each API Gateway stage.
💲 USAGE_BASED Billed as CloudWatch Logs ingestion for the API logs. (Varies with traffic)
AWS CLI
aws apigateway update-stage --rest-api-id "$API" --stage-name "$STAGE" \
  --patch-operations op=replace,path=/*/*/logging/loglevel,value=INFO
Terraform
resource "aws_api_gateway_method_settings" "all" {
  rest_api_id = aws_api_gateway_rest_api.this.id
  stage_name  = aws_api_gateway_stage.this.stage_name
  method_path = "*/*"
  settings { logging_level = "INFO" }
}

↩ Rollback / revert

Reversible: remove the access log settings / set LoggingLevel=OFF.
API Gateway v2 stages have access logging
apigatewayv2.logging-enabled · apigatewayv2
1 finding(s)
[PASS] eu-central-1 apigatewayv2-demo-resource — Compliant.
LOW PASS AWS_FSBP APIGateway.9PCI_DSS 10.2.1NIST_800_53 AU-2
AppSync APIs don't use API-key auth
appsync.no-api-key · appsync
1 finding(s)
[FAIL] eu-central-1 appsync-demo-resource — Sample finding — AppSync APIs don't use API-key auth not satisfied.
MEDIUM FAIL AWS_FSBP AppSync.5NIST_800_53 IA-2
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
Athena workgroups encrypt query results
athena.workgroup-encryption · athena
1 finding(s)
[PASS] eu-central-1 athena-demo-resource — Compliant.
LOW PASS AWS_FSBP Athena.1PCI_DSS 3.5.1NIST_800_53 SC-28
An AWS Backup plan exists
backup.plan-exists · backup
1 finding(s)
[PASS] eu-central-1 backup-demo-resource — Compliant.
LOW PASS AWS_FSBP Backup.1NIST_800_53 CP-9
CloudFront sets a default root object
cloudfront.default-root-object · cloudfront
1 finding(s)
[FAIL] eu-central-1 cloudfront-demo-resource — Sample finding — CloudFront sets a default root object not satisfied.
LOW FAIL AWS_FSBP CloudFront.1NIST_800_53 AC-3
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
CloudFront distributions require HTTPS for viewers
cloudfront.https-only · cloudfront
1 finding(s)
[PASS] eu-central-1 cloudfront-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP CloudFront.3PCI_DSS 4.2.1NIST_800_53 SC-8
CloudFront distributions have logging
cloudfront.logging-enabled · cloudfront
1 finding(s)
[FAIL] eu-central-1 cloudfront-demo-resource — Sample finding — CloudFront distributions have logging not satisfied.
LOW FAIL AWS_FSBP CloudFront.5PCI_DSS 10.2.1NIST_800_53 AU-2
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
CloudTrail log bucket is not public
cloudtrail.bucket-not-public · cloudtrail
1 finding(s)
[PASS] eu-central-1 cloudtrail-demo-resource — Compliant.
HIGH PASS CIS_AWS_FOUNDATIONS 3.3AWS_FSBP CloudTrail.6NIST_800_53 AC-3
CloudTrail is integrated with CloudWatch Logs
cloudtrail.cwl-integration · cloudtrail
1 finding(s)
[PASS] eu-central-1 cloudtrail-demo-resource — Compliant.
MEDIUM PASS CIS_AWS_FOUNDATIONS 3.4AWS_FSBP CloudTrail.5NIST_800_53 AU-6
CloudTrail logs are encrypted with KMS
cloudtrail.kms-encrypted · cloudtrail
1 finding(s)
[PASS] eu-central-1 cloudtrail-demo-resource — Compliant.
MEDIUM PASS CIS_AWS_FOUNDATIONS 3.5AWS_FSBP CloudTrail.2NIST_800_53 SC-28
CloudTrail log file validation is enabled
cloudtrail.log-file-validation · cloudtrail
1 finding(s)
[FAIL] eu-central-1 cloudtrail-demo-resource — Sample finding — CloudTrail log file validation is enabled not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 3.2AWS_FSBP CloudTrail.4PCI_DSS 10.5.2NIST_800_53 AU-9
Recommended actions
Enable log file integrity validation on every CloudTrail trail.
💲 NONE Free — log file validation adds no charge. ($0)
AWS CLI
aws cloudtrail update-trail --name org-multiregion-trail --enable-log-file-validation
Terraform
resource "aws_cloudtrail" "main" {
  name                       = "org-multiregion-trail"
  enable_log_file_validation = true
  # ...other settings...
}
AWS CDK
new cloudtrail.Trail(this, 'OrgTrail', { enableFileValidation: true });
Pulumi
new aws.cloudtrail.Trail("org-multiregion-trail", { enableLogFileValidation: true });

↩ Rollback / revert

Reversible: disable validation again with --no-enable-log-file-validation (or set the IaC flag false and re-apply).
A multi-region CloudTrail is enabled and logging
cloudtrail.multi-region-trail · cloudtrail
1 finding(s)
[FAIL] eu-central-1 cloudtrail-demo-resource — Sample finding — A multi-region CloudTrail is enabled and logging not satisfied.
HIGH FAIL CIS_AWS_FOUNDATIONS 3.1AWS_FSBP CloudTrail.1PCI_DSS 10.2.1NIST_800_53 AU-2
Recommended actions
Create a multi-region CloudTrail trail delivering to a dedicated S3 bucket and start logging.
💲 USAGE_BASED The trail itself is free for management events; you pay only for S3 log storage and any optional data events. (~$0–5/month for a typical small/medium account)
  • Management events: the FIRST copy delivered to S3 by a trail is free in every region — a single multi-region trail logging management events costs $0 for the events.
  • Additional copies of management events (a 2nd+ trail): $2.00 per 100,000 events.
  • Data events (S3 object-level, Lambda invoke) are OPT-IN and billed at $0.10 per 100,000 events — leave them off unless required.
  • S3 storage for the log files: standard S3 pricing (~$0.023/GB-month); logs are small, usually a few cents to a few dollars per month. Add a lifecycle rule to expire/Glacier old logs.
  • Optional add-ons that cost extra if enabled: CloudWatch Logs delivery, a KMS CMK for encryption (~$1/key/month + usage), and Athena queries.
AWS CLI
aws cloudtrail create-trail \
  --name org-multiregion-trail \
  --s3-bucket-name my-cloudtrail-logs-bucket \
  --is-multi-region-trail --enable-log-file-validation
aws cloudtrail start-logging --name org-multiregion-trail
Terraform
resource "aws_cloudtrail" "main" {
  name                          = "org-multiregion-trail"
  s3_bucket_name                = aws_s3_bucket.trail.id
  is_multi_region_trail         = true
  enable_log_file_validation    = true
  include_global_service_events = true
}

resource "aws_s3_bucket" "trail" {
  bucket = "my-cloudtrail-logs-bucket"
}
# NB: attach the CloudTrail bucket policy granting cloudtrail.amazonaws.com PutObject.
AWS CDK
new cloudtrail.Trail(this, 'OrgTrail', {
  isMultiRegionTrail: true,
  enableFileValidation: true,
  includeGlobalServiceEvents: true,
});
Pulumi
const bucket = new aws.s3.BucketV2("trail-logs", {});
new aws.cloudtrail.Trail("org-multiregion-trail", {
  s3BucketName:               bucket.id,
  isMultiRegionTrail:         true,
  enableLogFileValidation:    true,
  includeGlobalServiceEvents: true,
});

↩ Rollback / revert

Fully reversible: delete the trail (and optionally its log bucket). Removing the trail only stops logging — it does not affect any running workload.
⚠ Keep the S3 log bucket if you need to retain historical logs for audit/forensics; deleting the trail alone stops new events.
AWS CLI
aws cloudtrail stop-logging  --name org-multiregion-trail
aws cloudtrail delete-trail  --name org-multiregion-trail
# Optional (only if you do not need the historical logs):
# aws s3 rb s3://my-cloudtrail-logs-bucket --force
Metric filter & alarm for CloudTrail configuration changes
cloudwatch.cloudtrail-config-changes · cloudwatch
MEDIUM NOT_APPLICABLE CIS_AWS_FOUNDATIONS 4.5PCI_DSS 10.5.2NIST_800_53 AU-6
Metric filter & alarm for AWS Config changes
cloudwatch.config-changes · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for AWS Config changes not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 4.9PCI_DSS 10.5.2NIST_800_53 AU-6
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventName=PutConfigurationRecorder)||($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-config-changes" \
  --filter-pattern '{ ($.eventName=PutConfigurationRecorder)||($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) }' \
  --metric-transformations metricName=CIS-config-changes,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-config-changes" \
  --metric-name CIS-config-changes --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-config-changes" {
  name           = "CIS-config-changes"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventName=PutConfigurationRecorder)||($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) }"
  metric_transformation {
    name      = "CIS-config-changes"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-config-changes" {
  alarm_name          = "CIS-config-changes"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-config-changes"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for console authentication failures
cloudwatch.console-auth-failures · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for console authentication failures not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 4.6PCI_DSS 10.4.1NIST_800_53 SI-4
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventName = ConsoleLogin) && ($.errorMessage = "Failed authentication") }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-console-auth-failures" \
  --filter-pattern '{ ($.eventName = ConsoleLogin) && ($.errorMessage = "Failed authentication") }' \
  --metric-transformations metricName=CIS-console-auth-failures,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-console-auth-failures" \
  --metric-name CIS-console-auth-failures --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-console-auth-failures" {
  name           = "CIS-console-auth-failures"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventName = ConsoleLogin) && ($.errorMessage = "Failed authentication") }"
  metric_transformation {
    name      = "CIS-console-auth-failures"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-console-auth-failures" {
  alarm_name          = "CIS-console-auth-failures"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-console-auth-failures"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for console sign-in without MFA
cloudwatch.console-signin-no-mfa · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for console sign-in without MFA not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 4.2PCI_DSS 8.4.2NIST_800_53 SI-4
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-console-signin-no-mfa" \
  --filter-pattern '{ ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }' \
  --metric-transformations metricName=CIS-console-signin-no-mfa,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-console-signin-no-mfa" \
  --metric-name CIS-console-signin-no-mfa --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-console-signin-no-mfa" {
  name           = "CIS-console-signin-no-mfa"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }"
  metric_transformation {
    name      = "CIS-console-signin-no-mfa"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-console-signin-no-mfa" {
  alarm_name          = "CIS-console-signin-no-mfa"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-console-signin-no-mfa"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for IAM policy changes
cloudwatch.iam-policy-changes · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for IAM policy changes not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 4.4PCI_DSS 10.2.2NIST_800_53 AU-6
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventName=DeleteGroupPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=AttachRolePolicy) }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-iam-policy-changes" \
  --filter-pattern '{ ($.eventName=DeleteGroupPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=AttachRolePolicy) }' \
  --metric-transformations metricName=CIS-iam-policy-changes,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-iam-policy-changes" \
  --metric-name CIS-iam-policy-changes --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-iam-policy-changes" {
  name           = "CIS-iam-policy-changes"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventName=DeleteGroupPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=AttachRolePolicy) }"
  metric_transformation {
    name      = "CIS-iam-policy-changes"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-iam-policy-changes" {
  alarm_name          = "CIS-iam-policy-changes"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-iam-policy-changes"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for disabling or scheduled deletion of KMS keys
cloudwatch.kms-key-deletion · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for disabling or scheduled deletion of KMS keys not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 4.7PCI_DSS 3.6.1NIST_800_53 SI-4
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-kms-key-deletion" \
  --filter-pattern '{ ($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }' \
  --metric-transformations metricName=CIS-kms-key-deletion,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-kms-key-deletion" \
  --metric-name CIS-kms-key-deletion --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-kms-key-deletion" {
  name           = "CIS-kms-key-deletion"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }"
  metric_transformation {
    name      = "CIS-kms-key-deletion"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-kms-key-deletion" {
  alarm_name          = "CIS-kms-key-deletion"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-kms-key-deletion"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for network ACL changes
cloudwatch.nacl-changes · cloudwatch
LOW NOT_APPLICABLE CIS_AWS_FOUNDATIONS 4.11PCI_DSS 1.2.1NIST_800_53 SI-4
Metric filter & alarm for network gateway changes
cloudwatch.network-gateway-changes · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for network gateway changes not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 4.12PCI_DSS 1.2.1NIST_800_53 SI-4
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventName=CreateCustomerGateway)||($.eventName=AttachInternetGateway)||($.eventName=DeleteInternetGateway) }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-network-gateway-changes" \
  --filter-pattern '{ ($.eventName=CreateCustomerGateway)||($.eventName=AttachInternetGateway)||($.eventName=DeleteInternetGateway) }' \
  --metric-transformations metricName=CIS-network-gateway-changes,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-network-gateway-changes" \
  --metric-name CIS-network-gateway-changes --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-network-gateway-changes" {
  name           = "CIS-network-gateway-changes"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventName=CreateCustomerGateway)||($.eventName=AttachInternetGateway)||($.eventName=DeleteInternetGateway) }"
  metric_transformation {
    name      = "CIS-network-gateway-changes"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-network-gateway-changes" {
  alarm_name          = "CIS-network-gateway-changes"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-network-gateway-changes"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for AWS Organizations changes
cloudwatch.org-changes · cloudwatch
LOW NOT_APPLICABLE CIS_AWS_FOUNDATIONS 4.15PCI_DSS 10.2.2NIST_800_53 AU-6
Metric filter & alarm for root account usage
cloudwatch.root-usage · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for root account usage not satisfied.
HIGH FAIL CIS_AWS_FOUNDATIONS 4.3PCI_DSS 10.2.1NIST_800_53 AU-6
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-root-usage" \
  --filter-pattern '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }' \
  --metric-transformations metricName=CIS-root-usage,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-root-usage" \
  --metric-name CIS-root-usage --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-root-usage" {
  name           = "CIS-root-usage"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }"
  metric_transformation {
    name      = "CIS-root-usage"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-root-usage" {
  alarm_name          = "CIS-root-usage"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-root-usage"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for route table changes
cloudwatch.route-table-changes · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for route table changes not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 4.13PCI_DSS 1.2.1NIST_800_53 SI-4
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventName=CreateRoute)||($.eventName=DeleteRoute)||($.eventName=DeleteRouteTable)||($.eventName=ReplaceRoute) }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-route-table-changes" \
  --filter-pattern '{ ($.eventName=CreateRoute)||($.eventName=DeleteRoute)||($.eventName=DeleteRouteTable)||($.eventName=ReplaceRoute) }' \
  --metric-transformations metricName=CIS-route-table-changes,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-route-table-changes" \
  --metric-name CIS-route-table-changes --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-route-table-changes" {
  name           = "CIS-route-table-changes"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventName=CreateRoute)||($.eventName=DeleteRoute)||($.eventName=DeleteRouteTable)||($.eventName=ReplaceRoute) }"
  metric_transformation {
    name      = "CIS-route-table-changes"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-route-table-changes" {
  alarm_name          = "CIS-route-table-changes"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-route-table-changes"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
Metric filter & alarm for S3 bucket policy changes
cloudwatch.s3-policy-changes · cloudwatch
1 finding(s)
[PASS] eu-central-1 cloudwatch-demo-resource — Compliant.
LOW PASS CIS_AWS_FOUNDATIONS 4.8PCI_DSS 10.2.2NIST_800_53 SI-4
Metric filter & alarm for security group changes
cloudwatch.security-group-changes · cloudwatch
LOW NOT_APPLICABLE CIS_AWS_FOUNDATIONS 4.10PCI_DSS 1.2.1NIST_800_53 SI-4
Metric filter & alarm for unauthorized API calls
cloudwatch.unauthorized-api-calls · cloudwatch
1 finding(s)
[PASS] eu-central-1 cloudwatch-demo-resource — Compliant.
MEDIUM PASS CIS_AWS_FOUNDATIONS 4.1PCI_DSS 10.4.1NIST_800_53 SI-4
Metric filter & alarm for VPC changes
cloudwatch.vpc-changes · cloudwatch
1 finding(s)
[FAIL] eu-central-1 cloudwatch-demo-resource — Sample finding — Metric filter & alarm for VPC changes not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 4.14PCI_DSS 1.2.1NIST_800_53 SI-4
Recommended actions
Create a CloudWatch Logs metric filter on the CloudTrail log group matching the control's event pattern, an alarm on that metric, and an SNS topic with a subscriber so the alarm is actionable.
⚠ Requires CloudTrail delivering to a CloudWatch Logs log group (see cloudtrail checks). Filter pattern: { ($.eventName=CreateVpc)||($.eventName=DeleteVpc)||($.eventName=ModifyVpcAttribute)||($.eventName=CreateVpcPeeringConnection) }
💲 LOW A few cents/month: one CloudWatch alarm + minimal metric data + SNS notifications. (~$0.10-0.30/month per alarm)
  • CloudWatch alarms: $0.10 per standard alarm/month.
  • Metric filters are free; you pay only for any custom-metric data published.
  • SNS: first 1k email notifications/month free.
AWS CLI
LOG_GROUP=<your-cloudtrail-log-group>; TOPIC_ARN=<your-sns-topic-arn>
aws logs put-metric-filter --log-group-name "$LOG_GROUP" \
  --filter-name "CIS-vpc-changes" \
  --filter-pattern '{ ($.eventName=CreateVpc)||($.eventName=DeleteVpc)||($.eventName=ModifyVpcAttribute)||($.eventName=CreateVpcPeeringConnection) }' \
  --metric-transformations metricName=CIS-vpc-changes,metricNamespace=CISBenchmark,metricValue=1
aws cloudwatch put-metric-alarm --alarm-name "CIS-vpc-changes" \
  --metric-name CIS-vpc-changes --namespace CISBenchmark \
  --statistic Sum --period 300 --threshold 1 --evaluation-periods 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions "$TOPIC_ARN"
Terraform
resource "aws_cloudwatch_log_metric_filter" "CIS-vpc-changes" {
  name           = "CIS-vpc-changes"
  log_group_name = var.cloudtrail_log_group
  pattern        = "{ ($.eventName=CreateVpc)||($.eventName=DeleteVpc)||($.eventName=ModifyVpcAttribute)||($.eventName=CreateVpcPeeringConnection) }"
  metric_transformation {
    name      = "CIS-vpc-changes"
    namespace = "CISBenchmark"
    value     = "1"
  }
}
resource "aws_cloudwatch_metric_alarm" "CIS-vpc-changes" {
  alarm_name          = "CIS-vpc-changes"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "CIS-vpc-changes"
  namespace           = "CISBenchmark"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_actions       = [var.sns_topic_arn]
}

↩ Rollback / revert

Reversible: delete the alarm (delete-alarms) and the metric filter (delete-metric-filter).
CodeBuild projects don't run privileged
codebuild.no-privileged-mode · codebuild
1 finding(s)
[FAIL] eu-central-1 codebuild-demo-resource — Sample finding — CodeBuild projects don't run privileged not satisfied.
MEDIUM FAIL AWS_FSBP CodeBuild.5NIST_800_53 CM-7
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
AWS Config recorder is enabled and recording
config.recorder-enabled · config
1 finding(s)
[FAIL] eu-central-1 config-demo-resource — Sample finding — AWS Config recorder is enabled and recording not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 3.3AWS_FSBP Config.1PCI_DSS 10.5.1NIST_800_53 CM-8
Recommended actions
Enable an AWS Config recorder and delivery channel in every region so resource configuration changes are tracked.
💲 USAGE_BASED Billed per configuration item recorded and per rule evaluation; cost scales with resource count and change rate. (~$2–15/month for a small/medium account, more with high change volume)
  • Configuration items: $0.003 per item recorded.
  • Config rule evaluations: $0.001 per evaluation (first 100k/region/month).
  • S3 storage for the configuration snapshots/history (small).
  • Use recording exclusions or periodic recording to control cost on noisy resource types.
AWS CLI
# Requires an S3 bucket and an IAM role for Config first.
aws configservice put-configuration-recorder --region "$REGION" \
  --configuration-recorder name=default,roleARN="$CONFIG_ROLE_ARN" \
  --recording-group allSupported=true,includeGlobalResourceTypes=true
aws configservice put-delivery-channel --region "$REGION" \
  --delivery-channel name=default,s3BucketName="$CONFIG_BUCKET"
aws configservice start-configuration-recorder --region "$REGION" --configuration-recorder-name default
Terraform
resource "aws_config_configuration_recorder" "this" {
  name     = "default"
  role_arn = aws_iam_role.config.arn
  recording_group { all_supported = true, include_global_resource_types = true }
}
resource "aws_config_delivery_channel" "this" {
  name           = "default"
  s3_bucket_name = aws_s3_bucket.config.bucket
  depends_on     = [aws_config_configuration_recorder.this]
}
resource "aws_config_configuration_recorder_status" "this" {
  name       = aws_config_configuration_recorder.this.name
  is_enabled = true
  depends_on = [aws_config_delivery_channel.this]
}
Pulumi
const recorder = new aws.cfg.Recorder("default", { roleArn: configRole.arn,
  recordingGroup: { allSupported: true, includeGlobalResourceTypes: true } });
new aws.cfg.DeliveryChannel("default", { s3BucketName: bucket.bucket }, { dependsOn: recorder });

↩ Rollback / revert

Reversible: stop the recorder (aws configservice stop-configuration-recorder) or delete it and the delivery channel.
DAX clusters are encrypted at rest
dax.cluster-encrypted · dax
1 finding(s)
[FAIL] eu-central-1 dax-demo-resource — Sample finding — DAX clusters are encrypted at rest not satisfied.
MEDIUM FAIL AWS_FSBP DAX.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable encryption for this resource (KMS where supported). Many services set encryption at creation only and require recreation/migration.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Console
Open the resource's settings and enable encryption (select a customer-managed KMS key to also satisfy requireCmk).

↩ Rollback / revert

Where encryption is set at creation it cannot be removed in place; keep the original until the encrypted replacement is validated.
Amazon Detective is enabled
detective.enabled · detective
1 finding(s)
[PASS] eu-central-1 detective-demo-resource — Compliant.
LOW PASS AWS_FSBP Detective.1NIST_800_53 SI-4
DMS replication instances are not public
dms.replication-not-public · dms
1 finding(s)
[PASS] eu-central-1 dms-demo-resource — Compliant.
HIGH PASS AWS_FSBP DMS.1PCI_DSS 1.3.1NIST_800_53 SC-7
DocumentDB clusters are encrypted at rest
docdb.encrypted · docdb
1 finding(s)
[FAIL] eu-central-1 docdb-demo-resource — Sample finding — DocumentDB clusters are encrypted at rest not satisfied.
HIGH FAIL AWS_FSBP DocumentDB.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Recreate the DocumentDB cluster with storage encryption enabled (restore from a snapshot with --storage-encrypted).
⚠ Encryption is set at creation; migrate via an encrypted snapshot restore.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Terraform
resource "aws_docdb_cluster" "c" { storage_encrypted = true, kms_key_id = aws_kms_key.cmk.arn /* ... */ }

↩ Rollback / revert

Keep the source cluster/snapshot until the encrypted cluster is validated.
DynamoDB tables have point-in-time recovery
dynamodb.pitr-enabled · dynamodb
1 finding(s)
[PASS] eu-central-1 dynamodb-demo-resource — Compliant.
LOW PASS AWS_FSBP DynamoDB.2NIST_800_53 CP-9
DynamoDB tables use KMS encryption
dynamodb.table-kms-encrypted · dynamodb
1 finding(s)
[FAIL] eu-central-1 dynamodb-demo-resource — Sample finding — DynamoDB tables use KMS encryption not satisfied.
LOW FAIL AWS_FSBP DynamoDB.4PCI_DSS 3.5.1NIST_800_53 SC-12
Recommended actions
Enable KMS-based encryption on the DynamoDB table (SSE with an AWS-managed key or a customer CMK).
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
AWS CLI
aws dynamodb update-table --table-name "$T" \
  --sse-specification Enabled=true,SSEType=KMS
Terraform
resource "aws_dynamodb_table" "t" {
  # ...
  server_side_encryption { enabled = true, kms_key_arn = aws_kms_key.cmk.arn }
}

↩ Rollback / revert

Reversible: update-table to revert SSE to the default AWS-owned key.
Default security groups restrict all traffic
ec2.default-sg-restricts-traffic · ec2
1 finding(s)
[FAIL] eu-central-1 ec2-demo-resource — Sample finding — Default security groups restrict all traffic not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 5.4AWS_FSBP EC2.2PCI_DSS 1.2.1NIST_800_53 SC-7
Recommended actions
Remove all inbound and outbound rules from every VPC's default security group so it permits no traffic.
🖱 Console: VPC console > Security groups > select each 'default' group > remove all inbound and outbound rules.
⚠ Apply in every region. Workloads should use purpose-built security groups, never the default.
💲 NONE Free — security groups and rule changes have no charge. ($0)
AWS CLI
# Strips the default SG rules in every region — but BACKS THEM UP FIRST to
# ./sg-backup/ so you can roll back (see the Rollback section below).
# describe-regions itself needs a region to call into:
mkdir -p sg-backup
for region in $(aws ec2 describe-regions --region us-east-1 --query 'Regions[].RegionName' --output text); do
  sg=$(aws ec2 describe-security-groups --region "$region" \
    --filters Name=group-name,Values=default \
    --query 'SecurityGroups[0].GroupId' --output text)
  # 1) Back up the current rules BEFORE touching anything:
  aws ec2 describe-security-groups --region "$region" --group-ids "$sg" \
    --query 'SecurityGroups[0].{ingress:IpPermissions,egress:IpPermissionsEgress}' \
    --output json > "sg-backup/${region}_${sg}.json"
  # 2) Revoke all rules (skip when already empty):
  ing=$(aws ec2 describe-security-groups --region "$region" --group-ids "$sg" --query 'SecurityGroups[0].IpPermissions' --output json)
  egr=$(aws ec2 describe-security-groups --region "$region" --group-ids "$sg" --query 'SecurityGroups[0].IpPermissionsEgress' --output json)
  [ "$ing" != "[]" ] && aws ec2 revoke-security-group-ingress --region "$region" --group-id "$sg" --ip-permissions "$ing"
  [ "$egr" != "[]" ] && aws ec2 revoke-security-group-egress  --region "$region" --group-id "$sg" --ip-permissions "$egr"
done
Terraform
# Adopt the default SG and declare no rules (empty ingress/egress):
resource "aws_default_security_group" "default" {
  vpc_id  = aws_vpc.main.id
  ingress = []
  egress  = []
}
AWS CDK
// CDK has no default-SG L2; restrict it via a custom resource:
new cr.AwsCustomResource(this, 'LockDefaultSg', {
  onUpdate: {
    service: 'EC2',
    action: 'revokeSecurityGroupIngress',
    parameters: { GroupId: vpc.vpcDefaultSecurityGroup, IpPermissions: [] },
    physicalResourceId: cr.PhysicalResourceId.of('lock-default-sg'),
  },
  policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE }),
});
Pulumi
new aws.ec2.DefaultSecurityGroup("default", {
  vpcId:   vpc.id,
  ingress: [],
  egress:  [],
});

↩ Rollback / revert

Restore every default SG's rules from the ./sg-backup/ files the apply step wrote. Run this if revoking the rules broke connectivity. Requires jq.
⚠ This is why the apply snippet backs up first. If you applied via Terraform/CDK/Pulumi instead, roll back by reverting the code change in version control and re-applying — state restores the prior rules.
AWS CLI
# Restore default SG rules from the backup captured during remediation:
for f in sg-backup/*.json; do
  region=$(basename "$f" | cut -d_ -f1)
  sg=$(basename "$f" .json | cut -d_ -f2)
  ing=$(jq -c '.ingress' "$f")
  egr=$(jq -c '.egress' "$f")
  [ "$ing" != "[]" ] && aws ec2 authorize-security-group-ingress --region "$region" --group-id "$sg" --ip-permissions "$ing"
  [ "$egr" != "[]" ] && aws ec2 authorize-security-group-egress  --region "$region" --group-id "$sg" --ip-permissions "$egr"
done
EBS encryption by default is enabled
ec2.ebs-encryption-by-default · ec2
1 finding(s)
[PASS] eu-central-1 ec2-demo-resource — Compliant.
MEDIUM PASS CIS_AWS_FOUNDATIONS 2.2.1AWS_FSBP EC2.7PCI_DSS 3.5.1NIST_800_53 SC-28
EBS volumes are encrypted at rest
ec2.ebs-volume-encrypted · ec2
1 finding(s)
[FAIL] eu-central-1 ec2-demo-resource — Sample finding — EBS volumes are encrypted at rest not satisfied.
MEDIUM FAIL AWS_FSBP EC2.3PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Encrypt the EBS volume (snapshot, copy with encryption, restore) and enable EBS encryption-by-default to prevent recurrence.
⚠ Existing volumes can't be encrypted in place; create an encrypted snapshot copy and replace the volume.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
AWS CLI
aws ec2 enable-ebs-encryption-by-default --region "$REGION"  # prevent new unencrypted volumes

↩ Rollback / revert

Keep the original volume/snapshot until the encrypted replacement is validated.
EC2 instances require IMDSv2
ec2.imdsv2-required · ec2
1 finding(s)
[FAIL] eu-central-1 ec2-demo-resource — Sample finding — EC2 instances require IMDSv2 not satisfied.
HIGH FAIL CIS_AWS_FOUNDATIONS 5.6AWS_FSBP EC2.8NIST_800_53 AC-6
Recommended actions
Require IMDSv2 on the instance by setting HttpTokens=required (and optionally lowering the hop limit).
⚠ Verify SDKs/agents on the instance support IMDSv2 (modern ones do) before enforcing fleet-wide.
💲 NONE Free — configuration change only. ($0)
AWS CLI
aws ec2 modify-instance-metadata-options --region "$REGION" \
  --instance-id "$ID" --http-tokens required --http-put-response-hop-limit 1
Terraform
resource "aws_instance" "this" {
  # ...
  metadata_options { http_tokens = "required", http_endpoint = "enabled" }
}
Pulumi
new aws.ec2.Instance("this", { metadataOptions: { httpTokens: "required" } /* ... */ });

↩ Rollback / revert

Reversible: set HttpTokens=optional via modify-instance-metadata-options.
No security group exposes admin ports to the internet
ec2.no-public-admin-ports · ec2
HIGH NOT_APPLICABLE CIS_AWS_FOUNDATIONS 5.2AWS_FSBP EC2.13PCI_DSS 1.3.1NIST_800_53 SC-7
No self-owned AMIs are public
ec2.no-public-ami · ec2
1 finding(s)
[FAIL] eu-central-1 ec2-demo-resource — Sample finding — No self-owned AMIs are public not satisfied.
HIGH FAIL AWS_FSBP EC2.5NIST_800_53 AC-3
Recommended actions
Remove public launch permission from the AMI so it is private again.
⚠ A public AMI may expose embedded secrets and data; remediate immediately.
💲 NONE Free — configuration change only. ($0)
AWS CLI
aws ec2 modify-image-attribute --region "$REGION" --image-id "$AMI" \
  --launch-permission '{"Remove":[{"Group":"all"}]}'

↩ Rollback / revert

Reversible (discouraged): re-add the 'all' launch permission group.
No EBS snapshots are publicly restorable
ec2.no-public-ebs-snapshots · ec2
1 finding(s)
[FAIL] eu-central-1 ec2-demo-resource — Sample finding — No EBS snapshots are publicly restorable not satisfied.
CRITICAL FAIL AWS_FSBP EC2.1PCI_DSS 3.5.1NIST_800_53 AC-3
Recommended actions
Remove public 'all' create-volume permission from every shared EBS snapshot so it is private again.
⚠ CRITICAL: a public snapshot exposes the entire volume's data to every AWS account. Remediate immediately.
💲 NONE Free — changing snapshot permissions has no charge. ($0)
AWS CLI
aws ec2 modify-snapshot-attribute --region "$REGION" \
  --snapshot-id "$SNAP_ID" --attribute createVolumePermission \
  --operation-type remove --group-names all
Terraform
# Manage sharing explicitly; do NOT include a public ec2_snapshot share.
# Detect/prevent drift with the aws_ebs_snapshot resource and avoid
# aws_snapshot_create_volume_permission granting account_id = "all".

↩ Rollback / revert

Reversible (but discouraged): re-add the 'all' group via modify-snapshot-attribute --attribute createVolumePermission --operation-type add --group-names all.
ECR repositories scan images on push
ecr.image-scanning · ecr
1 finding(s)
[FAIL] eu-central-1 ecr-demo-resource — Sample finding — ECR repositories scan images on push not satisfied.
MEDIUM FAIL AWS_FSBP ECR.1PCI_DSS 6.3.1NIST_800_53 RA-5
Recommended actions
Enable scan-on-push on ECR repositories (or set registry-level scanning) so images are scanned for CVEs when pushed.
💲 NONE Basic scanning is free; enhanced scanning (Amazon Inspector) is usage-based. ($0 (basic))
  • Basic scanning (CVE database): no charge.
  • Enhanced scanning via Amazon Inspector is billed per image scanned if you opt into it.
AWS CLI
aws ecr put-image-scanning-configuration --region "$REGION" \
  --repository-name "$REPO" --image-scanning-configuration scanOnPush=true
Terraform
resource "aws_ecr_repository" "this" {
  name = "my-repo"
  image_scanning_configuration { scan_on_push = true }
}
Pulumi
new aws.ecr.Repository("this", { imageScanningConfiguration: { scanOnPush: true } });

↩ Rollback / revert

Reversible: set scan-on-push back to false (put-image-scanning-configuration).
ECR repositories use immutable tags
ecr.tag-immutability · ecr
1 finding(s)
[PASS] eu-central-1 ecr-demo-resource — Compliant.
LOW PASS AWS_FSBP ECR.2NIST_800_53 CM-2
ECS clusters have Container Insights enabled
ecs.container-insights · ecs
LOW NOT_APPLICABLE AWS_FSBP ECS.12PCI_DSS 10.4.1NIST_800_53 SI-4
ECS task definitions don't pass secrets in plaintext
ecs.no-plaintext-secrets · ecs
1 finding(s)
[FAIL] eu-central-1 ecs-demo-resource — Sample finding — ECS task definitions don't pass secrets in plaintext not satisfied.
HIGH FAIL AWS_FSBP ECS.8PCI_DSS 8.3.1NIST_800_53 IA-5
Recommended actions
Move secret values out of plaintext environment variables into the container `secrets` field, backed by SSM Parameter Store or Secrets Manager.
💲 LOW Secrets Manager ~$0.40/secret/month; SSM SecureString standard params are free. ($0 (SSM) / ~$0.40/secret (Secrets Manager))
Terraform
# In the container definition, replace environment with secrets:
# "secrets": [{ "name": "DB_PASSWORD",
#   "valueFrom": "arn:aws:secretsmanager:...:secret:db-password" }]
AWS CLI
# Store the secret, then reference it via valueFrom in a new task def revision:
aws secretsmanager create-secret --name db-password --secret-string "$VALUE"

↩ Rollback / revert

Register the prior task-definition revision again (revisions are immutable and retained).
ECS containers do not run privileged
ecs.no-privileged-containers · ecs
1 finding(s)
[PASS] eu-central-1 ecs-demo-resource — Compliant.
HIGH PASS AWS_FSBP ECS.4PCI_DSS 2.2.1NIST_800_53 CM-7
EFS file systems have automatic backups
efs.backup-enabled · efs
LOW NOT_APPLICABLE AWS_FSBP EFS.2NIST_800_53 CP-9
EFS file systems are encrypted at rest
efs.encrypted · efs
1 finding(s)
[FAIL] eu-central-1 efs-demo-resource — Sample finding — EFS file systems are encrypted at rest not satisfied.
MEDIUM FAIL AWS_FSBP EFS.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Recreate the EFS file system with encryption enabled (encryption cannot be turned on in place); migrate data via DataSync/backup.
⚠ EFS encryption is set at creation only. Create a new encrypted file system and migrate, then cut over.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
AWS CLI
aws efs create-file-system --encrypted --kms-key-id alias/aws/elasticfilesystem
Terraform
resource "aws_efs_file_system" "fs" {
  encrypted  = true
  kms_key_id = aws_kms_key.cmk.arn  # CMK to satisfy requireCmk
}

↩ Rollback / revert

Keep the original file system until migration is validated; not an in-place toggle.
EKS clusters have control-plane audit logging enabled
eks.control-plane-logging · eks
1 finding(s)
[PASS] eu-central-1 eks-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP EKS.8PCI_DSS 10.2.1NIST_800_53 AU-2
EKS cluster API endpoints are not open to the internet
eks.no-public-endpoint · eks
HIGH NOT_APPLICABLE AWS_FSBP EKS.1PCI_DSS 1.3.1NIST_800_53 SC-7
EKS clusters encrypt Kubernetes secrets with KMS
eks.secrets-encryption · eks
1 finding(s)
[FAIL] eu-central-1 eks-demo-resource — Sample finding — EKS clusters encrypt Kubernetes secrets with KMS not satisfied.
MEDIUM FAIL AWS_FSBP EKS.3PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable envelope encryption of Kubernetes secrets on the EKS cluster with a KMS key.
⚠ Secrets encryption can be enabled on an existing cluster but cannot be removed afterward; choose the KMS key deliberately.
💲 LOW A customer CMK is ~$1/key/month plus KMS request charges. (~$1/mo)
AWS CLI
aws eks associate-encryption-config --region "$REGION" --cluster-name "$CLUSTER" \
  --encryption-config '[{"resources":["secrets"],"provider":{"keyArn":"'$CMK_ARN'"}}]'
Terraform
resource "aws_eks_cluster" "this" {
  # ...
  encryption_config {
    resources = ["secrets"]
    provider { key_arn = aws_kms_key.cmk.arn }
  }
}

↩ Rollback / revert

Not reversible once enabled; keep the KMS key available for the cluster lifetime.
ElastiCache replication groups are encrypted at rest
elasticache.encrypted · elasticache
MEDIUM NOT_APPLICABLE AWS_FSBP ElastiCache.4PCI_DSS 3.5.1NIST_800_53 SC-28
ElastiCache encrypts data in transit
elasticache.transit-encryption · elasticache
1 finding(s)
[PASS] eu-central-1 elasticache-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP ElastiCache.5PCI_DSS 4.2.1NIST_800_53 SC-8
Load balancers have access logging
elb.access-logging · elb
1 finding(s)
[PASS] eu-central-1 elb-demo-resource — Compliant.
LOW PASS AWS_FSBP ELB.5PCI_DSS 10.2.1NIST_800_53 AU-2
ALB HTTP listeners redirect to HTTPS
elb.https-only · elb
1 finding(s)
[PASS] eu-central-1 elb-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP ELB.1PCI_DSS 4.2.1NIST_800_53 SC-8
EMR block public access is enabled
emr.not-public · emr
1 finding(s)
[PASS] eu-central-1 emr-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP EMR.1PCI_DSS 1.3.1NIST_800_53 SC-7
Firehose streams are encrypted at rest
firehose.stream-encrypted · firehose
1 finding(s)
[FAIL] eu-central-1 firehose-demo-resource — Sample finding — Firehose streams are encrypted at rest not satisfied.
MEDIUM FAIL AWS_FSBP Firehose.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable encryption for this resource (KMS where supported). Many services set encryption at creation only and require recreation/migration.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Console
Open the resource's settings and enable encryption (select a customer-managed KMS key to also satisfy requireCmk).

↩ Rollback / revert

Where encryption is set at creation it cannot be removed in place; keep the original until the encrypted replacement is validated.
Glue Data Catalog is encrypted at rest
glue.catalog-encryption · glue
1 finding(s)
[FAIL] eu-central-1 glue-demo-resource — Sample finding — Glue Data Catalog is encrypted at rest not satisfied.
LOW FAIL AWS_FSBP Glue.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable encryption for this resource (KMS where supported). Many services set encryption at creation only and require recreation/migration.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Console
Open the resource's settings and enable encryption (select a customer-managed KMS key to also satisfy requireCmk).

↩ Rollback / revert

Where encryption is set at creation it cannot be removed in place; keep the original until the encrypted replacement is validated.
Amazon GuardDuty is enabled
guardduty.enabled · guardduty
1 finding(s)
[FAIL] eu-central-1 guardduty-demo-resource — Sample finding — Amazon GuardDuty is enabled not satisfied.
MEDIUM FAIL AWS_FSBP GuardDuty.1PCI_DSS 11.5.1NIST_800_53 SI-4
Recommended actions
Enable Amazon GuardDuty in every region (ideally org-wide via a delegated administrator).
💲 USAGE_BASED Priced on the volume of CloudTrail events, VPC flow logs, and DNS logs analyzed; includes a 30-day free trial. (Varies with account activity; small accounts often a few $/month)
  • First 30 days are free and show an estimated ongoing cost in the console.
  • Charged per million CloudTrail events and per GB of VPC flow log and DNS data analyzed.
  • Optional protection plans (S3, EKS, Malware, RDS) add cost only if enabled.
AWS CLI
for region in $(aws ec2 describe-regions --region us-east-1 --query 'Regions[].RegionName' --output text); do
  aws guardduty create-detector --region "$region" --enable >/dev/null 2>&1 \
    && echo "enabled in $region" || echo "already enabled in $region"
done
Terraform
resource "aws_guardduty_detector" "this" {
  enable = true
}
AWS CDK
new guardduty.CfnDetector(this, 'Detector', { enable: true });
Pulumi
new aws.guardduty.Detector("this", { enable: true });

↩ Rollback / revert

Reversible: delete the detector (aws guardduty delete-detector --detector-id <id>) to disable.
IAM Access Analyzer is enabled
iam.access-analyzer-enabled · iam
1 finding(s)
[PASS] eu-central-1 iam-demo-resource — Compliant.
LOW PASS AWS_FSBP IAM.28NIST_800_53 AC-6
IAM access keys are rotated within 90 days
iam.access-keys-rotated · iam
1 finding(s)
[PASS] eu-central-1 iam-demo-resource — Compliant.
MEDIUM PASS CIS_AWS_FOUNDATIONS 1.14AWS_FSBP IAM.3PCI_DSS 8.3.9NIST_800_53 IA-5(1)
All IAM users with console access have MFA
iam.console-users-mfa · iam
1 finding(s)
[FAIL] eu-central-1 iam-demo-resource — Sample finding — All IAM users with console access have MFA not satisfied.
HIGH FAIL CIS_AWS_FOUNDATIONS 1.10AWS_FSBP IAM.5PCI_DSS 8.4.2NIST_800_53 IA-2(1)
Recommended actions
Register an MFA device for every IAM user that has console access (or remove their console login profile).
🖱 Console: IAM > Users > select user > Security credentials > assign an MFA device.
⚠ Prefer federated SSO (IAM Identity Center) over long-lived IAM users; enforce MFA at the identity provider.
💲 NONE Free — MFA devices and IAM users have no charge. ($0)
AWS CLI
# Enforce via policy: deny actions unless MFA is present (attach to users/groups).
# To remove console access entirely for a service user:
aws iam delete-login-profile --user-name "$USER"
Terraform
# Require MFA with a policy condition:
data "aws_iam_policy_document" "require_mfa" {
  statement {
    effect    = "Deny"
    actions   = ["*"]
    resources = ["*"]
    condition {
      test     = "BoolIfExists"
      variable = "aws:MultiFactorAuthPresent"
      values   = ["false"]
    }
  }
}

↩ Rollback / revert

Reversible: deactivate the MFA device (aws iam deactivate-mfa-device). Not recommended — it re-creates the exposure.
IAM users have no inline/attached policies
iam.no-inline-user-policies · iam
1 finding(s)
[FAIL] eu-central-1 iam-demo-resource — Sample finding — IAM users have no inline/attached policies not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 1.15AWS_FSBP IAM.2NIST_800_53 AC-6
Recommended actions
Move user permissions into IAM groups and detach inline/directly-attached user policies.
💲 NONE Free — configuration change only. ($0)
AWS CLI
aws iam add-user-to-group --user-name "$U" --group-name "$G"
aws iam detach-user-policy --user-name "$U" --policy-arn "$ARN"

↩ Rollback / revert

Re-attach the policy to the user if a group migration breaks access (capture policy ARNs first).
No customer-managed IAM policy grants full admin (*:*)
iam.no-wildcard-policies · iam
HIGH NOT_APPLICABLE CIS_AWS_FOUNDATIONS 1.16AWS_FSBP IAM.1PCI_DSS 7.2.1NIST_800_53 AC-6
IAM password policy enforces a strong minimum length
iam.password-policy-strong · iam
1 finding(s)
[FAIL] eu-central-1 iam-demo-resource — Sample finding — IAM password policy enforces a strong minimum length not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 1.8AWS_FSBP IAM.7PCI_DSS 8.3.6NIST_800_53 IA-5(1)
Recommended actions
Configure an IAM account password policy requiring at least 14 characters with complexity and rotation.
💲 NONE Free — IAM and account password policies have no charge. ($0)
AWS CLI
aws iam update-account-password-policy \
  --minimum-password-length 14 \
  --require-symbols --require-numbers \
  --require-uppercase-characters --require-lowercase-characters \
  --max-password-age 90 --password-reuse-prevention 24
Terraform
resource "aws_iam_account_password_policy" "strict" {
  minimum_password_length        = 14
  require_symbols                = true
  require_numbers                = true
  require_uppercase_characters   = true
  require_lowercase_characters   = true
  max_password_age               = 90
  password_reuse_prevention      = 24
}
AWS CDK
// No L2 construct; set it via a custom resource:
new cr.AwsCustomResource(this, 'PasswordPolicy', {
  onUpdate: {
    service: 'IAM',
    action: 'updateAccountPasswordPolicy',
    parameters: {
      MinimumPasswordLength: 14, RequireSymbols: true, RequireNumbers: true,
      RequireUppercaseCharacters: true, RequireLowercaseCharacters: true,
      MaxPasswordAge: 90, PasswordReusePrevention: 24,
    },
    physicalResourceId: cr.PhysicalResourceId.of('account-password-policy'),
  },
  policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE }),
});
Pulumi
new aws.iam.AccountPasswordPolicy("strict", {
  minimumPasswordLength:      14,
  requireSymbols:             true,
  requireNumbers:             true,
  requireUppercaseCharacters: true,
  requireLowercaseCharacters: true,
  maxPasswordAge:             90,
  passwordReusePrevention:    24,
});

↩ Rollback / revert

Back up the existing policy first; to revert, re-apply the backup. This account had NO prior policy, so reverting means deleting the policy entirely.
⚠ A stricter password policy does not lock out existing sessions; it applies at next password change. Reverting weakens security and is rarely advisable.
AWS CLI
# Back up BEFORE applying (run this first):
aws iam get-account-password-policy > password-policy-backup.json 2>/dev/null \
  || echo "no prior password policy on this account"

# Revert to the previous state (no policy) for this account:
aws iam delete-account-password-policy
IAM password policy prevents password reuse
iam.password-reuse-prevention · iam
1 finding(s)
[FAIL] eu-central-1 iam-demo-resource — Sample finding — IAM password policy prevents password reuse not satisfied.
LOW FAIL CIS_AWS_FOUNDATIONS 1.9AWS_FSBP IAM.16PCI_DSS 8.3.7NIST_800_53 IA-5(1)
Recommended actions
Set the IAM account password policy to prevent reuse of at least the last 24 passwords.
💲 NONE Free — IAM password policy settings have no charge. ($0)
AWS CLI
aws iam update-account-password-policy --password-reuse-prevention 24 \
  --minimum-password-length 14 --require-symbols --require-numbers \
  --require-uppercase-characters --require-lowercase-characters
Terraform
resource "aws_iam_account_password_policy" "strict" {
  password_reuse_prevention = 24
  minimum_password_length   = 14
  # ...complexity settings...
}
Pulumi
new aws.iam.AccountPasswordPolicy("strict", {
  passwordReusePrevention: 24,
  minimumPasswordLength:   14,
});

↩ Rollback / revert

Back up the policy first (aws iam get-account-password-policy); to revert, re-apply the prior value or lower password_reuse_prevention.
⚠ Tightening reuse prevention does not affect existing sessions; reverting weakens security.
No root user access keys
iam.root-access-keys-absent · iam
1 finding(s)
[FAIL] eu-central-1 iam-demo-resource — Sample finding — No root user access keys not satisfied.
CRITICAL FAIL CIS_AWS_FOUNDATIONS 1.4AWS_FSBP IAM.4PCI_DSS 7.2.1NIST_800_53 AC-6(5)
Recommended actions
Delete any access keys belonging to the root user and use IAM roles/users for programmatic access.
🖱 Console: Sign in as root > Security credentials > Access keys > delete each root access key.
⚠ Root access keys can only be removed while signed in as root; there is no cross-account API to delete them. The IaC below deploys continuous detection.
💲 NONE Free — deleting access keys has no charge. ($0)
  • Removing root access keys and creating IAM roles/users is free.
  • The AWS Config detective rule below incurs standard AWS Config charges if deployed.
AWS CLI
# Verify only (root keys are managed in the root console):
aws iam get-account-summary --query 'SummaryMap.AccountAccessKeysPresent'

aws configservice put-config-rule --config-rule '{
  "ConfigRuleName": "iam-root-access-key-check",
  "Source": {"Owner": "AWS", "SourceIdentifier": "IAM_ROOT_ACCESS_KEY_CHECK"}
}'
Terraform
resource "aws_config_config_rule" "root_access_key" {
  name = "iam-root-access-key-check"
  source {
    owner             = "AWS"
    source_identifier = "IAM_ROOT_ACCESS_KEY_CHECK"
  }
}
AWS CDK
new config.ManagedRule(this, 'RootAccessKeyCheck', {
  identifier: config.ManagedRuleIdentifiers.IAM_ROOT_ACCESS_KEY_CHECK,
  configRuleName: 'iam-root-access-key-check',
});
Pulumi
new aws.cfg.Rule("iam-root-access-key-check", {
  source: { owner: "AWS", sourceIdentifier: "IAM_ROOT_ACCESS_KEY_CHECK" },
});

↩ Rollback / revert

If a process unexpectedly depended on the deleted root key, do NOT recreate a root key. Instead create a scoped key on a dedicated IAM user and migrate the workload to it.
⚠ Root access keys cannot be recreated via API/CLI and should never be recreated. Capture which workload broke, then issue least-privilege IAM credentials instead.
AWS CLI
# Safe replacement path (NOT a root key): create a dedicated IAM user + key.
aws iam create-user --user-name svc-legacy-migration
aws iam create-access-key --user-name svc-legacy-migration
# Attach a least-privilege policy, then update the broken workload to use it.
MFA enabled for the root user
iam.root-mfa-enabled · iam
1 finding(s)
[FAIL] eu-central-1 iam-demo-resource — Sample finding — MFA enabled for the root user not satisfied.
CRITICAL FAIL CIS_AWS_FOUNDATIONS 1.5AWS_FSBP IAM.9PCI_DSS 8.4.2NIST_800_53 IA-2(1)
Recommended actions
Enable a multi-factor authentication device for the AWS account root user.
🖱 Console: Sign in as the root user > top-right account menu > Security credentials > Multi-factor authentication (MFA) > Assign MFA device.
⚠ Root MFA cannot be enabled via API, CLI, or IaC — it must be assigned while signed in as root. The snippets below deploy a continuous detection control instead.
💲 NONE Free — virtual MFA apps and passkeys cost nothing. ($0)
  • Virtual MFA (authenticator app) and passkeys/FIDO security keys built into a device: free.
  • A physical hardware key (e.g. YubiKey) is an optional one-time purchase (~$25–70 per key).
  • If you deploy the AWS Config detective rule below, AWS Config charges apply (see note on Config cost).
AWS CLI
# Verify only (cannot enable root MFA via CLI):
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'

# Deploy the managed detective rule:
aws configservice put-config-rule --config-rule '{
  "ConfigRuleName": "root-account-mfa-enabled",
  "Source": {"Owner": "AWS", "SourceIdentifier": "ROOT_ACCOUNT_MFA_ENABLED"}
}'
Terraform
resource "aws_config_config_rule" "root_mfa" {
  name = "root-account-mfa-enabled"
  source {
    owner             = "AWS"
    source_identifier = "ROOT_ACCOUNT_MFA_ENABLED"
  }
}
AWS CDK
new config.ManagedRule(this, 'RootMfaEnabled', {
  identifier: config.ManagedRuleIdentifiers.ROOT_ACCOUNT_MFA_ENABLED,
  configRuleName: 'root-account-mfa-enabled',
});
Pulumi
new aws.cfg.Rule("root-account-mfa-enabled", {
  source: { owner: "AWS", sourceIdentifier: "ROOT_ACCOUNT_MFA_ENABLED" },
});

↩ Rollback / revert

Low risk to reverse: enabling MFA does not break workloads. To undo, sign in as root > Security credentials > deactivate/remove the MFA device.
⚠ Reverting is strongly discouraged — an account without root MFA is a critical exposure. There is no API to remove root MFA; it must be done in the root console.
Credentials unused for 45+ days are disabled
iam.unused-credentials-disabled · iam
1 finding(s)
[FAIL] eu-central-1 iam-demo-resource — Sample finding — Credentials unused for 45+ days are disabled not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 1.12AWS_FSBP IAM.22PCI_DSS 8.2.6NIST_800_53 AC-2
Recommended actions
Disable or delete IAM passwords and access keys not used in the last 45 days.
💲 NONE Free — disabling/removing credentials has no charge. ($0)
AWS CLI
# Deactivate an unused access key:
aws iam update-access-key --user-name "$USER" --access-key-id "$KEY" --status Inactive
# Remove unused console access:
aws iam delete-login-profile --user-name "$USER"

↩ Rollback / revert

Reversible: reactivate the key (update-access-key --status Active) or re-create the login profile. Prefer leaving disabled.
Amazon Inspector is enabled
inspector.enabled · inspector
1 finding(s)
[FAIL] eu-central-1 inspector-demo-resource — Sample finding — Amazon Inspector is enabled not satisfied.
LOW FAIL AWS_FSBP Inspector.1PCI_DSS 6.3.1NIST_800_53 RA-5
Recommended actions
Enable Amazon Inspector (EC2/ECR/Lambda scanning) in the region or org-wide via a delegated administrator.
💲 USAGE_BASED Billed per instance/image/function scanned. (Varies with workload count)
AWS CLI
aws inspector2 enable --region "$REGION" --resource-types EC2 ECR LAMBDA

↩ Rollback / revert

Reversible: inspector2 disable.
Kinesis streams are encrypted at rest
kinesis.stream-encrypted · kinesis
1 finding(s)
[FAIL] eu-central-1 kinesis-demo-resource — Sample finding — Kinesis streams are encrypted at rest not satisfied.
MEDIUM FAIL AWS_FSBP Kinesis.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable encryption for this resource (KMS where supported). Many services set encryption at creation only and require recreation/migration.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Console
Open the resource's settings and enable encryption (select a customer-managed KMS key to also satisfy requireCmk).

↩ Rollback / revert

Where encryption is set at creation it cannot be removed in place; keep the original until the encrypted replacement is validated.
KMS customer keys have rotation enabled
kms.key-rotation-enabled · kms
1 finding(s)
[PASS] eu-central-1 kms-demo-resource — Compliant.
MEDIUM PASS CIS_AWS_FOUNDATIONS 3.8AWS_FSBP KMS.4PCI_DSS 3.6.1NIST_800_53 SC-12
Lambda functions are not publicly accessible
lambda.no-public-access · lambda
1 finding(s)
[PASS] eu-central-1 lambda-demo-resource — Compliant.
HIGH PASS AWS_FSBP Lambda.1PCI_DSS 7.2.1NIST_800_53 AC-3
Lambda function URLs are not public
lambda.no-public-function-url · lambda
1 finding(s)
[FAIL] eu-central-1 lambda-demo-resource — Sample finding — Lambda function URLs are not public not satisfied.
HIGH FAIL AWS_FSBP Lambda.7PCI_DSS 7.2.1NIST_800_53 AC-3
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
CloudWatch log groups are encrypted with KMS
logs.group-kms-encrypted · logs
1 finding(s)
[PASS] eu-central-1 logs-demo-resource — Compliant.
LOW PASS AWS_FSBP CloudWatch.16PCI_DSS 3.5.1NIST_800_53 SC-28
Amazon Macie is enabled
macie.enabled · macie
LOW NOT_APPLICABLE AWS_FSBP Macie.1NIST_800_53 RA-5
MemoryDB clusters encrypt data in transit
memorydb.in-transit-encryption · memorydb
1 finding(s)
[FAIL] eu-central-1 memorydb-demo-resource — Sample finding — MemoryDB clusters encrypt data in transit not satisfied.
MEDIUM FAIL AWS_FSBP MemoryDB.1PCI_DSS 4.2.1NIST_800_53 SC-8
Recommended actions
Enable encryption for this resource (KMS where supported). Many services set encryption at creation only and require recreation/migration.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Console
Open the resource's settings and enable encryption (select a customer-managed KMS key to also satisfy requireCmk).

↩ Rollback / revert

Where encryption is set at creation it cannot be removed in place; keep the original until the encrypted replacement is validated.
Amazon MQ brokers are not public
mq.not-public · mq
1 finding(s)
[FAIL] eu-central-1 mq-demo-resource — Sample finding — Amazon MQ brokers are not public not satisfied.
HIGH FAIL AWS_FSBP MQ.5PCI_DSS 1.3.1NIST_800_53 SC-7
Recommended actions
Remove public accessibility: place the resource in private subnets and/or strip public principals/CIDRs from its policy or network configuration.
⚠ Public exposure of data resources is high-risk; remediate promptly.
💲 NONE Free — configuration change only. ($0)
Console
Disable public access in the resource settings and restrict its security group / resource policy to known principals.

↩ Rollback / revert

Capture the current configuration first; re-enabling public access is reversible but strongly discouraged.
MSK clusters encrypt data in transit
msk.in-transit-encryption · msk
1 finding(s)
[FAIL] eu-central-1 msk-demo-resource — Sample finding — MSK clusters encrypt data in transit not satisfied.
MEDIUM FAIL AWS_FSBP MSK.1PCI_DSS 4.2.1NIST_800_53 SC-8
Recommended actions
Enable encryption for this resource (KMS where supported). Many services set encryption at creation only and require recreation/migration.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Console
Open the resource's settings and enable encryption (select a customer-managed KMS key to also satisfy requireCmk).

↩ Rollback / revert

Where encryption is set at creation it cannot be removed in place; keep the original until the encrypted replacement is validated.
Neptune clusters are encrypted at rest
neptune.encrypted · neptune
1 finding(s)
[FAIL] eu-central-1 neptune-demo-resource — Sample finding — Neptune clusters are encrypted at rest not satisfied.
HIGH FAIL AWS_FSBP Neptune.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable encryption for this resource (KMS where supported). Many services set encryption at creation only and require recreation/migration.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Console
Open the resource's settings and enable encryption (select a customer-managed KMS key to also satisfy requireCmk).

↩ Rollback / revert

Where encryption is set at creation it cannot be removed in place; keep the original until the encrypted replacement is validated.
Network Firewall has deletion protection
networkfirewall.deletion-protection · networkfirewall
1 finding(s)
[FAIL] eu-central-1 networkfirewall-demo-resource — Sample finding — Network Firewall has deletion protection not satisfied.
LOW FAIL AWS_FSBP NetworkFirewall.9NIST_800_53 CM-2
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
OpenSearch domains are encrypted at rest
opensearch.encrypted-at-rest · opensearch
1 finding(s)
[FAIL] eu-central-1 opensearch-demo-resource — Sample finding — OpenSearch domains are encrypted at rest not satisfied.
MEDIUM FAIL AWS_FSBP Opensearch.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable encryption at rest on the OpenSearch domain (requires a domain that supports it; may need recreation for legacy domains).
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
Terraform
resource "aws_opensearch_domain" "d" { encrypt_at_rest { enabled = true } /* ... */ }

↩ Rollback / revert

Encryption at rest cannot be disabled once enabled.
OpenSearch domains run inside a VPC
opensearch.not-public · opensearch
1 finding(s)
[FAIL] eu-central-1 opensearch-demo-resource — Sample finding — OpenSearch domains run inside a VPC not satisfied.
HIGH FAIL AWS_FSBP Opensearch.2PCI_DSS 1.3.1NIST_800_53 SC-7
Recommended actions
Deploy the OpenSearch domain inside a VPC instead of using a public endpoint (requires recreation).
💲 NONE Free — configuration change only. ($0)
Terraform
resource "aws_opensearch_domain" "d" { vpc_options { subnet_ids = var.subnets, security_group_ids = var.sgs } }

↩ Rollback / revert

Migrating between public and VPC endpoints requires recreating the domain.
QLDB ledgers have deletion protection
qldb.deletion-protection · qldb
1 finding(s)
[FAIL] eu-central-1 qldb-demo-resource — Sample finding — QLDB ledgers have deletion protection not satisfied.
LOW FAIL AWS_FSBP QLDB.1NIST_800_53 CP-9
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
RDS instances auto-apply minor upgrades
rds.auto-minor-upgrade · rds
1 finding(s)
[PASS] eu-central-1 rds-demo-resource — Compliant.
LOW PASS AWS_FSBP RDS.13PCI_DSS 6.3.3NIST_800_53 SI-2
RDS instances have automated backups
rds.backup-retention · rds
1 finding(s)
[FAIL] eu-central-1 rds-demo-resource — Sample finding — RDS instances have automated backups not satisfied.
MEDIUM FAIL AWS_FSBP RDS.11NIST_800_53 CP-9
Recommended actions
Set a backup retention period of at least 7 days on the RDS instance.
💲 USAGE_BASED Backup storage beyond the DB size is billed; modest for typical retention. (Usually a few $/month)
AWS CLI
aws rds modify-db-instance --db-instance-identifier "$DB" --backup-retention-period 7 --apply-immediately
Terraform
resource "aws_db_instance" "this" { backup_retention_period = 7 /* ... */ }

↩ Rollback / revert

Reversible: lower the retention period via modify-db-instance.
RDS instances have deletion protection
rds.deletion-protection · rds
1 finding(s)
[PASS] eu-central-1 rds-demo-resource — Compliant.
LOW PASS AWS_FSBP RDS.8NIST_800_53 CP-9
RDS instances enable IAM database authentication
rds.iam-auth · rds
1 finding(s)
[FAIL] eu-central-1 rds-demo-resource — Sample finding — RDS instances enable IAM database authentication not satisfied.
LOW FAIL AWS_FSBP RDS.10NIST_800_53 IA-2
Recommended actions
Update the resource configuration to the recommended secure setting (see the check description for the specific attribute).
💲 NONE Free — configuration change only. ($0)
Console
Adjust the relevant setting in the resource's console page or via its modify/update API.

↩ Rollback / revert

Reversible: restore the prior configuration value via the same API.
RDS instances are Multi-AZ
rds.multi-az · rds
1 finding(s)
[FAIL] eu-central-1 rds-demo-resource — Sample finding — RDS instances are Multi-AZ not satisfied.
LOW FAIL AWS_FSBP RDS.5NIST_800_53 CP-10
Recommended actions
Convert the RDS instance to a Multi-AZ deployment for high availability.
💲 USAGE_BASED Multi-AZ roughly doubles instance/storage cost (standby replica). (~2x single-AZ)
AWS CLI
aws rds modify-db-instance --db-instance-identifier "$DB" --multi-az --apply-immediately
Terraform
resource "aws_db_instance" "this" { multi_az = true /* ... */ }

↩ Rollback / revert

Reversible: modify-db-instance --no-multi-az.
RDS instances are not publicly accessible
rds.no-public-access · rds
1 finding(s)
[PASS] eu-central-1 rds-demo-resource — Compliant.
HIGH PASS AWS_FSBP RDS.2PCI_DSS 1.3.1NIST_800_53 SC-7
No RDS snapshots are public
rds.no-public-snapshots · rds
1 finding(s)
[PASS] eu-central-1 rds-demo-resource — Compliant.
CRITICAL PASS AWS_FSBP RDS.1PCI_DSS 3.5.1NIST_800_53 AC-3
RDS storage is encrypted at rest
rds.storage-encrypted · rds
1 finding(s)
[FAIL] eu-central-1 rds-demo-resource — Sample finding — RDS storage is encrypted at rest not satisfied.
HIGH FAIL CIS_AWS_FOUNDATIONS 2.3.1AWS_FSBP RDS.3PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable storage encryption at rest for RDS instances.
⚠ Encryption cannot be toggled on an existing unencrypted instance in place — create an encrypted snapshot copy and restore, or recreate the instance with encryption enabled.
💲 NONE Free — RDS encryption with the default KMS key adds no charge. ($0)
  • Encryption itself is free; a customer-managed KMS key (optional) is ~$1/key/month.
  • Snapshot copy/restore for migration incurs temporary storage cost.
AWS CLI
# Migrate an existing instance to encrypted via snapshot copy:
aws rds create-db-snapshot --db-instance-identifier "$DB" --db-snapshot-identifier "$DB-snap"
aws rds copy-db-snapshot --source-db-snapshot-identifier "$DB-snap" \
  --target-db-snapshot-identifier "$DB-snap-enc" --kms-key-id alias/aws/rds
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier "$DB-enc" --db-snapshot-identifier "$DB-snap-enc"
Terraform
resource "aws_db_instance" "this" {
  # ...
  storage_encrypted = true
  kms_key_id        = aws_kms_key.rds.arn # optional CMK
}
Pulumi
new aws.rds.Instance("this", { storageEncrypted: true /* ... */ });

↩ Rollback / revert

Not directly reversible (you would restore from the original unencrypted snapshot). Keep the pre-migration snapshot until validated.
Redshift clusters are encrypted at rest
redshift.encrypted · redshift
1 finding(s)
[FAIL] eu-central-1 redshift-demo-resource — Sample finding — Redshift clusters are encrypted at rest not satisfied.
HIGH FAIL AWS_FSBP Redshift.1PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Enable encryption at rest on the Redshift cluster (modify-cluster --encrypted), optionally with a customer CMK.
⚠ Enabling encryption migrates the cluster to a new encrypted instance and can take time for large clusters; plan a maintenance window.
💲 NONE Free with the default key; a customer CMK is ~$1/key/month. ($0 / ~$1/mo (CMK))
AWS CLI
aws redshift modify-cluster --cluster-identifier "$CLUSTER" --encrypted
Terraform
resource "aws_redshift_cluster" "this" {
  # ...
  encrypted  = true
  kms_key_id = aws_kms_key.cmk.arn  # optional CMK
}

↩ Rollback / revert

Decryption is also a modify-cluster migration; keep a snapshot before changing encryption state.
Redshift clusters are not publicly accessible
redshift.not-public · redshift
1 finding(s)
[FAIL] eu-central-1 redshift-demo-resource — Sample finding — Redshift clusters are not publicly accessible not satisfied.
HIGH FAIL AWS_FSBP Redshift.1PCI_DSS 1.3.1NIST_800_53 SC-7
Recommended actions
Remove public accessibility: place the resource in private subnets and/or strip public principals/CIDRs from its policy or network configuration.
⚠ Public exposure of data resources is high-risk; remediate promptly.
💲 NONE Free — configuration change only. ($0)
Console
Disable public access in the resource settings and restrict its security group / resource policy to known principals.

↩ Rollback / revert

Capture the current configuration first; re-enabling public access is reversible but strongly discouraged.
Public hosted zones log DNS queries
route53.query-logging · route53
1 finding(s)
[FAIL] eu-central-1 route53-demo-resource — Sample finding — Public hosted zones log DNS queries not satisfied.
LOW FAIL AWS_FSBP Route53.2PCI_DSS 10.2.1NIST_800_53 AU-2
Recommended actions
Enable this AWS security/logging service in the region (or org-wide via a delegated administrator).
💲 USAGE_BASED Most detection/logging services bill by data analyzed or events ingested. (Varies with activity)
Console
Enable the service from its console page, or via the corresponding enable/create API; prefer org-wide delegated administration.

↩ Rollback / revert

Reversible: disable the service again in the console/API.
Account-level S3 Block Public Access enabled
s3.account-public-access-block · s3
1 finding(s)
[FAIL] eu-central-1 s3-demo-resource — Sample finding — Account-level S3 Block Public Access enabled not satisfied.
HIGH FAIL CIS_AWS_FOUNDATIONS 2.1.4AWS_FSBP S3.1PCI_DSS 1.3.1NIST_800_53 AC-3
Recommended actions
Enable all four account-level S3 Block Public Access settings so no bucket can be made public.
🖱 Console: S3 console > Block Public Access settings for this account > Edit > enable all four > Save.
💲 NONE Free — Block Public Access is a free S3 setting. ($0)
  • No charge to enable the setting.
  • Indirect benefit: prevents accidental public exposure and the data-transfer/egress costs of unintended public downloads.
AWS CLI
aws s3control put-public-access-block \
  --account-id "$(aws sts get-caller-identity --query Account --output text)" \
  --public-access-block-configuration \
    BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Terraform
data "aws_caller_identity" "current" {}

resource "aws_s3_account_public_access_block" "account" {
  account_id              = data.aws_caller_identity.current.account_id
  block_public_acls       = true
  ignore_public_acls      = true
  block_public_policy      = true
  restrict_public_buckets  = true
}
AWS CDK
new s3.CfnAccountPublicAccessBlock(this, 'AccountPab', {
  blockPublicAcls: true,
  ignorePublicAcls: true,
  blockPublicPolicy: true,
  restrictPublicBuckets: true,
});
Pulumi
const id = aws.getCallerIdentity({});
new aws.s3.AccountPublicAccessBlock("account", {
  accountId:             id.then(i => i.accountId),
  blockPublicAcls:       true,
  ignorePublicAcls:      true,
  blockPublicPolicy:     true,
  restrictPublicBuckets: true,
});

↩ Rollback / revert

Back up the current setting first; to revert, re-apply it. This account had NO account-level block configured, so reverting means removing it.
⚠ Reverting re-opens the ability to make buckets public. Only do so if a legitimate public bucket (e.g. static website) was blocked — prefer per-bucket settings over account-wide removal.
AWS CLI
ACCT=$(aws sts get-caller-identity --query Account --output text)
# Back up BEFORE applying:
aws s3control get-public-access-block --account-id "$ACCT" > pab-backup.json 2>/dev/null \
  || echo "no prior account-level public access block"

# Revert to the previous state (no account-level block):
aws s3control delete-public-access-block --account-id "$ACCT"
S3 buckets are not publicly accessible
s3.bucket-level-public · s3
1 finding(s)
[PASS] eu-central-1 s3-demo-resource — Compliant.
HIGH PASS CIS_AWS_FOUNDATIONS 2.1.5AWS_FSBP S3.2PCI_DSS 1.3.1NIST_800_53 AC-3
S3 buckets have server access logging
s3.bucket-logging · s3
1 finding(s)
[FAIL] eu-central-1 s3-demo-resource — Sample finding — S3 buckets have server access logging not satisfied.
LOW FAIL AWS_FSBP S3.9PCI_DSS 10.2.1NIST_800_53 AU-2
Recommended actions
Enable server access logging to a dedicated target bucket.
💲 USAGE_BASED You pay S3 storage for the delivered logs. (Usually a few cents/month)
Terraform
resource "aws_s3_bucket_logging" "l" { bucket = aws_s3_bucket.b.id, target_bucket = aws_s3_bucket.logs.id, target_prefix = "s3/" }

↩ Rollback / revert

Reversible: remove the logging configuration (put-bucket-logging with empty config).
S3 bucket policies deny non-SSL access
s3.bucket-ssl-only · s3
MEDIUM NOT_APPLICABLE CIS_AWS_FOUNDATIONS 2.1.1AWS_FSBP S3.5PCI_DSS 4.2.1NIST_800_53 SC-8
S3 buckets have versioning enabled
s3.bucket-versioning · s3
1 finding(s)
[FAIL] eu-central-1 s3-demo-resource — Sample finding — S3 buckets have versioning enabled not satisfied.
LOW FAIL AWS_FSBP S3.14NIST_800_53 CP-9
Recommended actions
Enable versioning on the bucket to retain prior object versions.
💲 USAGE_BASED You pay storage for retained noncurrent versions. (Varies; add a lifecycle rule to expire old versions)
AWS CLI
aws s3api put-bucket-versioning --bucket "$B" --versioning-configuration Status=Enabled
Terraform
resource "aws_s3_bucket_versioning" "v" { bucket = aws_s3_bucket.b.id, versioning_configuration { status = "Enabled" } }

↩ Rollback / revert

Versioning can be suspended (put-bucket-versioning Status=Suspended) but not fully disabled once enabled.
S3 buckets have default encryption
s3.default-encryption · s3
1 finding(s)
[FAIL] eu-central-1 s3-demo-resource — Sample finding — S3 buckets have default encryption not satisfied.
MEDIUM FAIL AWS_FSBP S3.4PCI_DSS 3.5.1NIST_800_53 SC-28
Recommended actions
Configure default server-side encryption (SSE-S3 or SSE-KMS) on the bucket.
💲 NONE Free with the default AWS-managed key; a customer CMK is ~$1/key/month. ($0 (AWS-managed) / ~$1/mo (CMK))
AWS CLI
aws s3api put-bucket-encryption --bucket "$B" \
  --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"aws:kms"}}]}'
Terraform
resource "aws_s3_bucket_server_side_encryption_configuration" "e" {
  bucket = aws_s3_bucket.b.id
  rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" } }
}

↩ Rollback / revert

Reversible: delete-bucket-encryption (not recommended).
SageMaker notebooks have no direct internet access
sagemaker.notebook-no-direct-internet · sagemaker
1 finding(s)
[FAIL] eu-central-1 sagemaker-demo-resource — Sample finding — SageMaker notebooks have no direct internet access not satisfied.
MEDIUM FAIL AWS_FSBP SageMaker.1NIST_800_53 SC-7
Recommended actions
Recreate the notebook instance with DirectInternetAccess=Disabled and attach it to a VPC subnet with controlled egress.
💲 NONE Free — configuration change only. ($0)
Terraform
resource "aws_sagemaker_notebook_instance" "n" { direct_internet_access = "Disabled", subnet_id = var.subnet /* ... */ }

↩ Rollback / revert

Direct internet access is set at creation; recreate to change.
Secrets Manager secrets have rotation enabled
secretsmanager.rotation-enabled · secretsmanager
1 finding(s)
[PASS] eu-central-1 secretsmanager-demo-resource — Compliant.
LOW PASS AWS_FSBP SecretsManager.1PCI_DSS 8.3.9NIST_800_53 IA-5
AWS Security Hub is enabled
securityhub.enabled · securityhub
1 finding(s)
[FAIL] eu-central-1 securityhub-demo-resource — Sample finding — AWS Security Hub is enabled not satisfied.
MEDIUM FAIL AWS_FSBP SecurityHub.1PCI_DSS 10.6.1NIST_800_53 CA-7
Recommended actions
Enable AWS Security Hub in every region (ideally via an org delegated administrator) to aggregate and continuously evaluate findings.
💲 USAGE_BASED Billed per security check and per finding ingested; 30-day free trial. (Varies; small accounts often a few $/month)
  • Security checks: ~$0.0010 per check for the first 100k/region/month.
  • Finding ingestion: first 10k/region/month free, then ~$0.00003 each.
  • Costs compound with Config (required for many controls).
AWS CLI
for region in $(aws ec2 describe-regions --region us-east-1 --query 'Regions[].RegionName' --output text); do
  aws securityhub enable-security-hub --region "$region" --enable-default-standards 2>/dev/null \
    && echo "enabled in $region" || echo "already enabled in $region"
done
Terraform
resource "aws_securityhub_account" "this" {}
Pulumi
new aws.securityhub.Account("this", {});

↩ Rollback / revert

Reversible: aws securityhub disable-security-hub (per region).
Step Functions state machines have logging
sfn.logging-enabled · stepfunctions
1 finding(s)
[FAIL] eu-central-1 stepfunctions-demo-resource — Sample finding — Step Functions state machines have logging not satisfied.
LOW FAIL AWS_FSBP StepFunctions.1PCI_DSS 10.2.1NIST_800_53 AU-2
Recommended actions
Enable execution logging (level ALL or ERROR) to CloudWatch Logs on the state machine.
💲 USAGE_BASED CloudWatch Logs ingestion for execution history. (Varies with executions)
Terraform
resource "aws_sfn_state_machine" "sm" {
  logging_configuration { level = "ALL", log_destination = "${aws_cloudwatch_log_group.sfn.arn}:*", include_execution_data = true }
}

↩ Rollback / revert

Reversible: update-state-machine with logging level OFF.
SNS topics are encrypted at rest
sns.topic-encrypted · sns
MEDIUM NOT_APPLICABLE AWS_FSBP SNS.1PCI_DSS 3.5.1NIST_800_53 SC-28
SNS topic policies are not public
sns.topic-not-public · sns
1 finding(s)
[FAIL] eu-central-1 sns-demo-resource — Sample finding — SNS topic policies are not public not satisfied.
HIGH FAIL AWS_FSBP SNS.2NIST_800_53 AC-3
Recommended actions
Remove public accessibility: place the resource in private subnets and/or strip public principals/CIDRs from its policy or network configuration.
⚠ Public exposure of data resources is high-risk; remediate promptly.
💲 NONE Free — configuration change only. ($0)
Console
Disable public access in the resource settings and restrict its security group / resource policy to known principals.

↩ Rollback / revert

Capture the current configuration first; re-enabling public access is reversible but strongly discouraged.
SQS queues are encrypted at rest
sqs.queue-encrypted · sqs
1 finding(s)
[PASS] eu-central-1 sqs-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP SQS.1PCI_DSS 3.5.1NIST_800_53 SC-28
SQS queue policies are not public
sqs.queue-not-public · sqs
1 finding(s)
[FAIL] eu-central-1 sqs-demo-resource — Sample finding — SQS queue policies are not public not satisfied.
HIGH FAIL AWS_FSBP SQS.2NIST_800_53 AC-3
Recommended actions
Remove public accessibility: place the resource in private subnets and/or strip public principals/CIDRs from its policy or network configuration.
⚠ Public exposure of data resources is high-risk; remediate promptly.
💲 NONE Free — configuration change only. ($0)
Console
Disable public access in the resource settings and restrict its security group / resource policy to known principals.

↩ Rollback / revert

Capture the current configuration first; re-enabling public access is reversible but strongly discouraged.
No SSM documents are public
ssm.no-public-documents · ssm
1 finding(s)
[PASS] eu-central-1 ssm-demo-resource — Compliant.
HIGH PASS AWS_FSBP SSM.4NIST_800_53 AC-3
Transfer servers don't use plaintext FTP
transfer.no-plaintext-ftp · transfer
1 finding(s)
[PASS] eu-central-1 transfer-demo-resource — Compliant.
HIGH PASS AWS_FSBP Transfer.2PCI_DSS 4.2.1NIST_800_53 SC-8
VPC flow logs are enabled
vpc.flow-logs-enabled · ec2
1 finding(s)
[FAIL] eu-central-1 ec2-demo-resource — Sample finding — VPC flow logs are enabled not satisfied.
MEDIUM FAIL CIS_AWS_FOUNDATIONS 3.9AWS_FSBP EC2.6PCI_DSS 10.3.1NIST_800_53 AU-12
Recommended actions
Enable flow logs on every VPC, delivering to CloudWatch Logs or S3.
💲 USAGE_BASED Billed on the volume of log data ingested/stored at the destination. (Typically a few $/month per active VPC)
  • Flow logs themselves have no fee; you pay CloudWatch Logs ingestion (~$0.50/GB) or S3 storage.
  • Use a sampling/aggregation interval and lifecycle rules to control cost.
AWS CLI
aws ec2 create-flow-logs --region "$REGION" \
  --resource-type VPC --resource-ids "$VPC_ID" \
  --traffic-type ALL --log-destination-type s3 \
  --log-destination arn:aws:s3:::my-flow-logs-bucket
Terraform
resource "aws_flow_log" "this" {
  vpc_id               = aws_vpc.main.id
  traffic_type         = "ALL"
  log_destination      = aws_s3_bucket.flow_logs.arn
  log_destination_type = "s3"
}
AWS CDK
vpc.addFlowLog('FlowLog', { destination: ec2.FlowLogDestination.toS3(bucket) });
Pulumi
new aws.ec2.FlowLog("this", {
  vpcId: vpc.id, trafficType: "ALL",
  logDestinationType: "s3", logDestination: bucket.arn,
});

↩ Rollback / revert

Reversible: delete the flow log (aws ec2 delete-flow-logs --flow-log-ids <id>).
WAF web ACLs have logging enabled
wafv2.logging-enabled · wafv2
1 finding(s)
[PASS] eu-central-1 wafv2-demo-resource — Compliant.
LOW PASS AWS_FSBP WAF.1PCI_DSS 10.2.1NIST_800_53 AU-2
WorkSpaces volumes are encrypted
workspaces.volume-encryption · workspaces
1 finding(s)
[PASS] eu-central-1 workspaces-demo-resource — Compliant.
MEDIUM PASS AWS_FSBP WorkSpaces.2PCI_DSS 3.5.1NIST_800_53 SC-28