[Entity Analytics][Privilege User Monitoring] Add Privileged User CRUD API (#218098)

## Summary

This PR introduces basic CRUD routes for handling Privileged Users in
Entity Analytics.


The following routes are available:

* CREATE: `POST /api/entity_analytics/monitoring/users`
* GET:    `GET /api/entity_analytics/monitoring/users/{id}`
* LIST:   `GET /api/entity_analytics/monitoring/users/list`
* UPDATE: `PUT /api/entity_analytics/monitoring/users/{id}`
* DELETE: `DELETE /api/entity_analytics/monitoring/users/{id}`


For CREATE and UPDATE, the request body should be of type:
```
{ "user_name": string, is_monitored: boolean }
```

The reason for snake_case is to align better with the upcoming csv and
json upload work.
This PR already introduces boilerplate code (registering the endpoints
and handlers) for those routes). We might want to change this.

## How to test


1. Start a fresh Kibana instance
2. Enable the `EntityAnalyticsPrivilegeMonitoring` Feature Flag.
3. Initialise the privmon engine with `POST
kbn:/api/entity_analytics/monitoring/engine/init`
4. Test any of the endpoints above
* Make sure to either note down the returned `id`s or simply query the
underlying index: `.entity_analytics.monitoring.users-<your namespace>`

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Tiago Vila Verde 2025-05-07 14:17:33 +02:00 committed by GitHub
parent 1971ff261c
commit 7059a8a1c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 2393 additions and 2 deletions

View file

@ -13412,6 +13412,173 @@ paths:
summary: Health check on Privilege Monitoring
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users:
post:
operationId: CreatePrivMonUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_UserName'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
description: User created successfully
summary: Create a new monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_csv:
post:
operationId: BulkUploadUsersCSV
requestBody:
content:
text/csv:
schema:
type: string
description: CSV file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via CSV upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_json:
post:
operationId: BulkUploadUsersJSON
requestBody:
content:
text/json:
schema:
type: object
properties:
users:
items:
type: object
properties:
is_monitored:
type: boolean
user_name:
type: string
type: array
description: JSON file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via JSON upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/{id}:
delete:
operationId: DeletePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
aknowledged:
description: Indicates if the deletion was successful
type: boolean
message:
description: A message providing additional information about the deletion status
type: string
required:
- success
description: User deleted successfully
summary: Delete a monitored user
tags:
- Security Entity Analytics API
get:
operationId: GetPrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
description: User details retrieved
summary: Retrieve a monitored user by ID
tags:
- Security Entity Analytics API
put:
operationId: UpdatePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
description: User updated successfully
summary: Update a monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/list:
get:
operationId: ListPrivMonUsers
parameters:
- description: KQL query to filter the list of monitored users
in: query
name: kql
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
type: array
description: List of monitored users
summary: List all monitored users
tags:
- Security Entity Analytics API
/api/entity_store/enable:
post:
operationId: InitEntityStore
@ -65337,6 +65504,65 @@ components:
type: string
Security_Entity_Analytics_API_Metadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_TransformStatsMetadata'
Security_Entity_Analytics_API_MonitoredUserDoc:
type: object
properties:
'@timestamp':
format: date-time
type: string
entity_analytics_monitoring:
type: object
properties:
labels:
items:
type: object
properties:
field:
type: string
source:
type: string
value:
type: string
type: array
event:
type: object
properties:
ingested:
format: date-time
type: string
id:
type: string
labels:
type: object
properties:
monitoring:
type: object
properties:
privileged_users:
enum:
- monitored
- deleted
type: string
source_indices:
items:
type: string
type: array
source_integrations:
items:
type: string
type: array
sources:
items:
enum:
- csv
- index_sync
- api
type: array
user:
type: object
properties:
name:
type: string
Security_Entity_Analytics_API_MonitoringEngineDescriptor:
type: object
properties:
@ -65587,6 +65813,15 @@ components:
required:
- user
- entity
Security_Entity_Analytics_API_UserName:
type: object
properties:
user:
type: object
properties:
name:
description: The name of the user.
type: string
Security_Exceptions_API_CreateExceptionListItemComment:
type: object
properties:

View file

@ -15571,6 +15571,173 @@ paths:
summary: Health check on Privilege Monitoring
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users:
post:
operationId: CreatePrivMonUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_UserName'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
description: User created successfully
summary: Create a new monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_csv:
post:
operationId: BulkUploadUsersCSV
requestBody:
content:
text/csv:
schema:
type: string
description: CSV file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via CSV upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_json:
post:
operationId: BulkUploadUsersJSON
requestBody:
content:
text/json:
schema:
type: object
properties:
users:
items:
type: object
properties:
is_monitored:
type: boolean
user_name:
type: string
type: array
description: JSON file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via JSON upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/{id}:
delete:
operationId: DeletePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
aknowledged:
description: Indicates if the deletion was successful
type: boolean
message:
description: A message providing additional information about the deletion status
type: string
required:
- success
description: User deleted successfully
summary: Delete a monitored user
tags:
- Security Entity Analytics API
get:
operationId: GetPrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
description: User details retrieved
summary: Retrieve a monitored user by ID
tags:
- Security Entity Analytics API
put:
operationId: UpdatePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
description: User updated successfully
summary: Update a monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/list:
get:
operationId: ListPrivMonUsers
parameters:
- description: KQL query to filter the list of monitored users
in: query
name: kql
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoredUserDoc'
type: array
description: List of monitored users
summary: List all monitored users
tags:
- Security Entity Analytics API
/api/entity_store/enable:
post:
operationId: InitEntityStore
@ -74838,6 +75005,65 @@ components:
type: string
Security_Entity_Analytics_API_Metadata:
$ref: '#/components/schemas/Security_Entity_Analytics_API_TransformStatsMetadata'
Security_Entity_Analytics_API_MonitoredUserDoc:
type: object
properties:
'@timestamp':
format: date-time
type: string
entity_analytics_monitoring:
type: object
properties:
labels:
items:
type: object
properties:
field:
type: string
source:
type: string
value:
type: string
type: array
event:
type: object
properties:
ingested:
format: date-time
type: string
id:
type: string
labels:
type: object
properties:
monitoring:
type: object
properties:
privileged_users:
enum:
- monitored
- deleted
type: string
source_indices:
items:
type: string
type: array
source_integrations:
items:
type: string
type: array
sources:
items:
enum:
- csv
- index_sync
- api
type: array
user:
type: object
properties:
name:
type: string
Security_Entity_Analytics_API_MonitoringEngineDescriptor:
type: object
properties:
@ -75088,6 +75314,15 @@ components:
required:
- user
- entity
Security_Entity_Analytics_API_UserName:
type: object
properties:
user:
type: object
properties:
name:
description: The name of the user.
type: string
Security_Exceptions_API_CreateExceptionListItemComment:
type: object
properties:

View file

