Skip to content

Commit d6a3976

Browse files
authored
[Entity Analytics][Privileged user monitoring] Discover privileged users from the Entity Analytics Okta integration (#237129)
## Summary Enable discovering privileged users from the Okta entity analytics integration. Users from any of the following roles will be considered privileged: - Super Administrator - Organization Administrator - Group Administrator - Application Administrator - Mobile Administrator - Help Desk Administrator - Report Administrator - API Access Management Administrator - Group Membership Administrator - Read-only Administrator If a user has one of these roles removed or is deleted, their privileged status will be removed. This PR also fixes deletion detection so that labels in entity_analytics_monitoring.labels are also removed when their source type is deleted. For example: when a user is marked as stale during integrations sync, any related entity_analytics_monitoring labels (such as roles or groups from user matchers) will now be cleaned up as well. Example labels for a user with roles matchers fields. ``` "entity_analytics_monitoring": { "labels": [ { "field": "user.roles", "source": "efcd1d7d-8e22-4d81-b518-d841cc84cc29", "value": "Help Desk Administrator" } ] ``` Expected outcome for that user becoming stale: ``` { "id": "OpjWn5kBU9rgIRFRDV7n", "entity_analytics_monitoring": { "labels": [] // }, "@timestamp": "2025-10-01T12:53:50.714Z", "event": { "ingested": "2025-10-01T12:53:50.714Z" }, "user": { "is_privileged": false, "name": "Carlee.Littel" }, "labels": { "source_ids": [], "sources": [] } }, ``` ## How To Test 1. Generate 100 (or many) users - a high number increases the likeliness of users within a given full sync window. `yarn start privileged-user-monitoring` select integrations sync, input your number of users 1.5 Change the interval in _security_solution/server/lib/entity_analytics/privilege_monitoring/constants.ts_ to a shorter time if you want to run multiple times 2. Run Kibana and open dev tools 3. Enable advanced setting - privileged users monitoring 4. Open dev tools, have a look at the integrations data generated, grab one of the middle timestamps from a user (@timestamp, top level of the document) --> Use this as a base for events completed and started 5. Create an entity event pair - started a few minutes before above timestamp, completed - a few minutes after. ``` POST logs-entityanalytics_okta.entity-default/_bulk { "create": {} } { "@timestamp": "2025-09-28T13:20:53.851Z", "event": { "action": "started", "agent_id_status": "verified", "dataset": "entityanalytics_okta.entity", "kind": "asset", "start": "2025-09-28T13:20:53.851Z", "ingested": "2025-09-28T10:25:53.851Z" } } { "create": {} } { "@timestamp": "2025-09-28T13:30:53.851Z", "event": { "action": "completed", "agent_id_status": "verified", "dataset": "entityanalytics_okta.entity", "kind": "asset", "end":"2025-09-28T13:30:53.851Z", "ingested": "2025-09-28T13:30:53.851Z" } } ``` 5. Run init `POST kbn:/api/entity_analytics/monitoring/engine/init ` 6. Inspect users from sync, see that the deleted / stale users monitoring labels are removed, as above.
1 parent c30b6a6 commit d6a3976

File tree

8 files changed

+10
-84
lines changed

8 files changed

+10
-84
lines changed

x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,6 @@ export const allowedExperimentalValues = Object.freeze({
237237
serviceEntityStoreEnabled: true,
238238
/**
239239
240-
/**
241-
* Enables Integrations Sync for Privileged User Monitoring
242-
*/
243-
integrationsSyncEnabled: false,
244240
245241
/**
246242
* Disables the siem migrations feature

x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/privilege_monitoring/data_sources/bulk/soft_delete.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,14 @@ export const bulkSoftDeleteOperationsFactory =
4343
source: `
4444
ctx._source['@timestamp'] = params.now;
4545
ctx._source.event.ingested = params.now;
46+
4647
if (ctx._source.labels?.source_ids != null && !ctx._source.labels?.source_ids.isEmpty()) {
4748
ctx._source.labels.source_ids.removeIf(idx -> idx == params.source_id);
4849
}
50+
51+
if (ctx._source.entity_analytics_monitoring != null && ctx._source.entity_analytics_monitoring.labels != null) {
52+
ctx._source.entity_analytics_monitoring.labels.removeIf(l -> l != null && l.source == params.source_id);
53+
}
4954
5055
if (ctx._source.labels?.source_ids == null || ctx._source.labels.source_ids.isEmpty()) {
5156
if (ctx._source.labels?.sources != null) {

x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/privilege_monitoring/data_sources/data_sources_service.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ export const createDataSourcesService = (
1717
soClient: SavedObjectsClientContract,
1818
maxUsersAllowed: number
1919
) => {
20-
const { deps } = dataClient;
2120
const esClient = dataClient.deps.clusterClient.asCurrentUser;
2221
const indexSyncService = createIndexSyncService(dataClient, maxUsersAllowed);
2322
const integrationsSyncService = createIntegrationsSyncService(dataClient, soClient);
24-
const integrationsSyncFlag = deps.experimentalFeatures?.integrationsSyncEnabled ?? false;
2523

2624
/**
2725
* This creates an index for the user to populate privileged users.
@@ -64,7 +62,7 @@ export const createDataSourcesService = (
6462
};
6563
const syncAllSources = async () => {
6664
const jobs = [indexSyncService.plainIndexSync(soClient)];
67-
if (integrationsSyncFlag) jobs.push(integrationsSyncService.integrationsSync());
65+
jobs.push(integrationsSyncService.integrationsSync());
6866

6967
const settled = await Promise.allSettled(jobs);
7068
settled

x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/privilege_monitoring/data_sources/sync/integrations/deletion_detection/deletion_detection.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,12 @@ export const createDeletionDetectionService = (
7171
completedEventTimeStamp,
7272
});
7373

74-
dataClient.log(`debug`, `allIntegrationsUserNames: ${allIntegrationsUserNames}`);
7574
// get all users in the privileged index for this source that are not in integrations docs
7675
const staleUsers = await findStaleUsers(
7776
source.id,
7877
allIntegrationsUserNames,
7978
'entity_analytics_integration' // TODO: confirm index/type constant
8079
);
81-
dataClient.log(`debug`, `staleUsers: ${staleUsers}`);
8280
const ops = bulkUtilsService.bulkSoftDeleteOperations(
8381
staleUsers,
8482
dataClient.index,

x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/privilege_monitoring/data_sources/sync/integrations/update_detection/privileged_status_update.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const createPrivilegeStatusUpdateService = (dataClient: PrivilegeMonitori
1515
users: PrivMonBulkUser[],
1616
source: MonitoringEntitySource
1717
) => {
18-
dataClient.log('debug', `Updating internal index for users: ${JSON.stringify(users, null, 2)}`);
18+
dataClient.log('debug', `Updating internal index for users ${users.length}`);
1919
await applyPrivilegedUpdates({ users, dataClient, source });
2020
};
2121

x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/privilege_monitoring/engine/initialisation_service.ts

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ import moment from 'moment';
99
import type { SavedObjectsClientContract } from '@kbn/core/server';
1010
import {
1111
MonitoringEngineComponentResourceEnum,
12-
type CreateMonitoringEntitySource,
1312
type MonitoringEngineDescriptor,
1413
} from '../../../../../common/api/entity_analytics';
15-
import { defaultMonitoringUsersIndex } from '../../../../../common/entity_analytics/privileged_user_monitoring/utils';
1614
import type { PrivilegeMonitoringDataClient } from './data_client';
1715
import { PrivilegeMonitoringEngineActions } from '../auditing/actions';
1816
import { PRIVILEGE_MONITORING_ENGINE_STATUS } from '../constants';
@@ -63,13 +61,8 @@ export const createInitialisationService = (
6361
const descriptor = await descriptorClient.init();
6462
dataClient.log('info', `Initialized privileged monitoring engine saved object`);
6563

66-
if (deps.experimentalFeatures?.integrationsSyncEnabled ?? false) {
67-
// upsert index AND integration sources
68-
await InitSourceCreationService.upsertSources(monitoringIndexSourceClient);
69-
} else {
70-
// upsert ONLY index source
71-
await createOrUpdateDefaultDataSource(monitoringIndexSourceClient);
72-
}
64+
// upsert index AND integration sources
65+
await InitSourceCreationService.upsertSources(monitoringIndexSourceClient);
7366

7467
try {
7568
dataClient.log('debug', 'Creating privilege user monitoring event.ingested pipeline');
@@ -123,66 +116,5 @@ export const createInitialisationService = (
123116
return descriptor;
124117
};
125118

126-
const createOrUpdateDefaultDataSource = async (
127-
monitoringIndexSourceClient: MonitoringEntitySourceDescriptorClient
128-
) => {
129-
const sourceName = dataClient.index;
130-
131-
const defaultIndexSource: CreateMonitoringEntitySource = {
132-
type: 'index',
133-
managed: true,
134-
indexPattern: defaultMonitoringUsersIndex(deps.namespace),
135-
name: sourceName,
136-
};
137-
138-
const existingSources = await monitoringIndexSourceClient.find({
139-
name: sourceName,
140-
});
141-
142-
if (existingSources.saved_objects.length > 0) {
143-
dataClient.log('info', 'Default index source already exists, updating it.');
144-
const existingSource = existingSources.saved_objects[0];
145-
try {
146-
await monitoringIndexSourceClient.update({
147-
id: existingSource.id,
148-
...defaultIndexSource,
149-
});
150-
} catch (e) {
151-
dataClient.log(
152-
'error',
153-
`Failed to update default index source for privilege monitoring: ${e.message}`
154-
);
155-
dataClient.audit(
156-
PrivilegeMonitoringEngineActions.INIT,
157-
MonitoringEngineComponentResourceEnum.privmon_engine,
158-
'Failed to update default index source for privilege monitoring',
159-
e
160-
);
161-
}
162-
} else {
163-
dataClient.log('info', 'Creating default index source for privilege monitoring.');
164-
165-
try {
166-
// TODO: failing test, empty sources array. FIX
167-
const indexSourceDescriptor = monitoringIndexSourceClient.create(defaultIndexSource);
168-
169-
dataClient.log(
170-
'debug',
171-
`Created index source for privilege monitoring: ${JSON.stringify(indexSourceDescriptor)}`
172-
);
173-
} catch (e) {
174-
dataClient.log(
175-
'error',
176-
`Failed to create default index source for privilege monitoring: ${e.message}`
177-
);
178-
dataClient.audit(
179-
PrivilegeMonitoringEngineActions.INIT,
180-
MonitoringEngineComponentResourceEnum.privmon_engine,
181-
'Failed to create default index source for privilege monitoring',
182-
e
183-
);
184-
}
185-
}
186-
};
187119
return { init };
188120
};

x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/monitoring/trial_license_complete_tier/configs/ess.config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
1818
serverArgs: [
1919
...functionalConfig.get('kbnTestServer.serverArgs'),
2020
'--xpack.securitySolution.entityAnalytics.monitoring.privileges.users.maxPrivilegedUsersAllowed=100',
21-
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
22-
'integrationsSyncEnabled',
23-
])}`,
21+
`--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`,
2422
],
2523
},
2624
testFiles: [require.resolve('..')],

x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/monitoring/trial_license_complete_tier/configs/serverless.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export default createTestConfig({
1515
{ product_line: 'cloud', product_tier: 'complete' },
1616
])}`,
1717
'--xpack.securitySolution.entityAnalytics.monitoring.privileges.users.maxPrivilegedUsersAllowed=100',
18-
`--xpack.securitySolution.enableExperimental=${JSON.stringify(['integrationsSyncEnabled'])}`,
1918
],
2019
testFiles: [require.resolve('..')],
2120
junit: {

0 commit comments

Comments
 (0)