mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 21:11:53 +00:00
feat(aws): add new check cloudformation_stack_cdktoolkit_bootstrap_version (#6323)
This commit is contained in:
@@ -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
|
||||
|
||||
+30
@@ -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": ""
|
||||
}
|
||||
+39
@@ -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
|
||||
+7
-3
@@ -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)
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+97
@@ -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
|
||||
+4
-4
@@ -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 (
|
||||
|
||||
+2
-2
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user