feat(compliance): add C5 Germany for aws (#8830)

Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com>
This commit is contained in:
Pedro Martín
2025-10-15 11:47:23 +02:00
committed by GitHub
parent b2d91c97d8
commit 564ad56d2f
30 changed files with 11383 additions and 92 deletions

View File

@@ -7,6 +7,7 @@ All notable changes to the **Prowler API** are documented in this file.
### Added
- Default JWT keys are generated and stored if they are missing from configuration [(#8655)](https://github.com/prowler-cloud/prowler/pull/8655)
- `compliance_name` for each compliance [(#7920)](https://github.com/prowler-cloud/prowler/pull/7920)
- Support C5 compliance framework for the AWS provider [(#8830)](https://github.com/prowler-cloud/prowler/pull/8830)
- Support for M365 Certificate authentication [(#8538)](https://github.com/prowler-cloud/prowler/pull/8538)
- API Key support [(#8805)](https://github.com/prowler-cloud/prowler/pull/8805)
- SAML role mapping protection for single-admin tenants to prevent accidental lockout [(#8882)](https://github.com/prowler-cloud/prowler/pull/8882)

View File

@@ -20,6 +20,7 @@ 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.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
from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
@@ -73,6 +74,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.startswith("c5_"), AWSC5),
],
"azure": [
(lambda name: name.startswith("cis_"), AzureCIS),

View File

@@ -0,0 +1,43 @@
import warnings
from dashboard.common_methods import get_section_containers_3_levels
warnings.filterwarnings("ignore")
def get_table(data):
data["REQUIREMENTS_DESCRIPTION"] = (
data["REQUIREMENTS_ID"] + " - " + data["REQUIREMENTS_DESCRIPTION"]
)
data["REQUIREMENTS_DESCRIPTION"] = data["REQUIREMENTS_DESCRIPTION"].apply(
lambda x: x[:150] + "..." if len(str(x)) > 150 else x
)
data["REQUIREMENTS_ATTRIBUTES_SECTION"] = data[
"REQUIREMENTS_ATTRIBUTES_SECTION"
].apply(lambda x: x[:80] + "..." if len(str(x)) > 80 else x)
data["REQUIREMENTS_ATTRIBUTES_SUBSECTION"] = data[
"REQUIREMENTS_ATTRIBUTES_SUBSECTION"
].apply(lambda x: x[:150] + "..." if len(str(x)) > 150 else x)
aux = data[
[
"REQUIREMENTS_DESCRIPTION",
"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_DESCRIPTION",
)

View File

@@ -11,6 +11,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- LLM provider using `promptfoo` [(#8555)](https://github.com/prowler-cloud/prowler/pull/8555)
- Documentation for renaming checks [(#8717)](https://github.com/prowler-cloud/prowler/pull/8717)
- Add explicit "name" field for each compliance framework and include "FRAMEWORK" and "NAME" in CSV output [(#7920)](https://github.com/prowler-cloud/prowler/pull/7920)
- Add C5 compliance framework for the AWS provider [(#8830)](https://github.com/prowler-cloud/prowler/pull/8830)
- 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)

View File

@@ -49,6 +49,7 @@ 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.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
from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
@@ -554,6 +555,18 @@ def prowler():
)
generated_outputs["compliance"].append(prowler_threatscore)
prowler_threatscore.batch_write_data_to_file()
elif compliance_name == "c5_aws":
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
c5 = AWSC5(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
file_path=filename,
)
generated_outputs["compliance"].append(c5)
c5.batch_write_data_to_file()
else:
filename = (
f"{output_options.output_directory}/compliance/"

File diff suppressed because it is too large Load Diff

View File

@@ -200,6 +200,17 @@ class Prowler_ThreatScore_Requirement_Attribute(BaseModel):
Weight: int
# C5 Germany Requirement Attribute
class C5Germany_Requirement_Attribute(BaseModel):
"""C5 Germany Requirement Attribute"""
Section: str
SubSection: str
Type: str
AboutCriteria: str
ComplementaryCriteria: str
# Base Compliance Model
# TODO: move this to compliance folder
class Compliance_Requirement(BaseModel):
@@ -216,6 +227,7 @@ class Compliance_Requirement(BaseModel):
AWS_Well_Architected_Requirement_Attribute,
KISA_ISMSP_Requirement_Attribute,
Prowler_ThreatScore_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,98 @@
from colorama import Fore, Style
from tabulate import tabulate
from prowler.config.config import orange_color
def get_c5_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 == "C5":
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"
)

View File

@@ -0,0 +1,92 @@
from prowler.config.config import timestamp
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.c5.models import AWSC5Model
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.finding import Finding
class AWSC5(ComplianceOutput):
"""
This class represents the AWS C5 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 C5 compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into AWS C5 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 = AWSC5Model(
Provider=finding.provider,
Description=compliance.Description,
AccountId=finding.account_uid,
Region=finding.region,
AssessmentDate=str(timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_Type=attribute.Type,
Requirements_Attributes_AboutCriteria=attribute.AboutCriteria,
Requirements_Attributes_ComplementaryCriteria=attribute.ComplementaryCriteria,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_uid,
ResourceName=finding.resource_name,
CheckId=finding.check_id,
Muted=finding.muted,
Framework=compliance.Framework,
Name=compliance.Name,
)
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 = AWSC5Model(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
AccountId="",
Region="",
AssessmentDate=str(timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_Type=attribute.Type,
Requirements_Attributes_AboutCriteria=attribute.AboutCriteria,
Requirements_Attributes_ComplementaryCriteria=attribute.ComplementaryCriteria,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
Framework=compliance.Framework,
Name=compliance.Name,
)
self._data.append(compliance_row)

View File

@@ -0,0 +1,30 @@
from typing import Optional
from pydantic.v1 import BaseModel
class AWSC5Model(BaseModel):
"""
AWSC5Model generates a finding's output in AWS C5 Compliance format.
"""
Provider: str
Description: str
AccountId: str
Region: str
AssessmentDate: str
Requirements_Id: str
Requirements_Description: str
Requirements_Attributes_Section: str
Requirements_Attributes_SubSection: str = None
Requirements_Attributes_Type: str = None
Requirements_Attributes_AboutCriteria: Optional[str] = None
Requirements_Attributes_ComplementaryCriteria: Optional[str] = None
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool
Framework: str
Name: str

View File

@@ -2,6 +2,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.cis.cis import get_cis_table
from prowler.lib.outputs.compliance.ens.ens import get_ens_table
from prowler.lib.outputs.compliance.generic.generic_table import (
@@ -84,6 +85,15 @@ def display_compliance_table(
output_directory,
compliance_overview,
)
elif "c5_" in compliance_framework:
get_c5_table(
findings,
bulk_checks_metadata,
compliance_framework,
output_filename,
output_directory,
compliance_overview,
)
else:
get_generic_compliance_table(
findings,

View File

@@ -12,6 +12,7 @@ All notable changes to the **Prowler UI** are documented in this file.
- React Compiler support for automatic optimization [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
- Turbopack support for faster development builds [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
- Add compliance name in compliance detail view [(#8775)](https://github.com/prowler-cloud/prowler/pull/8775)
- 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)

View File

@@ -7,12 +7,18 @@ interface ComplianceAccordionRequirementTitleProps {
}
export const ComplianceAccordionRequirementTitle = ({
type,
name,
status,
}: ComplianceAccordionRequirementTitleProps) => {
return (
<div className="flex w-full items-center justify-between gap-2">
<div className="flex w-5/6 items-center gap-1">
<div className="flex w-5/6 items-center gap-2">
{type && (
<span className="bg-primary/10 text-primary rounded-md px-2 py-0.5 text-xs font-medium">
{type}
</span>
)}
<span>{name}</span>
</div>
<StatusFindingBadge status={status} />

View File

@@ -118,11 +118,13 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
onClick={navigateToDetail}
>
<div className="flex w-full items-center gap-4">
<Image
src={getComplianceIcon(title)}
alt={`${title} logo`}
className="h-10 w-10 min-w-10 rounded-md border border-gray-300 bg-white object-contain p-1"
/>
{getComplianceIcon(title) && (
<Image
src={getComplianceIcon(title)}
alt={`${title} logo`}
className="h-10 w-10 min-w-10 rounded-md border border-gray-300 bg-white object-contain p-1"
/>
)}
<div className="flex w-full flex-col">
<h4 className="text-small mb-1 leading-5 font-bold">
{formatTitle(title)}

View File

@@ -0,0 +1,38 @@
import { Requirement } from "@/types/compliance";
import {
ComplianceDetailContainer,
ComplianceDetailSection,
ComplianceDetailText,
} from "./shared-components";
export const C5CustomDetails = ({
requirement,
}: {
requirement: Requirement;
}) => {
const sections = [
{
title: "Description",
content: requirement.description,
},
{
title: "About Criteria",
content: requirement.about_criteria as string | undefined,
},
{
title: "Complementary Criteria",
content: requirement.complementary_criteria as string | undefined,
},
].filter((section) => section.content);
return (
<ComplianceDetailContainer>
{sections.map((section) => (
<ComplianceDetailSection key={section.title} title={section.title}>
<ComplianceDetailText>{section.content}</ComplianceDetailText>
</ComplianceDetailSection>
))}
</ComplianceDetailContainer>
);
};

View File

@@ -1,4 +1,5 @@
import AWSLogo from "./aws.svg";
import C5Logo from "./c5.svg";
import CISLogo from "./cis.svg";
import CISALogo from "./cisa.svg";
import ENSLogo from "./ens.png";
@@ -17,59 +18,31 @@ import PROWLERTHREATLogo from "./prowlerThreat.svg";
import RBILogo from "./rbi.svg";
import SOC2Logo from "./soc2.svg";
const COMPLIANCE_LOGOS = {
aws: AWSLogo,
cisa: CISALogo,
cis: CISLogo,
ens: ENSLogo,
ffiec: FFIECLogo,
fedramp: FedRAMPLogo,
gdpr: GDPRLogo,
gxp: GxPLogo,
hipaa: HIPAALogo,
iso: ISOLogo,
mitre: MITRELogo,
nist: NISTLogo,
pci: PCILogo,
rbi: RBILogo,
soc2: SOC2Logo,
kisa: KISALogo,
prowlerthreatscore: PROWLERTHREATLogo,
nis2: NIS2Logo,
c5: C5Logo,
} as const;
export const getComplianceIcon = (complianceTitle: string) => {
if (complianceTitle.toLowerCase().includes("aws")) {
return AWSLogo;
}
if (complianceTitle.toLowerCase().includes("cisa")) {
return CISALogo;
}
if (complianceTitle.toLowerCase().includes("cis")) {
return CISLogo;
}
if (complianceTitle.toLowerCase().includes("ens")) {
return ENSLogo;
}
if (complianceTitle.toLowerCase().includes("ffiec")) {
return FFIECLogo;
}
if (complianceTitle.toLowerCase().includes("fedramp")) {
return FedRAMPLogo;
}
if (complianceTitle.toLowerCase().includes("gdpr")) {
return GDPRLogo;
}
if (complianceTitle.toLowerCase().includes("gxp")) {
return GxPLogo;
}
if (complianceTitle.toLowerCase().includes("hipaa")) {
return HIPAALogo;
}
if (complianceTitle.toLowerCase().includes("iso")) {
return ISOLogo;
}
if (complianceTitle.toLowerCase().includes("mitre")) {
return MITRELogo;
}
if (complianceTitle.toLowerCase().includes("nist")) {
return NISTLogo;
}
if (complianceTitle.toLowerCase().includes("pci")) {
return PCILogo;
}
if (complianceTitle.toLowerCase().includes("rbi")) {
return RBILogo;
}
if (complianceTitle.toLowerCase().includes("soc2")) {
return SOC2Logo;
}
if (complianceTitle.toLowerCase().includes("kisa")) {
return KISALogo;
}
if (complianceTitle.toLowerCase().includes("prowlerthreatscore")) {
return PROWLERTHREATLogo;
}
if (complianceTitle.toLowerCase().includes("nis2")) {
return NIS2Logo;
}
const lowerTitle = complianceTitle.toLowerCase();
return Object.entries(COMPLIANCE_LOGOS).find(([keyword]) =>
lowerTitle.includes(keyword),
)?.[1];
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -8,6 +8,7 @@ import {
AWSWellArchitectedAttributesMetadata,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
@@ -68,9 +69,9 @@ export const mapComplianceData = (
description: description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
well_architected_name: attrs.Name,
well_architected_question_id: attrs.WellArchitectedQuestionId,
well_architected_practice_id: attrs.WellArchitectedPracticeId,

179
ui/lib/compliance/c5.tsx Normal file
View File

@@ -0,0 +1,179 @@
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,
C5AttributesMetadata,
Control,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
import {
calculateFrameworkCounters,
createRequirementsMap,
findOrCreateCategory,
findOrCreateControl,
findOrCreateFramework,
} from "./commons";
const getStatusCounters = (status: RequirementStatus) => ({
pass: status === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: status === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: status === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
});
export const mapComplianceData = (
attributesData: AttributesData,
requirementsData: RequirementsData,
): Framework[] => {
const attributes = attributesData?.data || [];
const requirementsMap = createRequirementsMap(requirementsData);
const frameworks: Framework[] = [];
// Process attributes and merge with requirements data
for (const attributeItem of attributes) {
const id = attributeItem.id;
const metadataArray = attributeItem.attributes?.attributes
?.metadata as unknown as C5AttributesMetadata[];
const attrs = metadataArray?.[0];
if (!attrs) continue;
// Get corresponding requirement data
const requirementData = requirementsMap.get(id);
if (!requirementData) continue;
const frameworkName = attributeItem.attributes.framework;
const categoryName = attrs.Section; // Level 1: Section (e.g., "Organisation of Information Security (OIS)")
const controlLabel = attrs.SubSection; // Level 2: SubSection (e.g., "OIS-01 Information Security Management System (ISMS)")
const description = attributeItem.attributes.description;
const status = requirementData.attributes.status || "";
const checks = attributeItem.attributes.attributes.check_ids || [];
const requirementName = id;
// Find or create framework using common helper
const framework = findOrCreateFramework(frameworks, frameworkName);
// Find or create category (Section) using common helper
const category = findOrCreateCategory(framework.categories, categoryName);
// Find or create control (SubSection) using common helper
const control = findOrCreateControl(category.controls, controlLabel);
// Create requirement
const finalStatus: RequirementStatus = status as RequirementStatus;
const requirement: Requirement = {
name: requirementName,
description,
status: finalStatus,
check_ids: checks,
...getStatusCounters(finalStatus),
type: attrs.Type,
about_criteria: attrs.AboutCriteria,
complementary_criteria: attrs.ComplementaryCriteria,
};
control.requirements.push(requirement);
}
// Calculate counters using common helper
calculateFrameworkCounters(frameworks);
return frameworks;
};
const createRequirementItem = (
requirement: Requirement,
frameworkName: string,
categoryName: string,
controlIndex: number,
reqIndex: number,
scanId: string,
): AccordionItemProps => ({
key: `${frameworkName}-${categoryName}-control-${controlIndex}-req-${reqIndex}`,
title: (
<ComplianceAccordionRequirementTitle
type={requirement.type as string}
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: [],
});
const createControlItem = (
control: Control,
frameworkName: string,
categoryName: string,
controlIndex: number,
scanId: string,
): AccordionItemProps => ({
key: `${frameworkName}-${categoryName}-control-${controlIndex}`,
title: (
<ComplianceAccordionTitle
label={control.label}
pass={control.pass}
fail={control.fail}
manual={control.manual}
/>
),
content: "",
items: control.requirements.map((requirement, reqIndex) =>
createRequirementItem(
requirement,
frameworkName,
categoryName,
controlIndex,
reqIndex,
scanId,
),
),
isDisabled: control.pass === 0 && control.fail === 0 && control.manual === 0,
});
export const toAccordionItems = (
data: Framework[],
scanId: string | undefined,
): AccordionItemProps[] => {
const safeId = scanId || "";
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, controlIndex) =>
createControlItem(
control,
framework.name,
category.name,
controlIndex,
safeId,
),
),
})),
);
};

View File

@@ -8,6 +8,7 @@ import {
CISAttributesMetadata,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
@@ -79,9 +80,9 @@ export const mapComplianceData = (
description: attrs.Description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
profile: attrs.Profile,
subsection: attrs.SubSection || "",
assessment_status: attrs.AssessmentStatus,

View File

@@ -3,6 +3,7 @@ import {
FailedSection,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementItemData,
RequirementsData,
RequirementStatus,
@@ -12,11 +13,11 @@ export const updateCounters = (
target: { pass: number; fail: number; manual: number },
status: RequirementStatus,
) => {
if (status === "MANUAL") {
if (status === REQUIREMENT_STATUS.MANUAL) {
target.manual++;
} else if (status === "PASS") {
} else if (status === REQUIREMENT_STATUS.PASS) {
target.pass++;
} else if (status === "FAIL") {
} else if (status === REQUIREMENT_STATUS.FAIL) {
target.fail++;
}
};
@@ -30,7 +31,7 @@ export const getTopFailedSections = (
framework.categories.forEach((category) => {
category.controls.forEach((control) => {
control.requirements.forEach((requirement) => {
if (requirement.status === "FAIL") {
if (requirement.status === REQUIREMENT_STATUS.FAIL) {
const sectionName = category.name;
if (!failedSectionMap.has(sectionName)) {

View File

@@ -1,6 +1,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 { 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";
@@ -22,6 +23,10 @@ import {
mapComplianceData as mapAWSWellArchitectedComplianceData,
toAccordionItems as toAWSWellArchitectedAccordionItems,
} from "./aws-well-architected";
import {
mapComplianceData as mapC5ComplianceData,
toAccordionItems as toC5AccordionItems,
} from "./c5";
import {
mapComplianceData as mapCISComplianceData,
toAccordionItems as toCISAccordionItems,
@@ -80,6 +85,15 @@ const getDefaultMapper = (): ComplianceMapper => ({
});
const getComplianceMappers = (): Record<string, ComplianceMapper> => ({
C5: {
mapComplianceData: mapC5ComplianceData,
toAccordionItems: toC5AccordionItems,
getTopFailedSections,
calculateCategoryHeatmapData: (data: Framework[]) =>
calculateCategoryHeatmapData(data),
getDetailsComponent: (requirement: Requirement) =>
React.createElement(C5CustomDetails, { requirement }),
},
ENS: {
mapComplianceData: mapENSComplianceData,
toAccordionItems: toENSAccordionItems,

View File

@@ -8,6 +8,7 @@ import {
ENSAttributesMetadata,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
@@ -82,7 +83,7 @@ export const mapComplianceData = (
// Create requirement
const finalStatus: RequirementStatus = isManual
? "MANUAL"
? REQUIREMENT_STATUS.MANUAL
: (status as RequirementStatus);
const requirement: Requirement = {
name: requirementName,
@@ -90,9 +91,9 @@ export const mapComplianceData = (
status: finalStatus,
type,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
nivel: attrs.Nivel || "",
dimensiones: attrs.Dimensiones || [],
};

View File

@@ -8,6 +8,7 @@ import {
Framework,
GenericAttributesMetadata,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
@@ -41,9 +42,9 @@ const createRequirement = (itemData: ProcessedItem): Requirement => {
description: description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
item_id: attrs.ItemId,
subsection: attrs.SubSection,
subgroup: attrs.SubGroup || undefined,

View File

@@ -8,6 +8,7 @@ import {
Framework,
ISO27001AttributesMetadata,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
@@ -66,9 +67,9 @@ export const mapComplianceData = (
description: description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
objetive_name: objetiveName,
check_summary: checkSummary,
control_label: controlLabel,

View File

@@ -8,6 +8,7 @@ import {
Framework,
KISAAttributesMetadata,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
@@ -65,9 +66,9 @@ export const mapComplianceData = (
description: description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
section: sectionName,
audit_checklist: attrs.AuditChecklist,
related_regulations: attrs.RelatedRegulations,

View File

@@ -9,6 +9,7 @@ import {
Framework,
MITREAttributesMetadata,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
@@ -62,9 +63,9 @@ export const mapComplianceData = (
description: description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
// MITRE specific fields
technique_id: id,
technique_name: techniqueName,
@@ -154,7 +155,7 @@ export const getTopFailedSections = (
const requirements = (framework as any).requirements || [];
requirements.forEach((requirement: Requirement) => {
if (requirement.status === "FAIL") {
if (requirement.status === REQUIREMENT_STATUS.FAIL) {
const tactics = (requirement.tactics as string[]) || [];
tactics.forEach((tactic) => {

View File

@@ -7,6 +7,7 @@ import {
AttributesData,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
ThreatAttributesMetadata,
@@ -56,7 +57,7 @@ export const mapComplianceData = (
const totalFindings = requirementData.attributes.total_findings || 0;
// Calculate score: if PASS = levelOfRisk * weight, if FAIL = 0
const score = status === "PASS" ? levelOfRisk * weight : 0;
const score = status === REQUIREMENT_STATUS.PASS ? levelOfRisk * weight : 0;
// Find or create framework using common helper
const framework = findOrCreateFramework(frameworks, frameworkName);
@@ -74,9 +75,9 @@ export const mapComplianceData = (
description: description,
status: finalStatus,
check_ids: checks,
pass: finalStatus === "PASS" ? 1 : 0,
fail: finalStatus === "FAIL" ? 1 : 0,
manual: finalStatus === "MANUAL" ? 1 : 0,
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
title: title,
levelOfRisk: levelOfRisk,
weight: weight,

View File

@@ -1,4 +1,12 @@
export type RequirementStatus = "PASS" | "FAIL" | "MANUAL" | "No findings";
export const REQUIREMENT_STATUS = {
PASS: "PASS",
FAIL: "FAIL",
MANUAL: "MANUAL",
NO_FINDINGS: "No findings",
} as const;
export type RequirementStatus =
(typeof REQUIREMENT_STATUS)[keyof typeof REQUIREMENT_STATUS];
export interface CompliancesOverview {
data: ComplianceOverviewData[];
@@ -133,6 +141,14 @@ export interface KISAAttributesMetadata {
NonComplianceCases: string[];
}
export interface C5AttributesMetadata {
Section: string;
SubSection: string;
Type: string;
AboutCriteria: string;
ComplementaryCriteria: string;
}
export interface MITREAttributesMetadata {
// Dynamic cloud service field - could be AWSService, GCPService, AzureService, etc.
[key: string]: string;
@@ -167,6 +183,7 @@ export interface AttributesItemData {
| AWSWellArchitectedAttributesMetadata[]
| ThreatAttributesMetadata[]
| KISAAttributesMetadata[]
| C5AttributesMetadata[]
| MITREAttributesMetadata[]
| GenericAttributesMetadata[];
check_ids: string[];