mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
feat(ui): add CMMC compliance framework (#12414)
This commit is contained in:
@@ -60,6 +60,21 @@ export const CROSS_PROVIDER_FRAMEWORKS: CrossProviderFrameworkEntry[] = [
|
||||
"Digital Operational Resilience Act (EU 2022/2554) — the EU framework for the digital operational resilience of the financial sector.",
|
||||
compatibleProviders: ["aws", "azure", "gcp", "alibabacloud", "cloudflare"],
|
||||
},
|
||||
{
|
||||
complianceId: "cmmc_2.0",
|
||||
title: "CMMC",
|
||||
version: "2.0",
|
||||
description:
|
||||
"Cybersecurity Maturity Model Certification (CMMC) 2.0 (32 CFR Part 170) — the U.S. Department of Defense program verifying that defense contractors protect FCI and CUI across three levels.",
|
||||
compatibleProviders: [
|
||||
"aws",
|
||||
"azure",
|
||||
"gcp",
|
||||
"m365",
|
||||
"alibabacloud",
|
||||
"oraclecloud",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** Resolves only canonical catalog links. Missing, unknown, or mismatched
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CMMC 2.0 universal compliance framework rendering: dedicated icon, Domain/Level requirement mapper and cross-provider catalog tile
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Requirement } from "@/types/compliance";
|
||||
|
||||
import {
|
||||
ComplianceBadge,
|
||||
ComplianceBadgeContainer,
|
||||
ComplianceDetailContainer,
|
||||
ComplianceDetailSection,
|
||||
ComplianceDetailText,
|
||||
} from "./shared-components";
|
||||
|
||||
interface CMMCDetailsProps {
|
||||
requirement: Requirement;
|
||||
}
|
||||
|
||||
export const CMMCCustomDetails = ({ requirement }: CMMCDetailsProps) => {
|
||||
return (
|
||||
<ComplianceDetailContainer>
|
||||
{requirement.description && (
|
||||
<ComplianceDetailSection title="Description">
|
||||
<ComplianceDetailText>{requirement.description}</ComplianceDetailText>
|
||||
</ComplianceDetailSection>
|
||||
)}
|
||||
|
||||
<ComplianceBadgeContainer>
|
||||
{requirement.domain && (
|
||||
<ComplianceBadge
|
||||
label="Domain"
|
||||
value={requirement.domain as string}
|
||||
variant="tag"
|
||||
/>
|
||||
)}
|
||||
{requirement.level && (
|
||||
<ComplianceBadge
|
||||
label="Level"
|
||||
value={requirement.level as string}
|
||||
variant="tag"
|
||||
/>
|
||||
)}
|
||||
{requirement.source_requirement && (
|
||||
<ComplianceBadge
|
||||
label="Source Requirement"
|
||||
value={requirement.source_requirement as string}
|
||||
variant="tag"
|
||||
/>
|
||||
)}
|
||||
</ComplianceBadgeContainer>
|
||||
</ComplianceDetailContainer>
|
||||
);
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import C5Logo from "./c5.svg";
|
||||
import CCCLogo from "./ccc.svg";
|
||||
import CISLogo from "./cis.svg";
|
||||
import CISALogo from "./cisa.svg";
|
||||
import CMMCLogo from "./cmmc.svg";
|
||||
import CSALogo from "./csa.svg";
|
||||
import DORALogo from "./dora.svg";
|
||||
import ENSLogo from "./ens.png";
|
||||
@@ -69,6 +70,10 @@ const COMPLIANCE_LOGOS = [
|
||||
["c5", C5Logo],
|
||||
["ccc", CCCLogo],
|
||||
["csa", CSALogo],
|
||||
// CMMC 2.0 — universal framework (`prowler/compliance/cmmc_2.0.json`). The
|
||||
// compliance_id is `cmmc_2.0` and the `framework`/title is `CMMC`; the `cmmc`
|
||||
// keyword matches both via `includes`, with no provider suffix.
|
||||
["cmmc", CMMCLogo],
|
||||
// DORA — universal framework (`prowler/compliance/dora_2022_2554.json`).
|
||||
// The compliance_id is `dora_2022_2554`; the `dora` keyword still matches
|
||||
// it via `includes`, with no provider suffix.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 170" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="cmmcGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#0B2A4A"/>
|
||||
<stop offset="100%" style="stop-color:#1E4E79"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<rect x="0" y="20" width="400" height="130" rx="16" fill="url(#cmmcGradient)"/>
|
||||
<text x="200" y="100" font-family="Helvetica, Arial, sans-serif" font-size="66" font-weight="700" fill="#FFFFFF" text-anchor="middle" letter-spacing="4">CMMC</text>
|
||||
<text x="200" y="135" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="500" fill="#7FB2E5" text-anchor="middle" letter-spacing="3">2.0</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 738 B |
@@ -0,0 +1,240 @@
|
||||
import { isValidElement } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
// `cmmc.tsx` re-exports `toAccordionItems` which builds JSX referencing
|
||||
// client-side accordion components. Those components transitively import
|
||||
// server-only code (next-auth → next/server) and would crash vitest at load
|
||||
// time. Mocking the JSX deps lets us load the module and exercise the real
|
||||
// `mapComplianceData` and `toAccordionItems` functions.
|
||||
vi.mock(
|
||||
"@/components/compliance/compliance-accordion/client-accordion-content",
|
||||
() => ({
|
||||
ClientAccordionContent: () => null,
|
||||
}),
|
||||
);
|
||||
vi.mock(
|
||||
"@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title",
|
||||
() => ({
|
||||
ComplianceAccordionRequirementTitle: () => null,
|
||||
}),
|
||||
);
|
||||
vi.mock(
|
||||
"@/components/compliance/compliance-accordion/compliance-accordion-title",
|
||||
() => ({
|
||||
ComplianceAccordionTitle: () => null,
|
||||
}),
|
||||
);
|
||||
|
||||
import {
|
||||
AttributesData,
|
||||
AttributesItemData,
|
||||
CMMCAttributesMetadata,
|
||||
CMMCLevel,
|
||||
REQUIREMENT_STATUS,
|
||||
RequirementItemData,
|
||||
RequirementsData,
|
||||
RequirementStatus,
|
||||
} from "@/types/compliance";
|
||||
|
||||
import { mapComplianceData, toAccordionItems } from "./cmmc";
|
||||
|
||||
const FRAMEWORK = "CMMC";
|
||||
|
||||
const baseMetadata = (
|
||||
overrides: Partial<CMMCAttributesMetadata> = {},
|
||||
): CMMCAttributesMetadata => ({
|
||||
Domain: "Access Control",
|
||||
Level: "Level 1" as CMMCLevel,
|
||||
SourceRequirement: "48 CFR 52.204-21(b)(1)(i)",
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const buildAttribute = (
|
||||
id: string,
|
||||
metadata: CMMCAttributesMetadata,
|
||||
{ name = "" }: { name?: string } = {},
|
||||
): AttributesItemData => ({
|
||||
type: "compliance-requirements-attributes",
|
||||
id,
|
||||
attributes: {
|
||||
framework_description: "CMMC 2.0",
|
||||
name,
|
||||
framework: FRAMEWORK,
|
||||
version: "2.0",
|
||||
description: "Requirement clause text.",
|
||||
attributes: {
|
||||
metadata: [metadata],
|
||||
check_ids: ["check_one"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const buildRequirement = (
|
||||
id: string,
|
||||
status: RequirementStatus = REQUIREMENT_STATUS.PASS,
|
||||
): RequirementItemData => ({
|
||||
type: "compliance-requirements-details",
|
||||
id,
|
||||
attributes: {
|
||||
framework: FRAMEWORK,
|
||||
version: "2.0",
|
||||
description: "Canonical CMMC requirement text.",
|
||||
status,
|
||||
},
|
||||
});
|
||||
|
||||
const buildInputs = (
|
||||
pairs: Array<{
|
||||
attribute: AttributesItemData;
|
||||
requirement: RequirementItemData;
|
||||
}>,
|
||||
): { attributesData: AttributesData; requirementsData: RequirementsData } => ({
|
||||
attributesData: { data: pairs.map((p) => p.attribute) },
|
||||
requirementsData: { data: pairs.map((p) => p.requirement) },
|
||||
});
|
||||
|
||||
describe("mapComplianceData (CMMC 2.0)", () => {
|
||||
it("returns an empty list when there are no attributes", () => {
|
||||
const { attributesData, requirementsData } = buildInputs([]);
|
||||
expect(mapComplianceData(attributesData, requirementsData)).toEqual([]);
|
||||
});
|
||||
|
||||
it("groups requirements by Domain", () => {
|
||||
const attrA = buildAttribute(
|
||||
"AC.L1-b.1.i",
|
||||
baseMetadata({ Domain: "Access Control" }),
|
||||
);
|
||||
const attrB = buildAttribute(
|
||||
"AC.L2-3.1.3",
|
||||
baseMetadata({ Domain: "Access Control" }),
|
||||
);
|
||||
|
||||
const { attributesData, requirementsData } = buildInputs([
|
||||
{ attribute: attrA, requirement: buildRequirement("AC.L1-b.1.i") },
|
||||
{ attribute: attrB, requirement: buildRequirement("AC.L2-3.1.3") },
|
||||
]);
|
||||
|
||||
const [framework] = mapComplianceData(attributesData, requirementsData);
|
||||
|
||||
expect(framework.name).toBe(FRAMEWORK);
|
||||
expect(framework.categories).toHaveLength(1);
|
||||
expect(framework.categories[0].name).toBe("Access Control");
|
||||
expect(framework.categories[0].controls[0].requirements).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("orders domains by the canonical NIST 800-171 family order", () => {
|
||||
const attrSI = buildAttribute(
|
||||
"SI.L1-b.1.xiv",
|
||||
baseMetadata({ Domain: "System and Information Integrity" }),
|
||||
);
|
||||
const attrAC = buildAttribute(
|
||||
"AC.L1-b.1.i",
|
||||
baseMetadata({ Domain: "Access Control" }),
|
||||
);
|
||||
|
||||
const { attributesData, requirementsData } = buildInputs([
|
||||
{ attribute: attrSI, requirement: buildRequirement("SI.L1-b.1.xiv") },
|
||||
{ attribute: attrAC, requirement: buildRequirement("AC.L1-b.1.i") },
|
||||
]);
|
||||
|
||||
const [framework] = mapComplianceData(attributesData, requirementsData);
|
||||
|
||||
expect(framework.categories.map((c) => c.name)).toEqual([
|
||||
"Access Control",
|
||||
"System and Information Integrity",
|
||||
]);
|
||||
});
|
||||
|
||||
it("propagates Domain, Level and SourceRequirement onto the requirement", () => {
|
||||
const attribute = buildAttribute(
|
||||
"AC.L1-b.1.i",
|
||||
baseMetadata({
|
||||
Domain: "Access Control",
|
||||
Level: "Level 1" as CMMCLevel,
|
||||
SourceRequirement: "48 CFR 52.204-21(b)(1)(i)",
|
||||
}),
|
||||
{ name: "Limit information system access" },
|
||||
);
|
||||
|
||||
const { attributesData, requirementsData } = buildInputs([
|
||||
{ attribute, requirement: buildRequirement("AC.L1-b.1.i") },
|
||||
]);
|
||||
|
||||
const [framework] = mapComplianceData(attributesData, requirementsData);
|
||||
const requirementOut = framework.categories[0].controls[0].requirements[0];
|
||||
|
||||
expect(requirementOut.name).toBe(
|
||||
"AC.L1-b.1.i - Limit information system access",
|
||||
);
|
||||
expect(requirementOut.domain).toBe("Access Control");
|
||||
expect(requirementOut.level).toBe("Level 1");
|
||||
expect(requirementOut.source_requirement).toBe("48 CFR 52.204-21(b)(1)(i)");
|
||||
});
|
||||
|
||||
it("derives counters from RequirementStatus", () => {
|
||||
const STATUS_COUNTER = {
|
||||
PASS: "pass",
|
||||
FAIL: "fail",
|
||||
MANUAL: "manual",
|
||||
} as const;
|
||||
type StatusCounter = (typeof STATUS_COUNTER)[keyof typeof STATUS_COUNTER];
|
||||
|
||||
const cases: Array<{
|
||||
status: RequirementStatus;
|
||||
expected: StatusCounter;
|
||||
}> = [
|
||||
{ status: REQUIREMENT_STATUS.PASS, expected: STATUS_COUNTER.PASS },
|
||||
{ status: REQUIREMENT_STATUS.FAIL, expected: STATUS_COUNTER.FAIL },
|
||||
{ status: REQUIREMENT_STATUS.MANUAL, expected: STATUS_COUNTER.MANUAL },
|
||||
];
|
||||
|
||||
for (const { status, expected } of cases) {
|
||||
const attribute = buildAttribute(`AC-${status}`, baseMetadata());
|
||||
const { attributesData, requirementsData } = buildInputs([
|
||||
{ attribute, requirement: buildRequirement(`AC-${status}`, status) },
|
||||
]);
|
||||
|
||||
const [framework] = mapComplianceData(attributesData, requirementsData);
|
||||
expect(framework[expected]).toBe(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("toAccordionItems (CMMC 2.0)", () => {
|
||||
it("produces one accordion item per domain with its requirement leaves", () => {
|
||||
const attrAC = buildAttribute(
|
||||
"AC.L1-b.1.i",
|
||||
baseMetadata({ Domain: "Access Control" }),
|
||||
);
|
||||
const attrIA = buildAttribute(
|
||||
"IA.L1-b.1.v",
|
||||
baseMetadata({ Domain: "Identification and Authentication" }),
|
||||
);
|
||||
|
||||
const frameworks = mapComplianceData(
|
||||
{ data: [attrAC, attrIA] },
|
||||
{
|
||||
data: [
|
||||
buildRequirement("AC.L1-b.1.i"),
|
||||
buildRequirement("IA.L1-b.1.v"),
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const items = toAccordionItems(frameworks, "scan-1");
|
||||
|
||||
expect(items).toHaveLength(2);
|
||||
expect(items[0].key).toBe(`${FRAMEWORK}-Access Control`);
|
||||
expect(isValidElement(items[0].title)).toBe(true);
|
||||
expect(items[0].items).toHaveLength(1);
|
||||
// Requirement keys are stable (derived from the requirement id), not
|
||||
// positional indexes — reordering must not remap expanded state.
|
||||
expect(items[0].items?.[0]?.key).toBe(
|
||||
`${FRAMEWORK}-Access Control-AC.L1-b.1.i`,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns an empty list when given no frameworks", () => {
|
||||
expect(toAccordionItems([], "scan-1")).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,164 @@
|
||||
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/shadcn/accordion/Accordion";
|
||||
import { FindingStatus } from "@/components/shadcn/table/status-finding-badge";
|
||||
import {
|
||||
AttributesData,
|
||||
CMMCAttributesMetadata,
|
||||
Framework,
|
||||
Requirement,
|
||||
REQUIREMENT_STATUS,
|
||||
RequirementsData,
|
||||
RequirementStatus,
|
||||
} from "@/types/compliance";
|
||||
|
||||
import {
|
||||
calculateFrameworkCounters,
|
||||
createRequirementsMap,
|
||||
findOrCreateCategory,
|
||||
findOrCreateControl,
|
||||
findOrCreateFramework,
|
||||
} from "./commons";
|
||||
|
||||
// Canonical NIST SP 800-171 family order for the 14 CMMC domains, so the
|
||||
// accordion always reads in the same order regardless of the API response.
|
||||
export const CMMC_DOMAIN_ORDER: readonly string[] = [
|
||||
"Access Control",
|
||||
"Awareness and Training",
|
||||
"Audit and Accountability",
|
||||
"Configuration Management",
|
||||
"Identification and Authentication",
|
||||
"Incident Response",
|
||||
"Maintenance",
|
||||
"Media Protection",
|
||||
"Personnel Security",
|
||||
"Physical Protection",
|
||||
"Risk Assessment",
|
||||
"Security Assessment",
|
||||
"System and Communications Protection",
|
||||
"System and Information Integrity",
|
||||
];
|
||||
|
||||
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 CMMCAttributesMetadata[];
|
||||
const attrs = metadataArray?.[0];
|
||||
if (!attrs) continue;
|
||||
|
||||
const requirementData = requirementsMap.get(id);
|
||||
if (!requirementData) continue;
|
||||
|
||||
const frameworkName = attributeItem.attributes.framework;
|
||||
// Group by Domain. Level and SourceRequirement live inside the requirement
|
||||
// so they show up on the detail drawer.
|
||||
const categoryName = attrs.Domain;
|
||||
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);
|
||||
// Flat 2-level structure: domain → requirements (no intermediate control).
|
||||
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,
|
||||
invalid_config: requirementData.attributes.invalid_config || false,
|
||||
...getStatusCounters(finalStatus),
|
||||
domain: attrs.Domain,
|
||||
level: attrs.Level,
|
||||
source_requirement: attrs.SourceRequirement,
|
||||
};
|
||||
|
||||
control.requirements.push(requirement);
|
||||
}
|
||||
|
||||
// Sort domains by the canonical NIST 800-171 family order.
|
||||
for (const framework of frameworks) {
|
||||
framework.categories.sort((a, b) => {
|
||||
const ia = CMMC_DOMAIN_ORDER.indexOf(a.name);
|
||||
const ib = CMMC_DOMAIN_ORDER.indexOf(b.name);
|
||||
const orderA = ia === -1 ? CMMC_DOMAIN_ORDER.length : ia;
|
||||
const orderB = ib === -1 ? CMMC_DOMAIN_ORDER.length : ib;
|
||||
return orderA - orderB;
|
||||
});
|
||||
}
|
||||
|
||||
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: "",
|
||||
// Domain → requirements (flat, no intermediate "control" level).
|
||||
// Keys are derived from the requirement name (which starts with the
|
||||
// unique CMMC id, e.g. "AC.L1-b.1.i") instead of the array index, so
|
||||
// expanded state stays attached to the right requirement even if the
|
||||
// list is reordered or filtered.
|
||||
items: category.controls.flatMap((control) =>
|
||||
control.requirements.map((requirement) => ({
|
||||
key: `${framework.name}-${category.name}-${requirement.name}`,
|
||||
title: (
|
||||
<ComplianceAccordionRequirementTitle
|
||||
type=""
|
||||
name={requirement.name}
|
||||
status={requirement.status as FindingStatus}
|
||||
invalidConfig={requirement.invalid_config}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<ClientAccordionContent
|
||||
key={`content-${framework.name}-${category.name}-${requirement.name}`}
|
||||
requirement={requirement}
|
||||
scanId={safeId}
|
||||
framework={framework.name}
|
||||
disableFindings={
|
||||
requirement.check_ids.length === 0 && requirement.manual === 0
|
||||
}
|
||||
/>
|
||||
),
|
||||
items: [],
|
||||
})),
|
||||
),
|
||||
})),
|
||||
);
|
||||
};
|
||||
@@ -38,6 +38,10 @@ vi.mock(
|
||||
"@/components/compliance/compliance-custom-details/cis-details",
|
||||
() => ({ CISCustomDetails: stubFactory("CISStub") }),
|
||||
);
|
||||
vi.mock(
|
||||
"@/components/compliance/compliance-custom-details/cmmc-details",
|
||||
() => ({ CMMCCustomDetails: stubFactory("CMMCStub") }),
|
||||
);
|
||||
vi.mock(
|
||||
"@/components/compliance/compliance-custom-details/csa-details",
|
||||
() => ({ CSACustomDetails: stubFactory("CSAStub") }),
|
||||
@@ -148,6 +152,7 @@ describe("getComplianceMapper", () => {
|
||||
{ framework: "ProwlerThreatScore", expected: "ThreatStub" },
|
||||
{ framework: "CCC", expected: "CCCStub" },
|
||||
{ framework: "CSA-CCM", expected: "CSAStub" },
|
||||
{ framework: "CMMC", expected: "CMMCStub" },
|
||||
{ framework: "Okta-IDaaS-STIG", expected: "OktaIDaaSStigStub" },
|
||||
];
|
||||
|
||||
@@ -193,6 +198,7 @@ describe("getComplianceMapper", () => {
|
||||
"ProwlerThreatScore",
|
||||
"CCC",
|
||||
"CSA-CCM",
|
||||
"CMMC",
|
||||
"Okta-IDaaS-STIG",
|
||||
]) {
|
||||
const mapper = getComplianceMapper(framework);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { C5CustomDetails } from "@/components/compliance/compliance-custom-detai
|
||||
import { CCCCustomDetails } from "@/components/compliance/compliance-custom-details/ccc-details";
|
||||
import { CISControlsCustomDetails } from "@/components/compliance/compliance-custom-details/cis-controls-details";
|
||||
import { CISCustomDetails } from "@/components/compliance/compliance-custom-details/cis-details";
|
||||
import { CMMCCustomDetails } from "@/components/compliance/compliance-custom-details/cmmc-details";
|
||||
import { CSACustomDetails } from "@/components/compliance/compliance-custom-details/csa-details";
|
||||
import { DORACustomDetails } from "@/components/compliance/compliance-custom-details/dora-details";
|
||||
import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-details";
|
||||
@@ -49,6 +50,10 @@ import {
|
||||
mapComplianceData as mapCISControlsComplianceData,
|
||||
toAccordionItems as toCISControlsAccordionItems,
|
||||
} from "./cis-controls";
|
||||
import {
|
||||
mapComplianceData as mapCMMCComplianceData,
|
||||
toAccordionItems as toCMMCAccordionItems,
|
||||
} from "./cmmc";
|
||||
import { calculateCategoryHeatmapData, getTopFailedSections } from "./commons";
|
||||
import {
|
||||
mapComplianceData as mapCSAComplianceData,
|
||||
@@ -259,6 +264,19 @@ const getComplianceMappers = (): Record<string, ComplianceMapper> => ({
|
||||
getDetailsComponent: (requirement: Requirement) =>
|
||||
createElement(DORACustomDetails, { requirement }),
|
||||
},
|
||||
// CMMC 2.0 — universal framework keyed by the `framework` field of
|
||||
// `prowler/compliance/cmmc_2.0.json` ("CMMC"). Groups by Domain (14 NIST
|
||||
// 800-171 families) and surfaces Domain / Level / Source Requirement in the
|
||||
// requirement detail drawer.
|
||||
CMMC: {
|
||||
mapComplianceData: mapCMMCComplianceData,
|
||||
toAccordionItems: toCMMCAccordionItems,
|
||||
getTopFailedSections,
|
||||
calculateCategoryHeatmapData: (data: Framework[]) =>
|
||||
calculateCategoryHeatmapData(data),
|
||||
getDetailsComponent: (requirement: Requirement) =>
|
||||
createElement(CMMCCustomDetails, { requirement }),
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ describe("isOcsfSupported", () => {
|
||||
expect(isOcsfSupported("dora_2022_2554")).toBe(true);
|
||||
expect(isOcsfSupported("csa_ccm_4.0")).toBe(true);
|
||||
expect(isOcsfSupported("cis_controls_8.1")).toBe(true);
|
||||
expect(isOcsfSupported("cmmc_2.0")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for legacy/per-provider frameworks without OCSF output", () => {
|
||||
|
||||
@@ -166,9 +166,9 @@ export const pickLatestCisPerProvider = (
|
||||
*
|
||||
* Only universal compliance frameworks that declare an ``outputs`` block in
|
||||
* their schema (see ``prowler/compliance/<name>.json``) produce a dedicated
|
||||
* OCSF artifact during scan output generation. Today that is DORA and
|
||||
* CSA CCM 4.0. Any other framework only offers CSV (and, for the curated
|
||||
* list above, PDF).
|
||||
* OCSF artifact during scan output generation. Today that is DORA,
|
||||
* CSA CCM 4.0, CIS Controls 8.1 and CMMC 2.0. Any other framework only
|
||||
* offers CSV (and, for the curated list above, PDF).
|
||||
*
|
||||
* Keep this Set in lock-step with the backend: ``get_prowler_provider_compliance``
|
||||
* + ``ComplianceFramework.outputs`` is the source of truth. The API will
|
||||
@@ -181,6 +181,7 @@ const OCSF_SUPPORTED_COMPLIANCE_IDS: ReadonlySet<string> = new Set([
|
||||
"dora_2022_2554",
|
||||
"csa_ccm_4.0",
|
||||
"cis_controls_8.1",
|
||||
"cmmc_2.0",
|
||||
]);
|
||||
|
||||
export const isOcsfSupported = (complianceId: string | undefined): boolean =>
|
||||
|
||||
@@ -427,6 +427,23 @@ export interface CISControlsRequirement extends Requirement {
|
||||
implementation_groups?: string[];
|
||||
}
|
||||
|
||||
// CMMC 2.0 (Cybersecurity Maturity Model Certification, 32 CFR Part 170).
|
||||
// Universal framework — flat attributes dict with Domain/Level/SourceRequirement.
|
||||
// `Domain` is the grouping key; `Level` (1/2/3) and `SourceRequirement` are
|
||||
// surfaced in the requirement detail drawer.
|
||||
export const CMMC_LEVEL = {
|
||||
LEVEL_1: "Level 1",
|
||||
LEVEL_2: "Level 2",
|
||||
LEVEL_3: "Level 3",
|
||||
} as const;
|
||||
export type CMMCLevel = (typeof CMMC_LEVEL)[keyof typeof CMMC_LEVEL];
|
||||
|
||||
export interface CMMCAttributesMetadata {
|
||||
Domain: string;
|
||||
Level: CMMCLevel;
|
||||
SourceRequirement: string;
|
||||
}
|
||||
|
||||
export interface AttributesItemData {
|
||||
type: "compliance-requirements-attributes";
|
||||
id: string;
|
||||
@@ -452,6 +469,7 @@ export interface AttributesItemData {
|
||||
| OktaIDaaSStigAttributesMetadata[]
|
||||
| DORAAttributesMetadata[]
|
||||
| CISControlsAttributesMetadata[]
|
||||
| CMMCAttributesMetadata[]
|
||||
| GenericAttributesMetadata[];
|
||||
check_ids: string[];
|
||||
// MITRE structure
|
||||
|
||||
Reference in New Issue
Block a user