Compliance posture — automated + attested
| Framework | Type | Automated | Attested | Combined |
|---|---|---|---|---|
| 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
| Framework | Type | Score | Pass / Fail | Coverage |
|---|---|---|---|---|
CIS Amazon Web Services Foundations Benchmark v3.0.0
About & recommendationPrescriptive, 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 & recommendationAWS'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 & recommendationMandatory 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 & recommendationFederal 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 & recommendationTrust 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 & recommendationSafeguards 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 & recommendationInternational 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 & recommendationCloud-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 & recommendationEU 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 & recommendationCalifornia 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 & recommendationVoluntary, 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. |
| Check | Severity | Status | Mapped 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 actionsEnable 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 / revertReversible: 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 actionsUpdate 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 / revertReversible: 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 actionsUpdate 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 / revertReversible: 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 actionsUpdate 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 / revertReversible: 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 actionsEnable 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 / revertReversible: 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 actionsCreate 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)
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 / revertFully 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsCreate 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)
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 / revertReversible: 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 actionsUpdate 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 / revertReversible: 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 actionsEnable 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)
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 / revertReversible: 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 actionsEnable 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 / revertWhere 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 actionsRecreate 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 / revertKeep 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 actionsEnable 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 / revertReversible: 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 actionsRemove 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 / revertRestore 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 actionsEncrypt 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 / revertKeep 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 actionsRequire 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 / revertReversible: 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 actionsRemove 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 / revertReversible (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 actionsRemove 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 / revertReversible (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 actionsEnable 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))
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 / revertReversible: 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 actionsMove 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 / revertRegister 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 actionsRecreate 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 / revertKeep 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 actionsEnable 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 / revertNot 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 actionsEnable 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 / revertWhere 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 actionsEnable 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 / revertWhere 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 actionsEnable 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)
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 / revertReversible: 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 actionsRegister 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 / revertReversible: 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 actionsMove 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 / revertRe-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 actionsConfigure 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 / revertBack 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 actionsSet 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 / revertBack 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 actionsDelete 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)
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 / revertIf 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 actionsEnable 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)
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 / revertLow 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 actionsDisable 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 / revertReversible: 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 actionsEnable 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 / revertReversible: 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 actionsEnable 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 / revertWhere 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 actionsUpdate 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 / revertReversible: 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 actionsEnable 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 / revertWhere 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 actionsRemove 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 / revertCapture 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 actionsEnable 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 / revertWhere 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 actionsEnable 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 / revertWhere 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 actionsUpdate 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 / revertReversible: 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 actionsEnable 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 / revertEncryption 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 actionsDeploy 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 / revertMigrating 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 actionsUpdate 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 / revertReversible: 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 actionsSet 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 / revertReversible: 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 actionsUpdate 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 / revertReversible: 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 actionsConvert 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 / revertReversible: 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 actionsEnable 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)
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 / revertNot 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 actionsEnable 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 / revertDecryption 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 actionsRemove 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 / revertCapture 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 actionsEnable 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 / revertReversible: 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 actionsEnable 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)
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 / revertBack 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 actionsEnable 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 / revertReversible: 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 actionsEnable 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 / revertVersioning 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 actionsConfigure 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 / revertReversible: 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 actionsRecreate 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 / revertDirect 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 actionsEnable 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)
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 / revertReversible: 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 actionsEnable 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 / revertReversible: 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 actionsRemove 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 / revertCapture 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 actionsRemove 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 / revertCapture 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 actionsEnable 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)
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 / revertReversible: 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 |