mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
chore: solve conflicts with main branch
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
import { ColumnsFindings, SkeletonTableFindings } from "@/components";
|
||||
import { DataTableProvider } from "@/components/providers";
|
||||
import {
|
||||
ColumnsFindings,
|
||||
DataTableFindings,
|
||||
SkeletonTableFindings,
|
||||
} from "@/components/findings";
|
||||
import { Header } from "@/components/ui";
|
||||
|
||||
export default async function Findings() {
|
||||
@@ -10,7 +13,7 @@ export default async function Findings() {
|
||||
<>
|
||||
<Header title="Findings" icon="ph:list-checks-duotone" />
|
||||
<Spacer />
|
||||
<div className="flex flex-col items-end w-full">
|
||||
<div className="flex flex-col items-start w-full overflow-hidden">
|
||||
<Spacer y={6} />
|
||||
<Suspense fallback={<SkeletonTableFindings />}>
|
||||
<SSRDataTable />
|
||||
@@ -22,7 +25,7 @@ export default async function Findings() {
|
||||
|
||||
const SSRDataTable = async () => {
|
||||
return (
|
||||
<DataTableProvider
|
||||
<DataTableFindings
|
||||
columns={ColumnsFindings}
|
||||
data={[
|
||||
{
|
||||
@@ -36,6 +39,35 @@ const SSRDataTable = async () => {
|
||||
service: "cloudformation",
|
||||
account: "dev (106908755756)",
|
||||
},
|
||||
card: {
|
||||
resourceId:
|
||||
"StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a",
|
||||
resourceLink:
|
||||
"https://app.prowler.pro/app/findings?date=2024-08-05&search=StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a",
|
||||
resourceARN:
|
||||
"arn:aws:cloudformation:eu-west-1:714274078102:stack/StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a/9656eda0-909c-11ec-8fb2-06f4f86422d5",
|
||||
checkId: "cloudformation_stack_outputs_find_secrets",
|
||||
checkLink:
|
||||
"https://app.prowler.pro/app/findings?date=2024-08-05&search=cloudformation_stack_outputs_find_secrets",
|
||||
type: "Not applicable",
|
||||
scanTime: "2024-08-05 @ 14:22:00 UTC",
|
||||
findingId: "ba123291-03a5-49a1-b962-6fdb1d2b9c9b",
|
||||
findingLink:
|
||||
"https://app.prowler.pro/app/findings?date=2024-08-05&search=ba123291-03a5-49a1-b962-6fdb1d2b9c9b",
|
||||
details:
|
||||
"Potential secret found in Stack StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a Outputs.",
|
||||
riskLink:
|
||||
"https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html",
|
||||
riskDetails:
|
||||
"Secrets hardcoded into CloudFormation outputs can be used by malware and bad actors to gain lateral access to other services.",
|
||||
recommendationLink:
|
||||
"https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-secret-generatesecretstring.html",
|
||||
recommendationDetails:
|
||||
"Implement automated detective control to scan accounts for passwords and secrets. Use secrets manager service to store and retrieve passwords and secrets.",
|
||||
referenceInformation: "CLI",
|
||||
referenceLink:
|
||||
"https://docs.prowler.com/checks/aws/secrets-policies/bc_aws_secrets_2/#cli-command",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "67891",
|
||||
@@ -47,6 +79,30 @@ const SSRDataTable = async () => {
|
||||
service: "cloudformation",
|
||||
account: "stg (987654321987)",
|
||||
},
|
||||
card: {
|
||||
resourceId: "<root_account>",
|
||||
resourceLink:
|
||||
"https://app.prowler.pro/app/findings?search=%3Croot_account%3E",
|
||||
resourceARN: "arn:aws:iam::714274078102:root",
|
||||
checkId: "iam_root_mfa_enabled",
|
||||
checkLink:
|
||||
"https://app.prowler.pro/app/findings?search=iam_root_mfa_enabled",
|
||||
type: "Software and Configuration Checks, Industry and Regulatory Standards, CIS AWS Foundations Benchmark",
|
||||
scanTime: "2024-08-05 @ 14:22:00 UTC",
|
||||
findingId: "bc3a34e0-16f0-4ea1-ac62-f796c8af3448",
|
||||
findingLink:
|
||||
"https://app.prowler.pro/app/findings?date=2024-08-05&search=bc3a34e0-16f0-4ea1-ac62-f796c8af3448",
|
||||
details: "MFA is not enabled for root account.",
|
||||
riskLink: "",
|
||||
riskDetails:
|
||||
"The root account is the most privileged user in an AWS account. MFA adds an extra layer of protection on top of a user name and password. With MFA enabled when a user signs in to an AWS website they will be prompted for their user name and password as well as for an authentication code from their AWS MFA device. When virtual MFA is used for root accounts it is recommended that the device used is NOT a personal device but rather a dedicated mobile device (tablet or phone) that is managed to be kept charged and secured independent of any individual personal devices. (non-personal virtual MFA) This lessens the risks of losing access to the MFA due to device loss / trade-in or if the individual owning the device is no longer employed at the company.",
|
||||
recommendationLink:
|
||||
"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html#id_root-user_manage_mfa",
|
||||
recommendationDetails:
|
||||
"Using IAM console navigate to Dashboard and expand Activate MFA on your root account.",
|
||||
referenceInformation: "",
|
||||
referenceLink: "",
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./table/ColumnsFindings";
|
||||
export * from "./table/DataTableFindings";
|
||||
export * from "./table/FindingsCard";
|
||||
export * from "./table/SkeletonTableFindings";
|
||||
+10
-9
@@ -10,19 +10,20 @@ import {
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { FindingsProps } from "@/types";
|
||||
import { StatusBadge } from "@/components/ui";
|
||||
import { FindingProps } from "@/types";
|
||||
|
||||
const getFindingsAttributes = (row: { original: FindingsProps }) => {
|
||||
const getFindingsAttributes = (row: { original: FindingProps }) => {
|
||||
return row.original.attributes;
|
||||
};
|
||||
|
||||
export const ColumnsFindings: ColumnDef<FindingsProps>[] = [
|
||||
export const ColumnsFindings: ColumnDef<FindingProps>[] = [
|
||||
{
|
||||
accessorKey: "checkTitle",
|
||||
header: "Check",
|
||||
cell: ({ row }) => {
|
||||
const { CheckTitle } = getFindingsAttributes(row);
|
||||
return <p className="text-medium">{CheckTitle}</p>;
|
||||
return <p className="text-sm">{CheckTitle}</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -30,7 +31,7 @@ export const ColumnsFindings: ColumnDef<FindingsProps>[] = [
|
||||
header: "Severity",
|
||||
cell: ({ row }) => {
|
||||
const { severity } = getFindingsAttributes(row);
|
||||
return <p className="text-medium">{severity}</p>;
|
||||
return <p className="text-sm text-nowrap">{severity}</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -38,7 +39,7 @@ export const ColumnsFindings: ColumnDef<FindingsProps>[] = [
|
||||
header: "Status",
|
||||
cell: ({ row }) => {
|
||||
const { status } = getFindingsAttributes(row);
|
||||
return <p className="text-medium">{status}</p>;
|
||||
return <StatusBadge status={status} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -46,7 +47,7 @@ export const ColumnsFindings: ColumnDef<FindingsProps>[] = [
|
||||
header: "Region",
|
||||
cell: ({ row }) => {
|
||||
const { region } = getFindingsAttributes(row);
|
||||
return <p className="text-medium">{region}</p>;
|
||||
return <p className="text-sm text-nowrap">{region}</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -54,7 +55,7 @@ export const ColumnsFindings: ColumnDef<FindingsProps>[] = [
|
||||
header: "Service",
|
||||
cell: ({ row }) => {
|
||||
const { service } = getFindingsAttributes(row);
|
||||
return <p className="text-medium">{service}</p>;
|
||||
return <p className="text-sm">{service}</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -62,7 +63,7 @@ export const ColumnsFindings: ColumnDef<FindingsProps>[] = [
|
||||
header: "Account",
|
||||
cell: ({ row }) => {
|
||||
const { account } = getFindingsAttributes(row);
|
||||
return <p className="text-medium">{account}</p>;
|
||||
return <p className="text-sm">{account}</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -0,0 +1,127 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@nextui-org/react";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
Row,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import { FindingsCard } from "@/components/findings/table/FindingsCard";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table/Table";
|
||||
import { FindingProps } from "@/types";
|
||||
|
||||
interface DataTableFindingsProps<TData extends FindingProps, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
}
|
||||
|
||||
export function DataTableFindings<TData extends FindingProps, TValue>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableFindingsProps<TData, TValue>) {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
});
|
||||
|
||||
const selectedRow = table.getSelectedRowModel().rows[0]?.original;
|
||||
|
||||
const onClick = (row: Row<TData>) => {
|
||||
table.resetRowSelection(true);
|
||||
row.toggleSelected();
|
||||
return;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row w-full">
|
||||
<div className="w-full">
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
onClick={() => onClick(row)}
|
||||
className={"hover:cursor-pointer"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<FindingsCard selectedRow={selectedRow} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Button, Divider, Link } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { FindingProps } from "@/types";
|
||||
interface FindingsCardProps {
|
||||
selectedRow: FindingProps;
|
||||
}
|
||||
|
||||
export const FindingsCard: React.FC<FindingsCardProps> = ({ selectedRow }) => {
|
||||
const { attributes, card } = selectedRow || {};
|
||||
const { CheckTitle } = attributes || {};
|
||||
const {
|
||||
resourceLink,
|
||||
resourceId,
|
||||
resourceARN,
|
||||
checkLink,
|
||||
checkId,
|
||||
type,
|
||||
scanTime,
|
||||
findingLink,
|
||||
findingId,
|
||||
details,
|
||||
riskDetails,
|
||||
riskLink,
|
||||
recommendationDetails,
|
||||
recommendationLink,
|
||||
referenceInformation,
|
||||
referenceLink,
|
||||
} = card || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`rounded-md border bg-background transition-transform duration-300 ${selectedRow ? "w-2/5 translate-x-0 ml-2 p-3" : "translate-x-full w-0"}`}
|
||||
>
|
||||
<p className="font-bold mb-3">{CheckTitle}</p>
|
||||
<Divider />
|
||||
<div className="text-sm mt-3">
|
||||
<p className="font-bold">Resource ID:</p>
|
||||
<Link className="text-sm" href={resourceLink}>
|
||||
{resourceId}
|
||||
</Link>
|
||||
<p className="font-bold mt-3">Resource ARN:</p>
|
||||
<p>{resourceARN}</p>
|
||||
<p className="font-bold mt-3">Check ID:</p>
|
||||
<Link className="text-sm" href={checkLink}>
|
||||
{checkId}
|
||||
</Link>
|
||||
<p className="font-bold mt-3">Type:</p>
|
||||
<p>{type}</p>
|
||||
<p className="font-bold mt-3">Scan Time:</p>
|
||||
<p>{scanTime}</p>
|
||||
<p className="font-bold mt-3">Prowler Finding ID:</p>
|
||||
<Link className="text-sm" href={findingLink}>
|
||||
{findingId}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{details && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-yellow-500 p-2">
|
||||
<p className="font-bold">Details:</p>
|
||||
<p>{details}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{riskDetails && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-red-500 p-2">
|
||||
<p className="font-bold flex justify-between items-center mb-2">
|
||||
<span>Risk:</span>
|
||||
{riskLink && (
|
||||
<Button
|
||||
href={riskLink}
|
||||
as={Link}
|
||||
color="primary"
|
||||
variant="flat"
|
||||
isExternal
|
||||
size="sm"
|
||||
className="h-[28px] p-2"
|
||||
>
|
||||
View Source
|
||||
</Button>
|
||||
)}
|
||||
</p>
|
||||
<p>{riskDetails}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{recommendationDetails && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-green-500 p-2">
|
||||
<div className="font-bold flex justify-between items-center mb-2">
|
||||
<span>Recommendation:</span>
|
||||
{recommendationLink && (
|
||||
<Button
|
||||
href={recommendationLink}
|
||||
as={Link}
|
||||
color="primary"
|
||||
variant="flat"
|
||||
isExternal
|
||||
size="sm"
|
||||
className="h-[28px] p-2"
|
||||
>
|
||||
View Source
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p>{recommendationDetails}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{referenceInformation && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-gray-500 p-2">
|
||||
<div className="font-bold flex justify-between items-center mb-2">
|
||||
<span>Reference Information:</span>
|
||||
{referenceLink && (
|
||||
<Button
|
||||
href={referenceLink}
|
||||
as={Link}
|
||||
color="primary"
|
||||
variant="flat"
|
||||
isExternal
|
||||
size="sm"
|
||||
className="h-[28px] p-2"
|
||||
>
|
||||
View Source
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p>{referenceInformation}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./findings/ColumnsFindings";
|
||||
export * from "./findings/SkeletonTableFindings";
|
||||
@@ -23,7 +23,7 @@ import { MetaDataProps } from "@/types";
|
||||
|
||||
import { DataTablePagination } from "./DataTablePagination";
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
interface DataTableProviderProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
metadata?: MetaDataProps;
|
||||
@@ -33,7 +33,7 @@ export function DataTableProvider<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
metadata,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
}: DataTableProviderProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const table = useReactTable({
|
||||
data,
|
||||
|
||||
@@ -35,7 +35,7 @@ export const SidebarWrap = () => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"relative flex h-screen flex-col !border-r-small border-divider transition-width",
|
||||
"relative flex h-screen flex-col !border-r-small border-divider transition-width",
|
||||
{
|
||||
"w-72 p-6": !isCompact,
|
||||
"w-16 items-center px-2 py-6": isCompact,
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import { Chip } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
type Status = "completed" | "pending" | "cancelled";
|
||||
type Status =
|
||||
| "completed"
|
||||
| "pending"
|
||||
| "cancelled"
|
||||
| "fail"
|
||||
| "success"
|
||||
| "muted";
|
||||
|
||||
export const statusColorMap: Record<Status, "success" | "danger" | "warning"> =
|
||||
{
|
||||
completed: "success",
|
||||
pending: "warning",
|
||||
cancelled: "danger",
|
||||
};
|
||||
export const statusColorMap: Record<
|
||||
Status,
|
||||
"success" | "danger" | "warning" | "default"
|
||||
> = {
|
||||
completed: "success",
|
||||
pending: "warning",
|
||||
cancelled: "danger",
|
||||
fail: "danger",
|
||||
success: "success",
|
||||
muted: "default",
|
||||
};
|
||||
|
||||
export const StatusBadge = ({ status }: { status: Status }) => {
|
||||
return (
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
Django==5.0.7
|
||||
Django==5.0.8
|
||||
djangorestframework==3.15.2
|
||||
django-cors-headers==4.3.1
|
||||
|
||||
+21
-3
@@ -42,16 +42,34 @@ export interface ProviderProps {
|
||||
};
|
||||
}
|
||||
|
||||
export interface FindingsProps {
|
||||
export interface FindingProps {
|
||||
id: string;
|
||||
attributes: {
|
||||
CheckTitle: string;
|
||||
severity: string;
|
||||
status: string;
|
||||
severity: "critical" | "high" | "medium" | "low";
|
||||
status: "fail" | "success" | "muted";
|
||||
region: string;
|
||||
service: string;
|
||||
account: string;
|
||||
};
|
||||
card: {
|
||||
resourceId: string;
|
||||
resourceLink: string;
|
||||
resourceARN: string;
|
||||
checkId: string;
|
||||
checkLink: string;
|
||||
type: string;
|
||||
scanTime: string;
|
||||
findingId: string;
|
||||
findingLink: string;
|
||||
details: string;
|
||||
riskLink: string;
|
||||
riskDetails: string;
|
||||
recommendationLink: string;
|
||||
recommendationDetails: string;
|
||||
referenceInformation: string;
|
||||
referenceLink: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MetaDataProps {
|
||||
|
||||
Reference in New Issue
Block a user