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

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)

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),

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",
)

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",
)

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",
)

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

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

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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

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

View File

@@ -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>
);
};

View File

@@ -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) => {

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
ui/lib/compliance/ccc.tsx Normal file
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,
};
}),
})),
);
};

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 }),
},
});
/**

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