From a18ee2352856260511b40a0a632332fdfc5e0c60 Mon Sep 17 00:00:00 2001 From: Maksym Yadlovskyi Date: Wed, 11 Dec 2024 11:25:16 +0100 Subject: [PATCH] Add Entity data table to security events (#21091) * Add Entity data table to security events * Add event definitions * Refactoring of expand row content * fix alert filter * Add event definition filter * fix tsc errors * fix tests * add feature flag * fix linter errors * fix linter errors * fix linter errors * Fixing formatting. --------- Co-authored-by: Dennis Oelkers --- .../graylog2/featureflag/feature-flag.config | 3 + .../src/components/events/Constants.ts | 37 ++++---- .../src/components/events/ExpandedSection.tsx | 21 ++--- .../events/events/ColumnRenderers.tsx | 85 ++++++++----------- .../events/events/EventDefinitionLink.tsx | 42 +++++++++ .../events/events/EventDetailsTable.tsx | 49 +++++------ .../events/GeneralEventDetailsTable.tsx | 37 ++++++++ .../events/hooks/useNonDisplayedAttributes.ts | 37 ++++++++ .../src/components/events/fetchEvents.ts | 22 ++--- .../events/EventsList/DefaultDetails.tsx | 4 +- 10 files changed, 222 insertions(+), 115 deletions(-) create mode 100644 graylog2-web-interface/src/components/events/events/EventDefinitionLink.tsx create mode 100644 graylog2-web-interface/src/components/events/events/GeneralEventDetailsTable.tsx create mode 100644 graylog2-web-interface/src/components/events/events/hooks/useNonDisplayedAttributes.ts diff --git a/graylog2-server/src/main/resources/org/graylog2/featureflag/feature-flag.config b/graylog2-server/src/main/resources/org/graylog2/featureflag/feature-flag.config index 87d69ca85a8d..598fc99fe0bf 100644 --- a/graylog2-server/src/main/resources/org/graylog2/featureflag/feature-flag.config +++ b/graylog2-server/src/main/resources/org/graylog2/featureflag/feature-flag.config @@ -93,3 +93,6 @@ data_warehouse_search=off # Enable preview of input setup wizard setup_mode=off + +# Show security events in paginated entity data table +show_security_events_in_pedt=off diff --git a/graylog2-web-interface/src/components/events/Constants.ts b/graylog2-web-interface/src/components/events/Constants.ts index 74fe0cfbe7bc..289c607018c0 100644 --- a/graylog2-web-interface/src/components/events/Constants.ts +++ b/graylog2-web-interface/src/components/events/Constants.ts @@ -19,13 +19,7 @@ import EventDefinitionPriorityEnum from 'logic/alerts/EventDefinitionPriorityEnu export const EVENTS_ENTITY_TABLE_ID = 'events'; -export const detailsAttributes: Array = [ - { - id: 'id', - title: 'ID', - type: 'STRING', - sortable: true, - }, +export const commonEventAttributes: Array = [ { id: 'priority', title: 'Priority', @@ -58,6 +52,21 @@ export const detailsAttributes: Array = [ type: 'STRING', sortable: true, }, + { + id: 'key', + title: 'Key', + type: 'STRING', + sortable: true, + searchable: false, + }, + { + id: 'group_by_fields', + title: 'Group-By Fields', + sortable: false, + }, +]; +export const detailsAttributes: Array = [ + ...commonEventAttributes, { id: 'remediation_steps', title: 'Remediation Steps', @@ -71,8 +80,8 @@ export const detailsAttributes: Array = [ filterable: true, }, { - id: 'key', - title: 'Key', + id: 'id', + title: 'ID', type: 'STRING', sortable: true, searchable: true, @@ -84,13 +93,8 @@ export const detailsAttributes: Array = [ type: 'STRING', sortable: false, }, - { - id: 'group_by_fields', - title: 'Group-By Fields', - sortable: false, - }, ]; -export const additionalAttributes: Array = [ +export const eventsTableSpecificAttributes: Array = [ { id: 'message', title: 'Description', @@ -106,6 +110,9 @@ export const additionalAttributes: Array = [ filterable: true, filter_options: [{ value: 'false', title: 'Event' }, { value: 'true', title: 'Alert' }], }, +]; +export const additionalAttributes: Array = [ + ...eventsTableSpecificAttributes, ...detailsAttributes, ]; diff --git a/graylog2-web-interface/src/components/events/ExpandedSection.tsx b/graylog2-web-interface/src/components/events/ExpandedSection.tsx index 47c6b6683d3b..74ab165a4340 100644 --- a/graylog2-web-interface/src/components/events/ExpandedSection.tsx +++ b/graylog2-web-interface/src/components/events/ExpandedSection.tsx @@ -14,14 +14,13 @@ * along with this program. If not, see * . */ -import React, { useMemo } from 'react'; +import React from 'react'; -import useTableLayout from 'components/common/EntityDataTable/hooks/useTableLayout'; -import { useTableFetchContext } from 'components/common/PaginatedEntityTable'; -import type { Attribute } from 'stores/PaginationTypes'; +import type useTableLayout from 'components/common/EntityDataTable/hooks/useTableLayout'; import type { Event, EventsAdditionalData } from 'components/events/events/types'; import useMetaDataContext from 'components/common/EntityDataTable/hooks/useMetaDataContext'; -import EventDetailsTable from 'components/events/events/EventDetailsTable'; +import GeneralEventDetailsTable from 'components/events/events/GeneralEventDetailsTable'; +import useNonDisplayedAttributes from 'components/events/events/hooks/useNonDisplayedAttributes'; type Props = { defaultLayout: Parameters[0], @@ -30,20 +29,12 @@ type Props = { const ExpandedSection = ({ defaultLayout, event }: Props) => { const { meta } = useMetaDataContext(); - const { layoutConfig: { displayedAttributes }, isInitialLoading } = useTableLayout(defaultLayout); - const { attributes } = useTableFetchContext(); - const nonDisplayedAttributes = useMemo(() => { - if (isInitialLoading) return []; - - const displayedAttributesSet = new Set(displayedAttributes); - - return attributes.filter(({ id }) => !displayedAttributesSet.has(id)).map(({ id, title }: Attribute) => ({ id, title })); - }, [attributes, displayedAttributes, isInitialLoading]); + const nonDisplayedAttributes = useNonDisplayedAttributes(defaultLayout); if (!nonDisplayedAttributes.length) return No further details; - return ; + return ; }; export default ExpandedSection; diff --git a/graylog2-web-interface/src/components/events/events/ColumnRenderers.tsx b/graylog2-web-interface/src/components/events/events/ColumnRenderers.tsx index b4960fc71e88..90fb31fb9c0a 100644 --- a/graylog2-web-interface/src/components/events/events/ColumnRenderers.tsx +++ b/graylog2-web-interface/src/components/events/events/ColumnRenderers.tsx @@ -20,11 +20,8 @@ import { useMemo } from 'react'; import styled from 'styled-components'; import isEmpty from 'lodash/isEmpty'; -import { isPermitted } from 'util/PermissionsMixin'; import type { ColumnRenderers } from 'components/common/EntityDataTable'; import EventTypeLabel from 'components/events/events/EventTypeLabel'; -import { Link } from 'components/common/router'; -import Routes from 'routing/Routes'; import type { Event, EventsAdditionalData } from 'components/events/events/types'; import PriorityName from 'components/events/events/PriorityName'; import usePluginEntities from 'hooks/usePluginEntities'; @@ -32,26 +29,13 @@ import EventFields from 'components/events/events/EventFields'; import { MarkdownPreview } from 'components/common/MarkdownEditor'; import useExpandedSections from 'components/common/EntityDataTable/hooks/useExpandedSections'; import { Timestamp } from 'components/common'; -import useCurrentUser from 'hooks/useCurrentUser'; +import type { ColumnRenderersByAttribute, EntityBase } from 'components/common/EntityDataTable/types'; +import EventDefinitionLink from 'components/events/events/EventDefinitionLink'; const EventDefinitionRenderer = ({ eventDefinitionId, meta }: { eventDefinitionId: string, meta: EventsAdditionalData }) => { - const { permissions } = useCurrentUser(); - const { context: eventsContext } = meta; - const eventDefinitionContext = eventsContext?.event_definitions?.[eventDefinitionId]; - - if (!eventDefinitionContext) { - return {eventDefinitionId}; - } - - if (isPermitted(permissions, `eventdefinitions:edit:${eventDefinitionContext.id}`)) { - return ( - - {eventDefinitionContext.title} - - ); - } + const title = meta?.context?.event_definitions?.[eventDefinitionId]?.title; - return <>{eventDefinitionContext.title}; + return ; }; const EventDefinitionTypeRenderer = ({ type }: { type: string }) => { @@ -122,45 +106,48 @@ const TimeRangeRenderer = ({ eventData }: { eventData: Event}) => (eventData.tim No time range )); +export const getGeneralEventAttributeRenderers = (): ColumnRenderersByAttribute => ({ + message: { + minWidth: 300, + width: 0.5, + renderCell: (_message: string, event) => , + }, + key: { + renderCell: (_key: string) => {_key || No Key set for this Event.}, + staticWidth: 200, + }, + id: { + staticWidth: 300, + }, + alert: { + renderCell: (_alert: boolean) => , + staticWidth: 100, + }, + priority: { + renderCell: (_priority: number) => , + staticWidth: 100, + }, + event_definition_type: { + renderCell: (_type: string) => , + staticWidth: 200, + }, + group_by_fields: { + renderCell: (groupByFields: Record) => , + staticWidth: 400, + }, +}); const customColumnRenderers = (): ColumnRenderers => ({ attributes: { - message: { - minWidth: 300, - width: 0.5, - renderCell: (_message: string, event) => , - }, - key: { - renderCell: (_key: string) => {_key || No Key set for this Event.}, - staticWidth: 200, - }, - id: { - staticWidth: 300, - }, - alert: { - renderCell: (_alert: boolean) => , - staticWidth: 100, - }, + ...getGeneralEventAttributeRenderers(), event_definition_id: { minWidth: 300, width: 0.3, renderCell: (_eventDefinitionId: string, _, __, meta: EventsAdditionalData) => , }, - priority: { - renderCell: (_priority: number) => , - staticWidth: 100, - }, - event_definition_type: { - renderCell: (_type: string) => , - staticWidth: 200, - }, fields: { renderCell: (_fields: Record) => , staticWidth: 400, }, - group_by_fields: { - renderCell: (groupByFields: Record) => , - staticWidth: 400, - }, remediation_steps: { renderCell: (_, event: Event, __, meta: EventsAdditionalData) => , width: 0.3, @@ -172,6 +159,6 @@ const customColumnRenderers = (): ColumnRenderers => ({ }, }); -const useColumnRenderers = () => useMemo(customColumnRenderers, []); +const useColumnRenderers = () => useMemo>(customColumnRenderers, []); export default useColumnRenderers; diff --git a/graylog2-web-interface/src/components/events/events/EventDefinitionLink.tsx b/graylog2-web-interface/src/components/events/events/EventDefinitionLink.tsx new file mode 100644 index 000000000000..c3e51217ac32 --- /dev/null +++ b/graylog2-web-interface/src/components/events/events/EventDefinitionLink.tsx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import React from 'react'; + +import useCurrentUser from 'hooks/useCurrentUser'; +import { isPermitted } from 'util/PermissionsMixin'; +import { Link } from 'components/common/router'; +import Routes from 'routing/Routes'; + +const EventDefinitionLink = ({ title, id }: { title: string | undefined, id: string }) => { + const { permissions } = useCurrentUser(); + + if (!title) { + return {id}; + } + + if (isPermitted(permissions, `eventdefinitions:edit:${id}`)) { + return ( + + {title} + + ); + } + + return <>{title}; +}; + +export default EventDefinitionLink; diff --git a/graylog2-web-interface/src/components/events/events/EventDetailsTable.tsx b/graylog2-web-interface/src/components/events/events/EventDetailsTable.tsx index b07840061cfd..933bbdc5f4f3 100644 --- a/graylog2-web-interface/src/components/events/events/EventDetailsTable.tsx +++ b/graylog2-web-interface/src/components/events/events/EventDetailsTable.tsx @@ -19,38 +19,39 @@ import { styled } from 'styled-components'; import { Table } from 'components/bootstrap'; import type { Event, EventsAdditionalData } from 'components/events/events/types'; -import useColumnRenderers from 'components/events/events/ColumnRenderers'; +import type { ColumnRenderersByAttribute, EntityBase } from 'components/common/EntityDataTable/types'; +import DefaultColumnRenderers from 'components/common/EntityDataTable/DefaultColumnRenderers'; +import type { Attribute } from 'stores/PaginationTypes'; const TD = styled.td` white-space: nowrap; `; -type Props = { +type Props = { attributesList: Array<{ id: string, title: string}>, - event: Event, - meta: EventsAdditionalData, + event: T, + meta?: M | {}, + attributesRenderers: ColumnRenderersByAttribute } -const EventDetailsTable = ({ event, attributesList, meta }: Props) => { - const { attributes: attributesRenderers } = useColumnRenderers(); +const EventDetailsTable = ({ event, attributesList, meta = {}, attributesRenderers }: Props) => ( + + + {attributesList.map((attribute: Attribute) => { + const defaultTypeRenderer = DefaultColumnRenderers.types?.[attribute?.type]?.renderCell; + const typeRenderer = attributesRenderers?.types?.[attribute?.type]?.renderCell; + const renderCell = attributesRenderers[attribute.id]?.renderCell ?? typeRenderer ?? defaultTypeRenderer; + const value = event[attribute.id]; - return ( -
- - {attributesList.map((attribute) => { - const renderCell = attributesRenderers[attribute.id]?.renderCell; - const value = event[attribute.id]; - - return ( - - - - - ); - })} - -
{attribute.title}{renderCell ? renderCell(value, event, attribute, meta) : value}
- ); -}; + return ( + + {attribute.title} + {renderCell ? renderCell(value, event, attribute, meta) : value} + + ); + })} + + +); export default EventDetailsTable; diff --git a/graylog2-web-interface/src/components/events/events/GeneralEventDetailsTable.tsx b/graylog2-web-interface/src/components/events/events/GeneralEventDetailsTable.tsx new file mode 100644 index 000000000000..1f5d1791df0a --- /dev/null +++ b/graylog2-web-interface/src/components/events/events/GeneralEventDetailsTable.tsx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import React from 'react'; + +import type { Event, EventsAdditionalData } from 'components/events/events/types'; +import useColumnRenderers from 'components/events/events/ColumnRenderers'; +import EventDetailsTable from 'components/events/events/EventDetailsTable'; + +type Props = { + attributesList: Array<{ id: string, title: string}>, + event: Event, + meta: EventsAdditionalData, +} + +const GeneralEventDetailsTable = ({ event, attributesList, meta }: Props) => { + const { attributes: attributesRenderers } = useColumnRenderers(); + + return ( + event={event} meta={meta} attributesRenderers={attributesRenderers} attributesList={attributesList} /> + ); +}; + +export default GeneralEventDetailsTable; diff --git a/graylog2-web-interface/src/components/events/events/hooks/useNonDisplayedAttributes.ts b/graylog2-web-interface/src/components/events/events/hooks/useNonDisplayedAttributes.ts new file mode 100644 index 000000000000..d310caf191b9 --- /dev/null +++ b/graylog2-web-interface/src/components/events/events/hooks/useNonDisplayedAttributes.ts @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ + +import { useMemo } from 'react'; + +import { useTableFetchContext } from 'components/common/PaginatedEntityTable'; +import type { Attribute } from 'stores/PaginationTypes'; +import useTableLayout from 'components/common/EntityDataTable/hooks/useTableLayout'; + +const useNonDisplayedAttributes = (defaultLayout) => { + const { attributes } = useTableFetchContext(); + const { layoutConfig: { displayedAttributes }, isInitialLoading } = useTableLayout(defaultLayout); + + return useMemo>(() => { + if (isInitialLoading) return []; + + const displayedAttributesSet = new Set(displayedAttributes); + + return attributes.filter(({ id }) => !displayedAttributesSet.has(id)); + }, [attributes, displayedAttributes, isInitialLoading]); +}; + +export default useNonDisplayedAttributes; diff --git a/graylog2-web-interface/src/components/events/fetchEvents.ts b/graylog2-web-interface/src/components/events/fetchEvents.ts index 552e84ac02e0..5c23bfbd198f 100644 --- a/graylog2-web-interface/src/components/events/fetchEvents.ts +++ b/graylog2-web-interface/src/components/events/fetchEvents.ts @@ -73,6 +73,17 @@ const parseTimestampFilter = (timestamp: string | undefined) => { }; }; +export const parseTypeFilter = (alert: string) => { + switch (alert) { + case 'true': + return 'only'; + case 'false': + return 'exclude'; + default: + return 'include'; + } +}; + const parseFilters = (filters: UrlQueryFilters) => { const result: FiltersResult = { filter: {} }; @@ -98,16 +109,7 @@ const parseFilters = (filters: UrlQueryFilters) => { result.filter.priority = filters.get('priority'); } - switch (filters?.get('alert')?.[0]) { - case 'true': - result.filter.alerts = 'only'; - break; - case 'false': - result.filter.alerts = 'exclude'; - break; - default: - result.filter.alerts = 'include'; - } + result.filter.alerts = parseTypeFilter(filters?.get('alert')?.[0]); return result; }; diff --git a/graylog2-web-interface/src/views/components/widgets/events/EventsList/DefaultDetails.tsx b/graylog2-web-interface/src/views/components/widgets/events/EventsList/DefaultDetails.tsx index 52bf1675b2d4..20b09bc60945 100644 --- a/graylog2-web-interface/src/views/components/widgets/events/EventsList/DefaultDetails.tsx +++ b/graylog2-web-interface/src/views/components/widgets/events/EventsList/DefaultDetails.tsx @@ -17,7 +17,7 @@ import React, { useMemo } from 'react'; import type { Event, EventDefinitionContext } from 'components/events/events/types'; -import EventDetailsTable from 'components/events/events/EventDetailsTable'; +import GeneralEventDetailsTable from 'components/events/events/GeneralEventDetailsTable'; import { detailsAttributes } from 'components/events/Constants'; import DropdownButton from 'components/bootstrap/DropdownButton'; import useEventAction from 'components/events/events/hooks/useEventAction'; @@ -42,7 +42,7 @@ const DefaultDetails = ({ event, eventDefinitionContext }: Props) => { return ( <> - + {moreActions}