feat(aws): add new check cloudformation_stack_cdktoolkit_bootstrap_version (#6323)

This commit is contained in:
Sergio Garcia
2025-01-07 14:52:55 -05:00
committed by GitHub
parent fbd5235e15
commit b5180389f8
9 changed files with 185 additions and 11 deletions
+4
View File
@@ -73,6 +73,10 @@ aws:
# aws.cloudwatch_log_group_retention_policy_specific_days_enabled --> by default is 365 days
log_group_retention_days: 365
# AWS CloudFormation Configuration
# cloudformation_stack_cdktoolkit_bootstrap_version --> by default is 21
recommended_cdk_bootstrap_version: 21
# AWS AppStream Session Configuration
# aws.appstream_fleet_session_idle_disconnect_timeout
max_idle_disconnect_timeout_in_seconds: 600 # 10 Minutes
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "cloudformation_stack_cdktoolkit_bootstrap_version",
"CheckTitle": "Ensure that CDKToolkit stacks have a Bootstrap version of 21 or higher to mitigate security risks.",
"CheckType": [],
"ServiceName": "cloudformation",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudformation:region:account-id:stack/resource-id",
"Severity": "high",
"ResourceType": "AwsCloudFormationStack",
"Description": "Ensure that CDKToolkit stacks have a Bootstrap version of 21 or higher to mitigate security risks.",
"Risk": "Using outdated CDKToolkit Bootstrap versions can expose accounts to risks such as bucket takeover or privilege escalation.",
"RelatedUrl": "https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Update the CDKToolkit stack Bootstrap version to 21 or later by running the cdk bootstrap command with the latest CDK version.",
"Url": "https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,39 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudformation.cloudformation_client import (
cloudformation_client,
)
class cloudformation_stack_cdktoolkit_bootstrap_version(Check):
"""Check if a CDKToolkit CloudFormation Stack has a Bootstrap version less than recommended"""
def execute(self):
"""Execute the cloudformation_stack_cdktoolkit_bootstrap_version check"""
findings = []
recommended_cdk_bootstrap_version = cloudformation_client.audit_config.get(
"recommended_cdk_bootstrap_version", 21
)
for stack in cloudformation_client.stacks:
# Only check stacks named CDKToolkit
if stack.name == "CDKToolkit":
bootstrap_version = None
if stack.outputs:
for output in stack.outputs:
if output.startswith("BootstrapVersion:"):
bootstrap_version = int(output.split(":")[1])
break
if bootstrap_version:
report = Check_Report_AWS(self.metadata())
report.region = stack.region
report.resource_id = stack.name
report.resource_arn = stack.arn
report.resource_tags = stack.tags
report.status = "PASS"
report.status_extended = f"CloudFormation Stack CDKToolkit has a Bootstrap version {bootstrap_version}, which meets the recommended version."
if bootstrap_version < recommended_cdk_bootstrap_version:
report.status = "FAIL"
report.status_extended = f"CloudFormation Stack CDKToolkit has a Bootstrap version {bootstrap_version}, which is less than the recommended version {recommended_cdk_bootstrap_version}."
findings.append(report)
return findings
@@ -21,7 +21,9 @@ class cloudformation_stack_outputs_find_secrets(Check):
report.resource_arn = stack.arn
report.resource_tags = stack.tags
report.status = "PASS"
report.status_extended = f"No secrets found in Stack {stack.name} Outputs."
report.status_extended = (
f"No secrets found in CloudFormation Stack {stack.name} Outputs."
)
if stack.outputs:
data = ""
# Store the CloudFormation Stack Outputs into a file
@@ -40,11 +42,13 @@ class cloudformation_stack_outputs_find_secrets(Check):
]
)
report.status = "FAIL"
report.status_extended = f"Potential secret found in Stack {stack.name} Outputs -> {secrets_string}."
report.status_extended = f"Potential secret found in CloudFormation Stack {stack.name} Outputs -> {secrets_string}."
else:
report.status = "PASS"
report.status_extended = f"CloudFormation {stack.name} has no Outputs."
report.status_extended = (
f"CloudFormation Stack {stack.name} has no Outputs."
)
findings.append(report)
@@ -20,10 +20,10 @@ class cloudformation_stacks_termination_protection_enabled(Check):
if stack.enable_termination_protection:
report.status = "PASS"
report.status_extended = f"CloudFormation {stack.name} has termination protection enabled."
report.status_extended = f"CloudFormation Stack {stack.name} has termination protection enabled."
else:
report.status = "FAIL"
report.status_extended = f"CloudFormation {stack.name} has termination protection disabled."
report.status_extended = f"CloudFormation Stack {stack.name} has termination protection disabled."
findings.append(report)
return findings
@@ -0,0 +1,97 @@
from unittest import mock
from prowler.providers.aws.services.cloudformation.cloudformation_service import Stack
# Mock Test Region
AWS_REGION = "eu-west-1"
class Test_cloudformation_stack_cdktoolkit_bootstrap_version:
def test_no_stacks(self):
cloudformation_client = mock.MagicMock
cloudformation_client.stacks = []
cloudformation_client.audit_config = {"recommended_cdk_bootstrap_version": 21}
with mock.patch(
"prowler.providers.aws.services.cloudformation.cloudformation_client.cloudformation_client",
new=cloudformation_client,
):
from prowler.providers.aws.services.cloudformation.cloudformation_stack_cdktoolkit_bootstrap_version.cloudformation_stack_cdktoolkit_bootstrap_version import (
cloudformation_stack_cdktoolkit_bootstrap_version,
)
check = cloudformation_stack_cdktoolkit_bootstrap_version()
result = check.execute()
assert len(result) == 0
def test_stack_with_valid_bootstrap_version(self):
cloudformation_client = mock.MagicMock
cloudformation_client.stacks = [
Stack(
arn="arn:aws:cloudformation:eu-west-1:123456789012:stack/CDKToolkit/1234abcd",
name="CDKToolkit",
outputs=["BootstrapVersion:21"],
region=AWS_REGION,
)
]
cloudformation_client.audit_config = {"recommended_cdk_bootstrap_version": 21}
with mock.patch(
"prowler.providers.aws.services.cloudformation.cloudformation_client.cloudformation_client",
new=cloudformation_client,
):
from prowler.providers.aws.services.cloudformation.cloudformation_stack_cdktoolkit_bootstrap_version.cloudformation_stack_cdktoolkit_bootstrap_version import (
cloudformation_stack_cdktoolkit_bootstrap_version,
)
check = cloudformation_stack_cdktoolkit_bootstrap_version()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "CloudFormation Stack CDKToolkit has a Bootstrap version 21, which meets the recommended version."
)
assert result[0].resource_id == "CDKToolkit"
assert (
result[0].resource_arn
== "arn:aws:cloudformation:eu-west-1:123456789012:stack/CDKToolkit/1234abcd"
)
assert result[0].region == AWS_REGION
def test_stack_with_invalid_bootstrap_version(self):
cloudformation_client = mock.MagicMock
cloudformation_client.stacks = [
Stack(
arn="arn:aws:cloudformation:eu-west-1:123456789012:stack/CDKToolkit/1234abcd",
name="CDKToolkit",
outputs=["BootstrapVersion:20"],
region=AWS_REGION,
)
]
cloudformation_client.audit_config = {"recommended_cdk_bootstrap_version": 21}
with mock.patch(
"prowler.providers.aws.services.cloudformation.cloudformation_client.cloudformation_client",
new=cloudformation_client,
):
from prowler.providers.aws.services.cloudformation.cloudformation_stack_cdktoolkit_bootstrap_version.cloudformation_stack_cdktoolkit_bootstrap_version import (
cloudformation_stack_cdktoolkit_bootstrap_version,
)
check = cloudformation_stack_cdktoolkit_bootstrap_version()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "CloudFormation Stack CDKToolkit has a Bootstrap version 20, which is less than the recommended version 21."
)
assert result[0].resource_id == "CDKToolkit"
assert (
result[0].resource_arn
== "arn:aws:cloudformation:eu-west-1:123456789012:stack/CDKToolkit/1234abcd"
)
assert result[0].region == AWS_REGION
@@ -51,7 +51,7 @@ class Test_cloudformation_stack_outputs_find_secrets:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Potential secret found in Stack {stack_name} Outputs -> Secret Keyword in Output 1."
== f"Potential secret found in CloudFormation Stack {stack_name} Outputs -> Secret Keyword in Output 1."
)
assert result[0].resource_id == "Test-Stack"
assert (
@@ -90,7 +90,7 @@ class Test_cloudformation_stack_outputs_find_secrets:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"No secrets found in Stack {stack_name} Outputs."
== f"No secrets found in CloudFormation Stack {stack_name} Outputs."
)
assert result[0].resource_id == "Test-Stack"
assert (
@@ -127,7 +127,7 @@ class Test_cloudformation_stack_outputs_find_secrets:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"No secrets found in Stack {stack_name} Outputs."
== f"No secrets found in CloudFormation Stack {stack_name} Outputs."
)
assert result[0].resource_id == "Test-Stack"
assert (
@@ -164,7 +164,7 @@ class Test_cloudformation_stack_outputs_find_secrets:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"CloudFormation {stack_name} has no Outputs."
== f"CloudFormation Stack {stack_name} has no Outputs."
)
assert result[0].resource_id == "Test-Stack"
assert (
@@ -52,7 +52,7 @@ class Test_cloudformation_stacks_termination_protection_enabled:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"CloudFormation {stack_name} has termination protection enabled."
== f"CloudFormation Stack {stack_name} has termination protection enabled."
)
assert result[0].resource_id == "Test-Stack"
assert (
@@ -90,7 +90,7 @@ class Test_cloudformation_stacks_termination_protection_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"CloudFormation {stack_name} has termination protection disabled."
== f"CloudFormation Stack {stack_name} has termination protection disabled."
)
assert result[0].resource_id == "Test-Stack"
assert (