From 1a975b35c938c37f438a992082ff5f41e7ca27e0 Mon Sep 17 00:00:00 2001 From: machadoum Date: Fri, 11 Jul 2025 08:44:55 +0200 Subject: [PATCH 1/3] Fix account switch visualisation and remove filter for multiple fields --- .../privileged_user_activity/columns.tsx | 61 +++++++------------ .../privileged_users_table/columns.tsx | 6 +- .../queries/account_switches_esql_query.ts | 5 +- 3 files changed, 29 insertions(+), 43 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_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..ea0804423efc0 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 @@ -15,7 +15,8 @@ export const getAccountSwitchesEsqlSource = ( ) => { return `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 + | WHERE to_lower(process.command_line) RLIKE "(su .*|su|sudo su|sudo -i|sudo -s|ssh [^@]+@[^\s]+)" + | EVAL target_user = REPLACE(user.effective.name, "\\\\(.*\\\\)", "") + | RENAME process.command_line AS command_process, process.parent.real_group.name AS group_name, user.name as privileged_user, host.ip AS host_ip | KEEP @timestamp, privileged_user, host_ip, target_user, group_name, command_process, _id, _index`; }; From f0ddd3bca78eef417a2e0c1aa822bb1e96399743 Mon Sep 17 00:00:00 2001 From: machadoum Date: Mon, 14 Jul 2025 09:37:47 +0200 Subject: [PATCH 2/3] Updare AccountSwitches query to fetch data from endoint and non-endpoint --- .../queries/account_switches_esql_query.ts | 27 +++++++++++++------ ...lytics_privileged_user_monitoring_page.tsx | 5 ++-- 2 files changed, 22 insertions(+), 10 deletions(-) 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 ea0804423efc0..aaffe19276d19 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,17 +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 .*|su|sudo su|sudo -i|sudo -s|ssh [^@]+@[^\s]+)" - | EVAL target_user = REPLACE(user.effective.name, "\\\\(.*\\\\)", "") - | RENAME process.command_line AS command_process, process.parent.real_group.name AS group_name, 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..470fa01d88824 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 @@ -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); const isSourcererLoading = useMemo( () => (newDataViewPickerEnabled ? status !== 'ready' : oldIsSourcererLoading), From 8de69e5c51750859a5d541ebdf643263ed92f225 Mon Sep 17 00:00:00 2001 From: machadoum Date: Mon, 14 Jul 2025 09:49:49 +0200 Subject: [PATCH 3/3] Format the ESQL query --- .../queries/account_switches_esql_query.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 aaffe19276d19..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 @@ -20,14 +20,14 @@ export const getAccountSwitchesEsqlSource = ( | 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" 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 + 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` );