From 2539ce5aba40a078280c0a522105943d663432e0 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Tue, 8 Mar 2022 14:09:35 -0300 Subject: [PATCH 01/22] add Session Leader Table --- .../components/session_leader_table/index.tsx | 364 ++++++++++++++++++ .../components/session_leader_table/styles.ts | 32 ++ .../index.test.tsx | 118 ++++++ .../session_view_table_process_tree/index.tsx | 90 +++++ .../session_view/public/methods/index.tsx | 12 +- x-pack/plugins/session_view/public/plugin.ts | 3 +- x-pack/plugins/session_view/public/types.ts | 8 + 7 files changed, 625 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/session_view/public/components/session_leader_table/index.tsx create mode 100644 x-pack/plugins/session_view/public/components/session_leader_table/styles.ts create mode 100644 x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx create mode 100644 x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx diff --git a/x-pack/plugins/session_view/public/components/session_leader_table/index.tsx b/x-pack/plugins/session_view/public/components/session_leader_table/index.tsx new file mode 100644 index 0000000000000..fb8b0e53ac8b2 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/session_leader_table/index.tsx @@ -0,0 +1,364 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useCallback, useState } from 'react'; +import { + EuiCheckbox, + EuiButtonIcon, + EuiToolTip, + EuiPopover, + EuiContextMenuPanel, + EuiContextMenuItem, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useStyles } from './styles'; +import { SessionViewServices } from '../../types'; +import { PROCESS_EVENTS_INDEX } from '../../../common/constants'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { + ColumnHeaderOptions, + ActionProps, + CellValueElementProps, + RowRenderer, +} from '../../../../timelines/common'; +import { TGridState } from '../../../../timelines/public'; + +export interface SessionLeaderTableProps { + start: string; + end: string; + kuery?: string; + indexNames?: string[]; + defaultColumns?: ColumnHeaderOptions[]; + onStateChange?: (state: TGridState) => void; + setRefetch?: (ref: () => void) => void; + itemsPerPage?: number[]; + onExpand?: (props: ActionProps) => void; + onInspect?: (props: ActionProps) => void; + onAnalyzeSession?: (props: ActionProps) => void; + onOpenSessionViewer?: (props: ActionProps) => void; +} + +// Not sure why the timelines plugins doesn't have a type for the +// leading control columns props. So this is a hack to get that working for now +type RenderLeadingControllColumnProps = { + isSelectAllChecked: boolean; + onSelectAll: (props: { isSelected: boolean }) => void; +}; + +const STANDALONE_ID = 'standalone-t-grid'; + +const DEFAULT_COLUMNS: ColumnHeaderOptions[] = [ + { + columnHeaderType: 'not-filtered', + id: 'process.start', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'process.end', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'process.executable', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'user.name', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'process.interactive', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'process.pid', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'host.hostname', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'process.entry_leader.entry_meta.type', + initialWidth: 180, + isSortable: true, + }, + { + columnHeaderType: 'not-filtered', + id: 'process.entry_leader.entry_meta.source.ip', + initialWidth: 180, + isSortable: true, + }, +]; + +const DEFAULT_INDEX_NAMES = [PROCESS_EVENTS_INDEX]; +const DEFAULT_ITEMS_PER_PAGE = [10, 25, 50]; +const NO_ROW_RENDERERS: RowRenderer[] = []; + +export const SessionLeaderTable = (props: SessionLeaderTableProps) => { + const { + start, + end, + kuery = '', + indexNames = DEFAULT_INDEX_NAMES, + defaultColumns = DEFAULT_COLUMNS, + onStateChange = () => {}, + setRefetch = () => {}, + itemsPerPage = DEFAULT_ITEMS_PER_PAGE, + onExpand = () => {}, + onInspect = () => {}, + onAnalyzeSession = () => {}, + onOpenSessionViewer = () => {}, + } = props; + + const { timelines } = useKibana().services; + const [columns, setColumns] = useState(defaultColumns); + const [openPopoverId, setOpenPopoverId] = useState(''); + + const { rowButtonContainer, rowCheckbox } = useStyles(); + + const handleStateChange = useCallback( + (state: TGridState) => { + onStateChange(state); + const { timelineById } = state; + const { [STANDALONE_ID]: standAloneTGrid } = timelineById; + const { columns: newColumns } = standAloneTGrid; + setColumns(newColumns); + }, + [onStateChange] + ); + + const handleSetRefetch = (ref: () => void) => { + setRefetch(ref); + }; + + const handleMoreActionsClick = + (eventId: string = '') => + () => { + if (openPopoverId === eventId) { + setOpenPopoverId(''); + } else { + setOpenPopoverId(eventId); + } + }; + + const handleClosePopover = () => { + setOpenPopoverId(''); + }; + + // Must cast to any since the timelines plugin expects + // a React component. + const renderLeadingControlColumn = (renderProps: any) => { + const { isSelectAllChecked, onSelectAll } = renderProps as RenderLeadingControllColumnProps; + + return ( + onSelectAll({ isSelected: !isSelectAllChecked })} + /> + ); + }; + + const renderRowCheckbox = (actionProps: ActionProps) => { + const { ariaRowindex, eventId, checked, onRowSelected } = actionProps; + + const checkboxId = `row-${ariaRowindex}-checkbox`; + + return ( +
+ + onRowSelected({ + eventIds: [eventId], + isSelected: !checked, + }) + } + /> +
+ ); + }; + + const renderExpandButton = (actionProps: ActionProps) => { + return ( + + onExpand(actionProps)} + /> + + ); + }; + + const renderInspectButton = (actionProps: ActionProps) => { + return ( + + onInspect(actionProps)} + /> + + ); + }; + + const renderOpenMoreActionsButton = (actionProps: ActionProps) => { + const { eventId } = actionProps; + return ( + + } + > + { + onAnalyzeSession(actionProps); + handleClosePopover(); + }} + > + {i18n.translate('xpack.sessionView.sessionLeaderTable.analyzeSession', { + defaultMessage: 'Analyze Session', + })} + , + { + onOpenSessionViewer(actionProps); + handleClosePopover(); + }} + > + {i18n.translate('xpack.sessionView.sessionLeaderTable.openInSessionView', { + defaultMessage: 'Open in Session View', + })} + , + ]} + /> + + ); + }; + + const renderLeadingControlCell = (actionProps: ActionProps) => { + return ( +
+ {renderRowCheckbox(actionProps)} + {renderExpandButton(actionProps)} + {renderInspectButton(actionProps)} + {renderOpenMoreActionsButton(actionProps)} +
+ ); + }; + + const renderCellValue = ({ columnId, data }: CellValueElementProps) => { + const value = data.find((o) => o.field === columnId)?.value?.[0]; + return value || <>—; + }; + + const renderUnit = () => { + return 'events'; + }; + + const renderTimelinesTable = () => { + return timelines.getTGrid<'standalone'>({ + appId: 'session_view', + casesOwner: 'session_view_cases_owner', + casePermissions: null, + type: 'standalone', + columns, + deletedEventIds: [], + disabledCellActions: [], + start, + end, + filters: [], + entityType: 'events', + indexNames, + hasAlertsCrudPermissions: () => true, + itemsPerPageOptions: itemsPerPage, + loadingText: 'Loading text', + footerText: 'Session Entry Leaders', + onStateChange: handleStateChange, + query: { + query: `${ + kuery ? `${kuery} and ` : '' + }process.is_entry_leader: true and process.entry_leader.interactive: true`, + language: 'kuery', + }, + renderCellValue, + rowRenderers: NO_ROW_RENDERERS, + runtimeMappings: { + 'process.entity_id': { + type: 'keyword', + }, + 'process.entry_leader.entity_id': { + type: 'keyword', + }, + 'process.entry_leader.interactive': { + type: 'boolean', + }, + 'process.is_entry_leader': { + type: 'boolean', + script: { + source: + "emit(doc.containsKey('process.entry_leader.entity_id') && doc['process.entry_leader.entity_id'].size() > 0 && doc['process.entity_id'].value == doc['process.entry_leader.entity_id'].value)", + }, + }, + }, + setRefetch: handleSetRefetch, + sort: [], + filterStatus: 'open', + leadingControlColumns: [ + { + id: 'session-leader-table-leading-columns', + headerCellRender: renderLeadingControlColumn, + rowCellRender: renderLeadingControlCell, + width: 160, + }, + ], + trailingControlColumns: [], + unit: renderUnit, + }); + }; + + return
{renderTimelinesTable()}
; +}; + +SessionLeaderTable.displayName = 'SessionLeaderTable'; diff --git a/x-pack/plugins/session_view/public/components/session_leader_table/styles.ts b/x-pack/plugins/session_view/public/components/session_leader_table/styles.ts new file mode 100644 index 0000000000000..38518932160dd --- /dev/null +++ b/x-pack/plugins/session_view/public/components/session_leader_table/styles.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { useEuiTheme } from '@elastic/eui'; +import { CSSObject } from '@emotion/react'; + +export const useStyles = () => { + const { euiTheme } = useEuiTheme(); + + const cached = useMemo(() => { + const rowButtonContainer: CSSObject = { + display: 'flex', + alignItems: 'center', + }; + + const rowCheckbox: CSSObject = { + marginRight: euiTheme.size.base, + }; + + return { + rowButtonContainer, + rowCheckbox, + }; + }, [euiTheme]); + + return cached; +}; diff --git a/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx b/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx new file mode 100644 index 0000000000000..80fdd042a53d7 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { waitFor, fireEvent } from '@testing-library/react'; +import { SessionViewTableProcessTree } from '.'; +import { SessionLeaderTableProps } from '../session_leader_table'; +import { ActionProps } from '../../../../timelines/common'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; + +const mockActionProps: ActionProps = { + ariaRowindex: 0, + index: 0, + rowIndex: 0, + checked: false, + showCheckboxes: true, + columnId: 'test-column-id', + columnValues: 'test-column-values', + data: [], + ecsData: { + _id: 'test-ecs-data-id', + }, + eventId: 'test-event-id', + loadingEventIds: [], + onEventDetailsPanelOpened: () => {}, + onRowSelected: () => {}, + setEventsDeleted: () => {}, + setEventsLoading: () => {}, + timelineId: 'test-timeline-id', +}; + +jest.mock('../session_view/index.tsx', () => { + return { + SessionView: () => { + return
Mock
; + }, + }; +}); + +jest.mock('../session_leader_table/index.tsx', () => { + return { + SessionLeaderTable: (props: SessionLeaderTableProps) => { + const { onOpenSessionViewer = () => {} } = props; + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events +
onOpenSessionViewer(mockActionProps)} + > + Mock +
+ ); + }, + }; +}); + +describe('SessionViewTableProcessTree component', () => { + let render: () => ReturnType; + let renderResult: ReturnType; + let mockedContext: AppContextTestRender; + let mockedApi: AppContextTestRender['coreStart']['http']['get']; + + const waitForApiCall = () => waitFor(() => expect(mockedApi).toHaveBeenCalled()); + + beforeEach(() => { + mockedContext = createAppRootMockRenderer(); + mockedApi = mockedContext.coreStart.http.get; + }); + + describe('SessionViewTableProcessTree mounts', () => { + beforeEach(async () => { + mockedApi.mockResolvedValue({ + session_entry_leader: { + process: { + entity_id: 'test-entity-id', + }, + }, + }); + }); + + it('Renders session leader table initially', async () => { + renderResult = mockedContext.render(); + expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeTruthy(); + expect(renderResult.queryByTestId('sessionView:SessionView')).toBeNull(); + }); + + it('Switches to session view when the user picks a session', async () => { + renderResult = mockedContext.render(); + const sessionLeaderTable = renderResult.getByTestId('sessionView:SessionLeaderTable'); + fireEvent.click(sessionLeaderTable); + await waitForApiCall(); + + // Now that we fetched the entity id, session view should be visible + expect(renderResult.queryByTestId('sessionView:SessionView')).toBeTruthy(); + expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeNull(); + }); + + it('Close button works', async () => { + renderResult = mockedContext.render(); + const sessionLeaderTable = renderResult.getByTestId('sessionView:SessionLeaderTable'); + fireEvent.click(sessionLeaderTable); + await waitForApiCall(); + + expect(renderResult.queryByTestId('sessionView:SessionView')).toBeTruthy(); + expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeNull(); + + const closeButton = renderResult.getByTestId('sessionView:session-view-close-button'); + fireEvent.click(closeButton); + + expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeTruthy(); + expect(renderResult.queryByTestId('sessionView:SessionView')).toBeNull(); + }); + }); +}); diff --git a/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx b/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx new file mode 100644 index 0000000000000..cc64ae5ba9119 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useEffect } from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { useQuery } from 'react-query'; +import { CoreStart } from 'kibana/public'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { ActionProps } from '../../../../timelines/common'; +import { SESSION_ENTRY_LEADERS_ROUTE } from '../../../common/constants'; +import { SessionViewTableProcessTreeProps } from '../../types'; +import { SessionLeaderTable } from '../session_leader_table'; +import { SessionView } from '../session_view'; + +// Initializing react-query +const queryClient = new QueryClient(); + +export const SessionViewTableProcessTreeContent = (props: SessionViewTableProcessTreeProps) => { + const [eventId, setEventId] = useState(''); + const [selectedSessionEntityId, setSelectedSessionEntityId] = useState(''); + + const isFetchEnabled = !!(eventId && !selectedSessionEntityId); + + const { http } = useKibana().services; + const { data } = useQuery( + ['SessionViewTableProcessTreeEvent', eventId], + () => { + return http.get(SESSION_ENTRY_LEADERS_ROUTE, { + query: { + id: eventId, + }, + }); + }, + { + enabled: isFetchEnabled, + } + ); + + const handleCloseProcessTree = () => { + setEventId(''); + setSelectedSessionEntityId(''); + }; + + useEffect(() => { + if (data?.session_entry_leader?.process) { + setSelectedSessionEntityId(data.session_entry_leader.process.entity_id); + } + }, [data]); + + const handleOpenSessionViewer = (actionProps: ActionProps) => { + setEventId(actionProps.eventId); + }; + + if (selectedSessionEntityId) { + return ( +
+ + Close session viewer + + +
+ ); + } + + return ; +}; + +export const SessionViewTableProcessTree = (props: SessionViewTableProcessTreeProps) => { + return ( + + + + ); +}; + +SessionViewTableProcessTreeContent.displayName = 'SessionViewTableProcessTreeContent'; +SessionViewTableProcessTree.displayName = 'SessionViewTableProcessTree'; + +// eslint-disable-next-line import/no-default-export +export { SessionViewTableProcessTree as default }; diff --git a/x-pack/plugins/session_view/public/methods/index.tsx b/x-pack/plugins/session_view/public/methods/index.tsx index 560bb302ebabf..5735aba8b2a66 100644 --- a/x-pack/plugins/session_view/public/methods/index.tsx +++ b/x-pack/plugins/session_view/public/methods/index.tsx @@ -8,12 +8,22 @@ import React, { lazy, Suspense } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { QueryClient, QueryClientProvider } from 'react-query'; +import { SessionViewTableProcessTreeProps } from '../types'; // Initializing react-query const queryClient = new QueryClient(); +const SessionViewTableProcessTreeLazy = lazy( + () => import('../components/session_view_table_process_tree') +); const SessionViewLazy = lazy(() => import('../components/session_view')); - +export const getSessionViewTableProcessTreeLazy = (props: SessionViewTableProcessTreeProps) => { + return ( + }> + + + ); +}; export const getSessionViewLazy = (sessionEntityId: string) => { return ( diff --git a/x-pack/plugins/session_view/public/plugin.ts b/x-pack/plugins/session_view/public/plugin.ts index d25c95b00b2c6..5e3d2cd2e1851 100644 --- a/x-pack/plugins/session_view/public/plugin.ts +++ b/x-pack/plugins/session_view/public/plugin.ts @@ -7,13 +7,14 @@ import { CoreSetup, CoreStart, Plugin } from '../../../../src/core/public'; import { SessionViewServices } from './types'; -import { getSessionViewLazy } from './methods'; +import { getSessionViewTableProcessTreeLazy, getSessionViewLazy } from './methods'; export class SessionViewPlugin implements Plugin { public setup(core: CoreSetup) {} public start(core: CoreStart) { return { + getSessionViewTableProcessTree: getSessionViewTableProcessTreeLazy, getSessionView: (sessionEntityId: string) => getSessionViewLazy(sessionEntityId), }; } diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 2349b8423eb36..9150f333b8159 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -21,6 +21,14 @@ export interface EuiTabProps { prepend?: ReactNode; } +export interface SessionViewTableProcessTreeProps { + // expects an ISO 8601 string + start: string; + // expects an ISO 8601 string + end: string; + kuery?: string; +} + export interface DetailPanelProcess { id: string; start: string; From 130d176a400a54938035e65ecb539cd75b82ee7d Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Mon, 14 Mar 2022 16:52:04 -0300 Subject: [PATCH 02/22] WIP: Session Leader Table --- .../security_solution/common/constants.ts | 1 + .../common/types/timeline/index.ts | 2 + x-pack/plugins/security_solution/kibana.json | 3 +- .../sessions_viewer/default_headers.ts | 58 ++++++++ .../components/sessions_viewer/index.tsx | 33 +++++ .../sessions_viewer/sessions_table.tsx | 125 ++++++++++++++++++ .../sessions_viewer/translations.ts | 36 +++++ .../components/sessions_viewer/types.ts | 17 +++ .../public/common/mock/global_state.ts | 2 + .../public/hosts/pages/details/utils.ts | 1 + .../public/hosts/pages/hosts_tabs.tsx | 4 + .../public/hosts/pages/index.tsx | 6 +- .../public/hosts/pages/nav_tabs.tsx | 6 + .../public/hosts/pages/navigation/index.ts | 1 + .../pages/navigation/sessions_tab_body.tsx | 63 +++++++++ .../public/hosts/pages/translations.ts | 7 + .../public/hosts/store/helpers.test.ts | 16 +++ .../public/hosts/store/model.ts | 2 + .../public/hosts/store/reducer.ts | 8 ++ .../plugins/security_solution/public/types.ts | 3 +- .../plugins/security_solution/tsconfig.json | 3 +- x-pack/plugins/session_view/public/index.ts | 2 + x-pack/plugins/session_view/public/types.ts | 7 +- .../search_strategy/timeline/events/index.ts | 1 + 24 files changed, 401 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/default_headers.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts create mode 100644 x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 95132dc7894ff..341cdf6f0b175 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -114,6 +114,7 @@ export enum SecurityPageName { rules = 'rules', trustedApps = 'trusted_apps', uncommonProcesses = 'uncommon_processes', + sessions = 'sessions', } export const TIMELINES_PATH = '/timelines' as const; diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index a8a47d0ca9719..44c4fd51891da 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -314,6 +314,7 @@ export type TimelineWithoutExternalRefs = Omit = ({ + timelineId, + endDate, + entityType, + pageFilters, + startDate, +}) => { + return ( + + ); +}; + +SessionsViewComponent.displayName = 'SessionsViewComponent'; + +export const SessionsView = React.memo(SessionsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx new file mode 100644 index 0000000000000..28e7637c3719f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import type { Filter } from '@kbn/es-query'; +import { timelineActions } from '../../../timelines/store/timeline'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; +import { StatefulEventsViewer } from '../events_viewer'; +import { sessionsDefaultModel } from './default_headers'; +import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; +import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; +import * as i18n from './translations'; +import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions'; +import { useKibana } from '../../lib/kibana'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; +import type { EntityType } from '../../../../../timelines/common'; +import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; + +export interface OwnProps { + end: string; + id: string; + start: string; +} + +const defaultAlertsFilters: Filter[] = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'event.kind', + params: { + query: 'alert', + }, + }, + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match: { + 'event.kind': 'alert', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + }, +]; + +interface Props { + timelineId: TimelineIdLiteral; + endDate: string; + entityType?: EntityType; + startDate: string; + pageFilters?: Filter[]; +} + +const SessionsTableComponent: React.FC = ({ + timelineId, + endDate, + entityType = 'sessions', + startDate, + pageFilters = [], +}) => { + const dispatch = useDispatch(); + const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); + const { filterManager } = useKibana().services.data.query; + const ACTION_BUTTON_COUNT = 4; + + const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled'); + + useEffect(() => { + dispatch( + timelineActions.initializeTGridSettings({ + id: timelineId, + documentType: i18n.SESSIONS_DOCUMENT_TYPE, + filterManager, + defaultColumns: sessionsDefaultModel.columns.map((c) => + !tGridEnabled && c.initialWidth == null + ? { ...c, initialWidth: DEFAULT_COLUMN_MIN_WIDTH } + : c + ), + excludedRowRendererIds: sessionsDefaultModel.excludedRowRendererIds, + footerText: i18n.TOTAL_COUNT_OF_SESSIONS, + title: i18n.SESSIONS_TABLE_TITLE, + // TODO: avoid passing this through the store + }) + ); + }, [dispatch, filterManager, tGridEnabled, timelineId]); + + const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); + + return ( + + ); +}; + +export const SessionsTable = React.memo(SessionsTableComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts new file mode 100644 index 0000000000000..978d8612a47ce --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SESSIONS_DOCUMENT_TYPE = i18n.translate( + 'xpack.securitySolution.sessionsView.sessionsDocumentType', + { + defaultMessage: 'sessions', + } +); + +export const TOTAL_COUNT_OF_SESSIONS = i18n.translate( + 'xpack.securitySolution.sessionsView.totalCountOfSessions', + { + defaultMessage: 'sessions', + } +); + +export const SESSIONS_TABLE_TITLE = i18n.translate( + 'xpack.securitySolution.sessionsView.sessionsTableTitle', + { + defaultMessage: 'Sessions', + } +); + +export const ERROR_FETCHING_SESSIONS_DATA = i18n.translate( + 'xpack.securitySolution.sessionsView.errorFetchingSessionsData', + { + defaultMessage: 'Failed to query sessions data', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts new file mode 100644 index 0000000000000..09ad293b9c96a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { Filter } from '@kbn/es-query'; +import type { EntityType } from '../../../../../timelines/common'; +import { QueryTabBodyProps } from '../../../hosts/pages/navigation/types'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; + +export interface SessionsComponentsProps extends Pick { + timelineId: TimelineIdLiteral; + pageFilters: Filter[]; + defaultFilters?: Filter[]; + entityType?: EntityType; +} diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 7795e76c5fbbb..694d5f85c2984 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -87,6 +87,7 @@ export const mockGlobalState: State = { sort: { field: RiskScoreFields.riskScore, direction: Direction.desc }, severitySelection: [], }, + sessions: { activePage: 0, limit: 10 }, }, }, details: { @@ -108,6 +109,7 @@ export const mockGlobalState: State = { sort: { field: RiskScoreFields.riskScore, direction: Direction.desc }, severitySelection: [], }, + sessions: { activePage: 0, limit: 10 }, }, }, }, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts index 19975b6ad7abb..346ac7bf3517b 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts @@ -28,6 +28,7 @@ const TabNameMappedToI18nKey: Record = { [HostsTableType.events]: i18n.NAVIGATION_EVENTS_TITLE, [HostsTableType.alerts]: i18n.NAVIGATION_ALERTS_TITLE, [HostsTableType.risk]: i18n.NAVIGATION_HOST_RISK_TITLE, + [HostsTableType.sessions]: i18n.NAVIGATION_SESSIONS_TITLE, }; export const getBreadcrumbs = ( diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index 07979c289309a..0704329f3cdb8 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -22,6 +22,7 @@ import { AuthenticationsQueryTabBody, UncommonProcessQueryTabBody, EventsQueryTabBody, + SessionsTabBody, } from './navigation'; import { HostAlertsQueryTabBody } from './navigation/alerts_query_tab_body'; @@ -101,6 +102,9 @@ export const HostsTabs = memo( + + + ); } diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx index 4eb8175aea4cb..268eecb8ac238 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx @@ -23,7 +23,8 @@ const getHostsTabPath = () => `${HostsTableType.anomalies}|` + `${HostsTableType.events}|` + `${HostsTableType.risk}|` + - `${HostsTableType.alerts})`; + `${HostsTableType.alerts}|` + + `${HostsTableType.sessions})`; const getHostDetailsTabPath = () => `${hostDetailsPagePath}/:tabName(` + @@ -32,7 +33,8 @@ const getHostDetailsTabPath = () => `${HostsTableType.anomalies}|` + `${HostsTableType.events}|` + `${HostsTableType.risk}|` + - `${HostsTableType.alerts})`; + `${HostsTableType.alerts}|` + + `${HostsTableType.sessions})`; export const HostsContainer = React.memo(() => { return ( diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx index 0d8a5e252bfbb..30ad5e53abd2f 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx @@ -61,6 +61,12 @@ export const navTabsHosts = ( href: getTabsOnHostsUrl(HostsTableType.risk), disabled: false, }, + [HostsTableType.sessions]: { + id: HostsTableType.sessions, + name: i18n.NAVIGATION_SESSIONS_TITLE, + href: getTabsOnHostsUrl(HostsTableType.sessions), + disabled: false, + }, }; if (!hasMlUserPermissions) { diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts b/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts index 6b74418549164..405af6d3ad202 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts @@ -12,3 +12,4 @@ export * from './uncommon_process_query_tab_body'; export * from './alerts_query_tab_body'; export * from './host_risk_tab_body'; export * from './host_risk_score_tab_body'; +export * from './sessions_tab_body'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx new file mode 100644 index 0000000000000..3025a1c7e838f --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import type { Filter } from '@kbn/es-query'; +import { TimelineId } from '../../../../common/types/timeline'; +import { SessionsView } from '../../../common/components/sessions_viewer'; +import { AlertsComponentQueryProps } from './types'; + +const filterHostData: Filter[] = [ + { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + exists: { + field: 'host.name', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + meta: { + alias: '', + disabled: false, + key: 'bool', + negate: false, + type: 'custom', + value: + '{"query": {"bool": {"filter": [{"bool": {"should": [{"exists": {"field": "host.name"}}],"minimum_should_match": 1}}]}}}', + }, + }, +]; +export const SessionsTabBody = React.memo((alertsProps: AlertsComponentQueryProps) => { + const { pageFilters, ...rest } = alertsProps; + const hostPageFilters = useMemo( + () => (pageFilters != null ? [...filterHostData, ...pageFilters] : filterHostData), + [pageFilters] + ); + + return ( + + ); +}); + +SessionsTabBody.displayName = 'SessionsTabBody'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/translations.ts b/x-pack/plugins/security_solution/public/hosts/pages/translations.ts index c2ece6c40e701..8b92ef035405f 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/translations.ts @@ -64,6 +64,13 @@ export const NAVIGATION_HOST_RISK_TITLE = i18n.translate( } ); +export const NAVIGATION_SESSIONS_TITLE = i18n.translate( + 'xpack.securitySolution.hosts.navigation.sessionsTitle', + { + defaultMessage: 'Sessions', + } +); + export const ERROR_FETCHING_AUTHENTICATIONS_DATA = i18n.translate( 'xpack.securitySolution.hosts.navigaton.matrixHistogram.errorFetchingAuthenticationsData', { diff --git a/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts b/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts index 64e4d9088abd7..0e233166044a5 100644 --- a/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts @@ -45,6 +45,10 @@ export const mockHostsState: HostsModel = { }, severitySelection: [], }, + [HostsTableType.sessions]: { + activePage: 4, + limit: DEFAULT_TABLE_LIMIT, + }, }, }, details: { @@ -81,6 +85,10 @@ export const mockHostsState: HostsModel = { }, severitySelection: [], }, + [HostsTableType.sessions]: { + activePage: 4, + limit: DEFAULT_TABLE_LIMIT, + }, }, }, }; @@ -121,6 +129,10 @@ describe('Hosts redux store', () => { field: 'risk_stats.risk_score', }, }, + [HostsTableType.sessions]: { + activePage: 0, + limit: 10, + }, }); }); @@ -158,6 +170,10 @@ describe('Hosts redux store', () => { field: 'risk_stats.risk_score', }, }, + [HostsTableType.sessions]: { + activePage: 0, + limit: 10, + }, }); }); }); diff --git a/x-pack/plugins/security_solution/public/hosts/store/model.ts b/x-pack/plugins/security_solution/public/hosts/store/model.ts index 090a469c5fb76..09bec3643c4d4 100644 --- a/x-pack/plugins/security_solution/public/hosts/store/model.ts +++ b/x-pack/plugins/security_solution/public/hosts/store/model.ts @@ -25,6 +25,7 @@ export enum HostsTableType { anomalies = 'anomalies', alerts = 'externalAlerts', risk = 'hostRisk', + sessions = 'sessions', } export interface BasicQueryPaginated { @@ -50,6 +51,7 @@ export interface Queries { [HostsTableType.anomalies]: null | undefined; [HostsTableType.alerts]: BasicQueryPaginated; [HostsTableType.risk]: HostRiskScoreQuery; + [HostsTableType.sessions]: BasicQueryPaginated; } export interface GenericHostsModel { diff --git a/x-pack/plugins/security_solution/public/hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/hosts/store/reducer.ts index f413607b85e1c..1ea20bf5e2871 100644 --- a/x-pack/plugins/security_solution/public/hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/hosts/store/reducer.ts @@ -62,6 +62,10 @@ export const initialHostsState: HostsState = { }, severitySelection: [], }, + [HostsTableType.sessions]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, }, }, details: { @@ -98,6 +102,10 @@ export const initialHostsState: HostsState = { }, severitySelection: [], }, + [HostsTableType.sessions]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, }, }, }; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index d43f8752c9122..799eca2390846 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -25,10 +25,10 @@ import type { import type { CasesUiStart } from '../../cases/public'; import type { SecurityPluginSetup } from '../../security/public'; import type { TimelinesUIStart } from '../../timelines/public'; +import type { SessionViewUIStart } from '../../session_view/public'; import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { MlPluginSetup, MlPluginStart } from '../../ml/public'; - import type { Detections } from './detections'; import type { Cases } from './cases'; import type { Exceptions } from './exceptions'; @@ -69,6 +69,7 @@ export interface StartPlugins { ml?: MlPluginStart; spaces?: SpacesPluginStart; dataViewFieldEditor: IndexPatternFieldEditorStart; + sessionView: SessionViewUIStart; } export type StartServices = CoreStart & diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index d518eaf7f8243..8d94be5c9d081 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -41,6 +41,7 @@ { "path": "../ml/tsconfig.json" }, { "path": "../spaces/tsconfig.json" }, { "path": "../security/tsconfig.json" }, - { "path": "../timelines/tsconfig.json" } + { "path": "../timelines/tsconfig.json" }, + { "path": "../session_view/tsconfig.json" } ] } diff --git a/x-pack/plugins/session_view/public/index.ts b/x-pack/plugins/session_view/public/index.ts index 90043e9a691dc..3d66164bfec4f 100644 --- a/x-pack/plugins/session_view/public/index.ts +++ b/x-pack/plugins/session_view/public/index.ts @@ -7,6 +7,8 @@ import { SessionViewPlugin } from './plugin'; +export type { SessionViewUIStart } from './types'; + export function plugin() { return new SessionViewPlugin(); } diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 9150f333b8159..4ebe7919fb350 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ReactNode } from 'react'; +import { ReactElement, ReactNode } from 'react'; import { CoreStart } from '../../../../src/core/public'; import { TimelinesUIStart } from '../../timelines/public'; @@ -12,6 +12,11 @@ export type SessionViewServices = CoreStart & { timelines: TimelinesUIStart; }; +export interface SessionViewUIStart { + getSessionViewTableProcessTree: (props: any) => ReactElement; + getSessionView: (sessionEntityId: string) => ReactElement; +} + export interface EuiTabProps { id: string; name: string; diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts index 6b204224d3d5d..f7df9bfde3d05 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts @@ -20,5 +20,6 @@ export enum TimelineEventsQueries { export const EntityType = { ALERTS: 'alerts', EVENTS: 'events', + SESSIONS: 'sessions', } as const; export type EntityType = typeof EntityType[keyof typeof EntityType]; From e3e35016d15726d1f5d370bf3eca5fa4bd75ff0b Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 16 Mar 2022 16:15:53 -0300 Subject: [PATCH 03/22] sessions search strategy --- .../server/search_strategy/timeline/index.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts index 24e0edb5ddcc0..d04deccb71720 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts @@ -64,6 +64,14 @@ export const timelineSearchStrategyProvider = ({ }) ); }; + +const timelineSessionsSearchStrategy = ({ + es, + request, + options, + deps, + queryFactory, +}: { + es: ISearchStrategy; + request: TimelineStrategyRequestType; + options: ISearchOptions; + deps: SearchStrategyDependencies; + queryFactory: TimelineFactory; +}) => { + const indices = 'logs-endpoint.events.process-default'; + + const runtimeMappings = { + 'process.entity_id': { + type: 'keyword', + }, + 'process.entry_leader.entity_id': { + type: 'keyword', + }, + 'process.is_entry_leader': { + type: 'boolean', + script: { + source: + "emit(doc.containsKey('process.entry_leader.entity_id') && doc['process.entry_leader.entity_id'].size() > 0 && doc['process.entity_id'].value == doc['process.entry_leader.entity_id'].value)", + }, + }, + }; + + const requestSessionLeaders = { + ...request, + defaultIndex: indices, + indexName: indices, + }; + + const dsl = queryFactory.buildDsl(requestSessionLeaders); + + const params = { ...dsl, runtime_mappings: runtimeMappings }; + + return es.search({ ...requestSessionLeaders, params }, options, deps).pipe( + map((response) => { + return { + ...response, + rawResponse: shimHitsTotal(response.rawResponse, options), + }; + }), + mergeMap((esSearchRes) => queryFactory.parse(requestSessionLeaders, esSearchRes)) + ); +}; From e3eb4bca461d788d5e7375dc55c4331154195bf4 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 16 Mar 2022 16:16:48 -0300 Subject: [PATCH 04/22] session viewer component --- .../sessions_viewer/sessions_table.tsx | 62 +++++-------------- .../sessions_viewer/translations.ts | 20 +----- 2 files changed, 20 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx index 28e7637c3719f..5ba66bfda8d1c 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx @@ -5,21 +5,15 @@ * 2.0. */ -import React, { useEffect, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { useMemo } from 'react'; import type { Filter } from '@kbn/es-query'; -import { timelineActions } from '../../../timelines/store/timeline'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { StatefulEventsViewer } from '../events_viewer'; import { sessionsDefaultModel } from './default_headers'; import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; import * as i18n from './translations'; -import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions'; -import { useKibana } from '../../lib/kibana'; import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; -import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; import type { EntityType } from '../../../../../timelines/common'; import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; @@ -29,18 +23,8 @@ export interface OwnProps { start: string; } -const defaultAlertsFilters: Filter[] = [ +const defaultSessionsFilters: Filter[] = [ { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'event.kind', - params: { - query: 'alert', - }, - }, query: { bool: { filter: [ @@ -49,7 +33,7 @@ const defaultAlertsFilters: Filter[] = [ should: [ { match: { - 'event.kind': 'alert', + 'process.is_entry_leader': true, }, }, ], @@ -59,6 +43,14 @@ const defaultAlertsFilters: Filter[] = [ ], }, }, + meta: { + alias: null, + disabled: false, + key: 'process.is_entry_leader', + negate: false, + params: {}, + type: 'boolean', + }, }, ]; @@ -77,39 +69,18 @@ const SessionsTableComponent: React.FC = ({ startDate, pageFilters = [], }) => { - const dispatch = useDispatch(); - const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); - const { filterManager } = useKibana().services.data.query; + const sessionsFilter = useMemo(() => [...defaultSessionsFilters, ...pageFilters], [pageFilters]); const ACTION_BUTTON_COUNT = 4; - const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled'); - - useEffect(() => { - dispatch( - timelineActions.initializeTGridSettings({ - id: timelineId, - documentType: i18n.SESSIONS_DOCUMENT_TYPE, - filterManager, - defaultColumns: sessionsDefaultModel.columns.map((c) => - !tGridEnabled && c.initialWidth == null - ? { ...c, initialWidth: DEFAULT_COLUMN_MIN_WIDTH } - : c - ), - excludedRowRendererIds: sessionsDefaultModel.excludedRowRendererIds, - footerText: i18n.TOTAL_COUNT_OF_SESSIONS, - title: i18n.SESSIONS_TABLE_TITLE, - // TODO: avoid passing this through the store - }) - ); - }, [dispatch, filterManager, tGridEnabled, timelineId]); - const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); + const unit = (c: number) => + c > 1 ? i18n.TOTAL_COUNT_OF_SESSIONS : i18n.SINGLE_COUNT_OF_SESSIONS; + return ( = ({ rowRenderers={defaultRowRenderers} scopeId={SourcererScopeName.default} start={startDate} + unit={unit} /> ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts index 978d8612a47ce..606ae2b46fc6a 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts @@ -7,13 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const SESSIONS_DOCUMENT_TYPE = i18n.translate( - 'xpack.securitySolution.sessionsView.sessionsDocumentType', - { - defaultMessage: 'sessions', - } -); - export const TOTAL_COUNT_OF_SESSIONS = i18n.translate( 'xpack.securitySolution.sessionsView.totalCountOfSessions', { @@ -21,16 +14,9 @@ export const TOTAL_COUNT_OF_SESSIONS = i18n.translate( } ); -export const SESSIONS_TABLE_TITLE = i18n.translate( - 'xpack.securitySolution.sessionsView.sessionsTableTitle', - { - defaultMessage: 'Sessions', - } -); - -export const ERROR_FETCHING_SESSIONS_DATA = i18n.translate( - 'xpack.securitySolution.sessionsView.errorFetchingSessionsData', +export const SINGLE_COUNT_OF_SESSIONS = i18n.translate( + 'xpack.securitySolution.sessionsView.singleCountOfSessions', { - defaultMessage: 'Failed to query sessions data', + defaultMessage: 'session', } ); From 89d8d318adbc85c0608142f232d14f5c4afae4ec Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 16 Mar 2022 16:17:12 -0300 Subject: [PATCH 05/22] add timelineId --- x-pack/plugins/timelines/common/types/timeline/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/timelines/common/types/timeline/index.ts b/x-pack/plugins/timelines/common/types/timeline/index.ts index a6c8ed1b74bff..6b38a0fd4855a 100644 --- a/x-pack/plugins/timelines/common/types/timeline/index.ts +++ b/x-pack/plugins/timelines/common/types/timeline/index.ts @@ -312,6 +312,7 @@ export type SavedTimelineNote = runtimeTypes.TypeOf Date: Wed, 16 Mar 2022 16:17:42 -0300 Subject: [PATCH 06/22] remove session leader table --- .../components/session_leader_table/index.tsx | 364 ------------------ .../components/session_leader_table/styles.ts | 32 -- .../index.test.tsx | 118 ------ .../session_view_table_process_tree/index.tsx | 90 ----- .../session_view/public/methods/index.tsx | 11 - x-pack/plugins/session_view/public/plugin.ts | 3 +- x-pack/plugins/session_view/public/types.ts | 13 +- 7 files changed, 2 insertions(+), 629 deletions(-) delete mode 100644 x-pack/plugins/session_view/public/components/session_leader_table/index.tsx delete mode 100644 x-pack/plugins/session_view/public/components/session_leader_table/styles.ts delete mode 100644 x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx delete mode 100644 x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx diff --git a/x-pack/plugins/session_view/public/components/session_leader_table/index.tsx b/x-pack/plugins/session_view/public/components/session_leader_table/index.tsx deleted file mode 100644 index fb8b0e53ac8b2..0000000000000 --- a/x-pack/plugins/session_view/public/components/session_leader_table/index.tsx +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React, { useCallback, useState } from 'react'; -import { - EuiCheckbox, - EuiButtonIcon, - EuiToolTip, - EuiPopover, - EuiContextMenuPanel, - EuiContextMenuItem, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useStyles } from './styles'; -import { SessionViewServices } from '../../types'; -import { PROCESS_EVENTS_INDEX } from '../../../common/constants'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { - ColumnHeaderOptions, - ActionProps, - CellValueElementProps, - RowRenderer, -} from '../../../../timelines/common'; -import { TGridState } from '../../../../timelines/public'; - -export interface SessionLeaderTableProps { - start: string; - end: string; - kuery?: string; - indexNames?: string[]; - defaultColumns?: ColumnHeaderOptions[]; - onStateChange?: (state: TGridState) => void; - setRefetch?: (ref: () => void) => void; - itemsPerPage?: number[]; - onExpand?: (props: ActionProps) => void; - onInspect?: (props: ActionProps) => void; - onAnalyzeSession?: (props: ActionProps) => void; - onOpenSessionViewer?: (props: ActionProps) => void; -} - -// Not sure why the timelines plugins doesn't have a type for the -// leading control columns props. So this is a hack to get that working for now -type RenderLeadingControllColumnProps = { - isSelectAllChecked: boolean; - onSelectAll: (props: { isSelected: boolean }) => void; -}; - -const STANDALONE_ID = 'standalone-t-grid'; - -const DEFAULT_COLUMNS: ColumnHeaderOptions[] = [ - { - columnHeaderType: 'not-filtered', - id: 'process.start', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'process.end', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'process.executable', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'user.name', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'process.interactive', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'process.pid', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'host.hostname', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'process.entry_leader.entry_meta.type', - initialWidth: 180, - isSortable: true, - }, - { - columnHeaderType: 'not-filtered', - id: 'process.entry_leader.entry_meta.source.ip', - initialWidth: 180, - isSortable: true, - }, -]; - -const DEFAULT_INDEX_NAMES = [PROCESS_EVENTS_INDEX]; -const DEFAULT_ITEMS_PER_PAGE = [10, 25, 50]; -const NO_ROW_RENDERERS: RowRenderer[] = []; - -export const SessionLeaderTable = (props: SessionLeaderTableProps) => { - const { - start, - end, - kuery = '', - indexNames = DEFAULT_INDEX_NAMES, - defaultColumns = DEFAULT_COLUMNS, - onStateChange = () => {}, - setRefetch = () => {}, - itemsPerPage = DEFAULT_ITEMS_PER_PAGE, - onExpand = () => {}, - onInspect = () => {}, - onAnalyzeSession = () => {}, - onOpenSessionViewer = () => {}, - } = props; - - const { timelines } = useKibana().services; - const [columns, setColumns] = useState(defaultColumns); - const [openPopoverId, setOpenPopoverId] = useState(''); - - const { rowButtonContainer, rowCheckbox } = useStyles(); - - const handleStateChange = useCallback( - (state: TGridState) => { - onStateChange(state); - const { timelineById } = state; - const { [STANDALONE_ID]: standAloneTGrid } = timelineById; - const { columns: newColumns } = standAloneTGrid; - setColumns(newColumns); - }, - [onStateChange] - ); - - const handleSetRefetch = (ref: () => void) => { - setRefetch(ref); - }; - - const handleMoreActionsClick = - (eventId: string = '') => - () => { - if (openPopoverId === eventId) { - setOpenPopoverId(''); - } else { - setOpenPopoverId(eventId); - } - }; - - const handleClosePopover = () => { - setOpenPopoverId(''); - }; - - // Must cast to any since the timelines plugin expects - // a React component. - const renderLeadingControlColumn = (renderProps: any) => { - const { isSelectAllChecked, onSelectAll } = renderProps as RenderLeadingControllColumnProps; - - return ( - onSelectAll({ isSelected: !isSelectAllChecked })} - /> - ); - }; - - const renderRowCheckbox = (actionProps: ActionProps) => { - const { ariaRowindex, eventId, checked, onRowSelected } = actionProps; - - const checkboxId = `row-${ariaRowindex}-checkbox`; - - return ( -
- - onRowSelected({ - eventIds: [eventId], - isSelected: !checked, - }) - } - /> -
- ); - }; - - const renderExpandButton = (actionProps: ActionProps) => { - return ( - - onExpand(actionProps)} - /> - - ); - }; - - const renderInspectButton = (actionProps: ActionProps) => { - return ( - - onInspect(actionProps)} - /> - - ); - }; - - const renderOpenMoreActionsButton = (actionProps: ActionProps) => { - const { eventId } = actionProps; - return ( - - } - > - { - onAnalyzeSession(actionProps); - handleClosePopover(); - }} - > - {i18n.translate('xpack.sessionView.sessionLeaderTable.analyzeSession', { - defaultMessage: 'Analyze Session', - })} - , - { - onOpenSessionViewer(actionProps); - handleClosePopover(); - }} - > - {i18n.translate('xpack.sessionView.sessionLeaderTable.openInSessionView', { - defaultMessage: 'Open in Session View', - })} - , - ]} - /> - - ); - }; - - const renderLeadingControlCell = (actionProps: ActionProps) => { - return ( -
- {renderRowCheckbox(actionProps)} - {renderExpandButton(actionProps)} - {renderInspectButton(actionProps)} - {renderOpenMoreActionsButton(actionProps)} -
- ); - }; - - const renderCellValue = ({ columnId, data }: CellValueElementProps) => { - const value = data.find((o) => o.field === columnId)?.value?.[0]; - return value || <>—; - }; - - const renderUnit = () => { - return 'events'; - }; - - const renderTimelinesTable = () => { - return timelines.getTGrid<'standalone'>({ - appId: 'session_view', - casesOwner: 'session_view_cases_owner', - casePermissions: null, - type: 'standalone', - columns, - deletedEventIds: [], - disabledCellActions: [], - start, - end, - filters: [], - entityType: 'events', - indexNames, - hasAlertsCrudPermissions: () => true, - itemsPerPageOptions: itemsPerPage, - loadingText: 'Loading text', - footerText: 'Session Entry Leaders', - onStateChange: handleStateChange, - query: { - query: `${ - kuery ? `${kuery} and ` : '' - }process.is_entry_leader: true and process.entry_leader.interactive: true`, - language: 'kuery', - }, - renderCellValue, - rowRenderers: NO_ROW_RENDERERS, - runtimeMappings: { - 'process.entity_id': { - type: 'keyword', - }, - 'process.entry_leader.entity_id': { - type: 'keyword', - }, - 'process.entry_leader.interactive': { - type: 'boolean', - }, - 'process.is_entry_leader': { - type: 'boolean', - script: { - source: - "emit(doc.containsKey('process.entry_leader.entity_id') && doc['process.entry_leader.entity_id'].size() > 0 && doc['process.entity_id'].value == doc['process.entry_leader.entity_id'].value)", - }, - }, - }, - setRefetch: handleSetRefetch, - sort: [], - filterStatus: 'open', - leadingControlColumns: [ - { - id: 'session-leader-table-leading-columns', - headerCellRender: renderLeadingControlColumn, - rowCellRender: renderLeadingControlCell, - width: 160, - }, - ], - trailingControlColumns: [], - unit: renderUnit, - }); - }; - - return
{renderTimelinesTable()}
; -}; - -SessionLeaderTable.displayName = 'SessionLeaderTable'; diff --git a/x-pack/plugins/session_view/public/components/session_leader_table/styles.ts b/x-pack/plugins/session_view/public/components/session_leader_table/styles.ts deleted file mode 100644 index 38518932160dd..0000000000000 --- a/x-pack/plugins/session_view/public/components/session_leader_table/styles.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useMemo } from 'react'; -import { useEuiTheme } from '@elastic/eui'; -import { CSSObject } from '@emotion/react'; - -export const useStyles = () => { - const { euiTheme } = useEuiTheme(); - - const cached = useMemo(() => { - const rowButtonContainer: CSSObject = { - display: 'flex', - alignItems: 'center', - }; - - const rowCheckbox: CSSObject = { - marginRight: euiTheme.size.base, - }; - - return { - rowButtonContainer, - rowCheckbox, - }; - }, [euiTheme]); - - return cached; -}; diff --git a/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx b/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx deleted file mode 100644 index 80fdd042a53d7..0000000000000 --- a/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { waitFor, fireEvent } from '@testing-library/react'; -import { SessionViewTableProcessTree } from '.'; -import { SessionLeaderTableProps } from '../session_leader_table'; -import { ActionProps } from '../../../../timelines/common'; -import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; - -const mockActionProps: ActionProps = { - ariaRowindex: 0, - index: 0, - rowIndex: 0, - checked: false, - showCheckboxes: true, - columnId: 'test-column-id', - columnValues: 'test-column-values', - data: [], - ecsData: { - _id: 'test-ecs-data-id', - }, - eventId: 'test-event-id', - loadingEventIds: [], - onEventDetailsPanelOpened: () => {}, - onRowSelected: () => {}, - setEventsDeleted: () => {}, - setEventsLoading: () => {}, - timelineId: 'test-timeline-id', -}; - -jest.mock('../session_view/index.tsx', () => { - return { - SessionView: () => { - return
Mock
; - }, - }; -}); - -jest.mock('../session_leader_table/index.tsx', () => { - return { - SessionLeaderTable: (props: SessionLeaderTableProps) => { - const { onOpenSessionViewer = () => {} } = props; - return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events -
onOpenSessionViewer(mockActionProps)} - > - Mock -
- ); - }, - }; -}); - -describe('SessionViewTableProcessTree component', () => { - let render: () => ReturnType; - let renderResult: ReturnType; - let mockedContext: AppContextTestRender; - let mockedApi: AppContextTestRender['coreStart']['http']['get']; - - const waitForApiCall = () => waitFor(() => expect(mockedApi).toHaveBeenCalled()); - - beforeEach(() => { - mockedContext = createAppRootMockRenderer(); - mockedApi = mockedContext.coreStart.http.get; - }); - - describe('SessionViewTableProcessTree mounts', () => { - beforeEach(async () => { - mockedApi.mockResolvedValue({ - session_entry_leader: { - process: { - entity_id: 'test-entity-id', - }, - }, - }); - }); - - it('Renders session leader table initially', async () => { - renderResult = mockedContext.render(); - expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeTruthy(); - expect(renderResult.queryByTestId('sessionView:SessionView')).toBeNull(); - }); - - it('Switches to session view when the user picks a session', async () => { - renderResult = mockedContext.render(); - const sessionLeaderTable = renderResult.getByTestId('sessionView:SessionLeaderTable'); - fireEvent.click(sessionLeaderTable); - await waitForApiCall(); - - // Now that we fetched the entity id, session view should be visible - expect(renderResult.queryByTestId('sessionView:SessionView')).toBeTruthy(); - expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeNull(); - }); - - it('Close button works', async () => { - renderResult = mockedContext.render(); - const sessionLeaderTable = renderResult.getByTestId('sessionView:SessionLeaderTable'); - fireEvent.click(sessionLeaderTable); - await waitForApiCall(); - - expect(renderResult.queryByTestId('sessionView:SessionView')).toBeTruthy(); - expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeNull(); - - const closeButton = renderResult.getByTestId('sessionView:session-view-close-button'); - fireEvent.click(closeButton); - - expect(renderResult.queryByTestId('sessionView:SessionLeaderTable')).toBeTruthy(); - expect(renderResult.queryByTestId('sessionView:SessionView')).toBeNull(); - }); - }); -}); diff --git a/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx b/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx deleted file mode 100644 index cc64ae5ba9119..0000000000000 --- a/x-pack/plugins/session_view/public/components/session_view_table_process_tree/index.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState, useEffect } from 'react'; -import { EuiButtonEmpty } from '@elastic/eui'; -import { useQuery } from 'react-query'; -import { CoreStart } from 'kibana/public'; -import { QueryClient, QueryClientProvider } from 'react-query'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { ActionProps } from '../../../../timelines/common'; -import { SESSION_ENTRY_LEADERS_ROUTE } from '../../../common/constants'; -import { SessionViewTableProcessTreeProps } from '../../types'; -import { SessionLeaderTable } from '../session_leader_table'; -import { SessionView } from '../session_view'; - -// Initializing react-query -const queryClient = new QueryClient(); - -export const SessionViewTableProcessTreeContent = (props: SessionViewTableProcessTreeProps) => { - const [eventId, setEventId] = useState(''); - const [selectedSessionEntityId, setSelectedSessionEntityId] = useState(''); - - const isFetchEnabled = !!(eventId && !selectedSessionEntityId); - - const { http } = useKibana().services; - const { data } = useQuery( - ['SessionViewTableProcessTreeEvent', eventId], - () => { - return http.get(SESSION_ENTRY_LEADERS_ROUTE, { - query: { - id: eventId, - }, - }); - }, - { - enabled: isFetchEnabled, - } - ); - - const handleCloseProcessTree = () => { - setEventId(''); - setSelectedSessionEntityId(''); - }; - - useEffect(() => { - if (data?.session_entry_leader?.process) { - setSelectedSessionEntityId(data.session_entry_leader.process.entity_id); - } - }, [data]); - - const handleOpenSessionViewer = (actionProps: ActionProps) => { - setEventId(actionProps.eventId); - }; - - if (selectedSessionEntityId) { - return ( -
- - Close session viewer - - -
- ); - } - - return ; -}; - -export const SessionViewTableProcessTree = (props: SessionViewTableProcessTreeProps) => { - return ( - - - - ); -}; - -SessionViewTableProcessTreeContent.displayName = 'SessionViewTableProcessTreeContent'; -SessionViewTableProcessTree.displayName = 'SessionViewTableProcessTree'; - -// eslint-disable-next-line import/no-default-export -export { SessionViewTableProcessTree as default }; diff --git a/x-pack/plugins/session_view/public/methods/index.tsx b/x-pack/plugins/session_view/public/methods/index.tsx index 4139c0a2ff0ef..1eecdcbb3e50e 100644 --- a/x-pack/plugins/session_view/public/methods/index.tsx +++ b/x-pack/plugins/session_view/public/methods/index.tsx @@ -8,23 +8,12 @@ import React, { lazy, Suspense } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { SessionViewTableProcessTreeProps } from '../types'; import { SessionViewDeps } from '../types'; // Initializing react-query const queryClient = new QueryClient(); -const SessionViewTableProcessTreeLazy = lazy( - () => import('../components/session_view_table_process_tree') -); const SessionViewLazy = lazy(() => import('../components/session_view')); -export const getSessionViewTableProcessTreeLazy = (props: SessionViewTableProcessTreeProps) => { - return ( - }> - - - ); -}; export const getSessionViewLazy = ({ sessionEntityId, height, jumpToEvent }: SessionViewDeps) => { return ( diff --git a/x-pack/plugins/session_view/public/plugin.ts b/x-pack/plugins/session_view/public/plugin.ts index eeb854268105f..8b31dbe824e1e 100644 --- a/x-pack/plugins/session_view/public/plugin.ts +++ b/x-pack/plugins/session_view/public/plugin.ts @@ -6,7 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin } from '../../../../src/core/public'; -import { getSessionViewTableProcessTreeLazy, getSessionViewLazy } from './methods'; +import { getSessionViewLazy } from './methods'; import { SessionViewServices, SessionViewDeps } from './types'; export class SessionViewPlugin implements Plugin { @@ -14,7 +14,6 @@ export class SessionViewPlugin implements Plugin { public start(core: CoreStart) { return { - getSessionViewTableProcessTree: getSessionViewTableProcessTreeLazy, getSessionView: (sessionViewDeps: SessionViewDeps) => getSessionViewLazy(sessionViewDeps), }; } diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 4d49b3d7cf99f..25e7bb123f7ac 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -6,12 +6,9 @@ */ import { ReactElement, ReactNode } from 'react'; import { CoreStart } from '../../../../src/core/public'; -import { TimelinesUIStart } from '../../timelines/public'; import { ProcessEvent } from '../common/types/process_tree'; -export type SessionViewServices = CoreStart & { - timelines: TimelinesUIStart; -}; +export type SessionViewServices = CoreStart; export interface SessionViewUIStart { getSessionViewTableProcessTree: (props: any) => ReactElement; @@ -36,14 +33,6 @@ export interface EuiTabProps { prepend?: ReactNode; } -export interface SessionViewTableProcessTreeProps { - // expects an ISO 8601 string - start: string; - // expects an ISO 8601 string - end: string; - kuery?: string; -} - export interface DetailPanelProcess { id: string; start: string; From 807c501418b05b4f3f089debf52150f9ed3e89b8 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 16 Mar 2022 16:25:24 -0300 Subject: [PATCH 07/22] cleaning --- x-pack/plugins/session_view/public/plugin.ts | 3 +-- x-pack/plugins/session_view/public/types.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/plugins/session_view/public/plugin.ts b/x-pack/plugins/session_view/public/plugin.ts index 8b31dbe824e1e..fddcbd30d6b38 100644 --- a/x-pack/plugins/session_view/public/plugin.ts +++ b/x-pack/plugins/session_view/public/plugin.ts @@ -6,9 +6,8 @@ */ import { CoreSetup, CoreStart, Plugin } from '../../../../src/core/public'; -import { getSessionViewLazy } from './methods'; import { SessionViewServices, SessionViewDeps } from './types'; - +import { getSessionViewLazy } from './methods'; export class SessionViewPlugin implements Plugin { public setup(core: CoreSetup) {} diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 25e7bb123f7ac..e03e0c2c872f7 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -11,7 +11,6 @@ import { ProcessEvent } from '../common/types/process_tree'; export type SessionViewServices = CoreStart; export interface SessionViewUIStart { - getSessionViewTableProcessTree: (props: any) => ReactElement; getSessionView: (sessionEntityId: string) => ReactElement; } From 30de4b68622941833c76e0ae732fb6abadc94a38 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 16 Mar 2022 16:39:10 -0300 Subject: [PATCH 08/22] cleaning --- x-pack/plugins/session_view/public/plugin.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/session_view/public/plugin.ts b/x-pack/plugins/session_view/public/plugin.ts index fddcbd30d6b38..d51d1bc0e6c67 100644 --- a/x-pack/plugins/session_view/public/plugin.ts +++ b/x-pack/plugins/session_view/public/plugin.ts @@ -8,6 +8,7 @@ import { CoreSetup, CoreStart, Plugin } from '../../../../src/core/public'; import { SessionViewServices, SessionViewDeps } from './types'; import { getSessionViewLazy } from './methods'; + export class SessionViewPlugin implements Plugin { public setup(core: CoreSetup) {} From 4e5d445994bba7e7bd1391e270d89775cca8fd8d Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Fri, 18 Mar 2022 12:26:29 -0300 Subject: [PATCH 09/22] updating search strategy --- .../search_strategy/timeline/factory/helpers/constants.ts | 5 +++++ .../timelines/server/search_strategy/timeline/index.ts | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts index e764e32243c18..b0dd730c749d2 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts @@ -199,10 +199,14 @@ export const TIMELINE_EVENTS_FIELDS = [ 'tls.server_certificate.fingerprint.sha1', 'user.domain', 'winlog.event_id', + 'process.end', + 'process.entry_leader.entry_meta.type', + 'process.entry_leader.entry_meta.source.ip', 'process.exit_code', 'process.hash.md5', 'process.hash.sha1', 'process.hash.sha256', + 'process.interactive', 'process.parent.name', 'process.parent.pid', 'process.pid', @@ -211,6 +215,7 @@ export const TIMELINE_EVENTS_FIELDS = [ 'process.args', 'process.entity_id', 'process.executable', + 'process.start', 'process.title', 'process.working_directory', 'zeek.session_id', diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts index d04deccb71720..23d5d2cf603d4 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts @@ -203,12 +203,9 @@ const timelineSessionsSearchStrategy = ({ deps: SearchStrategyDependencies; queryFactory: TimelineFactory; }) => { - const indices = 'logs-endpoint.events.process-default'; + const indices = request.defaultIndex ?? request.indexType; const runtimeMappings = { - 'process.entity_id': { - type: 'keyword', - }, 'process.entry_leader.entity_id': { type: 'keyword', }, From ea61910a0ff0154ce363945ca082eb9041c0ffa7 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Fri, 18 Mar 2022 12:29:20 -0300 Subject: [PATCH 10/22] add space for open in session viewer icon --- .../public/common/components/sessions_viewer/sessions_table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx index 5ba66bfda8d1c..7fea50e67aef2 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx @@ -70,7 +70,7 @@ const SessionsTableComponent: React.FC = ({ pageFilters = [], }) => { const sessionsFilter = useMemo(() => [...defaultSessionsFilters, ...pageFilters], [pageFilters]); - const ACTION_BUTTON_COUNT = 4; + const ACTION_BUTTON_COUNT = 5; const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); From bfd20bbc76d445bf45558ed330ce4850a4e25dcb Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Fri, 18 Mar 2022 14:27:43 -0300 Subject: [PATCH 11/22] add sessionEntityId as key cache --- .../session_view/public/components/session_view/hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/session_view/public/components/session_view/hooks.ts b/x-pack/plugins/session_view/public/components/session_view/hooks.ts index 17574cfd28074..ae581f897f100 100644 --- a/x-pack/plugins/session_view/public/components/session_view/hooks.ts +++ b/x-pack/plugins/session_view/public/components/session_view/hooks.ts @@ -21,7 +21,7 @@ export const useFetchSessionViewProcessEvents = ( const jumpToCursor = jumpToEvent && jumpToEvent.process.start; const query = useInfiniteQuery( - 'sessionViewProcessEvents', + ['sessionViewProcessEvents', sessionEntityId], async ({ pageParam = {} }) => { let { cursor } = pageParam; const { forward } = pageParam; From 4426ab72bfc6e96b47c41969db1b8bf61a36945f Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 23 Mar 2022 17:07:18 -0300 Subject: [PATCH 12/22] updating deep links --- .../security_solution/public/app/deep_links/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 01b38fc706794..487631fedf7d5 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -192,6 +192,13 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ path: `${HOSTS_PATH}/anomalies`, isPremium: true, }, + { + id: SecurityPageName.sessions, + title: i18n.translate('xpack.securitySolution.search.hosts.sessions', { + defaultMessage: 'Sessions', + }), + path: `${HOSTS_PATH}/sessions`, + }, ], }, { From c9f06e83be8ddb6d54a0749285672d9257c002a0 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 23 Mar 2022 17:07:30 -0300 Subject: [PATCH 13/22] updating headers --- .../common/components/sessions_viewer/default_headers.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/default_headers.ts b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/default_headers.ts index 5374b87061431..ee2f2e5452298 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/default_headers.ts +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/default_headers.ts @@ -17,9 +17,13 @@ export const sessionsHeaders: ColumnHeaderOptions[] = [ id: 'process.start', initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, + // TODO: Using event.created as an way of getting the end time of the process. (Currently endpoint doesn't populate process.end) + // event.created of a event.action with value of "end" is what we consider that to be the end time of the process + // Current action are: 'start', 'exec', 'end', so we usually have three events per process. { columnHeaderType: defaultColumnHeaderType, - id: 'process.end', + id: 'event.created', + display: 'process.end', }, { columnHeaderType: defaultColumnHeaderType, @@ -54,5 +58,6 @@ export const sessionsHeaders: ColumnHeaderOptions[] = [ export const sessionsDefaultModel: SubsetTimelineModel = { ...timelineDefaults, columns: sessionsHeaders, + defaultColumns: sessionsHeaders, excludedRowRendererIds: Object.values(RowRendererId), }; From b194aea2ab50f59a9185aca9c2b341f264d3d5ca Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 23 Mar 2022 17:07:51 -0300 Subject: [PATCH 14/22] adding filterQuery --- .../components/sessions_viewer/index.tsx | 2 + .../sessions_viewer/sessions_table.tsx | 86 +++++++++++-------- .../components/sessions_viewer/types.ts | 1 + 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx index 34d38367e1a18..82e3d16aa8e53 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx @@ -16,6 +16,7 @@ const SessionsViewComponent: React.FC = ({ entityType, pageFilters, startDate, + filterQuery, }) => { return ( = ({ entityType={entityType} startDate={startDate} pageFilters={pageFilters} + filterQuery={filterQuery} /> ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx index 7fea50e67aef2..8ddc4fcbcc7fb 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import type { Filter } from '@kbn/es-query'; +import { ESBoolQuery } from '../../../../common/typed_json'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { StatefulEventsViewer } from '../events_viewer'; import { sessionsDefaultModel } from './default_headers'; @@ -17,42 +18,34 @@ import { SourcererScopeName } from '../../store/sourcerer/model'; import type { EntityType } from '../../../../../timelines/common'; import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; -export interface OwnProps { - end: string; - id: string; - start: string; -} - -const defaultSessionsFilters: Filter[] = [ - { - query: { - bool: { - filter: [ - { - bool: { - should: [ - { - match: { - 'process.is_entry_leader': true, - }, +const defaultSessionsFilter: Required> = { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match: { + 'process.is_entry_leader': true, }, - ], - minimum_should_match: 1, - }, + }, + ], + minimum_should_match: 1, }, - ], - }, - }, - meta: { - alias: null, - disabled: false, - key: 'process.is_entry_leader', - negate: false, - params: {}, - type: 'boolean', + }, + ], }, }, -]; + meta: { + alias: null, + disabled: false, + key: 'process.is_entry_leader', + negate: false, + params: {}, + type: 'boolean', + }, +}; interface Props { timelineId: TimelineIdLiteral; @@ -60,6 +53,7 @@ interface Props { entityType?: EntityType; startDate: string; pageFilters?: Filter[]; + filterQuery?: string; } const SessionsTableComponent: React.FC = ({ @@ -68,10 +62,34 @@ const SessionsTableComponent: React.FC = ({ entityType = 'sessions', startDate, pageFilters = [], + filterQuery = '', }) => { - const sessionsFilter = useMemo(() => [...defaultSessionsFilters, ...pageFilters], [pageFilters]); - const ACTION_BUTTON_COUNT = 5; + // TODO: Check for a better way to handle filterQuery + const parsedFilterQuery = useMemo(() => { + if (filterQuery && filterQuery !== '') { + return JSON.parse(filterQuery) as unknown as ESBoolQuery; + } + return {}; + }, [filterQuery]); + + const sessionsFilter = useMemo( + () => [ + { + ...defaultSessionsFilter, + query: { + ...defaultSessionsFilter.query, + bool: { + ...defaultSessionsFilter.query.bool, + filter: defaultSessionsFilter.query.bool.filter.concat(parsedFilterQuery), + }, + }, + }, + ...pageFilters, + ], + [pageFilters, parsedFilterQuery] + ); + const ACTION_BUTTON_COUNT = 5; const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); const unit = (c: number) => diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts index 09ad293b9c96a..3c1a73a3292b3 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/types.ts @@ -14,4 +14,5 @@ export interface SessionsComponentsProps extends Pick Date: Wed, 23 Mar 2022 17:08:26 -0300 Subject: [PATCH 15/22] adding timeline --- .../common/components/top_n/helpers.test.tsx | 1 + .../security_solution/public/hosts/index.ts | 1 + .../hosts/pages/details/details_tabs.tsx | 4 ++ .../public/hosts/pages/details/nav_tabs.tsx | 6 +++ .../pages/navigation/sessions_tab_body.tsx | 42 ++++--------------- .../timelines/public/store/t_grid/types.ts | 1 + 6 files changed, 20 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx index 6c87dafef942e..10613efc265ae 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx @@ -28,6 +28,7 @@ const detectionAlertsTimelines = [TimelineId.detectionsPage, TimelineId.detectio const otherTimelines = [ TimelineId.hostsPageEvents, TimelineId.hostsPageExternalAlerts, + TimelineId.hostsPageSessions, TimelineId.networkPageExternalAlerts, TimelineId.active, TimelineId.casePage, diff --git a/x-pack/plugins/security_solution/public/hosts/index.ts b/x-pack/plugins/security_solution/public/hosts/index.ts index cbb539f8e4107..f818a812b57a3 100644 --- a/x-pack/plugins/security_solution/public/hosts/index.ts +++ b/x-pack/plugins/security_solution/public/hosts/index.ts @@ -15,6 +15,7 @@ import { initialHostsState, hostsReducer, HostsState } from './store'; const HOST_TIMELINE_IDS: TimelineIdLiteral[] = [ TimelineId.hostsPageEvents, TimelineId.hostsPageExternalAlerts, + TimelineId.hostsPageSessions, ]; export class Hosts { diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx index 891db470161d4..79b6626ab92d3 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx @@ -26,6 +26,7 @@ import { EventsQueryTabBody, HostAlertsQueryTabBody, HostRiskTabBody, + SessionsTabBody, } from '../navigation'; export const HostDetailsTabs = React.memo( @@ -106,6 +107,9 @@ export const HostDetailsTabs = React.memo( + + + ); } diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx index c58fbde09aef1..5607a6c998f24 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx @@ -58,6 +58,12 @@ export const navTabsHostDetails = ( href: getTabsOnHostDetailsUrl(hostName, HostsTableType.risk), disabled: false, }, + [HostsTableType.sessions]: { + id: HostsTableType.risk, + name: i18n.NAVIGATION_SESSIONS_TITLE, + href: getTabsOnHostDetailsUrl(hostName, HostsTableType.sessions), + disabled: false, + }, }; if (!hasMlUserPermissions) { diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx index 3025a1c7e838f..0ff47a104ca21 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/sessions_tab_body.tsx @@ -6,47 +6,18 @@ */ import React, { useMemo } from 'react'; - -import type { Filter } from '@kbn/es-query'; import { TimelineId } from '../../../../common/types/timeline'; import { SessionsView } from '../../../common/components/sessions_viewer'; +import { filterHostExternalAlertData } from '../../../common/components/visualization_actions/utils'; import { AlertsComponentQueryProps } from './types'; -const filterHostData: Filter[] = [ - { - query: { - bool: { - filter: [ - { - bool: { - should: [ - { - exists: { - field: 'host.name', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - }, - }, - meta: { - alias: '', - disabled: false, - key: 'bool', - negate: false, - type: 'custom', - value: - '{"query": {"bool": {"filter": [{"bool": {"should": [{"exists": {"field": "host.name"}}],"minimum_should_match": 1}}]}}}', - }, - }, -]; export const SessionsTabBody = React.memo((alertsProps: AlertsComponentQueryProps) => { - const { pageFilters, ...rest } = alertsProps; + const { pageFilters, filterQuery, ...rest } = alertsProps; const hostPageFilters = useMemo( - () => (pageFilters != null ? [...filterHostData, ...pageFilters] : filterHostData), + () => + pageFilters != null + ? [...filterHostExternalAlertData, ...pageFilters] + : filterHostExternalAlertData, [pageFilters] ); @@ -56,6 +27,7 @@ export const SessionsTabBody = React.memo((alertsProps: AlertsComponentQueryProp timelineId={TimelineId.hostsPageSessions} {...rest} pageFilters={hostPageFilters} + filterQuery={filterQuery} /> ); }); diff --git a/x-pack/plugins/timelines/public/store/t_grid/types.ts b/x-pack/plugins/timelines/public/store/t_grid/types.ts index f30e83a763f31..de8e902a91fe5 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/types.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/types.ts @@ -44,6 +44,7 @@ export interface TimelineState { export enum TimelineId { hostsPageEvents = 'hosts-page-events', hostsPageExternalAlerts = 'hosts-page-external-alerts', + hostsPageSessions = 'hosts-page-sessions', detectionsRulesDetailsPage = 'detections-rules-details-page', detectionsPage = 'detections-page', networkPageExternalAlerts = 'network-page-external-alerts', From de69c73954c5d2409ecf2dd0d800b31bf1d7039f Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 23 Mar 2022 17:09:11 -0300 Subject: [PATCH 16/22] add runtime fields to search strategy --- .../server/search_strategy/timeline/index.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts index 23d5d2cf603d4..b2aad2f673981 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts @@ -206,9 +206,23 @@ const timelineSessionsSearchStrategy = ({ const indices = request.defaultIndex ?? request.indexType; const runtimeMappings = { + // TODO: remove once ECS is updated to use the new field name 'process.entry_leader.entity_id': { type: 'keyword', }, + // TODO: remove once ECS is updated to use the new field name + 'process.interactive': { + type: 'boolean', + }, + // TODO: remove once ECS is updated to use the new field name + 'process.entry_leader.entry_meta.type': { + type: 'keyword', + }, + // TODO: remove once ECS is updated to use the new field name + 'process.entry_leader.entry_meta.source.ip': { + type: 'ip', + }, + // TODO: replace by checking if process.group_leader.same_as_process is true, once ECS is updated to support same_as_process 'process.is_entry_leader': { type: 'boolean', script: { @@ -216,6 +230,20 @@ const timelineSessionsSearchStrategy = ({ "emit(doc.containsKey('process.entry_leader.entity_id') && doc['process.entry_leader.entity_id'].size() > 0 && doc['process.entity_id'].value == doc['process.entry_leader.entity_id'].value)", }, }, + // TODO: We need an way to override process.end date based on event.action, only when event.action is end, but the code below isn't working + // 'process.end': { + // type: 'date', + // script: { + // source: ` + // if(doc['event.action'].size() == 0) return; + // if(doc['event.action'].value == 'end') { + // return doc['event.created'].value; + // } + // return; + // `, + // lang: 'painless', + // }, + // }, }; const requestSessionLeaders = { From 4d370196f42daa6c91be751861390d9389845211 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Wed, 23 Mar 2022 17:10:28 -0300 Subject: [PATCH 17/22] updating comment --- .../public/common/components/sessions_viewer/sessions_table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx index 8ddc4fcbcc7fb..fcdb5358ad692 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx @@ -64,7 +64,7 @@ const SessionsTableComponent: React.FC = ({ pageFilters = [], filterQuery = '', }) => { - // TODO: Check for a better way to handle filterQuery + // TODO: Check for a better way to handle filterQuery, this is essentially to filter the host name when on the host details page const parsedFilterQuery = useMemo(() => { if (filterQuery && filterQuery !== '') { return JSON.parse(filterQuery) as unknown as ESBoolQuery; From 5fc9a8fa187496a1623746c43882d5d2c4f1f4b9 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Thu, 24 Mar 2022 11:02:03 -0300 Subject: [PATCH 18/22] fixing tests --- .../common/components/sessions_viewer/sessions_table.tsx | 4 ++-- .../security_solution/public/hosts/pages/details/nav_tabs.tsx | 2 +- .../security_solution/public/hosts/store/helpers.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx index fcdb5358ad692..3151dbbd5eda4 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx @@ -65,9 +65,9 @@ const SessionsTableComponent: React.FC = ({ filterQuery = '', }) => { // TODO: Check for a better way to handle filterQuery, this is essentially to filter the host name when on the host details page - const parsedFilterQuery = useMemo(() => { + const parsedFilterQuery: ESBoolQuery = useMemo(() => { if (filterQuery && filterQuery !== '') { - return JSON.parse(filterQuery) as unknown as ESBoolQuery; + return JSON.parse(filterQuery); } return {}; }, [filterQuery]); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx index b9d3e06a577bd..eb22e69e14c43 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx @@ -63,7 +63,7 @@ export const navTabsHostDetails = ({ disabled: false, }, [HostsTableType.sessions]: { - id: HostsTableType.risk, + id: HostsTableType.sessions, name: i18n.NAVIGATION_SESSIONS_TITLE, href: getTabsOnHostDetailsUrl(hostName, HostsTableType.sessions), disabled: false, diff --git a/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts b/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts index 0e233166044a5..111b7f0e79737 100644 --- a/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts @@ -46,7 +46,7 @@ export const mockHostsState: HostsModel = { severitySelection: [], }, [HostsTableType.sessions]: { - activePage: 4, + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, }, }, @@ -86,7 +86,7 @@ export const mockHostsState: HostsModel = { severitySelection: [], }, [HostsTableType.sessions]: { - activePage: 4, + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, }, }, From 395339167690b28febaa3d2ee4df467b3d156ac9 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Fri, 25 Mar 2022 12:05:19 -0300 Subject: [PATCH 19/22] removing unecessary intermediate component --- .../sessions_viewer/sessions_table.tsx | 115 ------------------ 1 file changed, 115 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx deleted file mode 100644 index 3151dbbd5eda4..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/sessions_table.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useMemo } from 'react'; -import type { Filter } from '@kbn/es-query'; -import { ESBoolQuery } from '../../../../common/typed_json'; -import { TimelineIdLiteral } from '../../../../common/types/timeline'; -import { StatefulEventsViewer } from '../events_viewer'; -import { sessionsDefaultModel } from './default_headers'; -import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; -import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; -import * as i18n from './translations'; -import { SourcererScopeName } from '../../store/sourcerer/model'; -import type { EntityType } from '../../../../../timelines/common'; -import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; - -const defaultSessionsFilter: Required> = { - query: { - bool: { - filter: [ - { - bool: { - should: [ - { - match: { - 'process.is_entry_leader': true, - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - }, - }, - meta: { - alias: null, - disabled: false, - key: 'process.is_entry_leader', - negate: false, - params: {}, - type: 'boolean', - }, -}; - -interface Props { - timelineId: TimelineIdLiteral; - endDate: string; - entityType?: EntityType; - startDate: string; - pageFilters?: Filter[]; - filterQuery?: string; -} - -const SessionsTableComponent: React.FC = ({ - timelineId, - endDate, - entityType = 'sessions', - startDate, - pageFilters = [], - filterQuery = '', -}) => { - // TODO: Check for a better way to handle filterQuery, this is essentially to filter the host name when on the host details page - const parsedFilterQuery: ESBoolQuery = useMemo(() => { - if (filterQuery && filterQuery !== '') { - return JSON.parse(filterQuery); - } - return {}; - }, [filterQuery]); - - const sessionsFilter = useMemo( - () => [ - { - ...defaultSessionsFilter, - query: { - ...defaultSessionsFilter.query, - bool: { - ...defaultSessionsFilter.query.bool, - filter: defaultSessionsFilter.query.bool.filter.concat(parsedFilterQuery), - }, - }, - }, - ...pageFilters, - ], - [pageFilters, parsedFilterQuery] - ); - - const ACTION_BUTTON_COUNT = 5; - const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); - - const unit = (c: number) => - c > 1 ? i18n.TOTAL_COUNT_OF_SESSIONS : i18n.SINGLE_COUNT_OF_SESSIONS; - - return ( - - ); -}; - -export const SessionsTable = React.memo(SessionsTableComponent); From cd04a4f3a940801a3e7d891adbd55a05b7b9fb31 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Fri, 25 Mar 2022 12:07:19 -0300 Subject: [PATCH 20/22] removing intermediary component --- .../components/sessions_viewer/index.tsx | 99 ++++++++++++++++--- 1 file changed, 87 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx index 82e3d16aa8e53..74a98f1e1f812 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx @@ -5,28 +5,103 @@ * 2.0. */ -import React from 'react'; - +import React, { useMemo } from 'react'; +import type { Filter } from '@kbn/es-query'; import { SessionsComponentsProps } from './types'; -import { SessionsTable } from './sessions_table'; +import { ESBoolQuery } from '../../../../common/typed_json'; +import { StatefulEventsViewer } from '../events_viewer'; +import { sessionsDefaultModel } from './default_headers'; +import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; +import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; +import * as i18n from './translations'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; + +export const TEST_ID = 'security_solution:sessions_viewer:sessions_view'; + +export const defaultSessionsFilter: Required> = { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match: { + 'process.is_entry_leader': true, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + meta: { + alias: null, + disabled: false, + key: 'process.is_entry_leader', + negate: false, + params: {}, + type: 'boolean', + }, +}; const SessionsViewComponent: React.FC = ({ timelineId, endDate, - entityType, + entityType = 'sessions', pageFilters, startDate, filterQuery, }) => { + const parsedFilterQuery: ESBoolQuery = useMemo(() => { + if (filterQuery && filterQuery !== '') { + return JSON.parse(filterQuery); + } + return {}; + }, [filterQuery]); + + const sessionsFilter = useMemo( + () => [ + { + ...defaultSessionsFilter, + query: { + ...defaultSessionsFilter.query, + bool: { + ...defaultSessionsFilter.query.bool, + filter: defaultSessionsFilter.query.bool.filter.concat(parsedFilterQuery), + }, + }, + }, + ...pageFilters, + ], + [pageFilters, parsedFilterQuery] + ); + + const ACTION_BUTTON_COUNT = 5; + const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); + + const unit = (c: number) => + c > 1 ? i18n.TOTAL_COUNT_OF_SESSIONS : i18n.SINGLE_COUNT_OF_SESSIONS; + return ( - +
+ +
); }; From a9b44c5c00ceab105cc19ceb29ebafd971365959 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Fri, 25 Mar 2022 12:07:27 -0300 Subject: [PATCH 21/22] adding tests for session viewer --- .../__snapshots__/index.test.tsx.snap | 107 ++++++++++++++ .../components/sessions_viewer/index.test.tsx | 137 ++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..32268e2f21e7f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/__snapshots__/index.test.tsx.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SessionsView renders correctly against snapshot 1`] = ` + + .c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; +} + +.c1 > * { + max-width: 100%; +} + +.c1 .inspectButtonComponent { + pointer-events: none; + opacity: 0; + -webkit-transition: opacity 250ms ease; + transition: opacity 250ms ease; +} + +.c1:hover .inspectButtonComponent { + pointer-events: auto; + opacity: 1; +} + +.c0 { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + width: 100%; +} + +
+
+
+
+
+ sessions +
+
+ 2022-03-22T22:10:56.794Z +
+
+ 2022-03-21T22:10:56.791Z +
+
+ hosts-page-sessions +
+
+ process.start +
+
+ process.end +
+
+ process.executable +
+
+ user.name +
+
+ process.interactive +
+
+ process.pid +
+
+ host.hostname +
+
+ process.entry_leader.entry_meta.type +
+
+ process.entry_leader.entry_meta.source.ip +
+
+
+
+
+
+`; diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx new file mode 100644 index 0000000000000..eb1b75b81a94b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { waitFor, render } from '@testing-library/react'; +import { TestProviders } from '../../mock'; +import { TEST_ID, SessionsView, defaultSessionsFilter } from '.'; +import { EntityType, TimelineId } from '../../../../../timelines/common'; +import { SessionsComponentsProps } from './types'; +import { TimelineModel } from '../../../timelines/store/timeline/model'; + +jest.mock('../../../common/lib/kibana'); + +jest.mock('../../components/url_state/normalize_time_range.ts'); + +const startDate = '2022-03-22T22:10:56.794Z'; +const endDate = '2022-03-21T22:10:56.791Z'; + +const filterQuery = + '{"bool":{"must":[],"filter":[{"match_phrase":{"host.name":{"query":"ubuntu-impish"}}}],"should":[],"must_not":[]}}'; + +const testProps: SessionsComponentsProps = { + timelineId: TimelineId.hostsPageSessions, + entityType: 'sessions', + pageFilters: [], + startDate, + endDate, + filterQuery, +}; + +type Props = Partial & { + start: string; + end: string; + entityType: EntityType; +}; + +const TEST_PREFIX = 'security_solution:sessions_viewer:sessions_view'; + +const callFilters = jest.fn(); + +// creating a dummy component for testing TGrid to avoid mocking all the implementation details +// but still test if the TGrid will render properly +const SessionsViewerTGrid: React.FC = ({ columns, start, end, id, filters, entityType }) => { + useEffect(() => { + callFilters(filters); + }, [filters]); + + return ( +
+
{entityType}
+
{start}
+
{end}
+
{id}
+ {columns?.map((header) => ( +
{header.display ?? header.id}
+ ))} +
+ ); +}; + +jest.mock('../../../../../timelines/public/mock/plugin_mock.tsx', () => { + const originalModule = jest.requireActual('../../../../../timelines/public/mock/plugin_mock.tsx'); + return { + ...originalModule, + createTGridMocks: () => ({ + ...originalModule.createTGridMocks, + getTGrid: SessionsViewerTGrid, + }), + }; +}); + +describe('SessionsView', () => { + it('renders the session view', async () => { + const wrapper = render( + + + + ); + + await waitFor(() => { + expect(wrapper.queryByTestId(TEST_ID)).toBeInTheDocument(); + }); + }); + + it('renders correctly against snapshot', async () => { + const { asFragment } = render( + + + + ); + + await waitFor(() => { + expect(asFragment()).toMatchSnapshot(); + }); + }); + + it('passes in the right parameters to TGrid', async () => { + const wrapper = render( + + + + ); + await waitFor(() => { + expect(wrapper.getByTestId(`${TEST_PREFIX}:entityType`)).toHaveTextContent('sessions'); + expect(wrapper.getByTestId(`${TEST_PREFIX}:startDate`)).toHaveTextContent(startDate); + expect(wrapper.getByTestId(`${TEST_PREFIX}:endDate`)).toHaveTextContent(endDate); + expect(wrapper.getByTestId(`${TEST_PREFIX}:timelineId`)).toHaveTextContent( + 'hosts-page-sessions' + ); + }); + }); + it('passes in the right filters to TGrid', async () => { + render( + + + + ); + await waitFor(() => { + expect(callFilters).toHaveBeenCalledWith([ + { + ...defaultSessionsFilter, + query: { + ...defaultSessionsFilter.query, + bool: { + ...defaultSessionsFilter.query.bool, + filter: defaultSessionsFilter.query.bool.filter.concat(JSON.parse(filterQuery)), + }, + }, + }, + ]); + }); + }); +}); From 9a5967720df3c99c3d5675b8a6e1e3764dc150a7 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Mon, 28 Mar 2022 15:21:19 -0300 Subject: [PATCH 22/22] remove unnecessary runtime_mappings --- .../components/sessions_viewer/index.tsx | 1 + .../server/search_strategy/timeline/index.ts | 32 +------------------ 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx index 74a98f1e1f812..3005d59bdc738 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx @@ -28,6 +28,7 @@ export const defaultSessionsFilter: Required> = { should: [ { match: { + // TODO: update to process.entry_leader.same_as_process once ECS is updated to support same_as_process 'process.is_entry_leader': true, }, }, diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts index b2aad2f673981..a5ef2f57888da 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts @@ -206,23 +206,7 @@ const timelineSessionsSearchStrategy = ({ const indices = request.defaultIndex ?? request.indexType; const runtimeMappings = { - // TODO: remove once ECS is updated to use the new field name - 'process.entry_leader.entity_id': { - type: 'keyword', - }, - // TODO: remove once ECS is updated to use the new field name - 'process.interactive': { - type: 'boolean', - }, - // TODO: remove once ECS is updated to use the new field name - 'process.entry_leader.entry_meta.type': { - type: 'keyword', - }, - // TODO: remove once ECS is updated to use the new field name - 'process.entry_leader.entry_meta.source.ip': { - type: 'ip', - }, - // TODO: replace by checking if process.group_leader.same_as_process is true, once ECS is updated to support same_as_process + // TODO: remove once ECS is updated to support process.entry_leader.same_as_process 'process.is_entry_leader': { type: 'boolean', script: { @@ -230,20 +214,6 @@ const timelineSessionsSearchStrategy = ({ "emit(doc.containsKey('process.entry_leader.entity_id') && doc['process.entry_leader.entity_id'].size() > 0 && doc['process.entity_id'].value == doc['process.entry_leader.entity_id'].value)", }, }, - // TODO: We need an way to override process.end date based on event.action, only when event.action is end, but the code below isn't working - // 'process.end': { - // type: 'date', - // script: { - // source: ` - // if(doc['event.action'].size() == 0) return; - // if(doc['event.action'].value == 'end') { - // return doc['event.created'].value; - // } - // return; - // `, - // lang: 'painless', - // }, - // }, }; const requestSessionLeaders = {