@ -0,0 +1,70 @@
/*
* 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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privilege Monitoring Users Common Schema
* version: 1
*/
import { z } from '@kbn/zod';
export type UserName = z.infer<typeof UserName>;
export const UserName = z.object({
user: z
.object({
/**
* The name of the user.
*/
name: z.string().optional(),
})
.optional(),
});
export type MonitoredUserDoc = z.infer<typeof MonitoredUserDoc>;
export const MonitoredUserDoc = z.object({
id: z.string().optional(),
event: z
.object({
ingested: z.string().datetime().optional(),
})
.optional(),
'@timestamp': z.string().datetime().optional(),
user: z
.object({
name: z.string().optional(),
})
.optional(),
labels: z
.object({
monitoring: z
.object({
privileged_users: z.enum(['monitored', 'deleted']).optional(),
})
.optional(),
sources: z.array(z.unknown()).optional(),
source_indices: z.array(z.string()).optional(),
source_integrations: z.array(z.string()).optional(),
})
.optional(),
entity_analytics_monitoring: z
.object({
labels: z
.array(
z.object({
field: z.string().optional(),
value: z.string().optional(),
source: z.string().optional(),
})
)
.optional(),
})
.optional(),
});

View file

@ -0,0 +1,83 @@
openapi: 3.0.0
info:
title: Privilege Monitoring Users Common Schema
description: Common schema for Privileged Users
version: "1"
paths: {}
components:
schemas:
UserName:
type: object
properties:
user:
type: object
properties:
name:
type: string
description: The name of the user.
MonitoredUserDoc:
type: object
properties:
id:
type: string
event:
type: object
properties:
ingested:
type: string
format: date-time
"@timestamp":
type: string
format: date-time
user:
type: object
properties:
name:
type: string
labels:
type: object
properties:
monitoring:
type: object
properties:
privileged_users:
type: string
enum:
- monitored
- deleted
sources:
type: array
items:
enum:
- csv
- index_sync
- api
source_indices:
type: array
items:
type: string
source_integrations:
type: array
items:
type: string
entity_analytics_monitoring:
type: object
properties:
labels:
type: array
items:
type: object
properties:
field:
type: string
value:
type: string
source:
type: string

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privileged User Monitoring API
* version: 2023-10-31
*/
import type { z } from '@kbn/zod';
import { UserName, MonitoredUserDoc } from './common.gen';
export type CreatePrivMonUserRequestBody = z.infer<typeof CreatePrivMonUserRequestBody>;
export const CreatePrivMonUserRequestBody = UserName;
export type CreatePrivMonUserRequestBodyInput = z.input<typeof CreatePrivMonUserRequestBody>;
export type CreatePrivMonUserResponse = z.infer<typeof CreatePrivMonUserResponse>;
export const CreatePrivMonUserResponse = MonitoredUserDoc;

View file

@ -0,0 +1,26 @@
openapi: 3.0.0
info:
title: Privileged User Monitoring API
version: "2023-10-31"
paths:
/api/entity_analytics/monitoring/users:
post:
x-labels: [ess, serverless]
x-codegen-enabled: true
operationId: CreatePrivMonUser
summary: Create a new monitored user
requestBody:
required: true
content:
application/json:
schema:
$ref: "./common.schema.yaml#/components/schemas/UserName"
responses:
"200":
description: User created successfully
content:
application/json:
schema:
$ref: "./common.schema.yaml#/components/schemas/MonitoredUserDoc"

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privileged User Monitoring API
* version: 2023-10-31
*/
import { z } from '@kbn/zod';
export type DeletePrivMonUserRequestParams = z.infer<typeof DeletePrivMonUserRequestParams>;
export const DeletePrivMonUserRequestParams = z.object({
id: z.string(),
});
export type DeletePrivMonUserRequestParamsInput = z.input<typeof DeletePrivMonUserRequestParams>;
export type DeletePrivMonUserResponse = z.infer<typeof DeletePrivMonUserResponse>;
export const DeletePrivMonUserResponse = z.object({
/**
* Indicates if the deletion was successful
*/
aknowledged: z.boolean().optional(),
/**
* A message providing additional information about the deletion status
*/
message: z.string().optional(),
});

View file

@ -0,0 +1,34 @@
openapi: 3.0.0
info:
title: Privileged User Monitoring API
version: "2023-10-31"
paths:
/api/entity_analytics/monitoring/users/{id}:
delete:
x-labels: [ess, serverless]
x-codegen-enabled: true
operationId: DeletePrivMonUser
summary: Delete a monitored user
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: User deleted successfully
content:
application/json:
schema:
type: object
required:
- success
properties:
aknowledged:
type: boolean
description: Indicates if the deletion was successful
message:
type: string
description: A message providing additional information about the deletion status

View file

@ -0,0 +1,28 @@
/*
* 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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privileged User Monitoring API
* version: 2023-10-31
*/
import { z } from '@kbn/zod';
import { MonitoredUserDoc } from './common.gen';
export type GetPrivMonUserRequestParams = z.infer<typeof GetPrivMonUserRequestParams>;
export const GetPrivMonUserRequestParams = z.object({
id: z.string(),
});
export type GetPrivMonUserRequestParamsInput = z.input<typeof GetPrivMonUserRequestParams>;
export type GetPrivMonUserResponse = z.infer<typeof GetPrivMonUserResponse>;
export const GetPrivMonUserResponse = MonitoredUserDoc;

View file

@ -0,0 +1,25 @@
openapi: 3.0.0
info:
title: Privileged User Monitoring API
version: "2023-10-31"
paths:
/api/entity_analytics/monitoring/users/{id}:
get:
x-labels: [ess, serverless]
x-codegen-enabled: true
operationId: GetPrivMonUser
summary: Retrieve a monitored user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: User details retrieved
content:
application/json:
schema:
$ref: "./common.schema.yaml#/components/schemas/MonitoredUserDoc"

View file

@ -0,0 +1,31 @@
/*
* 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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privileged User Monitoring API
* version: 2023-10-31
*/
import { z } from '@kbn/zod';
import { MonitoredUserDoc } from './common.gen';
export type ListPrivMonUsersRequestQuery = z.infer<typeof ListPrivMonUsersRequestQuery>;
export const ListPrivMonUsersRequestQuery = z.object({
/**
* KQL query to filter the list of monitored users
*/
kql: z.string().optional(),
});
export type ListPrivMonUsersRequestQueryInput = z.input<typeof ListPrivMonUsersRequestQuery>;
export type ListPrivMonUsersResponse = z.infer<typeof ListPrivMonUsersResponse>;
export const ListPrivMonUsersResponse = z.array(MonitoredUserDoc);

View file

