mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
feat(compliance): add DORA framework for AWS (#11131)
This commit is contained in:
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;
|
||||
|
||||
@@ -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 |
Reference in new issue
Block a user