feat(compliance): add DORA framework for AWS (#11131)

This commit is contained in:
Pedro Martín authored and GitHub committed 2026-06-03 11:43:55 +02:00
1 parent d573af911d
commit f7f8747512
50 files changed
+2357 -38277

No files matched your search

@@ -0,0 +1,49 @@
import { Requirement } from "@/types/compliance";
import {
ComplianceBadge,
ComplianceBadgeContainer,
ComplianceDetailContainer,
ComplianceDetailSection,
ComplianceDetailText,
} from "./shared-components";
interface DORADetailsProps {
requirement: Requirement;
}
export const DORACustomDetails = ({ requirement }: DORADetailsProps) => {
return (
<ComplianceDetailContainer>
{requirement.description && (
<ComplianceDetailSection title="Description">
<ComplianceDetailText>{requirement.description}</ComplianceDetailText>
</ComplianceDetailSection>
)}
<ComplianceBadgeContainer>
{requirement.pillar && (
<ComplianceBadge
label="Pillar"
value={requirement.pillar as string}
color="blue"
/>
)}
{requirement.article && (
<ComplianceBadge
label="Article"
value={requirement.article as string}
color="indigo"
/>
)}
{requirement.article_title && (
<ComplianceBadge
label="Article Title"
value={requirement.article_title as string}
color="gray"
/>
)}
</ComplianceBadgeContainer>
</ComplianceDetailContainer>
);
};
@@ -6,15 +6,19 @@ import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
const { downloadComplianceCsvMock, downloadCompliancePdfMock } = vi.hoisted(
() => ({
downloadComplianceCsvMock: vi.fn(),
downloadCompliancePdfMock: vi.fn(),
}),
);
const {
downloadComplianceCsvMock,
downloadComplianceOcsfMock,
downloadCompliancePdfMock,
} = vi.hoisted(() => ({
downloadComplianceCsvMock: vi.fn(),
downloadComplianceOcsfMock: vi.fn(),
downloadCompliancePdfMock: vi.fn(),
}));
vi.mock("@/lib/helper", () => ({
downloadComplianceCsv: downloadComplianceCsvMock,
downloadComplianceOcsf: downloadComplianceOcsfMock,
downloadCompliancePdf: downloadCompliancePdfMock,
}));
@@ -131,4 +135,51 @@ describe("ComplianceDownloadContainer", () => {
{},
);
});
it("should hide the OCSF action for frameworks without OCSF support", async () => {
const user = userEvent.setup();
render(
<ComplianceDownloadContainer
compact
presentation="dropdown"
scanId="scan-1"
complianceId="compliance-1"
/>,
);
await user.click(
screen.getByRole("button", { name: "Open compliance export actions" }),
);
expect(screen.queryByText("Download OCSF report")).not.toBeInTheDocument();
});
it("should surface and trigger the OCSF download for universal frameworks", async () => {
const user = userEvent.setup();
render(
<ComplianceDownloadContainer
compact
presentation="dropdown"
scanId="scan-1"
complianceId="dora"
/>,
);
await user.click(
screen.getByRole("button", { name: "Open compliance export actions" }),
);
expect(screen.getByText("Download OCSF report")).toBeInTheDocument();
await user.click(
screen.getByRole("menuitem", { name: /Download OCSF report/i }),
);
expect(downloadComplianceOcsfMock).toHaveBeenCalledWith(
"scan-1",
"dora",
{},
);
});
});
@@ -1,6 +1,6 @@
"use client";
import { DownloadIcon, FileTextIcon } from "lucide-react";
import { DownloadIcon, FileJsonIcon, FileTextIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/shadcn/button/button";
@@ -14,8 +14,15 @@ import {
TooltipTrigger,
} from "@/components/shadcn/tooltip";
import { toast } from "@/components/ui";
import type { ComplianceReportType } from "@/lib/compliance/compliance-report-types";
import { downloadComplianceCsv, downloadCompliancePdf } from "@/lib/helper";
import {
type ComplianceReportType,
isOcsfSupported,
} from "@/lib/compliance/compliance-report-types";
import {
downloadComplianceCsv,
downloadComplianceOcsf,
downloadCompliancePdf,
} from "@/lib/helper";
import { cn } from "@/lib/utils";
interface ComplianceDownloadContainerProps {
@@ -40,9 +47,14 @@ export const ComplianceDownloadContainer = ({
presentation = "buttons",
}: ComplianceDownloadContainerProps) => {
const [isDownloadingCsv, setIsDownloadingCsv] = useState(false);
const [isDownloadingOcsf, setIsDownloadingOcsf] = useState(false);
const [isDownloadingPdf, setIsDownloadingPdf] = useState(false);
const isIconWidth = buttonWidth === "icon";
const isDropdown = presentation === "dropdown";
// Only universal frameworks declaring an ``outputs`` block expose a
// per-framework OCSF artifact (today: DORA, CSA CCM 4.0). Hide the
// action everywhere else so the user never hits a guaranteed 404.
const ocsfAvailable = isOcsfSupported(complianceId);
const handleDownloadCsv = async () => {
if (isDownloadingCsv) return;
@@ -54,6 +66,16 @@ export const ComplianceDownloadContainer = ({
}
};
const handleDownloadOcsf = async () => {
if (!ocsfAvailable || isDownloadingOcsf) return;
setIsDownloadingOcsf(true);
try {
await downloadComplianceOcsf(scanId, complianceId, toast);
} finally {
setIsDownloadingOcsf(false);
}
};
const handleDownloadPdf = async () => {
if (!reportType || isDownloadingPdf) return;
setIsDownloadingPdf(true);
@@ -105,6 +127,18 @@ export const ComplianceDownloadContainer = ({
onSelect={handleDownloadCsv}
disabled={disabled || isDownloadingCsv}
/>
{ocsfAvailable && (
<ActionDropdownItem
icon={
<FileJsonIcon
className={isDownloadingOcsf ? "animate-download-icon" : ""}
/>
}
label="Download OCSF report"
onSelect={handleDownloadOcsf}
disabled={disabled || isDownloadingOcsf}
/>
)}
{reportType && (
<ActionDropdownItem
icon={
@@ -152,6 +186,29 @@ export const ComplianceDownloadContainer = ({
<TooltipContent>Download CSV report</TooltipContent>
)}
</Tooltip>
{ocsfAvailable && (
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="outline"
className={buttonClassName}
onClick={handleDownloadOcsf}
disabled={disabled || isDownloadingOcsf}
aria-label="Download compliance OCSF report"
>
<FileJsonIcon
size={14}
className={isDownloadingOcsf ? "animate-download-icon" : ""}
/>
<span className={labelClassName}>OCSF</span>
</Button>
</TooltipTrigger>
{showTooltip && (
<TooltipContent>Download OCSF report</TooltipContent>
)}
</Tooltip>
)}
{reportType && (
<Tooltip>
<TooltipTrigger asChild>
@@ -6,6 +6,7 @@ import CCCLogo from "./ccc.svg";
import CISLogo from "./cis.svg";
import CISALogo from "./cisa.svg";
import CSALogo from "./csa.svg";
import DORALogo from "./dora.svg";
import ENSLogo from "./ens.png";
import FedRAMPLogo from "./fedramp.svg";
import FFIECLogo from "./ffiec.svg";
@@ -67,6 +68,9 @@ const COMPLIANCE_LOGOS = [
["c5", C5Logo],
["ccc", CCCLogo],
["csa", CSALogo],
// DORA — universal framework (`prowler/compliance/dora.json`). The
// compliance_id is just `dora`, no provider suffix.
["dora", DORALogo],
["secnumcloud", ANSSILogo],
["aws", AWSLogo],
] as const;
+13
View File
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 170" fill="none">
<defs>
<linearGradient id="doraGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#003399"/>
<stop offset="100%" style="stop-color:#0055A5"/>
</linearGradient>
</defs>
<g>
<rect x="0" y="20" width="400" height="130" rx="16" fill="url(#doraGradient)"/>
<text x="200" y="100" font-family="Helvetica, Arial, sans-serif" font-size="76" font-weight="700" fill="#FFFFFF" text-anchor="middle" letter-spacing="6">DORA</text>
<text x="200" y="135" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="500" fill="#FFD700" text-anchor="middle" letter-spacing="3">EU 2022/2554</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 747 B