diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/columns.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/columns.tsx index 345ef114e56f6..7880e1a1864bc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/columns.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/columns.tsx @@ -42,7 +42,7 @@ const timestampColumn: EuiBasicTableColumn = { }, }; -const getPrivilegedUserColumn = (fieldName: string) => ({ +const getPrivilegedUserColumn = () => ({ field: 'privileged_user', name: ( ({ ), width: COLUMN_WIDTHS.privileged_user, render: (user: string[] | string) => - user != null - ? getRowItemsWithActions({ - values: isArray(user) ? user : [user], - // TODO We need a way to filter by several field with an OR expression - // because the ESQL queries several source indices that have different field names - // Issue to extend SecurityCellActions to support this: https://github.com/elastic/security-team/issues/12712 - fieldName, - idPrefix: 'privileged-user-monitoring-privileged-user', - render: (item) => , - displayCount: 1, - }) - : getEmptyTagValue(), + getRowItemsWithActions({ + values: isArray(user) ? user : [user], + fieldName: 'user.name', + idPrefix: 'privileged-user-monitoring-privileged-user', + render: (item) => , + displayCount: 1, + }), }); -const getTargetUserColumn = (fieldName: string) => ({ +const getTargetUserColumn = () => ({ field: 'target_user', name: ( ({ defaultMessage="Target user" /> ), - render: (user: string[] | string) => - user != null - ? getRowItemsWithActions({ - values: isArray(user) ? user : [user], - fieldName, - idPrefix: 'privileged-user-monitoring-target-user', - render: (item) => , - displayCount: 1, - }) - : getEmptyTagValue(), + render: (user: string) => + user != null ? : getEmptyTagValue(), }); const getIpColumn = (fieldName = 'source.ip') => ({ @@ -95,15 +82,13 @@ const getIpColumn = (fieldName = 'source.ip') => ({ /> ), render: (ips: string[] | string) => - ips != null - ? getRowItemsWithActions({ - values: isArray(ips) ? ips : [ips], - fieldName, - idPrefix: 'privileged-user-monitoring-ip', - render: (item) => , - displayCount: 1, - }) - : getEmptyTagValue(), + getRowItemsWithActions({ + values: isArray(ips) ? ips : [ips], + fieldName: '', // Dirty hack to disable CellActions, remove this when CellActions support multiple fields + idPrefix: 'privileged-user-monitoring-ip', + render: (item) => , + displayCount: 1, + }), }); const getActionsColumn = (openRightPanel: (props: FlyoutPanelProps) => void) => ({ @@ -148,8 +133,8 @@ export const buildGrantedRightsColumns = ( ): Array> => [ getActionsColumn(openRightPanel), timestampColumn, - getPrivilegedUserColumn('user.name'), - getTargetUserColumn('user.target.name'), + getPrivilegedUserColumn(), + getTargetUserColumn(), { field: 'group_name', name: ( @@ -167,9 +152,9 @@ export const buildAccountSwitchesColumns = ( ): Array> => [ getActionsColumn(openRightPanel), timestampColumn, - getPrivilegedUserColumn('process.real_user.name'), + getPrivilegedUserColumn(), { - ...getTargetUserColumn('process.group_leader.user.name'), + ...getTargetUserColumn(), name: ( > => [ getActionsColumn(openRightPanel), timestampColumn, - getPrivilegedUserColumn('client.user.name'), + getPrivilegedUserColumn(), { field: 'source', name: ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/index.test.tsx index 6fa91649d39e7..a35e83b8ea82f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_user_activity/index.test.tsx @@ -28,6 +28,14 @@ jest.mock('../../../../../common/hooks/use_space_id', () => ({ useSpaceId: jest.fn().mockReturnValue('default'), })); +jest.mock('../../queries/helpers', () => { + const originalModule = jest.requireActual('../../queries/helpers'); + return { + ...originalModule, + removeInvalidForkBranchesFromESQL: jest.fn((fields, esql) => esql), + }; +}); + const mockedSourcererDataView = { title: 'test-*', fields: {}, @@ -65,8 +73,6 @@ describe('UserActivityPrivilegedUsersPanel', () => { render(, { wrapper: TestProviders, }); - // select a visualization that doesn't require dataview fields - fireEvent.click(screen.getByTestId('account_switches')); expect(screen.getByTestId('esql-dashboard-panel')).toBeInTheDocument(); }); @@ -84,8 +90,7 @@ describe('UserActivityPrivilegedUsersPanel', () => { render(, { wrapper: TestProviders, }); - // select a visualization that doesn't require dataview fields - fireEvent.click(screen.getByTestId('account_switches')); + expect(screen.getByText('View all events')).toBeInTheDocument(); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_users_table/columns.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_users_table/columns.tsx index e19bf3a8883d5..04510673d2a98 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_users_table/columns.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/components/privileged_users_table/columns.tsx @@ -52,7 +52,7 @@ import { SCOPE_ID } from '../../constants'; const COLUMN_WIDTHS = { actions: '5%', '@timestamp': '20%', privileged_user: '15%' }; -const getPrivilegedUserColumn = (fieldName: string) => ({ +const getPrivilegedUserColumn = () => ({ field: 'user.name', name: ( ({ user != null ? getRowItemsWithActions({ values: isArray(user) ? user : [user], - fieldName, + fieldName: 'user.name', idPrefix: 'privileged-user-monitoring-privileged-user', render: (item) => , displayCount: 1, @@ -321,7 +321,7 @@ export const buildPrivilegedUsersTableColumns = ( euiTheme: EuiThemeComputed ): Array> => [ getActionsColumn(openUserFlyout), - getPrivilegedUserColumn('user.name'), + getPrivilegedUserColumn(), getRiskScoreColumn(euiTheme), getAssetCriticalityColumn(), getDataSourceColumn(), diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/queries/account_switches_esql_query.ts b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/queries/account_switches_esql_query.ts index e99384a918200..1664f847beb60 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/queries/account_switches_esql_query.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/privileged_user_monitoring/queries/account_switches_esql_query.ts @@ -6,16 +6,28 @@ */ import type { DataViewFieldMap } from '@kbn/data-views-plugin/common'; -import { getPrivilegedMonitorUsersJoin } from './helpers'; +import { getPrivilegedMonitorUsersJoin, removeInvalidForkBranchesFromESQL } from './helpers'; export const getAccountSwitchesEsqlSource = ( namespace: string, indexPattern: string, fields: DataViewFieldMap -) => { - return `FROM ${indexPattern} METADATA _id, _index +) => + removeInvalidForkBranchesFromESQL( + fields, + `FROM ${indexPattern} METADATA _id, _index ${getPrivilegedMonitorUsersJoin(namespace)} - | WHERE to_lower(process.command_line) RLIKE "(su|sudo su|sudo -i|sudo -s|ssh [^@]+@[^\s]+)" - | RENAME process.command_line AS command_process, process.group_leader.user.name AS target_user, process.parent.real_group.name AS group_name, process.real_user.name as privileged_user, host.ip AS host_ip - | KEEP @timestamp, privileged_user, host_ip, target_user, group_name, command_process, _id, _index`; -}; + | WHERE to_lower(process.command_line) RLIKE "(su .*|su|sudo su|sudo -i|sudo -s|ssh [^@]+@[^\s]+)" + | FORK + ( + WHERE event.dataset != "endpoint.events.process" AND event.action =="logged-on" + | EVAL target_user = REPLACE(user.effective.name, "\\\\(.*\\\\)", "") + | EVAL group_name = COALESCE(user.group.name, user.group.id) + | RENAME process.command_line AS command_process, user.name as privileged_user, host.ip AS host_ip + ) + ( + WHERE event.dataset == "endpoint.events.process" + | RENAME process.command_line AS command_process, process.group_leader.user.name AS target_user, process.parent.real_group.name AS group_name, process.real_user.name as privileged_user, host.ip AS host_ip + ) + | KEEP @timestamp, privileged_user, host_ip, target_user, group_name, command_process, _id, _index` + ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx index 7d1dc659e3519..05e90f522a525 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import React, { useCallback, useEffect, useMemo, useReducer } from 'react'; import { EuiButtonEmpty, @@ -40,6 +41,7 @@ import { PrivilegedUserMonitoringManageDataSources } from '../components/privile import { EmptyPrompt } from '../../common/components/empty_prompt'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; import { PageLoader } from '../../common/components/page_loader'; +import { DataViewManagerScopeName } from '../../data_view_manager/constants'; type PageState = | { type: 'fetchingEngineStatus' } @@ -109,8 +111,8 @@ export const EntityAnalyticsPrivilegedUserMonitoringPage = () => { sourcererDataView: oldSourcererDataViewSpec, } = useSourcererDataView(); const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView, status } = useDataView(); - const { dataViewSpec } = useDataViewSpec(); + const { dataView, status } = useDataView(DataViewManagerScopeName.explore); + const { dataViewSpec } = useDataViewSpec(DataViewManagerScopeName.explore); // TODO: newDataViewPicker - this could be left, as the fieldMap spec is actually being used const isSourcererLoading = useMemo( () => (newDataViewPickerEnabled ? status !== 'ready' : oldIsSourcererLoading),