@ -0,0 +1,28 @@
openapi: 3.0.0
info:
title: Privileged User Monitoring API
version: "2023-10-31"
paths:
/api/entity_analytics/monitoring/users/list:
get:
x-labels: [ess, serverless]
x-codegen-enabled: true
operationId: ListPrivMonUsers
summary: List all monitored users
parameters:
- name: kql
in: query
required: false
schema:
type: string
description: KQL query to filter the list of monitored users
responses:
"200":
description: List of monitored users
content:
application/json:
schema:
type: array
items:
$ref: "./common.schema.yaml#/components/schemas/MonitoredUserDoc"

View file

@ -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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privileged User Monitoring API
* version: 2023-10-31
*/
import { z } from '@kbn/zod';
import { MonitoredUserDoc } from './common.gen';
export type UpdatePrivMonUserRequestParams = z.infer<typeof UpdatePrivMonUserRequestParams>;
export const UpdatePrivMonUserRequestParams = z.object({
id: z.string(),
});
export type UpdatePrivMonUserRequestParamsInput = z.input<typeof UpdatePrivMonUserRequestParams>;
export type UpdatePrivMonUserRequestBody = z.infer<typeof UpdatePrivMonUserRequestBody>;
export const UpdatePrivMonUserRequestBody = MonitoredUserDoc;
export type UpdatePrivMonUserRequestBodyInput = z.input<typeof UpdatePrivMonUserRequestBody>;
export type UpdatePrivMonUserResponse = z.infer<typeof UpdatePrivMonUserResponse>;
export const UpdatePrivMonUserResponse = MonitoredUserDoc;

View file

@ -0,0 +1,32 @@
openapi: 3.0.0
info:
title: Privileged User Monitoring API
version: "2023-10-31"
paths:
/api/entity_analytics/monitoring/users/{id}:
put:
x-labels: [ess, serverless]
x-codegen-enabled: true
operationId: UpdatePrivMonUser
summary: Update a monitored user
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "./common.schema.yaml#/components/schemas/MonitoredUserDoc"
responses:
"200":
description: User updated successfully
content:
application/json:
schema:
$ref: "./common.schema.yaml#/components/schemas/MonitoredUserDoc"

View file

@ -0,0 +1,22 @@
/*
* 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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privileged User Monitoring API
* version: 2023-10-31
*/
import { z } from '@kbn/zod';
export type BulkUploadUsersCSVResponse = z.infer<typeof BulkUploadUsersCSVResponse>;
export const BulkUploadUsersCSVResponse = z.object({
upserted_count: z.number().int().optional(),
});

View file

@ -0,0 +1,29 @@
openapi: 3.0.0
info:
title: Privileged User Monitoring API
version: "2023-10-31"
paths:
/api/entity_analytics/monitoring/users/_csv:
post:
x-labels: [ess, serverless]
x-codegen-enabled: true
operationId: BulkUploadUsersCSV
summary: Upsert multiple monitored users via CSV upload
requestBody:
description: CSV file containing users to upsert
required: true
content:
text/csv:
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer

View file

@ -0,0 +1,22 @@
/*
* 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.
*/
/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Privileged User Monitoring API
* version: 2023-10-31
*/
import { z } from '@kbn/zod';
export type BulkUploadUsersJSONResponse = z.infer<typeof BulkUploadUsersJSONResponse>;
export const BulkUploadUsersJSONResponse = z.object({
upserted_count: z.number().int().optional(),
});

View file

@ -0,0 +1,39 @@
openapi: 3.0.0
info:
title: Privileged User Monitoring API
version: "2023-10-31"
paths:
/api/entity_analytics/monitoring/users/_json:
post:
x-labels: [ess, serverless]
x-codegen-enabled: true
operationId: BulkUploadUsersJSON
summary: Upsert multiple monitored users via JSON upload
requestBody:
description: JSON file containing users to upsert
required: true
content:
text/json:
schema:
type: object
properties:
users:
type: array
items:
type: object
properties:
user_name:
type: string
is_monitored:
type: boolean
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer

View file

