From ec23c4c97ab503f574bbc8001aa065970c4a0bd2 Mon Sep 17 00:00:00 2001 From: Alex Mylonas Date: Wed, 22 Apr 2020 15:06:12 +0300 Subject: [PATCH] Alert Title in Alert Tables + minor changes on AlertDetails & AlertTables (#707) * Add title in Alerts as required * Removed dedupString from alertSummary * isHash Util function and change for breadcrumb * Added updateTime * Updated AlertTables to a new format * Added dedupString to AlertDetails that missing rule * Fixed Fragments for Alerts * Added severity to AlertSummary * Updated Alert table on RuleDetails * Changed flex for Title * Reinstated licence for schema * Updated naming for regex * Changed flex for Title Column * Fixed broken Co-authored-by: Kostas Papageorgiou Co-authored-by: Aggelos Arvanitakis --- api/graphql/schema.graphql | 3 +- web/__generated__/schema.tsx | 6 ++- .../components/Breadcrumbs/Breadcrumbs.tsx | 6 ++- web/src/constants.ts | 2 + .../fragments/AlertDetailsFull.generated.ts | 2 + .../fragments/AlertDetailsFull.graphql | 1 + .../fragments/AlertSummaryFull.generated.ts | 40 +++++++++++++++ .../fragments/AlertSummaryFull.graphql | 9 ++++ web/src/helpers/utils.tsx | 7 +++ .../AlertDetailsInfo/AlertDetailsInfo.tsx | 51 +++++++++++++++++-- .../ListAlertsTable/ListAlertsTable.tsx | 9 +++- web/src/pages/ListAlerts/columns.tsx | 46 ++++++++--------- .../graphql/listAlerts.generated.ts | 25 ++------- .../ListAlerts/graphql/listAlerts.graphql | 8 +-- web/src/pages/RuleDetails/columns.tsx | 35 ++++++++++--- .../graphql/ruleDetails.generated.ts | 9 ++-- .../RuleDetails/graphql/ruleDetails.graphql | 3 +- 17 files changed, 184 insertions(+), 78 deletions(-) create mode 100644 web/src/graphql/fragments/AlertSummaryFull.generated.ts create mode 100644 web/src/graphql/fragments/AlertSummaryFull.graphql diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index c1d91a2865..68efb9bd5c 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -115,6 +115,7 @@ type LogIntegrationHealth { type AlertDetails { alertId: ID! ruleId: ID + title: String! creationTime: AWSDateTime! updateTime: AWSDateTime! eventsMatched: Int! @@ -132,10 +133,10 @@ type AlertSummary { alertId: String! creationTime: AWSDateTime! eventsMatched: Int! + title: String! updateTime: AWSDateTime! ruleId: String severity: String - dedupString: String! } input ListRulesInput { diff --git a/web/__generated__/schema.tsx b/web/__generated__/schema.tsx index f4e2dbc508..1f5e42de39 100644 --- a/web/__generated__/schema.tsx +++ b/web/__generated__/schema.tsx @@ -63,6 +63,7 @@ export type AlertDetails = { __typename?: 'AlertDetails'; alertId: Scalars['ID']; ruleId?: Maybe; + title: Scalars['String']; creationTime: Scalars['AWSDateTime']; updateTime: Scalars['AWSDateTime']; eventsMatched: Scalars['Int']; @@ -76,10 +77,10 @@ export type AlertSummary = { alertId: Scalars['String']; creationTime: Scalars['AWSDateTime']; eventsMatched: Scalars['Int']; + title: Scalars['String']; updateTime: Scalars['AWSDateTime']; ruleId?: Maybe; severity?: Maybe; - dedupString: Scalars['String']; }; export enum AnalysisTypeEnum { @@ -1329,6 +1330,7 @@ export type AlertDetailsResolvers< > = { alertId?: Resolver; ruleId?: Resolver, ParentType, ContextType>; + title?: Resolver; creationTime?: Resolver; updateTime?: Resolver; eventsMatched?: Resolver; @@ -1345,10 +1347,10 @@ export type AlertSummaryResolvers< alertId?: Resolver; creationTime?: Resolver; eventsMatched?: Resolver; + title?: Resolver; updateTime?: Resolver; ruleId?: Resolver, ParentType, ContextType>; severity?: Resolver, ParentType, ContextType>; - dedupString?: Resolver; __isTypeOf?: isTypeOfResolverFn; }; diff --git a/web/src/components/Breadcrumbs/Breadcrumbs.tsx b/web/src/components/Breadcrumbs/Breadcrumbs.tsx index 0028a90c5a..e226b2b0c2 100644 --- a/web/src/components/Breadcrumbs/Breadcrumbs.tsx +++ b/web/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -18,10 +18,12 @@ import { Box, Breadcrumbs as PounceBreadcrumbs } from 'pouncejs'; import * as React from 'react'; -import { isGuid, capitalize } from 'Helpers/utils'; +import { isGuid, capitalize, shortenId, isHash } from 'Helpers/utils'; import { Link as RRLink } from 'react-router-dom'; import useRouter from 'Hooks/useRouter'; +const transformBreadcrumbText = text => (isHash(text) ? shortenId(text) : text); + const Breadcrumbs: React.FC = () => { const { location: { pathname }, @@ -60,7 +62,7 @@ const Breadcrumbs: React.FC = () => { items={fragments} itemRenderer={item => ( - {item.text} + {transformBreadcrumbText(item.text)} )} /> diff --git a/web/src/constants.ts b/web/src/constants.ts index a2f219b857..02dedaa5a1 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -32,6 +32,8 @@ export const INCLUDE_UPPERCASE_REGEX = new RegExp('(?=.*[A-Z])'); export const INCLUDE_SPECIAL_CHAR_REGEX = new RegExp('[^\\d\\sA-Za-z]'); +export const CHECK_IF_HASH_REGEX = new RegExp('[a-f0-9]{32}'); + export const DEFAULT_POLICY_FUNCTION = 'def policy(resource):\n\t# Return False if the resource is non-compliant, which will trigger alerts/remediation.\n\treturn True'; diff --git a/web/src/graphql/fragments/AlertDetailsFull.generated.ts b/web/src/graphql/fragments/AlertDetailsFull.generated.ts index 7c3e23d5b4..70702ddb02 100644 --- a/web/src/graphql/fragments/AlertDetailsFull.generated.ts +++ b/web/src/graphql/fragments/AlertDetailsFull.generated.ts @@ -26,6 +26,7 @@ export type AlertDetailsFull = Pick< Types.AlertDetails, | 'alertId' | 'ruleId' + | 'title' | 'creationTime' | 'eventsMatched' | 'updateTime' @@ -38,6 +39,7 @@ export const AlertDetailsFull = gql` fragment AlertDetailsFull on AlertDetails { alertId ruleId + title creationTime eventsMatched updateTime diff --git a/web/src/graphql/fragments/AlertDetailsFull.graphql b/web/src/graphql/fragments/AlertDetailsFull.graphql index c823520ff2..60dccb080e 100644 --- a/web/src/graphql/fragments/AlertDetailsFull.graphql +++ b/web/src/graphql/fragments/AlertDetailsFull.graphql @@ -1,6 +1,7 @@ fragment AlertDetailsFull on AlertDetails { alertId ruleId + title creationTime eventsMatched updateTime diff --git a/web/src/graphql/fragments/AlertSummaryFull.generated.ts b/web/src/graphql/fragments/AlertSummaryFull.generated.ts new file mode 100644 index 0000000000..14a7612f4b --- /dev/null +++ b/web/src/graphql/fragments/AlertSummaryFull.generated.ts @@ -0,0 +1,40 @@ +/** + * Panther is a Cloud-Native SIEM for the Modern Security Team. + * Copyright (C) 2020 Panther Labs Inc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* eslint-disable import/order, import/no-duplicates, @typescript-eslint/no-unused-vars */ + +import * as Types from '../../../__generated__/schema'; + +import gql from 'graphql-tag'; + +export type AlertSummaryFull = Pick< + Types.AlertSummary, + 'alertId' | 'ruleId' | 'title' | 'severity' | 'creationTime' | 'eventsMatched' | 'updateTime' +>; + +export const AlertSummaryFull = gql` + fragment AlertSummaryFull on AlertSummary { + alertId + ruleId + title + severity + creationTime + eventsMatched + updateTime + } +`; diff --git a/web/src/graphql/fragments/AlertSummaryFull.graphql b/web/src/graphql/fragments/AlertSummaryFull.graphql new file mode 100644 index 0000000000..a1624915df --- /dev/null +++ b/web/src/graphql/fragments/AlertSummaryFull.graphql @@ -0,0 +1,9 @@ +fragment AlertSummaryFull on AlertSummary { + alertId + ruleId + title + severity + creationTime + eventsMatched + updateTime +} diff --git a/web/src/helpers/utils.tsx b/web/src/helpers/utils.tsx index d322e7bcd5..99aa0843be 100644 --- a/web/src/helpers/utils.tsx +++ b/web/src/helpers/utils.tsx @@ -31,6 +31,7 @@ import { INCLUDE_LOWERCASE_REGEX, INCLUDE_SPECIAL_CHAR_REGEX, INCLUDE_UPPERCASE_REGEX, + CHECK_IF_HASH_REGEX, } from 'Source/constants'; import mapValues from 'lodash-es/mapValues'; import sum from 'lodash-es/sum'; @@ -101,6 +102,12 @@ export const formatDatetime = (datetime: string) => { ); }; +/** Slice text to 7 characters, mostly used for hashIds */ +export const shortenId = (id: string) => id.slice(0, 7); + +/** Checking if string is a proper hash */ +export const isHash = (str: string) => CHECK_IF_HASH_REGEX.test(str); + /** Converts minutes integer to representative string i.e. 15 -> 15min, 120 -> 2h */ export const minutesToString = (minutes: number) => minutes < 60 ? `${minutes}min` : `${minutes / 60}h`; diff --git a/web/src/pages/AlertDetails/AlertDetailsInfo/AlertDetailsInfo.tsx b/web/src/pages/AlertDetails/AlertDetailsInfo/AlertDetailsInfo.tsx index 5d535dd417..62cf84dd22 100644 --- a/web/src/pages/AlertDetails/AlertDetailsInfo/AlertDetailsInfo.tsx +++ b/web/src/pages/AlertDetails/AlertDetailsInfo/AlertDetailsInfo.tsx @@ -24,6 +24,7 @@ import Linkify from 'Components/Linkify'; import { SEVERITY_COLOR_MAP } from 'Source/constants'; import { formatDatetime } from 'Helpers/utils'; import Panel from 'Components/Panel'; +import { Link as RRLink } from 'react-router-dom'; interface AlertDetailsInfoProps { alert: AlertDetails; @@ -44,7 +45,15 @@ const AlertDetailsInfo: React.FC = ({ alert, rule }) => { + + {alert.title} + + + + {alert.alertId} @@ -61,6 +70,14 @@ const AlertDetailsInfo: React.FC = ({ alert, rule }) => { DELETED + + + + {alert.dedupString} + + + + + + {formatDatetime(alert.updateTime)} + + @@ -80,7 +105,15 @@ const AlertDetailsInfo: React.FC = ({ alert, rule }) => { + + {alert.title} + + + + {alert.alertId} @@ -91,7 +124,7 @@ const AlertDetailsInfo: React.FC = ({ alert, rule }) => { RULE ORIGIN {rule ? ( - + {rule.displayName || rule.id} ) : ( @@ -163,6 +196,14 @@ const AlertDetailsInfo: React.FC = ({ alert, rule }) => { )} + + + + {alert.dedupString} + + - {alert.dedupString} + {formatDatetime(alert.updateTime)} diff --git a/web/src/pages/ListAlerts/ListAlertsTable/ListAlertsTable.tsx b/web/src/pages/ListAlerts/ListAlertsTable/ListAlertsTable.tsx index d4edc011f7..f354d0297c 100644 --- a/web/src/pages/ListAlerts/ListAlertsTable/ListAlertsTable.tsx +++ b/web/src/pages/ListAlerts/ListAlertsTable/ListAlertsTable.tsx @@ -21,19 +21,24 @@ import { AlertSummary } from 'Generated/schema'; import { generateEnumerationColumn } from 'Helpers/utils'; import { Table } from 'pouncejs'; import columns from 'Pages/ListAlerts/columns'; +import urls from 'Source/urls'; +import useRouter from 'Hooks/useRouter'; interface ListAlertsTableProps { - items?: Partial[]; + items?: AlertSummary[]; enumerationStartIndex?: number; } const ListAlertsTable: React.FC = ({ items }) => { + const { history } = useRouter(); + const enumeratedColumns = [generateEnumerationColumn(0), ...columns]; return ( - > + columns={enumeratedColumns} getItemKey={alert => alert.alertId} items={items} + onSelect={alert => history.push(urls.logAnalysis.alerts.details(alert.alertId))} /> ); }; diff --git a/web/src/pages/ListAlerts/columns.tsx b/web/src/pages/ListAlerts/columns.tsx index e219adb03a..5d7727e234 100644 --- a/web/src/pages/ListAlerts/columns.tsx +++ b/web/src/pages/ListAlerts/columns.tsx @@ -19,44 +19,33 @@ /* eslint-disable react/display-name */ import React from 'react'; -import { Badge, TableProps, Text, Link } from 'pouncejs'; +import { Badge, TableProps, Text } from 'pouncejs'; import { AlertSummary } from 'Generated/schema'; -import { formatDatetime } from 'Helpers/utils'; -import urls from 'Source/urls'; -import { Link as RRLink } from 'react-router-dom'; +import { formatDatetime, shortenId } from 'Helpers/utils'; import { SEVERITY_COLOR_MAP } from 'Source/constants'; // The columns that the associated table will show const columns = [ { - key: 'ruleId', + key: 'title', sortable: true, - header: 'Rule ID', - flex: '0 0 200px', - renderCell: item => ( - - {item.ruleId} - - ), + header: 'Title', + flex: '1 0 200px', }, + // Date needs to be formatted properly { - key: 'dedupString', + key: 'createdAt', sortable: true, - header: 'Dedup String', + header: 'Created At', flex: '0 0 200px', - }, - { - key: 'eventsMatched', - sortable: true, - header: 'Events Count', - flex: '1 0 50px', + renderCell: ({ creationTime }) => {formatDatetime(creationTime)}, }, // Render badges to showcase severity { key: 'severity', sortable: true, - flex: '1 0 100px', + flex: '0 0 150px', header: 'Severity', renderCell: ({ severity }) => { if (!severity) { @@ -70,14 +59,21 @@ const columns = [ }, }, - // Date needs to be formatted properly { - key: 'createdAt', + key: 'alertId', sortable: true, - header: 'Created At', + header: 'Alert ID', flex: '0 0 200px', - renderCell: ({ creationTime }) => {formatDatetime(creationTime)}, + renderCell: ({ alertId }) => {shortenId(alertId)}, }, + + { + key: 'eventsMatched', + sortable: true, + header: 'Events Count', + flex: '1 0 50px', + }, + // Date needs to be formatted properly { key: 'lastModified', diff --git a/web/src/pages/ListAlerts/graphql/listAlerts.generated.ts b/web/src/pages/ListAlerts/graphql/listAlerts.generated.ts index 84386efc9e..0a9560a471 100644 --- a/web/src/pages/ListAlerts/graphql/listAlerts.generated.ts +++ b/web/src/pages/ListAlerts/graphql/listAlerts.generated.ts @@ -20,6 +20,7 @@ import * as Types from '../../../../__generated__/schema'; +import { AlertSummaryFull } from '../../../graphql/fragments/AlertSummaryFull.generated'; import gql from 'graphql-tag'; import * as ApolloReactCommon from '@apollo/client'; import * as ApolloReactHooks from '@apollo/client'; @@ -31,20 +32,7 @@ export type ListAlertsVariables = { export type ListAlerts = { alerts?: Types.Maybe< Pick & { - alertSummaries: Array< - Types.Maybe< - Pick< - Types.AlertSummary, - | 'alertId' - | 'creationTime' - | 'eventsMatched' - | 'updateTime' - | 'ruleId' - | 'severity' - | 'dedupString' - > - > - >; + alertSummaries: Array>; } >; }; @@ -53,17 +41,12 @@ export const ListAlertsDocument = gql` query ListAlerts($input: ListAlertsInput) { alerts(input: $input) { alertSummaries { - alertId - creationTime - eventsMatched - updateTime - ruleId - severity - dedupString + ...AlertSummaryFull } lastEvaluatedKey } } + ${AlertSummaryFull} `; /** diff --git a/web/src/pages/ListAlerts/graphql/listAlerts.graphql b/web/src/pages/ListAlerts/graphql/listAlerts.graphql index 9346cbec9f..2a17066c28 100644 --- a/web/src/pages/ListAlerts/graphql/listAlerts.graphql +++ b/web/src/pages/ListAlerts/graphql/listAlerts.graphql @@ -1,13 +1,7 @@ query ListAlerts($input: ListAlertsInput) { alerts(input: $input) { alertSummaries { - alertId - creationTime - eventsMatched - updateTime - ruleId - severity - dedupString + ...AlertSummaryFull } lastEvaluatedKey } diff --git a/web/src/pages/RuleDetails/columns.tsx b/web/src/pages/RuleDetails/columns.tsx index 4aabdef3b0..21e3b6c090 100644 --- a/web/src/pages/RuleDetails/columns.tsx +++ b/web/src/pages/RuleDetails/columns.tsx @@ -21,23 +21,46 @@ import React from 'react'; import { Text, TableProps } from 'pouncejs'; import { AlertSummary } from 'Generated/schema'; -import { formatDatetime } from 'Helpers/utils'; +import { formatDatetime, shortenId } from 'Helpers/utils'; // The columns that the associated table will show const columns = [ // The name is the `id` of the alert { - key: 'alertId', - header: 'Alert', - flex: '2 0 450px', + key: 'title', + sortable: true, + header: 'Title', + flex: '1 0 200px', }, - { key: 'creationTime', header: 'Created At', - flex: '1 0 250px', + flex: '1 0 200px', renderCell: ({ creationTime }) => {formatDatetime(creationTime)}, }, + { + key: 'alertId', + sortable: true, + header: 'Alert ID', + flex: '0 0 200px', + renderCell: ({ alertId }) => {shortenId(alertId)}, + }, + + { + key: 'eventsMatched', + sortable: true, + header: 'Events Count', + flex: '1 0 50px', + }, + + // Date needs to be formatted properly + { + key: 'lastModified', + sortable: true, + header: 'Last Matched At', + flex: '0 0 200px', + renderCell: ({ updateTime }) => {formatDatetime(updateTime)}, + }, ] as TableProps['columns']; export default columns; diff --git a/web/src/pages/RuleDetails/graphql/ruleDetails.generated.ts b/web/src/pages/RuleDetails/graphql/ruleDetails.generated.ts index 375208e86b..a4624566f3 100644 --- a/web/src/pages/RuleDetails/graphql/ruleDetails.generated.ts +++ b/web/src/pages/RuleDetails/graphql/ruleDetails.generated.ts @@ -22,6 +22,7 @@ import * as Types from '../../../../__generated__/schema'; import { RuleBasic } from '../../../graphql/fragments/RuleBasic.generated'; import { RuleDates } from '../../../graphql/fragments/RuleDates.generated'; +import { AlertSummaryFull } from '../../../graphql/fragments/AlertSummaryFull.generated'; import gql from 'graphql-tag'; import * as ApolloReactCommon from '@apollo/client'; import * as ApolloReactHooks from '@apollo/client'; @@ -33,9 +34,7 @@ export type RuleDetailsVariables = { export type RuleDetails = { rule?: Types.Maybe; - alerts?: Types.Maybe<{ - alertSummaries: Array>>; - }>; + alerts?: Types.Maybe<{ alertSummaries: Array> }>; }; export const RuleDetailsDocument = gql` @@ -46,13 +45,13 @@ export const RuleDetailsDocument = gql` } alerts(input: $alertsForRuleInput) { alertSummaries { - alertId - creationTime + ...AlertSummaryFull } } } ${RuleBasic} ${RuleDates} + ${AlertSummaryFull} `; /** diff --git a/web/src/pages/RuleDetails/graphql/ruleDetails.graphql b/web/src/pages/RuleDetails/graphql/ruleDetails.graphql index 3c1e37cdcf..1a95e13104 100644 --- a/web/src/pages/RuleDetails/graphql/ruleDetails.graphql +++ b/web/src/pages/RuleDetails/graphql/ruleDetails.graphql @@ -5,8 +5,7 @@ query RuleDetails($ruleDetailsInput: GetRuleInput!, $alertsForRuleInput: ListAle } alerts(input: $alertsForRuleInput) { alertSummaries { - alertId - creationTime + ...AlertSummaryFull } } }