From ae8098d53ea7a35ba41f2125715cf6ae1570c43d Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 7 Aug 2024 17:26:32 -0500 Subject: [PATCH 1/3] Findings card initial setup (#31) * feat(findings): WIP - add on click for row, select one row at a time, pass ID to function * feat(findings) More WIP for Findings Card - add in dummy data, pass selected row into card * feat(findings): Pass selected row through * Fix additional merge conflict * feat(findings): Update to new file structure * feat(findings): Hook up initial card with hard-coded data as a sidepanel that expands when a row element is clicked * Merge main --- app/(prowler)/findings/page.tsx | 64 ++++++++- components/findings/index.ts | 4 + .../findings/{ => table}/ColumnsFindings.tsx | 18 +-- .../findings/table/DataTableFindings.tsx | 127 +++++++++++++++++ components/findings/table/FindingsCard.tsx | 134 ++++++++++++++++++ .../{ => table}/SkeletonTableFindings.tsx | 0 components/index.ts | 22 ++- .../providers/table/DataTableProvider.tsx | 4 +- components/ui/sidebar/SidebarWrap.tsx | 2 +- types/components.ts | 20 ++- 10 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 components/findings/index.ts rename components/findings/{ => table}/ColumnsFindings.tsx (78%) create mode 100644 components/findings/table/DataTableFindings.tsx create mode 100644 components/findings/table/FindingsCard.tsx rename components/findings/{ => table}/SkeletonTableFindings.tsx (100%) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 33a8746191..fd0ec5bedc 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -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() { <>
-
+
}> @@ -22,7 +25,7 @@ export default async function Findings() { const SSRDataTable = async () => { return ( - { 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: "", + 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: "", + }, }, ]} /> diff --git a/components/findings/index.ts b/components/findings/index.ts new file mode 100644 index 0000000000..20c1920f4e --- /dev/null +++ b/components/findings/index.ts @@ -0,0 +1,4 @@ +export * from "./table/ColumnsFindings"; +export * from "./table/DataTableFindings"; +export * from "./table/FindingsCard"; +export * from "./table/SkeletonTableFindings"; diff --git a/components/findings/ColumnsFindings.tsx b/components/findings/table/ColumnsFindings.tsx similarity index 78% rename from components/findings/ColumnsFindings.tsx rename to components/findings/table/ColumnsFindings.tsx index 90860b260e..afbd6c406f 100644 --- a/components/findings/ColumnsFindings.tsx +++ b/components/findings/table/ColumnsFindings.tsx @@ -10,19 +10,19 @@ import { import { ColumnDef } from "@tanstack/react-table"; import { VerticalDotsIcon } from "@/components/icons"; -import { FindingsProps } from "@/types"; +import { FindingProps } from "@/types"; -const getFindingsAttributes = (row: { original: FindingsProps }) => { +const getFindingsAttributes = (row: { original: FindingProps }) => { return row.original.attributes; }; -export const ColumnsFindings: ColumnDef[] = [ +export const ColumnsFindings: ColumnDef[] = [ { accessorKey: "checkTitle", header: "Check", cell: ({ row }) => { const { CheckTitle } = getFindingsAttributes(row); - return

{CheckTitle}

; + return

{CheckTitle}

; }, }, { @@ -30,7 +30,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Severity", cell: ({ row }) => { const { severity } = getFindingsAttributes(row); - return

{severity}

; + return

{severity}

; }, }, { @@ -38,7 +38,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Status", cell: ({ row }) => { const { status } = getFindingsAttributes(row); - return

{status}

; + return

{status}

; }, }, { @@ -46,7 +46,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Region", cell: ({ row }) => { const { region } = getFindingsAttributes(row); - return

{region}

; + return

{region}

; }, }, { @@ -54,7 +54,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Service", cell: ({ row }) => { const { service } = getFindingsAttributes(row); - return

{service}

; + return

{service}

; }, }, { @@ -62,7 +62,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Account", cell: ({ row }) => { const { account } = getFindingsAttributes(row); - return

{account}

; + return

{account}

; }, }, { diff --git a/components/findings/table/DataTableFindings.tsx b/components/findings/table/DataTableFindings.tsx new file mode 100644 index 0000000000..5004dc2e6d --- /dev/null +++ b/components/findings/table/DataTableFindings.tsx @@ -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 { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTableFindings({ + columns, + data, +}: DataTableFindingsProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + const selectedRow = table.getSelectedRowModel().rows[0]?.original; + + const onClick = (row: Row) => { + table.resetRowSelection(true); + row.toggleSelected(); + return; + }; + + return ( + <> +
+
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + onClick(row)} + className={"hover:cursor-pointer"} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ + +
+
+ +
+ + ); +} diff --git a/components/findings/table/FindingsCard.tsx b/components/findings/table/FindingsCard.tsx new file mode 100644 index 0000000000..22056b29eb --- /dev/null +++ b/components/findings/table/FindingsCard.tsx @@ -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 = ({ 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 ( + <> +
+

{CheckTitle}

+ +
+

Resource ID:

+ + {resourceId} + +

Resource ARN:

+

{resourceARN}

+

Check ID:

+ + {checkId} + +

Type:

+

{type}

+

Scan Time:

+

{scanTime}

+

Prowler Finding ID:

+ + {findingId} + +
+ + {details && ( +
+

Details:

+

{details}

+
+ )} + + {riskDetails && ( +
+

+ Risk: + {riskLink && ( + + )} +

+

{riskDetails}

+
+ )} + + {recommendationDetails && ( +
+
+ Recommendation: + {recommendationLink && ( + + )} +
+

{recommendationDetails}

+
+ )} + + {referenceInformation && ( +
+
+ Reference Information: + {referenceLink && ( + + )} +
+

{referenceInformation}

+
+ )} +
+ + ); +}; diff --git a/components/findings/SkeletonTableFindings.tsx b/components/findings/table/SkeletonTableFindings.tsx similarity index 100% rename from components/findings/SkeletonTableFindings.tsx rename to components/findings/table/SkeletonTableFindings.tsx diff --git a/components/index.ts b/components/index.ts index df78769815..8fb02340c9 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,2 +1,20 @@ -export * from "./findings/ColumnsFindings"; -export * from "./findings/SkeletonTableFindings"; +export * from "./findings/table/ColumnsFindings"; +export * from "./findings/table/DataTableFindings"; +export * from "./findings/table/SkeletonTableFindings"; +export * from "./providers/AddProvider"; +export * from "./providers/ButtonAddProvider"; +export * from "./providers/ButtonCheckConnectionProvider"; +export * from "./providers/ButtonDeleteProvider"; +export * from "./providers/CheckConnectionProvider"; +export * from "./providers/DateWithTime"; +export * from "./providers/ProviderInfo"; +export * from "./providers/ScanStatus"; +export * from "./providers/table/ColumnsProvider"; +export * from "./providers/table/DataTableProvider"; +export * from "./providers/table/SkeletonTableProvider"; +export * from "./ui/alert/Alert"; +export * from "./ui/header/Header"; +export * from "./ui/sidebar"; +export * from "./ui/table/StatusBadge"; +export * from "./ui/table/Table"; +export * from "./ui/toast"; diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index a24202685e..9fa480bd16 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -18,7 +18,7 @@ import { TableRow, } from "@/components/ui/table/Table"; -interface DataTableProps { +interface DataTableProviderProps { columns: ColumnDef[]; data: TData[]; } @@ -26,7 +26,7 @@ interface DataTableProps { export function DataTableProvider({ columns, data, -}: DataTableProps) { +}: DataTableProviderProps) { const table = useReactTable({ data, columns, diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 325e284f15..27d4a1af11 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -35,7 +35,7 @@ export const SidebarWrap = () => { return (
Date: Thu, 8 Aug 2024 10:51:52 -0500 Subject: [PATCH 2/3] chore(deps): bump django from 5.0.7 to 5.0.8 (#33) Bumps [django](https://github.com/django/django) from 5.0.7 to 5.0.8. - [Commits](https://github.com/django/django/compare/5.0.7...5.0.8) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e5e44befb9..8bca8ad202 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Django==5.0.7 +Django==5.0.8 djangorestframework==3.15.2 django-cors-headers==4.3.1 From b5b2e225ce7e6451982a6ba33839882dd693a4b7 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Thu, 8 Aug 2024 15:04:47 -0500 Subject: [PATCH 3/3] Findings page Status component (#34) * feat(findings): Severity and Status badge WIP * Remove SeverityBadge from PR --- components/findings/table/ColumnsFindings.tsx | 5 ++-- components/index.ts | 20 --------------- components/ui/table/StatusBadge.tsx | 25 +++++++++++++------ types/components.ts | 4 +-- 4 files changed, 23 insertions(+), 31 deletions(-) delete mode 100644 components/index.ts diff --git a/components/findings/table/ColumnsFindings.tsx b/components/findings/table/ColumnsFindings.tsx index afbd6c406f..7c8b6f2396 100644 --- a/components/findings/table/ColumnsFindings.tsx +++ b/components/findings/table/ColumnsFindings.tsx @@ -10,6 +10,7 @@ import { import { ColumnDef } from "@tanstack/react-table"; import { VerticalDotsIcon } from "@/components/icons"; +import { StatusBadge } from "@/components/ui"; import { FindingProps } from "@/types"; const getFindingsAttributes = (row: { original: FindingProps }) => { @@ -30,7 +31,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Severity", cell: ({ row }) => { const { severity } = getFindingsAttributes(row); - return

{severity}

; + return

{severity}

; }, }, { @@ -38,7 +39,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Status", cell: ({ row }) => { const { status } = getFindingsAttributes(row); - return

{status}

; + return ; }, }, { diff --git a/components/index.ts b/components/index.ts deleted file mode 100644 index 8fb02340c9..0000000000 --- a/components/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -export * from "./findings/table/ColumnsFindings"; -export * from "./findings/table/DataTableFindings"; -export * from "./findings/table/SkeletonTableFindings"; -export * from "./providers/AddProvider"; -export * from "./providers/ButtonAddProvider"; -export * from "./providers/ButtonCheckConnectionProvider"; -export * from "./providers/ButtonDeleteProvider"; -export * from "./providers/CheckConnectionProvider"; -export * from "./providers/DateWithTime"; -export * from "./providers/ProviderInfo"; -export * from "./providers/ScanStatus"; -export * from "./providers/table/ColumnsProvider"; -export * from "./providers/table/DataTableProvider"; -export * from "./providers/table/SkeletonTableProvider"; -export * from "./ui/alert/Alert"; -export * from "./ui/header/Header"; -export * from "./ui/sidebar"; -export * from "./ui/table/StatusBadge"; -export * from "./ui/table/Table"; -export * from "./ui/toast"; diff --git a/components/ui/table/StatusBadge.tsx b/components/ui/table/StatusBadge.tsx index a1dea200aa..c11e7271cb 100644 --- a/components/ui/table/StatusBadge.tsx +++ b/components/ui/table/StatusBadge.tsx @@ -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 = - { - 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 ( diff --git a/types/components.ts b/types/components.ts index 6741e118e2..209b6e4c0d 100644 --- a/types/components.ts +++ b/types/components.ts @@ -40,8 +40,8 @@ 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;