@ -264,6 +264,29 @@ import type {
} from './entity_analytics/monitoring/search_indices.gen';
import type { InitMonitoringEngineResponse } from './entity_analytics/privilege_monitoring/engine/init.gen';
import type { PrivMonHealthResponse } from './entity_analytics/privilege_monitoring/health.gen';
import type {
CreatePrivMonUserRequestBodyInput,
CreatePrivMonUserResponse,
} from './entity_analytics/privilege_monitoring/users/create.gen';
import type {
DeletePrivMonUserRequestParamsInput,
DeletePrivMonUserResponse,
} from './entity_analytics/privilege_monitoring/users/delete.gen';
import type {
GetPrivMonUserRequestParamsInput,
GetPrivMonUserResponse,
} from './entity_analytics/privilege_monitoring/users/get.gen';
import type {
ListPrivMonUsersRequestQueryInput,
ListPrivMonUsersResponse,
} from './entity_analytics/privilege_monitoring/users/list.gen';
import type {
UpdatePrivMonUserRequestParamsInput,
UpdatePrivMonUserRequestBodyInput,
UpdatePrivMonUserResponse,
} from './entity_analytics/privilege_monitoring/users/update.gen';
import type { BulkUploadUsersCSVResponse } from './entity_analytics/privilege_monitoring/users/upload_csv.gen';
import type { BulkUploadUsersJSONResponse } from './entity_analytics/privilege_monitoring/users/upload_json.gen';
import type { CleanUpRiskEngineResponse } from './entity_analytics/risk_engine/engine_cleanup_route.gen';
import type {
ConfigureRiskEngineSavedObjectRequestBodyInput,
@ -460,6 +483,30 @@ after 30 days. It also deletes other artifacts specific to the migration impleme
})
.catch(catchAxiosErrorFormatAndThrow);
}
async bulkUploadUsersCsv() {
this.log.info(`${new Date().toISOString()} Calling API BulkUploadUsersCSV`);
return this.kbnClient
.request<BulkUploadUsersCSVResponse>({
path: '/api/entity_analytics/monitoring/users/_csv',
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
},
method: 'POST',
})
.catch(catchAxiosErrorFormatAndThrow);
}
async bulkUploadUsersJson() {
this.log.info(`${new Date().toISOString()} Calling API BulkUploadUsersJSON`);
return this.kbnClient
.request<BulkUploadUsersJSONResponse>({
path: '/api/entity_analytics/monitoring/users/_json',
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
},
method: 'POST',
})
.catch(catchAxiosErrorFormatAndThrow);
}
/**
* Bulk upsert up to 1000 asset criticality records.
@ -595,6 +642,19 @@ If a record already exists for the specified entity, that record is overwritten
})
.catch(catchAxiosErrorFormatAndThrow);
}
async createPrivMonUser(props: CreatePrivMonUserProps) {
this.log.info(`${new Date().toISOString()} Calling API CreatePrivMonUser`);
return this.kbnClient
.request<CreatePrivMonUserResponse>({
path: '/api/entity_analytics/monitoring/users',
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
},
method: 'POST',
body: props.body,
})
.catch(catchAxiosErrorFormatAndThrow);
}
/**
* Create a new detection rule.
> warn
@ -772,6 +832,18 @@ For detailed information on Kibana actions and alerting, and additional API call
})
.catch(catchAxiosErrorFormatAndThrow);
}
async deletePrivMonUser(props: DeletePrivMonUserProps) {
this.log.info(`${new Date().toISOString()} Calling API DeletePrivMonUser`);
return this.kbnClient
.request<DeletePrivMonUserResponse>({
path: replaceParams('/api/entity_analytics/monitoring/users/{id}', props.params),
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
},
method: 'DELETE',
})
.catch(catchAxiosErrorFormatAndThrow);
}
/**
* Delete a detection rule using the `rule_id` or `id` field.
@ -1347,6 +1419,18 @@ finalize it.
})
.catch(catchAxiosErrorFormatAndThrow);
}
async getPrivMonUser(props: GetPrivMonUserProps) {
this.log.info(`${new Date().toISOString()} Calling API GetPrivMonUser`);
return this.kbnClient
.request<GetPrivMonUserResponse>({
path: replaceParams('/api/entity_analytics/monitoring/users/{id}', props.params),
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
},
method: 'GET',
})
.catch(catchAxiosErrorFormatAndThrow);
}
async getProtectionUpdatesNote(props: GetProtectionUpdatesNoteProps) {
this.log.info(`${new Date().toISOString()} Calling API GetProtectionUpdatesNote`);
return this.kbnClient
@ -1797,6 +1881,20 @@ providing you with the most current and effective threat detection capabilities.
})
.catch(catchAxiosErrorFormatAndThrow);
}
async listPrivMonUsers(props: ListPrivMonUsersProps) {
this.log.info(`${new Date().toISOString()} Calling API ListPrivMonUsers`);
return this.kbnClient
.request<ListPrivMonUsersResponse>({
path: '/api/entity_analytics/monitoring/users/list',
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
},
method: 'GET',
query: props.query,
})
.catch(catchAxiosErrorFormatAndThrow);
}
/**
* Update specific fields of an existing detection rule using the `rule_id` or `id` field.
@ -2298,6 +2396,19 @@ The difference between the `id` and `rule_id` is that the `id` is a unique rule
})
.catch(catchAxiosErrorFormatAndThrow);
}
async updatePrivMonUser(props: UpdatePrivMonUserProps) {
this.log.info(`${new Date().toISOString()} Calling API UpdatePrivMonUser`);
return this.kbnClient
.request<UpdatePrivMonUserResponse>({
path: replaceParams('/api/entity_analytics/monitoring/users/{id}', props.params),
headers: {
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
},
method: 'PUT',
body: props.body,
})
.catch(catchAxiosErrorFormatAndThrow);
}
/**
* Update a detection rule using the `rule_id` or `id` field. The original rule is replaced, and all unspecified fields are deleted.
@ -2405,6 +2516,9 @@ export interface CreateAlertsMigrationProps {
export interface CreateAssetCriticalityRecordProps {
body: CreateAssetCriticalityRecordRequestBodyInput;
}
export interface CreatePrivMonUserProps {
body: CreatePrivMonUserRequestBodyInput;
}
export interface CreateRuleProps {
body: CreateRuleRequestBodyInput;
}
@ -2429,6 +2543,9 @@ export interface DeleteEntityEngineProps {
export interface DeleteNoteProps {
body: DeleteNoteRequestBodyInput;
}
export interface DeletePrivMonUserProps {
params: DeletePrivMonUserRequestParamsInput;
}
export interface DeleteRuleProps {
query: DeleteRuleRequestQueryInput;
}
@ -2522,6 +2639,9 @@ export interface GetNotesProps {
export interface GetPolicyResponseProps {
query: GetPolicyResponseRequestQueryInput;
}
export interface GetPrivMonUserProps {
params: GetPrivMonUserRequestParamsInput;
}
export interface GetProtectionUpdatesNoteProps {
params: GetProtectionUpdatesNoteRequestParamsInput;
}
@ -2589,6 +2709,9 @@ export interface InternalUploadAssetCriticalityRecordsProps {
export interface ListEntitiesProps {
query: ListEntitiesRequestQueryInput;
}
export interface ListPrivMonUsersProps {
query: ListPrivMonUsersRequestQueryInput;
}
export interface PatchRuleProps {
body: PatchRuleRequestBodyInput;
}
@ -2661,6 +2784,10 @@ export interface SuggestUserProfilesProps {
export interface TriggerRiskScoreCalculationProps {
body: TriggerRiskScoreCalculationRequestBodyInput;
}
export interface UpdatePrivMonUserProps {
params: UpdatePrivMonUserRequestParamsInput;
body: UpdatePrivMonUserRequestBodyInput;
}
export interface UpdateRuleProps {
body: UpdateRuleRequestBodyInput;
}

View file

@ -335,6 +335,175 @@ paths:
summary: Health check on Privilege Monitoring
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users:
post:
operationId: CreatePrivMonUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserName'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
description: User created successfully
summary: Create a new monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_csv:
post:
operationId: BulkUploadUsersCSV
requestBody:
content:
text/csv:
schema:
type: string
description: CSV file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via CSV upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_json:
post:
operationId: BulkUploadUsersJSON
requestBody:
content:
text/json:
schema:
type: object
properties:
users:
items:
type: object
properties:
is_monitored:
type: boolean
user_name:
type: string
type: array
description: JSON file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via JSON upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/{id}:
delete:
operationId: DeletePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
aknowledged:
description: Indicates if the deletion was successful
type: boolean
message:
description: >-
A message providing additional information about the
deletion status
type: string
required:
- success
description: User deleted successfully
summary: Delete a monitored user
tags:
- Security Entity Analytics API
get:
operationId: GetPrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
description: User details retrieved
summary: Retrieve a monitored user by ID
tags:
- Security Entity Analytics API
put:
operationId: UpdatePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
description: User updated successfully
summary: Update a monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/list:
get:
operationId: ListPrivMonUsers
parameters:
- description: KQL query to filter the list of monitored users
in: query
name: kql
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/MonitoredUserDoc'
type: array
description: List of monitored users
summary: List all monitored users
tags:
- Security Entity Analytics API
/api/entity_store/enable:
post:
operationId: InitEntityStore
@ -1420,6 +1589,65 @@ components:
type: string
Metadata:
$ref: '#/components/schemas/TransformStatsMetadata'
MonitoredUserDoc:
type: object
properties:
'@timestamp':
format: date-time
type: string
entity_analytics_monitoring:
type: object
properties:
labels:
items:
type: object
properties:
field:
type: string
source:
type: string
value:
type: string
type: array
event:
type: object
properties:
ingested:
format: date-time
type: string
id:
type: string
labels:
type: object
properties:
monitoring:
type: object
properties:
privileged_users:
enum:
- monitored
- deleted
type: string
source_indices:
items:
type: string
type: array
source_integrations:
items:
type: string
type: array
sources:
items:
enum:
- csv
- index_sync
- api
type: array
user:
type: object
properties:
name:
type: string
MonitoringEngineDescriptor:
type: object
properties:
@ -1670,6 +1898,15 @@ components:
required:
- user
- entity
UserName:
type: object
properties:
user:
type: object
properties:
name:
description: The name of the user.
type: string
securitySchemes:
BasicAuth:
scheme: basic

View file

@ -335,6 +335,175 @@ paths:
summary: Health check on Privilege Monitoring
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users:
post:
operationId: CreatePrivMonUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserName'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
description: User created successfully
summary: Create a new monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_csv:
post:
operationId: BulkUploadUsersCSV
requestBody:
content:
text/csv:
schema:
type: string
description: CSV file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via CSV upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/_json:
post:
operationId: BulkUploadUsersJSON
requestBody:
content:
text/json:
schema:
type: object
properties:
users:
items:
type: object
properties:
is_monitored:
type: boolean
user_name:
type: string
type: array
description: JSON file containing users to upsert
required: true
responses:
'200':
content:
application/json:
schema:
type: object
properties:
upserted_count:
type: integer
description: Successful response
summary: Upsert multiple monitored users via JSON upload
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/{id}:
delete:
operationId: DeletePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
aknowledged:
description: Indicates if the deletion was successful
type: boolean
message:
description: >-
A message providing additional information about the
deletion status
type: string
required:
- success
description: User deleted successfully
summary: Delete a monitored user
tags:
- Security Entity Analytics API
get:
operationId: GetPrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
description: User details retrieved
summary: Retrieve a monitored user by ID
tags:
- Security Entity Analytics API
put:
operationId: UpdatePrivMonUser
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/MonitoredUserDoc'
description: User updated successfully
summary: Update a monitored user
tags:
- Security Entity Analytics API
/api/entity_analytics/monitoring/users/list:
get:
operationId: ListPrivMonUsers
parameters:
- description: KQL query to filter the list of monitored users
in: query
name: kql
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/MonitoredUserDoc'
type: array
description: List of monitored users
summary: List all monitored users
tags:
- Security Entity Analytics API
/api/entity_store/enable:
post:
operationId: InitEntityStore
@ -1420,6 +1589,65 @@ components:
type: string
Metadata:
$ref: '#/components/schemas/TransformStatsMetadata'
MonitoredUserDoc:
type: object
properties:
'@timestamp':
format: date-time
type: string
entity_analytics_monitoring:
type: object
properties:
labels:
items:
type: object
properties:
field:
type: string
source:
type: string
value:
type: string
type: array
event:
type: object
properties:
ingested:
format: date-time
type: string
id:
type: string
labels:
type: object
properties:
monitoring:
type: object
properties:
privileged_users:
enum:
- monitored
- deleted
type: string
source_indices:
items:
type: string
type: array
source_integrations:
items:
type: string
type: array
sources:
items:
enum:
- csv
- index_sync
- api
type: array
user:
type: object
properties:
name:
type: string
MonitoringEngineDescriptor:
type: object
properties:
@ -1670,6 +1898,15 @@ components:
required:
- user
- entity
UserName:
type: object
properties:
user:
type: object
properties:
name:
description: The name of the user.
type: string
securitySchemes:
BasicAuth:
scheme: basic

View file

@ -9,6 +9,7 @@ import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
// Static index names: may be more obvious and easier to manage.
export const privilegedMonitorBaseIndexName = '.entity_analytics.monitoring';
// Used in Phase 0.
export const getPrivilegedMonitorUsersIndex = (namespace: string) =>
`${privilegedMonitorBaseIndexName}.users-${namespace}`;

View file

@ -17,7 +17,16 @@ import type {
import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
import moment from 'moment';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { merge } from 'lodash';
import type { UpdatePrivMonUserRequestBody } from '../../../../common/api/entity_analytics/privilege_monitoring/users/update.gen';
import type {
CreatePrivMonUserRequestBody,
CreatePrivMonUserResponse,
} from '../../../../common/api/entity_analytics/privilege_monitoring/users/create.gen';
import type { InitMonitoringEngineResponse } from '../../../../common/api/entity_analytics/privilege_monitoring/engine/init.gen';
import type { MonitoredUserDoc } from '../../../../common/api/entity_analytics/privilege_monitoring/users/common.gen';
import {
EngineComponentResourceEnum,
type EngineComponentResource,
@ -38,6 +47,7 @@ import {
PRIVMON_ENGINE_INITIALIZATION_EVENT,
PRIVMON_ENGINE_RESOURCE_INIT_FAILURE_EVENT,
} from '../../telemetry/event_based/events';
import type { PrivMonUserSource } from './types';
interface PrivilegeMonitoringClientOpts {
logger: Logger;
@ -54,10 +64,12 @@ interface PrivilegeMonitoringClientOpts {
export class PrivilegeMonitoringDataClient {
private apiKeyGenerator?: ApiKeyManager;
private esClient: ElasticsearchClient;
private internalUserClient: ElasticsearchClient;
private engineClient: PrivilegeMonitoringEngineDescriptorClient;
constructor(private readonly opts: PrivilegeMonitoringClientOpts) {
this.esClient = opts.clusterClient.asCurrentUser;
this.internalUserClient = opts.clusterClient.asInternalUser;
this.apiKeyGenerator = opts.apiKeyManager;
this.engineClient = new PrivilegeMonitoringEngineDescriptorClient({
soClient: opts.soClient,
@ -130,11 +142,14 @@ export class PrivilegeMonitoringDataClient {
public async createOrUpdateIndex() {
await createOrUpdateIndex({
esClient: this.esClient,
esClient: this.internalUserClient,
logger: this.opts.logger,
options: {
index: this.getIndex(),
mappings: generateUserIndexMappings(),
settings: {
hidden: true,
},
},
});
}
@ -165,6 +180,71 @@ export class PrivilegeMonitoringDataClient {
return getPrivilegedMonitorUsersIndex(this.opts.namespace);
}
public async createUser(
user: CreatePrivMonUserRequestBody,
source: PrivMonUserSource
): Promise<CreatePrivMonUserResponse> {
const doc = merge(user, {
labels: {
monitoring: { privileged_users: 'monitored' },
sources: [source],
},
});
const res = await this.esClient.index({
index: this.getIndex(),
refresh: 'wait_for',
document: doc,
});
const newUser = await this.getUser(res._id);
if (!newUser) {
throw new Error(`Failed to create user: ${res._id}`);
}
return newUser;
}
public async getUser(id: string): Promise<MonitoredUserDoc | undefined> {
const response = await this.esClient.get<MonitoredUserDoc>({
index: this.getIndex(),
id,
});
return response.found
? ({ ...response._source, id: response._id } as MonitoredUserDoc)
: undefined;
}
public async updateUser(
id: string,
user: UpdatePrivMonUserRequestBody
): Promise<MonitoredUserDoc | undefined> {
await this.esClient.update<MonitoredUserDoc>({
index: this.getIndex(),
refresh: 'wait_for',
id,
doc: user,
});
return this.getUser(id);
}
public async deleteUser(id: string): Promise<void> {
await this.esClient.delete({
index: this.getIndex(),
id,
});
}
public async listUsers(kuery?: string): Promise<MonitoredUserDoc[]> {
const query = kuery ? toElasticsearchQuery(fromKueryExpression(kuery)) : { match_all: {} };
const response = await this.esClient.search({
index: this.getIndex(),
query,
});
return response.hits.hits.map((hit) => ({
id: hit._id,
...(hit._source as {}),
})) as MonitoredUserDoc[];
}
private log(level: Exclude<keyof Logger, 'get' | 'log' | 'isLevelEnabled'>, msg: string) {
this.opts.logger[level](
`[Privileged Monitoring Engine][namespace: ${this.opts.namespace}] ${msg}`

View file

@ -10,13 +10,29 @@ import { healthCheckPrivilegeMonitoringRoute } from './health';
import { initPrivilegeMonitoringEngineRoute } from './init';
import { searchPrivilegeMonitoringIndicesRoute } from './search_indices';
import {
getUserRoute,
createUserRoute,
deleteUserRoute,
listUsersRoute,
updateUserRoute,
uploadUsersCSVRoute,
uploadUsersJSONRoute,
} from './users';
export const registerPrivilegeMonitoringRoutes = ({
router,
logger,
getStartServices,
config,
}: EntityAnalyticsRoutesDeps) => {
initPrivilegeMonitoringEngineRoute(router, logger, config);
healthCheckPrivilegeMonitoringRoute(router, logger, config);
searchPrivilegeMonitoringIndicesRoute(router, logger, config);
getUserRoute(router, logger);
createUserRoute(router, logger);
deleteUserRoute(router, logger);
listUsersRoute(router, logger);
updateUserRoute(router, logger);
uploadUsersCSVRoute(router, logger);
uploadUsersJSONRoute(router, logger);
};

View file

@ -0,0 +1,56 @@
/*
* 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 { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import { CreatePrivMonUserRequestBody } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/create.gen';
import type { CreatePrivMonUserResponse } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/create.gen';
import { API_VERSIONS, APP_ID } from '../../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../../types';
export const createUserRoute = (router: EntityAnalyticsRoutesDeps['router'], logger: Logger) => {
router.versioned
.post({
access: 'public',
path: '/api/entity_analytics/monitoring/users',
security: {
authz: {
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {
body: CreatePrivMonUserRequestBody,
},
},
},
async (context, request, response): Promise<IKibanaResponse<CreatePrivMonUserResponse>> => {
const siemResponse = buildSiemResponse(response);
try {
const secSol = await context.securitySolution;
const body = await secSol
.getPrivilegeMonitoringDataClient()
.createUser(request.body, 'api');
return response.ok({ body });
} catch (e) {
const error = transformError(e);
logger.error(`Error creating user: ${error.message}`);
return siemResponse.error({
statusCode: error.statusCode,
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,54 @@
/*
* 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 { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import { DeletePrivMonUserRequestParams } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/delete.gen';
import type { DeletePrivMonUserResponse } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/delete.gen';
import { API_VERSIONS, APP_ID } from '../../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../../types';
export const deleteUserRoute = (router: EntityAnalyticsRoutesDeps['router'], logger: Logger) => {
router.versioned
.delete({
access: 'public',
path: '/api/entity_analytics/monitoring/users/{id}',
security: {
authz: {
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {
params: DeletePrivMonUserRequestParams,
},
},
},
async (context, request, response): Promise<IKibanaResponse<DeletePrivMonUserResponse>> => {
const siemResponse = buildSiemResponse(response);
try {
const secSol = await context.securitySolution;
await secSol.getPrivilegeMonitoringDataClient().deleteUser(request.params.id);
return response.ok({ body: { aknowledged: true } });
} catch (e) {
const error = transformError(e);
logger.error(`Error deleting user: ${error.message}`);
return siemResponse.error({
statusCode: error.statusCode,
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,54 @@
/*
* 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 { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import { GetPrivMonUserRequestParams } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/get.gen';
import type { GetPrivMonUserResponse } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/get.gen';
import { API_VERSIONS, APP_ID } from '../../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../../types';
export const getUserRoute = (router: EntityAnalyticsRoutesDeps['router'], logger: Logger) => {
router.versioned
.get({
access: 'public',
path: '/api/entity_analytics/monitoring/users/{id}',
security: {
authz: {
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {
params: GetPrivMonUserRequestParams,
},
},
},
async (context, request, response): Promise<IKibanaResponse<GetPrivMonUserResponse>> => {
const siemResponse = buildSiemResponse(response);
try {
const secSol = await context.securitySolution;
const body = await secSol.getPrivilegeMonitoringDataClient().getUser(request.params.id);
return response.ok({ body });
} catch (e) {
const error = transformError(e);
logger.error(`Error retrieving user: ${error.message}`);
return siemResponse.error({
statusCode: error.statusCode,
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,14 @@
/*
* 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.
*/
export * from './create';
export * from './get';
export * from './list';
export * from './update';
export * from './delete';
export * from './upload_json';
export * from './upload_csv';

