mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-05 03:12:14 +00:00
chore(ccc): update with latest version and improve mapping (#10625)
This commit is contained in:
@@ -97,7 +97,7 @@ COMPLIANCE_CLASS_MAP = {
|
||||
(lambda name: name.startswith("iso27001_"), AWSISO27001),
|
||||
(lambda name: name.startswith("kisa"), AWSKISAISMSP),
|
||||
(lambda name: name == "prowler_threatscore_aws", ProwlerThreatScoreAWS),
|
||||
(lambda name: name == "ccc_aws", CCC_AWS),
|
||||
(lambda name: name.startswith("ccc_"), CCC_AWS),
|
||||
(lambda name: name.startswith("c5_"), AWSC5),
|
||||
(lambda name: name.startswith("csa_"), AWSCSA),
|
||||
],
|
||||
@@ -106,7 +106,7 @@ COMPLIANCE_CLASS_MAP = {
|
||||
(lambda name: name == "mitre_attack_azure", AzureMitreAttack),
|
||||
(lambda name: name.startswith("ens_"), AzureENS),
|
||||
(lambda name: name.startswith("iso27001_"), AzureISO27001),
|
||||
(lambda name: name == "ccc_azure", CCC_Azure),
|
||||
(lambda name: name.startswith("ccc_"), CCC_Azure),
|
||||
(lambda name: name == "prowler_threatscore_azure", ProwlerThreatScoreAzure),
|
||||
(lambda name: name == "c5_azure", AzureC5),
|
||||
(lambda name: name.startswith("csa_"), AzureCSA),
|
||||
@@ -117,7 +117,7 @@ COMPLIANCE_CLASS_MAP = {
|
||||
(lambda name: name.startswith("ens_"), GCPENS),
|
||||
(lambda name: name.startswith("iso27001_"), GCPISO27001),
|
||||
(lambda name: name == "prowler_threatscore_gcp", ProwlerThreatScoreGCP),
|
||||
(lambda name: name == "ccc_gcp", CCC_GCP),
|
||||
(lambda name: name.startswith("ccc_"), CCC_GCP),
|
||||
(lambda name: name == "c5_gcp", GCPC5),
|
||||
(lambda name: name.startswith("csa_"), GCPCSA),
|
||||
],
|
||||
|
||||
@@ -21,6 +21,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222)
|
||||
- `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234)
|
||||
- `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189)
|
||||
- CCC improvements with the latest checks and new mappings [(#10625)](https://github.com/prowler-cloud/prowler/pull/10625)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
|
||||
+6053
-4233
File diff suppressed because it is too large
Load Diff
+5699
-4158
File diff suppressed because it is too large
Load Diff
+6011
-4452
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,98 @@
|
||||
from colorama import Fore, Style
|
||||
from tabulate import tabulate
|
||||
|
||||
from prowler.config.config import orange_color
|
||||
|
||||
|
||||
def get_ccc_table(
|
||||
findings: list,
|
||||
bulk_checks_metadata: dict,
|
||||
compliance_framework: str,
|
||||
output_filename: str,
|
||||
output_directory: str,
|
||||
compliance_overview: bool,
|
||||
):
|
||||
section_table = {
|
||||
"Provider": [],
|
||||
"Section": [],
|
||||
"Status": [],
|
||||
"Muted": [],
|
||||
}
|
||||
pass_count = []
|
||||
fail_count = []
|
||||
muted_count = []
|
||||
sections = {}
|
||||
for index, finding in enumerate(findings):
|
||||
check = bulk_checks_metadata[finding.check_metadata.CheckID]
|
||||
check_compliances = check.Compliance
|
||||
for compliance in check_compliances:
|
||||
if compliance.Framework == "CCC":
|
||||
for requirement in compliance.Requirements:
|
||||
for attribute in requirement.Attributes:
|
||||
section = attribute.Section
|
||||
|
||||
if section not in sections:
|
||||
sections[section] = {"FAIL": 0, "PASS": 0, "Muted": 0}
|
||||
|
||||
if finding.muted:
|
||||
if index not in muted_count:
|
||||
muted_count.append(index)
|
||||
sections[section]["Muted"] += 1
|
||||
else:
|
||||
if finding.status == "FAIL" and index not in fail_count:
|
||||
fail_count.append(index)
|
||||
sections[section]["FAIL"] += 1
|
||||
elif finding.status == "PASS" and index not in pass_count:
|
||||
pass_count.append(index)
|
||||
sections[section]["PASS"] += 1
|
||||
|
||||
sections = dict(sorted(sections.items()))
|
||||
for section in sections:
|
||||
section_table["Provider"].append(compliance.Provider)
|
||||
section_table["Section"].append(section)
|
||||
if sections[section]["FAIL"] > 0:
|
||||
section_table["Status"].append(
|
||||
f"{Fore.RED}FAIL({sections[section]['FAIL']}){Style.RESET_ALL}"
|
||||
)
|
||||
else:
|
||||
if sections[section]["PASS"] > 0:
|
||||
section_table["Status"].append(
|
||||
f"{Fore.GREEN}PASS({sections[section]['PASS']}){Style.RESET_ALL}"
|
||||
)
|
||||
else:
|
||||
section_table["Status"].append(f"{Fore.GREEN}PASS{Style.RESET_ALL}")
|
||||
section_table["Muted"].append(
|
||||
f"{orange_color}{sections[section]['Muted']}{Style.RESET_ALL}"
|
||||
)
|
||||
|
||||
if (
|
||||
len(fail_count) + len(pass_count) + len(muted_count) > 1
|
||||
): # If there are no resources, don't print the compliance table
|
||||
print(
|
||||
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
|
||||
)
|
||||
total_findings_count = len(fail_count) + len(pass_count) + len(muted_count)
|
||||
overview_table = [
|
||||
[
|
||||
f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
]
|
||||
]
|
||||
print(tabulate(overview_table, tablefmt="rounded_grid"))
|
||||
if not compliance_overview:
|
||||
if len(fail_count) > 0 and len(section_table["Section"]) > 0:
|
||||
print(
|
||||
f"\nFramework {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Results:"
|
||||
)
|
||||
print(
|
||||
tabulate(
|
||||
section_table,
|
||||
tablefmt="rounded_grid",
|
||||
headers="keys",
|
||||
)
|
||||
)
|
||||
print(f"\nDetailed results of {compliance_framework.upper()} are in:")
|
||||
print(
|
||||
f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n"
|
||||
)
|
||||
@@ -3,6 +3,7 @@ import sys
|
||||
from prowler.lib.check.models import Check_Report
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.compliance.c5.c5 import get_c5_table
|
||||
from prowler.lib.outputs.compliance.ccc.ccc import get_ccc_table
|
||||
from prowler.lib.outputs.compliance.cis.cis import get_cis_table
|
||||
from prowler.lib.outputs.compliance.csa.csa import get_csa_table
|
||||
from prowler.lib.outputs.compliance.ens.ens import get_ens_table
|
||||
@@ -104,6 +105,15 @@ def display_compliance_table(
|
||||
output_directory,
|
||||
compliance_overview,
|
||||
)
|
||||
elif compliance_framework.startswith("ccc_"):
|
||||
get_ccc_table(
|
||||
findings,
|
||||
bulk_checks_metadata,
|
||||
compliance_framework,
|
||||
output_filename,
|
||||
output_directory,
|
||||
compliance_overview,
|
||||
)
|
||||
else:
|
||||
get_generic_compliance_table(
|
||||
findings,
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
|
||||
from prowler.lib.outputs.compliance.ccc.ccc_aws import CCC_AWS
|
||||
from prowler.lib.outputs.compliance.ccc.models import CCC_AWSModel
|
||||
from tests.lib.outputs.compliance.fixtures import CCC_AWS_FIXTURE
|
||||
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
|
||||
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1
|
||||
|
||||
|
||||
class TestAWSCCC:
|
||||
def test_output_transform_evaluated_requirement(self):
|
||||
findings = [
|
||||
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
|
||||
]
|
||||
|
||||
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
|
||||
output_data = output.data[0]
|
||||
|
||||
assert isinstance(output_data, CCC_AWSModel)
|
||||
assert output_data.Provider == "aws"
|
||||
assert output_data.AccountId == AWS_ACCOUNT_NUMBER
|
||||
assert output_data.Region == AWS_REGION_EU_WEST_1
|
||||
assert output_data.Description == CCC_AWS_FIXTURE.Description
|
||||
assert output_data.Requirements_Id == CCC_AWS_FIXTURE.Requirements[0].Id
|
||||
assert (
|
||||
output_data.Requirements_Description
|
||||
== CCC_AWS_FIXTURE.Requirements[0].Description
|
||||
)
|
||||
attribute = CCC_AWS_FIXTURE.Requirements[0].Attributes[0]
|
||||
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
|
||||
assert (
|
||||
output_data.Requirements_Attributes_FamilyDescription
|
||||
== attribute.FamilyDescription
|
||||
)
|
||||
assert output_data.Requirements_Attributes_Section == attribute.Section
|
||||
assert output_data.Requirements_Attributes_SubSection == attribute.SubSection
|
||||
assert (
|
||||
output_data.Requirements_Attributes_SubSectionObjective
|
||||
== attribute.SubSectionObjective
|
||||
)
|
||||
assert (
|
||||
output_data.Requirements_Attributes_Applicability == attribute.Applicability
|
||||
)
|
||||
assert (
|
||||
output_data.Requirements_Attributes_Recommendation
|
||||
== attribute.Recommendation
|
||||
)
|
||||
assert (
|
||||
output_data.Requirements_Attributes_SectionThreatMappings
|
||||
== attribute.SectionThreatMappings
|
||||
)
|
||||
assert (
|
||||
output_data.Requirements_Attributes_SectionGuidelineMappings
|
||||
== attribute.SectionGuidelineMappings
|
||||
)
|
||||
assert output_data.Status == "PASS"
|
||||
assert output_data.StatusExtended == ""
|
||||
assert output_data.ResourceId == ""
|
||||
assert output_data.ResourceName == ""
|
||||
assert output_data.CheckId == "service_test_check_id"
|
||||
assert output_data.Muted is False
|
||||
|
||||
def test_output_transform_manual_requirement(self):
|
||||
# Use a finding for the evaluated requirement so the manual one is appended
|
||||
# by the manual-loop branch (Checks=[]).
|
||||
findings = [
|
||||
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
|
||||
]
|
||||
|
||||
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
|
||||
# data[0] is the evaluated PASS row, data[1] is the manual row
|
||||
manual_row = output.data[1]
|
||||
|
||||
assert isinstance(manual_row, CCC_AWSModel)
|
||||
assert manual_row.Provider == "aws"
|
||||
assert manual_row.AccountId == ""
|
||||
assert manual_row.Region == ""
|
||||
assert manual_row.Description == CCC_AWS_FIXTURE.Description
|
||||
assert manual_row.Requirements_Id == CCC_AWS_FIXTURE.Requirements[1].Id
|
||||
manual_attribute = CCC_AWS_FIXTURE.Requirements[1].Attributes[0]
|
||||
assert (
|
||||
manual_row.Requirements_Attributes_FamilyName == manual_attribute.FamilyName
|
||||
)
|
||||
assert manual_row.Requirements_Attributes_Section == manual_attribute.Section
|
||||
assert manual_row.Status == "MANUAL"
|
||||
assert manual_row.StatusExtended == "Manual check"
|
||||
assert manual_row.ResourceId == "manual_check"
|
||||
assert manual_row.ResourceName == "Manual check"
|
||||
assert manual_row.CheckId == "manual"
|
||||
assert manual_row.Muted is False
|
||||
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.ccc.ccc_aws.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
|
||||
]
|
||||
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
|
||||
output._file_descriptor = mock_file
|
||||
|
||||
with patch.object(mock_file, "close", return_value=None):
|
||||
output.batch_write_data_to_file()
|
||||
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
|
||||
# Header check: AWS-specific columns must be present
|
||||
header = content.split("\r\n", 1)[0]
|
||||
assert "ACCOUNTID" in header
|
||||
assert "REGION" in header
|
||||
assert "REQUIREMENTS_ATTRIBUTES_FAMILYNAME" in header
|
||||
assert "REQUIREMENTS_ATTRIBUTES_SECTION" in header
|
||||
assert "REQUIREMENTS_ATTRIBUTES_APPLICABILITY" in header
|
||||
assert "REQUIREMENTS_ATTRIBUTES_SECTIONTHREATMAPPINGS" in header
|
||||
# Header should NOT contain Azure or GCP-only columns
|
||||
assert "SUBSCRIPTIONID" not in header
|
||||
assert "PROJECTID" not in header
|
||||
|
||||
# Body checks: evaluated row + manual row
|
||||
rows = [r for r in content.split("\r\n") if r]
|
||||
assert len(rows) == 3 # header + evaluated + manual
|
||||
assert "CCC.Core.CN01.AR01" in rows[1]
|
||||
assert "PASS" in rows[1]
|
||||
assert AWS_ACCOUNT_NUMBER in rows[1]
|
||||
assert AWS_REGION_EU_WEST_1 in rows[1]
|
||||
assert "CCC.IAM.CN01.AR01" in rows[2]
|
||||
assert "MANUAL" in rows[2]
|
||||
assert "manual_check" in rows[2]
|
||||
# The frozen timestamp should appear
|
||||
assert "2025-01-01 00:00:00" in rows[1]
|
||||
@@ -0,0 +1,99 @@
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
|
||||
from prowler.lib.outputs.compliance.ccc.ccc_azure import CCC_Azure
|
||||
from prowler.lib.outputs.compliance.ccc.models import CCC_AzureModel
|
||||
from tests.lib.outputs.compliance.fixtures import CCC_AZURE_FIXTURE
|
||||
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
|
||||
from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION_ID
|
||||
|
||||
AZURE_LOCATION = "westeurope"
|
||||
|
||||
|
||||
class TestAzureCCC:
|
||||
def test_output_transform_evaluated_requirement(self):
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
provider="azure",
|
||||
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
|
||||
account_uid=AZURE_SUBSCRIPTION_ID,
|
||||
region=AZURE_LOCATION,
|
||||
)
|
||||
]
|
||||
|
||||
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
|
||||
output_data = output.data[0]
|
||||
|
||||
assert isinstance(output_data, CCC_AzureModel)
|
||||
assert output_data.Provider == "azure"
|
||||
assert output_data.SubscriptionId == AZURE_SUBSCRIPTION_ID
|
||||
assert output_data.Location == AZURE_LOCATION
|
||||
assert output_data.Description == CCC_AZURE_FIXTURE.Description
|
||||
assert output_data.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[0].Id
|
||||
attribute = CCC_AZURE_FIXTURE.Requirements[0].Attributes[0]
|
||||
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
|
||||
assert output_data.Requirements_Attributes_Section == attribute.Section
|
||||
assert (
|
||||
output_data.Requirements_Attributes_Applicability == attribute.Applicability
|
||||
)
|
||||
assert output_data.Status == "PASS"
|
||||
assert output_data.CheckId == "service_test_check_id"
|
||||
|
||||
def test_output_transform_manual_requirement(self):
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
provider="azure",
|
||||
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
|
||||
account_uid=AZURE_SUBSCRIPTION_ID,
|
||||
region=AZURE_LOCATION,
|
||||
)
|
||||
]
|
||||
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
|
||||
manual_row = output.data[1]
|
||||
|
||||
assert isinstance(manual_row, CCC_AzureModel)
|
||||
assert manual_row.Provider == "azure"
|
||||
assert manual_row.SubscriptionId == ""
|
||||
assert manual_row.Location == ""
|
||||
assert manual_row.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[1].Id
|
||||
assert manual_row.Status == "MANUAL"
|
||||
assert manual_row.CheckId == "manual"
|
||||
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.ccc.ccc_azure.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
provider="azure",
|
||||
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
|
||||
account_uid=AZURE_SUBSCRIPTION_ID,
|
||||
region=AZURE_LOCATION,
|
||||
)
|
||||
]
|
||||
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
|
||||
output._file_descriptor = mock_file
|
||||
|
||||
with patch.object(mock_file, "close", return_value=None):
|
||||
output.batch_write_data_to_file()
|
||||
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
header = content.split("\r\n", 1)[0]
|
||||
assert "SUBSCRIPTIONID" in header
|
||||
assert "LOCATION" in header
|
||||
assert "ACCOUNTID" not in header
|
||||
assert "PROJECTID" not in header
|
||||
assert "REGION" not in header
|
||||
rows = [r for r in content.split("\r\n") if r]
|
||||
assert len(rows) == 3
|
||||
assert "CCC.Core.CN01.AR01" in rows[1]
|
||||
assert AZURE_SUBSCRIPTION_ID in rows[1]
|
||||
assert "CCC.IAM.CN01.AR01" in rows[2]
|
||||
assert "MANUAL" in rows[2]
|
||||
@@ -0,0 +1,99 @@
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
|
||||
from prowler.lib.outputs.compliance.ccc.ccc_gcp import CCC_GCP
|
||||
from prowler.lib.outputs.compliance.ccc.models import CCC_GCPModel
|
||||
from tests.lib.outputs.compliance.fixtures import CCC_GCP_FIXTURE
|
||||
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
|
||||
|
||||
GCP_PROJECT_ID = "test-project"
|
||||
GCP_LOCATION = "europe-west1"
|
||||
|
||||
|
||||
class TestGCPCCC:
|
||||
def test_output_transform_evaluated_requirement(self):
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
provider="gcp",
|
||||
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
|
||||
account_uid=GCP_PROJECT_ID,
|
||||
region=GCP_LOCATION,
|
||||
)
|
||||
]
|
||||
|
||||
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
|
||||
output_data = output.data[0]
|
||||
|
||||
assert isinstance(output_data, CCC_GCPModel)
|
||||
assert output_data.Provider == "gcp"
|
||||
assert output_data.ProjectId == GCP_PROJECT_ID
|
||||
assert output_data.Location == GCP_LOCATION
|
||||
assert output_data.Description == CCC_GCP_FIXTURE.Description
|
||||
assert output_data.Requirements_Id == CCC_GCP_FIXTURE.Requirements[0].Id
|
||||
attribute = CCC_GCP_FIXTURE.Requirements[0].Attributes[0]
|
||||
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
|
||||
assert output_data.Requirements_Attributes_Section == attribute.Section
|
||||
assert (
|
||||
output_data.Requirements_Attributes_Applicability == attribute.Applicability
|
||||
)
|
||||
assert output_data.Status == "PASS"
|
||||
assert output_data.CheckId == "service_test_check_id"
|
||||
|
||||
def test_output_transform_manual_requirement(self):
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
provider="gcp",
|
||||
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
|
||||
account_uid=GCP_PROJECT_ID,
|
||||
region=GCP_LOCATION,
|
||||
)
|
||||
]
|
||||
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
|
||||
manual_row = output.data[1]
|
||||
|
||||
assert isinstance(manual_row, CCC_GCPModel)
|
||||
assert manual_row.Provider == "gcp"
|
||||
assert manual_row.ProjectId == ""
|
||||
assert manual_row.Location == ""
|
||||
assert manual_row.Requirements_Id == CCC_GCP_FIXTURE.Requirements[1].Id
|
||||
assert manual_row.Status == "MANUAL"
|
||||
assert manual_row.CheckId == "manual"
|
||||
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.ccc.ccc_gcp.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
provider="gcp",
|
||||
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
|
||||
account_uid=GCP_PROJECT_ID,
|
||||
region=GCP_LOCATION,
|
||||
)
|
||||
]
|
||||
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
|
||||
output._file_descriptor = mock_file
|
||||
|
||||
with patch.object(mock_file, "close", return_value=None):
|
||||
output.batch_write_data_to_file()
|
||||
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
header = content.split("\r\n", 1)[0]
|
||||
assert "PROJECTID" in header
|
||||
assert "LOCATION" in header
|
||||
assert "ACCOUNTID" not in header
|
||||
assert "SUBSCRIPTIONID" not in header
|
||||
assert "REGION" not in header
|
||||
rows = [r for r in content.split("\r\n") if r]
|
||||
assert len(rows) == 3
|
||||
assert "CCC.Core.CN01.AR01" in rows[1]
|
||||
assert GCP_PROJECT_ID in rows[1]
|
||||
assert "CCC.IAM.CN01.AR01" in rows[2]
|
||||
assert "MANUAL" in rows[2]
|
||||
@@ -1,5 +1,6 @@
|
||||
from prowler.lib.check.compliance_models import (
|
||||
AWS_Well_Architected_Requirement_Attribute,
|
||||
CCC_Requirement_Attribute,
|
||||
CIS_Requirement_Attribute,
|
||||
Compliance,
|
||||
Compliance_Requirement,
|
||||
@@ -1022,3 +1023,169 @@ PROWLER_THREATSCORE_M365 = Compliance(
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# CCC fixtures cover the three providers Prowler ships catalogs for. Each
|
||||
# fixture has one auto-evaluated requirement (with Checks) and one manual
|
||||
# requirement (Checks=[]) so test suites can exercise both paths.
|
||||
CCC_AWS_FIXTURE = Compliance(
|
||||
Framework="CCC",
|
||||
Name="Common Cloud Controls Catalog (CCC)",
|
||||
Provider="AWS",
|
||||
Version="v2025.10",
|
||||
Description="Common Cloud Controls Catalog (CCC) for AWS",
|
||||
Requirements=[
|
||||
Compliance_Requirement(
|
||||
Checks=["service_test_check_id"],
|
||||
Id="CCC.Core.CN01.AR01",
|
||||
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
|
||||
Attributes=[
|
||||
CCC_Requirement_Attribute(
|
||||
FamilyName="Data",
|
||||
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
|
||||
Section="CCC.Core.CN01 Encrypt Data for Transmission",
|
||||
SubSection="",
|
||||
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
|
||||
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
|
||||
Recommendation="Most cloud services enable TLS 1.3 by default.",
|
||||
SectionThreatMappings=[
|
||||
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
|
||||
],
|
||||
SectionGuidelineMappings=[
|
||||
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Compliance_Requirement(
|
||||
Checks=[],
|
||||
Id="CCC.IAM.CN01.AR01",
|
||||
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials or generating temporary session tokens.",
|
||||
Attributes=[
|
||||
CCC_Requirement_Attribute(
|
||||
FamilyName="Identity and Access Management",
|
||||
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
|
||||
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
|
||||
SubSection="",
|
||||
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
|
||||
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
|
||||
Recommendation="",
|
||||
SectionThreatMappings=[
|
||||
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
|
||||
],
|
||||
SectionGuidelineMappings=[
|
||||
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
CCC_AZURE_FIXTURE = Compliance(
|
||||
Framework="CCC",
|
||||
Name="Common Cloud Controls Catalog (CCC)",
|
||||
Provider="Azure",
|
||||
Version="v2025.10",
|
||||
Description="Common Cloud Controls Catalog (CCC) for Azure",
|
||||
Requirements=[
|
||||
Compliance_Requirement(
|
||||
Checks=["service_test_check_id"],
|
||||
Id="CCC.Core.CN01.AR01",
|
||||
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
|
||||
Attributes=[
|
||||
CCC_Requirement_Attribute(
|
||||
FamilyName="Data",
|
||||
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
|
||||
Section="CCC.Core.CN01 Encrypt Data for Transmission",
|
||||
SubSection="",
|
||||
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
|
||||
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
|
||||
Recommendation="Most cloud services enable TLS 1.3 by default.",
|
||||
SectionThreatMappings=[
|
||||
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
|
||||
],
|
||||
SectionGuidelineMappings=[
|
||||
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Compliance_Requirement(
|
||||
Checks=[],
|
||||
Id="CCC.IAM.CN01.AR01",
|
||||
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.",
|
||||
Attributes=[
|
||||
CCC_Requirement_Attribute(
|
||||
FamilyName="Identity and Access Management",
|
||||
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
|
||||
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
|
||||
SubSection="",
|
||||
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
|
||||
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
|
||||
Recommendation="",
|
||||
SectionThreatMappings=[
|
||||
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
|
||||
],
|
||||
SectionGuidelineMappings=[
|
||||
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
CCC_GCP_FIXTURE = Compliance(
|
||||
Framework="CCC",
|
||||
Name="Common Cloud Controls Catalog (CCC)",
|
||||
Provider="GCP",
|
||||
Version="v2025.10",
|
||||
Description="Common Cloud Controls Catalog (CCC) for GCP",
|
||||
Requirements=[
|
||||
Compliance_Requirement(
|
||||
Checks=["service_test_check_id"],
|
||||
Id="CCC.Core.CN01.AR01",
|
||||
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
|
||||
Attributes=[
|
||||
CCC_Requirement_Attribute(
|
||||
FamilyName="Data",
|
||||
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
|
||||
Section="CCC.Core.CN01 Encrypt Data for Transmission",
|
||||
SubSection="",
|
||||
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
|
||||
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
|
||||
Recommendation="Most cloud services enable TLS 1.3 by default.",
|
||||
SectionThreatMappings=[
|
||||
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
|
||||
],
|
||||
SectionGuidelineMappings=[
|
||||
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Compliance_Requirement(
|
||||
Checks=[],
|
||||
Id="CCC.IAM.CN01.AR01",
|
||||
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.",
|
||||
Attributes=[
|
||||
CCC_Requirement_Attribute(
|
||||
FamilyName="Identity and Access Management",
|
||||
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
|
||||
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
|
||||
SubSection="",
|
||||
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
|
||||
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
|
||||
Recommendation="",
|
||||
SectionThreatMappings=[
|
||||
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
|
||||
],
|
||||
SectionGuidelineMappings=[
|
||||
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user