mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
feat(compliance): add csa ccm 4.0 for the aws provider (#10018)
This commit is contained in:
@@ -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 |
@@ -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 }),
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -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: [],
|
||||
})),
|
||||
),
|
||||
})),
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user