feat(compliance): add CCC catalogs for AWS, Azure and GCP (#8000)

Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com>
This commit is contained in:
Pedro Martín
2025-10-15 18:03:20 +02:00
committed by GitHub
parent ec75b5d0a3
commit 4a61578dd8
23 changed files with 25324 additions and 0 deletions
+1
View File
@@ -13,6 +13,7 @@ All notable changes to the **Prowler API** are documented in this file.
- SAML role mapping protection for single-admin tenants to prevent accidental lockout [(#8882)](https://github.com/prowler-cloud/prowler/pull/8882)
- Support for `passed_findings` and `total_findings` fields in compliance requirement overview for accurate Prowler ThreatScore calculation [(#8582)](https://github.com/prowler-cloud/prowler/pull/8582)
- Database read replica support [(#8869)](https://github.com/prowler-cloud/prowler/pull/8869)
- Support Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000)
### Changed
- Now the MANAGE_ACCOUNT permission is required to modify or read user permissions instead of MANAGE_USERS [(#8281)](https://github.com/prowler-cloud/prowler/pull/8281)
+6
View File
@@ -20,6 +20,9 @@ from prowler.lib.outputs.asff.asff import ASFF
from prowler.lib.outputs.compliance.aws_well_architected.aws_well_architected import (
AWSWellArchitected,
)
from prowler.lib.outputs.compliance.ccc.ccc_aws import CCC_AWS
from prowler.lib.outputs.compliance.ccc.ccc_azure import CCC_Azure
from prowler.lib.outputs.compliance.ccc.ccc_gcp import CCC_GCP
from prowler.lib.outputs.compliance.c5.c5_aws import AWSC5
from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS
from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS
@@ -74,6 +77,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("c5_"), AWSC5),
],
"azure": [
@@ -81,6 +85,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 == "prowler_threatscore_azure", ProwlerThreatScoreAzure),
],
"gcp": [
@@ -89,6 +94,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),
],
"kubernetes": [
(lambda name: name.startswith("cis_"), KubernetesCIS),
+36
View File
@@ -0,0 +1,36 @@
import warnings
from dashboard.common_methods import get_section_containers_3_levels
warnings.filterwarnings("ignore")
def get_table(data):
data["REQUIREMENTS_ID"] = (
data["REQUIREMENTS_ID"] + " - " + data["REQUIREMENTS_DESCRIPTION"]
)
data["REQUIREMENTS_ID"] = data["REQUIREMENTS_ID"].apply(
lambda x: x[:150] + "..." if len(str(x)) > 150 else x
)
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"REQUIREMENTS_ATTRIBUTES_SUBSECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_3_levels(
aux,
"REQUIREMENTS_ATTRIBUTES_SECTION",
"REQUIREMENTS_ATTRIBUTES_SUBSECTION",
"REQUIREMENTS_ID",
)
+36
View File
@@ -0,0 +1,36 @@
import warnings
from dashboard.common_methods import get_section_containers_3_levels
warnings.filterwarnings("ignore")
def get_table(data):
data["REQUIREMENTS_ID"] = (
data["REQUIREMENTS_ID"] + " - " + data["REQUIREMENTS_DESCRIPTION"]
)
data["REQUIREMENTS_ID"] = data["REQUIREMENTS_ID"].apply(
lambda x: x[:150] + "..." if len(str(x)) > 150 else x
)
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"REQUIREMENTS_ATTRIBUTES_SUBSECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_3_levels(
aux,
"REQUIREMENTS_ATTRIBUTES_SECTION",
"REQUIREMENTS_ATTRIBUTES_SUBSECTION",
"REQUIREMENTS_ID",
)
+36
View File
@@ -0,0 +1,36 @@
import warnings
from dashboard.common_methods import get_section_containers_3_levels
warnings.filterwarnings("ignore")
def get_table(data):
data["REQUIREMENTS_ID"] = (
data["REQUIREMENTS_ID"] + " - " + data["REQUIREMENTS_DESCRIPTION"]
)
data["REQUIREMENTS_ID"] = data["REQUIREMENTS_ID"].apply(
lambda x: x[:150] + "..." if len(str(x)) > 150 else x
)
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"REQUIREMENTS_ATTRIBUTES_SUBSECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_3_levels(
aux,
"REQUIREMENTS_ATTRIBUTES_SECTION",
"REQUIREMENTS_ATTRIBUTES_SUBSECTION",
"REQUIREMENTS_ID",
)
+1
View File
@@ -15,6 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Equality validation for CheckID, filename and classname [(#8690)](https://github.com/prowler-cloud/prowler/pull/8690)
- Improve logging for Security Hub integration [(#8608)](https://github.com/prowler-cloud/prowler/pull/8608)
- Support for Atlassian Document Format (ADF) in Jira integration [(#8878)](https://github.com/prowler-cloud/prowler/pull/8878)
- Add Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000)
### Changed
+42
View File
@@ -49,6 +49,9 @@ from prowler.lib.outputs.asff.asff import ASFF
from prowler.lib.outputs.compliance.aws_well_architected.aws_well_architected import (
AWSWellArchitected,
)
from prowler.lib.outputs.compliance.ccc.ccc_aws import CCC_AWS
from prowler.lib.outputs.compliance.ccc.ccc_azure import CCC_Azure
from prowler.lib.outputs.compliance.ccc.ccc_gcp import CCC_GCP
from prowler.lib.outputs.compliance.c5.c5_aws import AWSC5
from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS
from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS
@@ -555,6 +558,21 @@ def prowler():
)
generated_outputs["compliance"].append(prowler_threatscore)
prowler_threatscore.batch_write_data_to_file()
elif compliance_name.startswith("ccc_"):
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
ccc_aws = CCC_AWS(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
file_path=filename,
)
generated_outputs["compliance"].append(ccc_aws)
ccc_aws.batch_write_data_to_file()
elif compliance_name == "c5_aws":
filename = (
f"{output_options.output_directory}/compliance/"
@@ -646,6 +664,18 @@ def prowler():
)
generated_outputs["compliance"].append(prowler_threatscore)
prowler_threatscore.batch_write_data_to_file()
elif compliance_name.startswith("ccc_"):
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
ccc_azure = CCC_Azure(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
file_path=filename,
)
generated_outputs["compliance"].append(ccc_azure)
ccc_azure.batch_write_data_to_file()
else:
filename = (
f"{output_options.output_directory}/compliance/"
@@ -725,6 +755,18 @@ def prowler():
)
generated_outputs["compliance"].append(prowler_threatscore)
prowler_threatscore.batch_write_data_to_file()
elif compliance_name.startswith("ccc_"):
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
ccc_gcp = CCC_GCP(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
file_path=filename,
)
generated_outputs["compliance"].append(ccc_gcp)
ccc_gcp.batch_write_data_to_file()
else:
filename = (
f"{output_options.output_directory}/compliance/"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16
View File
@@ -200,6 +200,21 @@ class Prowler_ThreatScore_Requirement_Attribute(BaseModel):
Weight: int
# CCC Requirement Attribute
class CCC_Requirement_Attribute(BaseModel):
"""CCC Requirement Attribute"""
FamilyName: str
FamilyDescription: str
Section: str
SubSection: str
SubSectionObjective: str
Applicability: list[str]
Recommendation: str
SectionThreatMappings: list[dict]
SectionGuidelineMappings: list[dict]
# C5 Germany Requirement Attribute
class C5Germany_Requirement_Attribute(BaseModel):
"""C5 Germany Requirement Attribute"""
@@ -227,6 +242,7 @@ class Compliance_Requirement(BaseModel):
AWS_Well_Architected_Requirement_Attribute,
KISA_ISMSP_Requirement_Attribute,
Prowler_ThreatScore_Requirement_Attribute,
CCC_Requirement_Attribute,
C5Germany_Requirement_Attribute,
# Generic_Compliance_Requirement_Attribute must be the last one since it is the fallback for generic compliance framework
Generic_Compliance_Requirement_Attribute,
@@ -0,0 +1,95 @@
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.ccc.models import CCC_AWSModel
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.finding import Finding
class CCC_AWS(ComplianceOutput):
"""
This class represents the AWS CCC compliance output.
Attributes:
- _data (list): A list to store transformed data from findings.
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
Methods:
- transform: Transforms findings into AWS CCC compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into AWS CCC compliance format.
Parameters:
- findings (list): A list of findings.
- compliance (Compliance): A compliance model.
- compliance_name (str): The name of the compliance model.
Returns:
- None
"""
for finding in findings:
# Get the compliance requirements for the finding
finding_requirements = finding.compliance.get(compliance_name, [])
for requirement in compliance.Requirements:
if requirement.Id in finding_requirements:
for attribute in requirement.Attributes:
compliance_row = CCC_AWSModel(
Provider=finding.provider,
Description=compliance.Description,
AccountId=finding.account_uid,
Region=finding.region,
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_FamilyName=attribute.FamilyName,
Requirements_Attributes_FamilyDescription=attribute.FamilyDescription,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_SubSectionObjective=attribute.SubSectionObjective,
Requirements_Attributes_Applicability=attribute.Applicability,
Requirements_Attributes_Recommendation=attribute.Recommendation,
Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings,
Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_uid,
ResourceName=finding.resource_name,
CheckId=finding.check_id,
Muted=finding.muted,
)
self._data.append(compliance_row)
# Add manual requirements to the compliance output
for requirement in compliance.Requirements:
if not requirement.Checks:
for attribute in requirement.Attributes:
compliance_row = CCC_AWSModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
AccountId="",
Region="",
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_FamilyName=attribute.FamilyName,
Requirements_Attributes_FamilyDescription=attribute.FamilyDescription,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_SubSectionObjective=attribute.SubSectionObjective,
Requirements_Attributes_Applicability=attribute.Applicability,
Requirements_Attributes_Recommendation=attribute.Recommendation,
Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings,
Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
)
self._data.append(compliance_row)
@@ -0,0 +1,95 @@
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.ccc.models import CCC_AzureModel
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.finding import Finding
class CCC_Azure(ComplianceOutput):
"""
This class represents the Azure CCC compliance output.
Attributes:
- _data (list): A list to store transformed data from findings.
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
Methods:
- transform: Transforms findings into Azure CCC compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into Azure CCC compliance format.
Parameters:
- findings (list): A list of findings.
- compliance (Compliance): A compliance model.
- compliance_name (str): The name of the compliance model.
Returns:
- None
"""
for finding in findings:
# Get the compliance requirements for the finding
finding_requirements = finding.compliance.get(compliance_name, [])
for requirement in compliance.Requirements:
if requirement.Id in finding_requirements:
for attribute in requirement.Attributes:
compliance_row = CCC_AzureModel(
Provider=finding.provider,
Description=compliance.Description,
SubscriptionId=finding.account_uid,
Location=finding.region,
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_FamilyName=attribute.FamilyName,
Requirements_Attributes_FamilyDescription=attribute.FamilyDescription,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_SubSectionObjective=attribute.SubSectionObjective,
Requirements_Attributes_Applicability=attribute.Applicability,
Requirements_Attributes_Recommendation=attribute.Recommendation,
Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings,
Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_uid,
ResourceName=finding.resource_name,
CheckId=finding.check_id,
Muted=finding.muted,
)
self._data.append(compliance_row)
# Add manual requirements to the compliance output
for requirement in compliance.Requirements:
if not requirement.Checks:
for attribute in requirement.Attributes:
compliance_row = CCC_AzureModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
SubscriptionId="",
Location="",
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_FamilyName=attribute.FamilyName,
Requirements_Attributes_FamilyDescription=attribute.FamilyDescription,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_SubSectionObjective=attribute.SubSectionObjective,
Requirements_Attributes_Applicability=attribute.Applicability,
Requirements_Attributes_Recommendation=attribute.Recommendation,
Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings,
Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
)
self._data.append(compliance_row)
@@ -0,0 +1,95 @@
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.ccc.models import CCC_GCPModel
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.finding import Finding
class CCC_GCP(ComplianceOutput):
"""
This class represents the GCP CCC compliance output.
Attributes:
- _data (list): A list to store transformed data from findings.
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
Methods:
- transform: Transforms findings into GCP CCC compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into GCP CCC compliance format.
Parameters:
- findings (list): A list of findings.
- compliance (Compliance): A compliance model.
- compliance_name (str): The name of the compliance model.
Returns:
- None
"""
for finding in findings:
# Get the compliance requirements for the finding
finding_requirements = finding.compliance.get(compliance_name, [])
for requirement in compliance.Requirements:
if requirement.Id in finding_requirements:
for attribute in requirement.Attributes:
compliance_row = CCC_GCPModel(
Provider=finding.provider,
Description=compliance.Description,
ProjectId=finding.account_uid,
Location=finding.region,
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_FamilyName=attribute.FamilyName,
Requirements_Attributes_FamilyDescription=attribute.FamilyDescription,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_SubSectionObjective=attribute.SubSectionObjective,
Requirements_Attributes_Applicability=attribute.Applicability,
Requirements_Attributes_Recommendation=attribute.Recommendation,
Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings,
Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_uid,
ResourceName=finding.resource_name,
CheckId=finding.check_id,
Muted=finding.muted,
)
self._data.append(compliance_row)
# Add manual requirements to the compliance output
for requirement in compliance.Requirements:
if not requirement.Checks:
for attribute in requirement.Attributes:
compliance_row = CCC_GCPModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
ProjectId="",
Location="",
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_FamilyName=attribute.FamilyName,
Requirements_Attributes_FamilyDescription=attribute.FamilyDescription,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_SubSectionObjective=attribute.SubSectionObjective,
Requirements_Attributes_Applicability=attribute.Applicability,
Requirements_Attributes_Recommendation=attribute.Recommendation,
Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings,
Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
)
self._data.append(compliance_row)
@@ -0,0 +1,90 @@
from typing import Optional
from pydantic import BaseModel
class CCC_AWSModel(BaseModel):
"""
CCC_AWSModel generates a finding's output in AWS CCC Compliance format.
"""
Provider: str
Description: str
AccountId: str
Region: str
AssessmentDate: str
Requirements_Id: str
Requirements_Description: str
Requirements_Attributes_FamilyName: str
Requirements_Attributes_FamilyDescription: str
Requirements_Attributes_Section: str
Requirements_Attributes_SubSection: Optional[str]
Requirements_Attributes_SubSectionObjective: str
Requirements_Attributes_Applicability: list[str]
Requirements_Attributes_Recommendation: str
Requirements_Attributes_SectionThreatMappings: list[dict]
Requirements_Attributes_SectionGuidelineMappings: list[dict]
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool
class CCC_AzureModel(BaseModel):
"""
CCC_AzureModel generates a finding's output in Azure CCC Compliance format.
"""
Provider: str
Description: str
SubscriptionId: str
Location: str
AssessmentDate: str
Requirements_Id: str
Requirements_Description: str
Requirements_Attributes_FamilyName: str
Requirements_Attributes_FamilyDescription: str
Requirements_Attributes_Section: str
Requirements_Attributes_SubSection: Optional[str]
Requirements_Attributes_SubSectionObjective: str
Requirements_Attributes_Applicability: list[str]
Requirements_Attributes_Recommendation: str
Requirements_Attributes_SectionThreatMappings: list[dict]
Requirements_Attributes_SectionGuidelineMappings: list[dict]
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool
class CCC_GCPModel(BaseModel):
"""
CCC_GCPModel generates a finding's output in GCP CCC Compliance format.
"""
Provider: str
Description: str
ProjectId: str
Location: str
AssessmentDate: str
Requirements_Id: str
Requirements_Description: str
Requirements_Attributes_FamilyName: str
Requirements_Attributes_FamilyDescription: str
Requirements_Attributes_Section: str
Requirements_Attributes_SubSection: Optional[str]
Requirements_Attributes_SubSectionObjective: str
Requirements_Attributes_Applicability: list[str]
Requirements_Attributes_Recommendation: str
Requirements_Attributes_SectionThreatMappings: list[dict]
Requirements_Attributes_SectionGuidelineMappings: list[dict]
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool
+1
View File
@@ -15,6 +15,7 @@ All notable changes to the **Prowler UI** are documented in this file.
- Support C5 compliance framework for the AWS provider [(#8830)](https://github.com/prowler-cloud/prowler/pull/8830)
- API key management in user profile [(#8308)](https://github.com/prowler-cloud/prowler/pull/8308)
- Refresh access token error handling [(#8864)](https://github.com/prowler-cloud/prowler/pull/8864)
- Support Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000)
### 🔄 Changed
@@ -0,0 +1,89 @@
import { cn } from "@/lib";
import { CCC_MAPPING_SECTIONS, CCC_TEXT_SECTIONS } from "@/lib/compliance/ccc";
import { Requirement } from "@/types/compliance";
import {
ComplianceBadge,
ComplianceBadgeContainer,
ComplianceChipContainer,
ComplianceDetailContainer,
ComplianceDetailSection,
ComplianceDetailText,
} from "./shared-components";
interface CCCDetailsProps {
requirement: Requirement;
}
export const CCCCustomDetails = ({ requirement }: CCCDetailsProps) => {
// Map text sections with requirement data
const textSections = CCC_TEXT_SECTIONS.map((section) => ({
...section,
content: requirement[section.key] as string | undefined,
})).filter((section) => section.content);
// Map mapping sections with requirement data
const mappingSections = CCC_MAPPING_SECTIONS.map((section) => ({
...section,
data: requirement[section.key] as Array<{
ReferenceId: string;
Identifiers: string[];
}>,
})).filter((section) => section.data);
return (
<ComplianceDetailContainer>
{textSections.map((section) => (
<ComplianceDetailSection key={section.title} title={section.title}>
<ComplianceDetailText className={section.className}>
{section.content}
</ComplianceDetailText>
</ComplianceDetailSection>
))}
{requirement.family_name && (
<ComplianceBadgeContainer>
<ComplianceBadge
label="Family"
value={requirement.family_name as string}
color="purple"
/>
</ComplianceBadgeContainer>
)}
{requirement.applicability && (
<ComplianceChipContainer
title="Applicability"
items={requirement.applicability as string[]}
/>
)}
{mappingSections.map((section) => (
<ComplianceDetailSection key={section.title} title={section.title}>
<div className="flex flex-col gap-3">
{section.data.map((mapping, index) => (
<div key={index} className="flex flex-col gap-1">
<span className="text-muted-foreground text-xs font-medium">
{mapping.ReferenceId}
</span>
<div className="flex flex-wrap gap-2">
{mapping.Identifiers.map((identifier, idx) => (
<span
key={idx}
className={cn(
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset",
section.colorClasses,
)}
>
{identifier}
</span>
))}
</div>
</div>
))}
</div>
</ComplianceDetailSection>
))}
</ComplianceDetailContainer>
);
};
@@ -1,5 +1,6 @@
import AWSLogo from "./aws.svg";
import C5Logo from "./c5.svg";
import CCCLogo from "./ccc.svg";
import CISLogo from "./cis.svg";
import CISALogo from "./cisa.svg";
import ENSLogo from "./ens.png";
@@ -38,6 +39,7 @@ const COMPLIANCE_LOGOS = {
prowlerthreatscore: PROWLERTHREATLogo,
nis2: NIS2Logo,
c5: C5Logo,
ccc: CCCLogo,
} as const;
export const getComplianceIcon = (complianceTitle: string) => {
+32
View File
@@ -0,0 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 170" fill="none">
<defs>
<linearGradient id="cccGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#0586BF"/>
<stop offset="100%" style="stop-color:#00BCE7"/>
</linearGradient>
</defs>
<!-- Left C -->
<g>
<circle cx="81" cy="85" r="75" fill="url(#cccGradient)"/>
<circle cx="81" cy="85" r="45" fill="white"/>
</g>
<!-- Connecting pill 1 -->
<ellipse cx="140" cy="85" rx="35" ry="16" fill="url(#cccGradient)"/>
<!-- Middle C -->
<g>
<circle cx="200" cy="85" r="75" fill="url(#cccGradient)"/>
<circle cx="200" cy="85" r="45" fill="white"/>
</g>
<!-- Connecting pill 2 -->
<ellipse cx="260" cy="85" rx="35" ry="16" fill="url(#cccGradient)"/>
<!-- Right C -->
<g>
<circle cx="319" cy="85" r="75" fill="url(#cccGradient)"/>
<circle cx="319" cy="85" r="45" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 944 B

+243
View File
@@ -0,0 +1,243 @@
import { ClientAccordionContent } from "@/components/compliance/compliance-accordion/client-accordion-content";
import { ComplianceAccordionRequirementTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title";
import { ComplianceAccordionTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-title";
import { AccordionItemProps } from "@/components/ui/accordion/Accordion";
import { FindingStatus } from "@/components/ui/table/status-finding-badge";
import {
AttributesData,
CCCAttributesMetadata,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
import {
calculateFrameworkCounters,
createRequirementsMap,
findOrCreateCategory,
findOrCreateControl,
findOrCreateFramework,
updateCounters,
} from "./commons";
interface ProcessedItem {
id: string;
attrs: CCCAttributesMetadata;
attributeItem: any;
requirementData: any;
}
// CCC-specific section configuration
export interface CCCTextSection {
title: string;
key: keyof Requirement;
className?: string;
}
export interface CCCMappingSection {
title: string;
key: keyof Requirement;
colorClasses: string;
}
export const CCC_TEXT_SECTIONS: CCCTextSection[] = [
{
title: "Description",
key: "description",
},
{
title: "Family Description",
key: "family_description",
},
{
title: "SubSection",
key: "subsection",
},
{
title: "SubSection Objective",
key: "subsection_objective",
className: "whitespace-pre-wrap",
},
{
title: "Recommendation",
key: "recommendation",
className: "whitespace-pre-wrap",
},
];
export const CCC_MAPPING_SECTIONS: CCCMappingSection[] = [
{
title: "Threat Mappings",
key: "section_threat_mappings",
colorClasses:
"bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20",
},
{
title: "Guideline Mappings",
key: "section_guideline_mappings",
colorClasses:
"bg-blue-50 text-blue-700 ring-blue-600/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/20",
},
];
const createRequirement = (itemData: ProcessedItem): Requirement => {
const { id, attrs, attributeItem, requirementData } = itemData;
const description = attributeItem.attributes.description;
const status = requirementData.attributes.status || "";
const checks = attributeItem.attributes.attributes.check_ids || [];
const finalStatus: RequirementStatus = status as RequirementStatus;
return {
name: id,
description: description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
family_name: attrs.FamilyName,
family_description: attrs.FamilyDescription,
subsection: attrs.SubSection,
subsection_objective: attrs.SubSectionObjective,
applicability: attrs.Applicability,
recommendation: attrs.Recommendation,
section_threat_mappings: attrs.SectionThreatMappings,
section_guideline_mappings: attrs.SectionGuidelineMappings,
};
};
export const mapComplianceData = (
attributesData: AttributesData,
requirementsData: RequirementsData,
): Framework[] => {
const attributes = attributesData?.data || [];
const requirementsMap = createRequirementsMap(requirementsData);
const frameworks: Framework[] = [];
const itemsByFramework = new Map<string, ProcessedItem[]>();
// First pass: collect all data
for (const attributeItem of attributes) {
const id = attributeItem.id;
const metadataArray = attributeItem.attributes?.attributes
?.metadata as unknown as CCCAttributesMetadata[];
const attrs = metadataArray?.[0];
if (!attrs) continue;
const requirementData = requirementsMap.get(id);
if (!requirementData) continue;
const frameworkName = attributeItem.attributes.framework;
if (!itemsByFramework.has(frameworkName)) {
itemsByFramework.set(frameworkName, []);
}
itemsByFramework.get(frameworkName)!.push({
id,
attrs,
attributeItem,
requirementData,
});
}
// Process each framework
for (const [frameworkName, items] of Array.from(itemsByFramework.entries())) {
const framework = findOrCreateFramework(frameworks, frameworkName);
// Group by FamilyName (Category) -> Section (Control) -> Requirements
for (const itemData of items) {
const requirement = createRequirement(itemData);
const familyName = itemData.attrs.FamilyName;
const sectionName = itemData.attrs.Section;
// Create 3-level hierarchy: FamilyName -> Section -> Requirements
const category = findOrCreateCategory(framework.categories, familyName);
const control = findOrCreateControl(category.controls, sectionName);
control.requirements.push(requirement);
updateCounters(control, requirement.status);
}
}
// Calculate counters using common helper
calculateFrameworkCounters(frameworks);
return frameworks;
};
// Helper function to create accordion item for requirement
const createRequirementAccordionItem = (
requirement: Requirement,
itemKey: string,
scanId: string,
frameworkName: string,
): AccordionItemProps => ({
key: itemKey,
title: (
<ComplianceAccordionRequirementTitle
type=""
name={requirement.name}
status={requirement.status as FindingStatus}
/>
),
content: (
<ClientAccordionContent
requirement={requirement}
scanId={scanId}
framework={frameworkName}
disableFindings={
requirement.check_ids.length === 0 && requirement.manual === 0
}
/>
),
items: [],
});
export const toAccordionItems = (
data: Framework[],
scanId: string | undefined,
): AccordionItemProps[] => {
return data.flatMap((framework) =>
framework.categories.map((category) => ({
key: `${framework.name}-${category.name}`,
title: (
<ComplianceAccordionTitle
label={category.name}
pass={category.pass}
fail={category.fail}
manual={category.manual}
isParentLevel={true}
/>
),
content: "",
items: category.controls.map((control, i: number) => {
const baseKey = `${framework.name}-${category.name}-control-${i}`;
return {
key: baseKey,
title: (
<ComplianceAccordionTitle
label={control.label}
pass={control.pass}
fail={control.fail}
manual={control.manual}
/>
),
content: "",
items: control.requirements.map((requirement, j: number) =>
createRequirementAccordionItem(
requirement,
`${baseKey}-req-${j}`,
scanId || "",
framework.name,
),
),
isDisabled:
control.pass === 0 && control.fail === 0 && control.manual === 0,
};
}),
})),
);
};
+14
View File
@@ -2,6 +2,7 @@ import React from "react";
import { AWSWellArchitectedCustomDetails } from "@/components/compliance/compliance-custom-details/aws-well-architected-details";
import { C5CustomDetails } from "@/components/compliance/compliance-custom-details/c5-details";
import { CCCCustomDetails } from "@/components/compliance/compliance-custom-details/ccc-details";
import { CISCustomDetails } from "@/components/compliance/compliance-custom-details/cis-details";
import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-details";
import { GenericCustomDetails } from "@/components/compliance/compliance-custom-details/generic-details";
@@ -27,6 +28,10 @@ import {
mapComplianceData as mapC5ComplianceData,
toAccordionItems as toC5AccordionItems,
} from "./c5";
import {
mapComplianceData as mapCCCComplianceData,
toAccordionItems as toCCCAccordionItems,
} from "./ccc";
import {
mapComplianceData as mapCISComplianceData,
toAccordionItems as toCISAccordionItems,
@@ -165,6 +170,15 @@ const getComplianceMappers = (): Record<string, ComplianceMapper> => ({
getDetailsComponent: (requirement: Requirement) =>
React.createElement(ThreatCustomDetails, { requirement }),
},
CCC: {
mapComplianceData: mapCCCComplianceData,
toAccordionItems: toCCCAccordionItems,
getTopFailedSections,
calculateCategoryHeatmapData: (data: Framework[]) =>
calculateCategoryHeatmapData(data),
getDetailsComponent: (requirement: Requirement) =>
React.createElement(CCCCustomDetails, { requirement }),
},
});
/**
+19
View File
@@ -166,6 +166,24 @@ export interface GenericAttributesMetadata {
Type: string | null;
}
export interface CCCAttributesMetadata {
FamilyName: string;
FamilyDescription: string;
Section: string;
SubSection: string;
SubSectionObjective: string;
Applicability: string[];
Recommendation: string;
SectionThreatMappings: Array<{
ReferenceId: string;
Identifiers: string[];
}>;
SectionGuidelineMappings: Array<{
ReferenceId: string;
Identifiers: string[];
}>;
}
export interface AttributesItemData {
type: "compliance-requirements-attributes";
id: string;
@@ -185,6 +203,7 @@ export interface AttributesItemData {
| KISAAttributesMetadata[]
| C5AttributesMetadata[]
| MITREAttributesMetadata[]
| CCCAttributesMetadata[]
| GenericAttributesMetadata[];
check_ids: string[];
// MITRE structure