View file

@ -0,0 +1,54 @@
/*
* 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 { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import { ListPrivMonUsersRequestQuery } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/list.gen';
import type { ListPrivMonUsersResponse } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/list.gen';
import { API_VERSIONS, APP_ID } from '../../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../../types';
export const listUsersRoute = (router: EntityAnalyticsRoutesDeps['router'], logger: Logger) => {
router.versioned
.get({
access: 'public',
path: '/api/entity_analytics/monitoring/users/list',
security: {
authz: {
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {
query: ListPrivMonUsersRequestQuery,
},
},
},
async (context, request, response): Promise<IKibanaResponse<ListPrivMonUsersResponse>> => {
const siemResponse = buildSiemResponse(response);
try {
const secSol = await context.securitySolution;
const body = await secSol.getPrivilegeMonitoringDataClient().listUsers(request.query.kql);
return response.ok({ body });
} catch (e) {
const error = transformError(e);
logger.error(`Error listing users: ${error.message}`);
return siemResponse.error({
statusCode: error.statusCode,
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,59 @@
/*
* 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 { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import {
UpdatePrivMonUserRequestParams,
UpdatePrivMonUserRequestBody,
} from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/update.gen';
import { API_VERSIONS, APP_ID } from '../../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../../types';
export const updateUserRoute = (router: EntityAnalyticsRoutesDeps['router'], logger: Logger) => {
router.versioned
.put({
access: 'public',
path: '/api/entity_analytics/monitoring/users/{id}',
security: {
authz: {
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {
params: UpdatePrivMonUserRequestParams,
body: UpdatePrivMonUserRequestBody,
},
},
},
async (context, request, response): Promise<IKibanaResponse> => {
const siemResponse = buildSiemResponse(response);
try {
const secSol = await context.securitySolution;
const body = await secSol
.getPrivilegeMonitoringDataClient()
.updateUser(request.params.id, request.body);
return response.ok({ body });
} catch (e) {
const error = transformError(e);
logger.error(`Error updating user: ${error.message}`);
return siemResponse.error({
statusCode: error.statusCode,
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,53 @@
/*
* 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 { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import type { BulkUploadUsersCSVResponse } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/upload_csv.gen';
import { API_VERSIONS, APP_ID } from '../../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../../types';
export const uploadUsersCSVRoute = (
router: EntityAnalyticsRoutesDeps['router'],
logger: Logger
) => {
router.versioned
.post({
access: 'public',
path: '/api/entity_analytics/monitoring/users/_csv',
security: {
authz: {
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {},
},
},
async (context, request, response): Promise<IKibanaResponse<BulkUploadUsersCSVResponse>> => {
const siemResponse = buildSiemResponse(response);
try {
// Placeholder for actual implementation
return response.ok({ body: { upserted_count: 15 } });
} catch (e) {
const error = transformError(e);
logger.error(`Error uploading users via CSV: ${error.message}`);
return siemResponse.error({
statusCode: error.statusCode,
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,53 @@
/*
* 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 { IKibanaResponse, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import type { BulkUploadUsersJSONResponse } from '../../../../../../common/api/entity_analytics/privilege_monitoring/users/upload_json.gen';
import { API_VERSIONS, APP_ID } from '../../../../../../common/constants';
import type { EntityAnalyticsRoutesDeps } from '../../../types';
export const uploadUsersJSONRoute = (
router: EntityAnalyticsRoutesDeps['router'],
logger: Logger
) => {
router.versioned
.post({
access: 'public',
path: '/api/entity_analytics/monitoring/users/_json',
security: {
authz: {
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
},
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {},
},
},
async (context, request, response): Promise<IKibanaResponse<BulkUploadUsersJSONResponse>> => {
const siemResponse = buildSiemResponse(response);
try {
// Placeholder for actual implementation
return response.ok({ body: { upserted_count: 10 } });
} catch (e) {
const error = transformError(e);
logger.error(`Error uploading users via JSON: ${error.message}`);
return siemResponse.error({
statusCode: error.statusCode,
body: error.message,
});
}
}
);
};

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export type PrivMonUserSource = 'csv' | 'api' | 'index_sync';

View file

@ -27,6 +27,7 @@ import { ConfigureRiskEngineSavedObjectRequestBodyInput } from '@kbn/security-so
import { CopyTimelineRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/copy_timeline/copy_timeline_route.gen';
import { CreateAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen';
import { CreateAssetCriticalityRecordRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/create_asset_criticality.gen';
import { CreatePrivMonUserRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/privilege_monitoring/users/create.gen';
import { CreateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.gen';
import {
CreateRuleMigrationRequestParamsInput,
@ -43,6 +44,7 @@ import {
DeleteEntityEngineRequestParamsInput,
} from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/delete.gen';
import { DeleteNoteRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_note/delete_note_route.gen';
import { DeletePrivMonUserRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/privilege_monitoring/users/delete.gen';
import { DeleteRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.gen';
import { DeleteTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_timelines/delete_timelines_route.gen';
import { DeprecatedTriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen';
@ -81,6 +83,7 @@ import { GetEntityEngineRequestParamsInput } from '@kbn/security-solution-plugin
import { GetEntityStoreStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/status.gen';
import { GetNotesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_notes/get_notes_route.gen';
import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy_response.gen';
import { GetPrivMonUserRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/privilege_monitoring/users/get.gen';
import { GetProtectionUpdatesNoteRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen';
import {
GetRuleExecutionEventsRequestQueryInput,
@ -118,6 +121,7 @@ import {
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
import { InstallPrepackedTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen';
import { ListEntitiesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/entities/list_entities.gen';
import { ListPrivMonUsersRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/privilege_monitoring/users/list.gen';
import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen';
import { PatchTimelineRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/patch_timelines/patch_timeline_route.gen';
import {
@ -150,6 +154,10 @@ import { StopEntityEngineRequestParamsInput } from '@kbn/security-solution-plugi
import { StopRuleMigrationRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
import { SuggestUserProfilesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/users/suggest_user_profiles_route.gen';
import { TriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen';
import {
UpdatePrivMonUserRequestParamsInput,
UpdatePrivMonUserRequestBodyInput,
} from '@kbn/security-solution-plugin/common/api/entity_analytics/privilege_monitoring/users/update.gen';
import { UpdateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.gen';
import {
UpdateRuleMigrationRequestParamsInput,
@ -214,6 +222,20 @@ after 30 days. It also deletes other artifacts specific to the migration impleme
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
},
bulkUploadUsersCsv(kibanaSpace: string = 'default') {
return supertest
.post(routeWithNamespace('/api/entity_analytics/monitoring/users/_csv', kibanaSpace))
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
},
bulkUploadUsersJson(kibanaSpace: string = 'default') {
return supertest
.post(routeWithNamespace('/api/entity_analytics/monitoring/users/_json', kibanaSpace))
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
},
/**
* Bulk upsert up to 1000 asset criticality records.
@ -318,6 +340,14 @@ If a record already exists for the specified entity, that record is overwritten
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
createPrivMonUser(props: CreatePrivMonUserProps, kibanaSpace: string = 'default') {
return supertest
.post(routeWithNamespace('/api/entity_analytics/monitoring/users', kibanaSpace))
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
/**
* Create a new detection rule.
> warn
@ -474,6 +504,18 @@ For detailed information on Kibana actions and alerting, and additional API call
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
deletePrivMonUser(props: DeletePrivMonUserProps, kibanaSpace: string = 'default') {
return supertest
.delete(
routeWithNamespace(
replaceParams('/api/entity_analytics/monitoring/users/{id}', props.params),
kibanaSpace
)
)
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
},
/**
* Delete a detection rule using the `rule_id` or `id` field.
@ -903,6 +945,18 @@ finalize it.
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.query(props.query);
},
getPrivMonUser(props: GetPrivMonUserProps, kibanaSpace: string = 'default') {
return supertest
.get(
routeWithNamespace(
replaceParams('/api/entity_analytics/monitoring/users/{id}', props.params),
kibanaSpace
)
)
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
},
getProtectionUpdatesNote(
props: GetProtectionUpdatesNoteProps,
kibanaSpace: string = 'default'
@ -1278,6 +1332,14 @@ providing you with the most current and effective threat detection capabilities.
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
},
listPrivMonUsers(props: ListPrivMonUsersProps, kibanaSpace: string = 'default') {
return supertest
.get(routeWithNamespace('/api/entity_analytics/monitoring/users/list', kibanaSpace))
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.query(props.query);
},
/**
* Update specific fields of an existing detection rule using the `rule_id` or `id` field.
@ -1645,6 +1707,19 @@ The difference between the `id` and `rule_id` is that the `id` is a unique rule
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
updatePrivMonUser(props: UpdatePrivMonUserProps, kibanaSpace: string = 'default') {
return supertest
.put(
routeWithNamespace(
replaceParams('/api/entity_analytics/monitoring/users/{id}', props.params),
kibanaSpace
)
)
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
/**
* Update a detection rule using the `rule_id` or `id` field. The original rule is replaced, and all unspecified fields are deleted.
@ -1742,6 +1817,9 @@ export interface CreateAlertsMigrationProps {
export interface CreateAssetCriticalityRecordProps {
body: CreateAssetCriticalityRecordRequestBodyInput;
}
export interface CreatePrivMonUserProps {
body: CreatePrivMonUserRequestBodyInput;
}
export interface CreateRuleProps {
body: CreateRuleRequestBodyInput;
}
@ -1766,6 +1844,9 @@ export interface DeleteEntityEngineProps {
export interface DeleteNoteProps {
body: DeleteNoteRequestBodyInput;
}
export interface DeletePrivMonUserProps {
params: DeletePrivMonUserRequestParamsInput;
}
export interface DeleteRuleProps {
query: DeleteRuleRequestQueryInput;
}
@ -1856,6 +1937,9 @@ export interface GetNotesProps {
export interface GetPolicyResponseProps {
query: GetPolicyResponseRequestQueryInput;
}
export interface GetPrivMonUserProps {
params: GetPrivMonUserRequestParamsInput;
}
export interface GetProtectionUpdatesNoteProps {
params: GetProtectionUpdatesNoteRequestParamsInput;
}
@ -1919,6 +2003,9 @@ export interface InstallPrepackedTimelinesProps {
export interface ListEntitiesProps {
query: ListEntitiesRequestQueryInput;
}
export interface ListPrivMonUsersProps {
query: ListPrivMonUsersRequestQueryInput;
}
export interface PatchRuleProps {
body: PatchRuleRequestBodyInput;
}
@ -1991,6 +2078,10 @@ export interface SuggestUserProfilesProps {
export interface TriggerRiskScoreCalculationProps {
body: TriggerRiskScoreCalculationRequestBodyInput;
}
export interface UpdatePrivMonUserProps {
params: UpdatePrivMonUserRequestParamsInput;
body: UpdatePrivMonUserRequestBodyInput;
}
export interface UpdateRuleProps {
body: UpdateRuleRequestBodyInput;
}

View file

@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('Entity Analytics - Privilege Monitoring', function () {
loadTestFile(require.resolve('./engine'));
loadTestFile(require.resolve('./search_indices'));
loadTestFile(require.resolve('./privileged_users/api'));
});
}

View file

@ -0,0 +1,119 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { dataViewRouteHelpersFactory } from '../../../utils/data_view';
import { PrivMonUtils } from './utils';
export default ({ getService }: FtrProviderContext) => {
const api = getService('securitySolutionApi');
const supertest = getService('supertest');
const es = getService('es');
const log = getService('log');
const privMonUtils = PrivMonUtils(getService);
describe('@ess @serverless @skipInServerlessMKI Entity Monitoring Privileged Users CRUD APIs', () => {
const dataView = dataViewRouteHelpersFactory(supertest);
before(async () => {
await dataView.create('security-solution');
await privMonUtils.initPrivMonEngine();
});
after(async () => {
await dataView.delete('security-solution');
});
describe('CRUD API', () => {
it('should create a user', async () => {
log.info(`creating a user`);
const res = await api.createPrivMonUser({
body: { user: { name: 'test_user1' } },
});
if (res.status !== 200) {
log.error(`Creating privmon user failed`);
log.error(JSON.stringify(res.body));
}
expect(res.status).eql(200);
expect(res.body);
});
it('should retrieve a user', async () => {
log.info(`retrieving a user`);
const { body } = await api.createPrivMonUser({
body: { user: { name: 'test_user2' } },
});
const res = await api.getPrivMonUser({ params: { id: body.id } });
if (res.status !== 200) {
log.error(`Retrieving privmon user failed`);
log.error(JSON.stringify(res.body));
}
expect(res.status).eql(200);
});
it('should update a user', async () => {
log.info(`updating a user`);
const { body } = await api.createPrivMonUser({
body: { user: { name: 'test_user3' } },
});
const res = await api.updatePrivMonUser({
body: { user: { name: 'updated' } },
params: { id: body.id },
});
if (res.status !== 200) {
log.error(`Updating privmon user failed`);
log.error(JSON.stringify(res.body));
}
expect(res.status).eql(200);
expect(res.body.user.name).to.be('updated');
});
it('should list users', async () => {
log.info(`listing users`);
const { body } = await api.createPrivMonUser({
body: { user: { name: 'test_user4' } },
});
// Ensure the data is indexed and available for searching, in case we ever remove `refresh: wait_for` when indexing
await es.indices.refresh({ index: body._index });
const res = await api.listPrivMonUsers({ query: { kql: `user.name: test*` } });
if (res.status !== 200) {
log.error(`Listing privmon users failed`);
log.error(JSON.stringify(res.body));
}
expect(res.status).eql(200);
expect(res.body.length).to.be.greaterThan(0);
});
it('should delete a user', async () => {
log.info(`deleting a user`);
const { body } = await api.createPrivMonUser({
body: { user: { name: 'test_user5' } },
});
const res = await api.deletePrivMonUser({ params: { id: body.id } });
if (res.status !== 200) {
log.error(`Deleting privmon user failed`);
log.error(JSON.stringify(res.body));
}
expect(res.status).eql(200);
expect(res.body).to.eql({ aknowledged: true });
});
});
});
};

View file

@ -0,0 +1,45 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
export const PrivMonUtils = (
getService: FtrProviderContext['getService'],
namespace: string = 'default'
) => {
const api = getService('securitySolutionApi');
const log = getService('log');
log.info(`Monitoring: Privileged Users: Using namespace ${namespace}`);
const initPrivMonEngine = async () => {
log.info(`Initializing Privilege Monitoring engine in namespace ${namespace || 'default'}`);
const res = await api.initMonitoringEngine(namespace);
if (res.status !== 200) {
log.error(`Failed to initialize engine`);
log.error(JSON.stringify(res.body));
}
expect(res.status).to.eql(200);
};
const retry = async <T>(fn: () => Promise<T>, retries: number = 5, delay: number = 1000) => {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) {
throw error;
}
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
};
return { initPrivMonEngine, retry };
};