chore(ccc): update with latest version and improve mapping (#10625)

This commit is contained in:
Pedro Martín
2026-04-09 15:27:18 +02:00
committed by GitHub
parent b0d8534907
commit 56c370d3a4
12 changed files with 18378 additions and 12846 deletions
@@ -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]
+167
View File
@@ -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"]}
],
)
],
),
],
)