From 6496187893b286ae96604198612426426dede4fd Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Wed, 16 Jul 2025 14:52:39 +0200 Subject: [PATCH 1/3] [SecuritySolution] Fix account switch visualisation and remove filter for multiple fields (#227574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary * Update the account switch query to return data from non-endpoint logs * It adds a FORK * Add `su .*` to the query filter for match commands like `su admin` * Remove filter from the UI for columns represented by multiple fields * This filter is removed to avoid bugs, since we can't filter by multiple fields * Update the new dataview to use the Explorer sourcerer *** To test the new data view, you have to enable `newDataViewPickerEnabled` [ECS docs](https://www.elastic.co/docs/reference/ecs/ecs-user) ⬇️ Location | Field Set | Description -- | -- | -- user.effective.* | user | User whose privileges were assumed (cherry picked from commit 7e191f90e8bcd2c08915045f518c4678f19708eb) # Conflicts: # x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx --- .../privileged_user_activity/columns.tsx | 61 +++++++------------ .../privileged_user_activity/index.test.tsx | 13 ++-- .../privileged_users_table/columns.tsx | 6 +- .../queries/account_switches_esql_query.ts | 26 +++++--- ...lytics_privileged_user_monitoring_page.tsx | 7 ++- 5 files changed, 58 insertions(+), 55 deletions(-) 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..41e123940bb06 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 @@ -1,4 +1,4 @@ -/* +op/* * 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 @@ -40,6 +40,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 +110,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), From b591febcd9fffbef5ef0e144c56788fae5407dbb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:10:58 +0000 Subject: [PATCH 2/3] [CI] Auto-commit changed files from 'node scripts/eslint_all_files --no-cache --fix' --- .../entity_analytics_privileged_user_monitoring_page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 41e123940bb06..9d1513d73243a 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 @@ -1,9 +1,11 @@ -op/* +/* * 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. */ + +op; import React, { useCallback, useEffect, useMemo, useReducer } from 'react'; import { EuiButtonEmpty, From f3dfd18e222ac28d0df263b246a1bdbc32716e76 Mon Sep 17 00:00:00 2001 From: jaredburgettelastic Date: Wed, 16 Jul 2025 12:26:11 -0500 Subject: [PATCH 3/3] Fixed some weird conflict issue that came from backporting --- .../pages/entity_analytics_privileged_user_monitoring_page.tsx | 1 - 1 file changed, 1 deletion(-) 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 9d1513d73243a..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 @@ -5,7 +5,6 @@ * 2.0. */ -op; import React, { useCallback, useEffect, useMemo, useReducer } from 'react'; import { EuiButtonEmpty,