feat(compliance): add csa ccm 4.0 for the aws provider (#10018)

This commit is contained in:
Pedro Martín
2026-02-12 13:10:59 +01:00
committed by GitHub
parent 52f98f1704
commit f55983a77d
20 changed files with 8176 additions and 5 deletions
+1
View File
@@ -7,6 +7,7 @@ All notable changes to the **Prowler UI** are documented in this file.
### 🔄 Changed
- Attack Paths: Query list now shows their name and short description, when one is selected it also shows a longer description and an attribution if it has it [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
- CSA CCM detailed view and small fix related with `Top Failed Sections` width [(#10018)](https://github.com/prowler-cloud/prowler/pull/10018)
---
@@ -151,8 +151,9 @@ export default async function ComplianceDetail({
{/* Mobile: each card on own row | Tablet: ThreatScore full row, others share row | Desktop: all 3 in one row */}
<div
className={cn(
"grid grid-cols-1 gap-6 md:grid-cols-2",
isThreatScore && "xl:grid-cols-[minmax(280px,320px)_auto_1fr]",
"grid grid-cols-1 gap-6 md:grid-cols-[minmax(280px,400px)_1fr]",
isThreatScore &&
"xl:grid-cols-[minmax(280px,320px)_minmax(280px,400px)_1fr]",
)}
>
{isThreatScore && (
@@ -209,7 +210,7 @@ const SSRComplianceContent = async ({
if (!scanId || type === "tasks") {
return (
<div className="flex flex-col gap-8">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
<div className="grid grid-cols-1 gap-6 md:grid-cols-[minmax(280px,400px)_1fr]">
<RequirementsStatusCard pass={0} fail={0} manual={0} />
<TopFailedSectionsCard sections={[]} />
{/* <SectionsFailureRateCard categories={[]} /> */}
@@ -244,8 +245,9 @@ const SSRComplianceContent = async ({
{/* Mobile: each card on own row | Tablet: ThreatScore full row, others share row | Desktop: all 3 in one row */}
<div
className={cn(
"grid grid-cols-1 gap-6 md:grid-cols-2",
threatScoreData && "xl:grid-cols-[minmax(280px,320px)_auto_1fr]",
"grid grid-cols-1 gap-6 md:grid-cols-[minmax(280px,400px)_1fr]",
threatScoreData &&
"xl:grid-cols-[minmax(280px,320px)_minmax(280px,400px)_1fr]",
)}
>
{threatScoreData && (
@@ -0,0 +1,93 @@
import { cn } from "@/lib";
import { CSA_MAPPING_SECTIONS } from "@/lib/compliance/csa";
import { Requirement } from "@/types/compliance";
import {
ComplianceBadge,
ComplianceBadgeContainer,
ComplianceDetailContainer,
ComplianceDetailSection,
ComplianceDetailText,
} from "./shared-components";
interface CSADetailsProps {
requirement: Requirement;
}
export const CSACustomDetails = ({ requirement }: CSADetailsProps) => {
const mappingSections = CSA_MAPPING_SECTIONS.map((section) => ({
...section,
data: requirement[section.key] as Array<{
ReferenceId: string;
Identifiers: string[];
}>,
})).filter((section) => section.data && section.data.length > 0);
return (
<ComplianceDetailContainer>
{requirement.description && (
<ComplianceDetailSection title="Description">
<ComplianceDetailText>{requirement.description}</ComplianceDetailText>
</ComplianceDetailSection>
)}
<ComplianceBadgeContainer>
{requirement.ccm_lite && (
<ComplianceBadge
label="CCM Lite"
value={requirement.ccm_lite as string}
color={requirement.ccm_lite === "Yes" ? "green" : "gray"}
/>
)}
{requirement.iaas && (
<ComplianceBadge
label="IaaS"
value={requirement.iaas as string}
color="blue"
/>
)}
{requirement.paas && (
<ComplianceBadge
label="PaaS"
value={requirement.paas as string}
color="blue"
/>
)}
{requirement.saas && (
<ComplianceBadge
label="SaaS"
value={requirement.saas as string}
color="blue"
/>
)}
</ComplianceBadgeContainer>
{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>
);
};
@@ -3,6 +3,7 @@ import C5Logo from "./c5.svg";
import CCCLogo from "./ccc.svg";
import CISLogo from "./cis.svg";
import CISALogo from "./cisa.svg";
import CSALogo from "./csa.svg";
import ENSLogo from "./ens.png";
import FedRAMPLogo from "./fedramp.svg";
import FFIECLogo from "./ffiec.svg";
@@ -40,6 +41,7 @@ const COMPLIANCE_LOGOS = {
nis2: NIS2Logo,
c5: C5Logo,
ccc: CCCLogo,
csa: CSALogo,
} as const;
export const getComplianceIcon = (complianceTitle: string) => {
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

+14
View File
@@ -4,6 +4,7 @@ import { AWSWellArchitectedCustomDetails } from "@/components/compliance/complia
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 { CSACustomDetails } from "@/components/compliance/compliance-custom-details/csa-details";
import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-details";
import { GenericCustomDetails } from "@/components/compliance/compliance-custom-details/generic-details";
import { ISOCustomDetails } from "@/components/compliance/compliance-custom-details/iso-details";
@@ -37,6 +38,10 @@ import {
toAccordionItems as toCISAccordionItems,
} from "./cis";
import { calculateCategoryHeatmapData, getTopFailedSections } from "./commons";
import {
mapComplianceData as mapCSAComplianceData,
toAccordionItems as toCSAAccordionItems,
} from "./csa";
import {
mapComplianceData as mapENSComplianceData,
toAccordionItems as toENSAccordionItems,
@@ -179,6 +184,15 @@ const getComplianceMappers = (): Record<string, ComplianceMapper> => ({
getDetailsComponent: (requirement: Requirement) =>
createElement(CCCCustomDetails, { requirement }),
},
"CSA-CCM": {
mapComplianceData: mapCSAComplianceData,
toAccordionItems: toCSAAccordionItems,
getTopFailedSections,
calculateCategoryHeatmapData: (data: Framework[]) =>
calculateCategoryHeatmapData(data),
getDetailsComponent: (requirement: Requirement) =>
createElement(CSACustomDetails, { requirement }),
},
});
/**
+143
View File
@@ -0,0 +1,143 @@
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,
CSAAttributesMetadata,
Framework,
Requirement,
REQUIREMENT_STATUS,
RequirementsData,
RequirementStatus,
} from "@/types/compliance";
import {
calculateFrameworkCounters,
createRequirementsMap,
findOrCreateCategory,
findOrCreateControl,
findOrCreateFramework,
} from "./commons";
export interface CSAMappingSection {
title: string;
key: keyof Requirement;
colorClasses: string;
}
export const CSA_MAPPING_SECTIONS: CSAMappingSection[] = [
{
title: "Scope Applicability",
key: "scope_applicability",
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 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[] = [];
for (const attributeItem of attributes) {
const id = attributeItem.id;
const metadataArray = attributeItem.attributes?.attributes
?.metadata as unknown as CSAAttributesMetadata[];
const attrs = metadataArray?.[0];
if (!attrs) continue;
const requirementData = requirementsMap.get(id);
if (!requirementData) continue;
const frameworkName = attributeItem.attributes.framework;
const categoryName = attrs.Section;
const requirementName = attributeItem.attributes.name || "";
const description = attributeItem.attributes.description;
const status = requirementData.attributes.status || "";
const checks = attributeItem.attributes.attributes.check_ids || [];
const framework = findOrCreateFramework(frameworks, frameworkName);
const category = findOrCreateCategory(framework.categories, categoryName);
// Use a single control per category to keep a flat 2-level structure
const control = findOrCreateControl(category.controls, categoryName);
const finalStatus: RequirementStatus = status as RequirementStatus;
const requirement: Requirement = {
name: requirementName ? `${id} - ${requirementName}` : id,
description,
status: finalStatus,
check_ids: checks,
...getStatusCounters(finalStatus),
ccm_lite: attrs.CCMLite,
iaas: attrs.IaaS,
paas: attrs.PaaS,
saas: attrs.SaaS,
scope_applicability: attrs.ScopeApplicability,
};
control.requirements.push(requirement);
}
calculateFrameworkCounters(frameworks);
return frameworks;
};
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: "",
// Flatten: requirements are direct children of the section
items: category.controls.flatMap((control) =>
control.requirements.map((requirement, reqIndex) => ({
key: `${framework.name}-${category.name}-req-${reqIndex}`,
title: (
<ComplianceAccordionRequirementTitle
type=""
name={requirement.name}
status={requirement.status as FindingStatus}
/>
),
content: (
<ClientAccordionContent
key={`content-${framework.name}-${category.name}-req-${reqIndex}`}
requirement={requirement}
scanId={safeId}
framework={framework.name}
disableFindings={
requirement.check_ids.length === 0 && requirement.manual === 0
}
/>
),
items: [],
})),
),
})),
);
};
+13
View File
@@ -189,6 +189,18 @@ export interface GenericAttributesMetadata {
Type: string | null;
}
export interface CSAAttributesMetadata {
Section: string;
CCMLite: string;
IaaS: string;
PaaS: string;
SaaS: string;
ScopeApplicability: Array<{
ReferenceId: string;
Identifiers: string[];
}>;
}
export interface CCCAttributesMetadata {
FamilyName: string;
FamilyDescription: string;
@@ -227,6 +239,7 @@ export interface AttributesItemData {
| C5AttributesMetadata[]
| MITREAttributesMetadata[]
| CCCAttributesMetadata[]
| CSAAttributesMetadata[]
| GenericAttributesMetadata[];
check_ids: string[];
// MITRE structure