mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Entity Analytics][Privilege Monitoring] Engine initialization API (#215663)
## Summary This PR introduces the first building blocks for the [Entity Analytics Privileged Monitoring](https://github.com/elastic/security-team/issues/9971). We follow the approach used in the Entity Store and add a new "Engine", which consists of the following components: * Public API * INIT and HEALTH routes * Kibana task * Privilege Monitoring Data Client * Engine Saved Object * API key manager * Related storage indices * Feature Flag: `privilegeMonitoringEnabled` set to `false` by default. * API integration test configuration * only tests that the health endpoint is available * Auditing and Telemetry ## Testing steps 1. Make sure to add `privilegeMonitoringEnabled` to your `kibana.dev.yaml` 2. In devtools, ensure the API is working with `GET kbn:/api/entity_analytics/monitoring/privileges/health` 3. Start the engine with: `POST kbn:/api/entity_analytics/monitoring/engine/init` 4. Look for `DEBUG` logs mentioning the `entity_analytics:monitoring:privileges:engine` task --------- Co-authored-by: CAWilson94 <charlotte.wilson@elastic.co> Co-authored-by: Charlotte Alexandra Wilson <CAWilson94@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
b4d3a2a8f2
commit
1bf39845da
52 changed files with 1930 additions and 23 deletions
|
@ -24,7 +24,7 @@ disabled:
|
||||||
# MKI only configs files
|
# MKI only configs files
|
||||||
- x-pack/test_serverless/functional/test_suites/security/config.mki_only.ts
|
- x-pack/test_serverless/functional/test_suites/security/config.mki_only.ts
|
||||||
|
|
||||||
defaultQueue: 'n2-4-spot'
|
defaultQueue: "n2-4-spot"
|
||||||
enabled:
|
enabled:
|
||||||
- x-pack/test_serverless/api_integration/test_suites/security/config.ts
|
- x-pack/test_serverless/api_integration/test_suites/security/config.ts
|
||||||
- x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts
|
- x-pack/test_serverless/api_integration/test_suites/security/config.feature_flags.ts
|
||||||
|
@ -106,6 +106,7 @@ enabled:
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/configs/serverless.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/configs/serverless.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/serverless.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/serverless.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts
|
||||||
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/monitoring/trial_license_complete_tier/configs/serverless.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/configs/serverless.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/configs/serverless.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/configs/serverless.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/configs/serverless.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/configs/serverless.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/configs/serverless.config.ts
|
||||||
|
|
|
@ -81,6 +81,7 @@ enabled:
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/configs/ess.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/configs/ess.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/ess.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/ess.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/ess.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/ess.config.ts
|
||||||
|
- x-pack/test/security_solution_api_integration/test_suites/entity_analytics/monitoring/trial_license_complete_tier/configs/ess.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/configs/ess.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/configs/ess.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/configs/ess.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/configs/ess.config.ts
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/ess.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/ess.config.ts
|
||||||
|
|
|
@ -13243,6 +13243,35 @@ paths:
|
||||||
summary: Create or update a protection updates note
|
summary: Create or update a protection updates note
|
||||||
tags:
|
tags:
|
||||||
- Security Endpoint Management API
|
- Security Endpoint Management API
|
||||||
|
/api/entity_analytics/monitoring/engine/init:
|
||||||
|
post:
|
||||||
|
operationId: InitMonitoringEngine
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoringEngineDescriptor'
|
||||||
|
description: Successful response
|
||||||
|
summary: Initialize the Privilege Monitoring Engine
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
|
/api/entity_analytics/monitoring/privileges/health:
|
||||||
|
get:
|
||||||
|
operationId: PrivMonHealth
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ok:
|
||||||
|
type: boolean
|
||||||
|
description: Successful response
|
||||||
|
summary: Health check on Privilege Monitoring
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
/api/entity_store/enable:
|
/api/entity_store/enable:
|
||||||
post:
|
post:
|
||||||
operationId: InitEntityStore
|
operationId: InitEntityStore
|
||||||
|
@ -62564,6 +62593,14 @@ components:
|
||||||
type: string
|
type: string
|
||||||
Security_Entity_Analytics_API_Metadata:
|
Security_Entity_Analytics_API_Metadata:
|
||||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_TransformStatsMetadata'
|
$ref: '#/components/schemas/Security_Entity_Analytics_API_TransformStatsMetadata'
|
||||||
|
Security_Entity_Analytics_API_MonitoringEngineDescriptor:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineStatus'
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- status
|
||||||
Security_Entity_Analytics_API_RiskEngineScheduleNowErrorResponse:
|
Security_Entity_Analytics_API_RiskEngineScheduleNowErrorResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -15402,6 +15402,35 @@ paths:
|
||||||
summary: Create or update a protection updates note
|
summary: Create or update a protection updates note
|
||||||
tags:
|
tags:
|
||||||
- Security Endpoint Management API
|
- Security Endpoint Management API
|
||||||
|
/api/entity_analytics/monitoring/engine/init:
|
||||||
|
post:
|
||||||
|
operationId: InitMonitoringEngine
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Security_Entity_Analytics_API_MonitoringEngineDescriptor'
|
||||||
|
description: Successful response
|
||||||
|
summary: Initialize the Privilege Monitoring Engine
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
|
/api/entity_analytics/monitoring/privileges/health:
|
||||||
|
get:
|
||||||
|
operationId: PrivMonHealth
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ok:
|
||||||
|
type: boolean
|
||||||
|
description: Successful response
|
||||||
|
summary: Health check on Privilege Monitoring
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
/api/entity_store/enable:
|
/api/entity_store/enable:
|
||||||
post:
|
post:
|
||||||
operationId: InitEntityStore
|
operationId: InitEntityStore
|
||||||
|
@ -72049,6 +72078,14 @@ components:
|
||||||
type: string
|
type: string
|
||||||
Security_Entity_Analytics_API_Metadata:
|
Security_Entity_Analytics_API_Metadata:
|
||||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_TransformStatsMetadata'
|
$ref: '#/components/schemas/Security_Entity_Analytics_API_TransformStatsMetadata'
|
||||||
|
Security_Entity_Analytics_API_MonitoringEngineDescriptor:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineStatus'
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- status
|
||||||
Security_Entity_Analytics_API_RiskEngineScheduleNowErrorResponse:
|
Security_Entity_Analytics_API_RiskEngineScheduleNowErrorResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -873,6 +873,9 @@
|
||||||
"policy-settings-protection-updates-note": [
|
"policy-settings-protection-updates-note": [
|
||||||
"note"
|
"note"
|
||||||
],
|
],
|
||||||
|
"privilege-monitoring-status": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
"product-doc-install-status": [
|
"product-doc-install-status": [
|
||||||
"index_name",
|
"index_name",
|
||||||
"installation_status",
|
"installation_status",
|
||||||
|
|
|
@ -2913,6 +2913,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"privilege-monitoring-status": {
|
||||||
|
"dynamic": false,
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"product-doc-install-status": {
|
"product-doc-install-status": {
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -11,4 +11,4 @@ export { registerCoreObjectTypes } from './registration';
|
||||||
|
|
||||||
// set minimum number of registered saved objects to ensure no object types are removed after 8.8
|
// set minimum number of registered saved objects to ensure no object types are removed after 8.8
|
||||||
// declared in internal implementation exclicilty to prevent unintended changes.
|
// declared in internal implementation exclicilty to prevent unintended changes.
|
||||||
export const SAVED_OBJECT_TYPES_COUNT = 128 as const;
|
export const SAVED_OBJECT_TYPES_COUNT = 129 as const;
|
||||||
|
|
|
@ -149,6 +149,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
||||||
"osquery-pack-asset": "cd140bc2e4b092e93692b587bf6e38051ef94c75",
|
"osquery-pack-asset": "cd140bc2e4b092e93692b587bf6e38051ef94c75",
|
||||||
"osquery-saved-query": "6095e288750aa3164dfe186c74bc5195c2bf2bd4",
|
"osquery-saved-query": "6095e288750aa3164dfe186c74bc5195c2bf2bd4",
|
||||||
"policy-settings-protection-updates-note": "33924bb246f9e5bcb876109cc83e3c7a28308352",
|
"policy-settings-protection-updates-note": "33924bb246f9e5bcb876109cc83e3c7a28308352",
|
||||||
|
"privilege-monitoring-status": "9b11c4a49e679e2827b0468ba27269a19345c049",
|
||||||
"product-doc-install-status": "ca6e96840228e4cc2f11bae24a0797f4f7238c8c",
|
"product-doc-install-status": "ca6e96840228e4cc2f11bae24a0797f4f7238c8c",
|
||||||
"query": "501bece68f26fe561286a488eabb1a8ab12f1137",
|
"query": "501bece68f26fe561286a488eabb1a8ab12f1137",
|
||||||
"risk-engine-configuration": "bab237d09c2e7189dddddcb1b28f19af69755efb",
|
"risk-engine-configuration": "bab237d09c2e7189dddddcb1b28f19af69755efb",
|
||||||
|
|
|
@ -117,6 +117,7 @@ const previouslyRegisteredTypes = [
|
||||||
'osquery-usage-metric',
|
'osquery-usage-metric',
|
||||||
'osquery-manager-usage-metric',
|
'osquery-manager-usage-metric',
|
||||||
'policy-settings-protection-updates-note',
|
'policy-settings-protection-updates-note',
|
||||||
|
'privilege-monitoring-status',
|
||||||
'product-doc-install-status',
|
'product-doc-install-status',
|
||||||
'query',
|
'query',
|
||||||
'rules-settings',
|
'rules-settings',
|
||||||
|
|
|
@ -58,26 +58,35 @@ export const canDisableEntityDiscovery = async (client: ElasticsearchClient) =>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const entityDefinitionRuntimePrivileges = (sourceIndices: string[]) => ({
|
export const entityDefinitionRuntimePrivileges = (sourceIndices: string[]) => {
|
||||||
cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'],
|
return {
|
||||||
index: [
|
cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'],
|
||||||
{
|
index: [
|
||||||
names: [ENTITY_INTERNAL_INDICES_PATTERN],
|
{
|
||||||
privileges: ['create_index', 'delete_index', 'index', 'create_doc', 'auto_configure', 'read'],
|
names: [ENTITY_INTERNAL_INDICES_PATTERN],
|
||||||
},
|
privileges: [
|
||||||
{
|
'create_index',
|
||||||
names: [...sourceIndices, ENTITY_INTERNAL_INDICES_PATTERN],
|
'delete_index',
|
||||||
privileges: ['read', 'view_index_metadata'],
|
'index',
|
||||||
},
|
'create_doc',
|
||||||
],
|
'auto_configure',
|
||||||
application: [
|
'read',
|
||||||
{
|
],
|
||||||
application: 'kibana-.kibana',
|
},
|
||||||
privileges: [`saved_object:${SO_ENTITY_DEFINITION_TYPE}/*`],
|
{
|
||||||
resources: ['*'],
|
names: [...sourceIndices, ENTITY_INTERNAL_INDICES_PATTERN],
|
||||||
},
|
privileges: ['read', 'view_index_metadata'],
|
||||||
],
|
},
|
||||||
});
|
],
|
||||||
|
application: [
|
||||||
|
{
|
||||||
|
application: 'kibana-.kibana',
|
||||||
|
privileges: [`saved_object:${SO_ENTITY_DEFINITION_TYPE}/*`],
|
||||||
|
resources: ['*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const entityDefinitionDeletionPrivileges = {
|
export const entityDefinitionDeletionPrivileges = {
|
||||||
cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'],
|
cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'],
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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 Common Schema
|
||||||
|
* version: 1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from '@kbn/zod';
|
||||||
|
|
||||||
|
export type EngineStatus = z.infer<typeof EngineStatus>;
|
||||||
|
export const EngineStatus = z.enum(['installing', 'started', 'stopped', 'updating', 'error']);
|
||||||
|
export type EngineStatusEnum = typeof EngineStatus.enum;
|
||||||
|
export const EngineStatusEnum = EngineStatus.enum;
|
||||||
|
|
||||||
|
export type MonitoringEngineDescriptor = z.infer<typeof MonitoringEngineDescriptor>;
|
||||||
|
export const MonitoringEngineDescriptor = z.object({
|
||||||
|
status: EngineStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type EngineComponentResource = z.infer<typeof EngineComponentResource>;
|
||||||
|
export const EngineComponentResource = z.enum(['privmon_engine', 'index', 'task']);
|
||||||
|
export type EngineComponentResourceEnum = typeof EngineComponentResource.enum;
|
||||||
|
export const EngineComponentResourceEnum = EngineComponentResource.enum;
|
||||||
|
|
||||||
|
export type EngineComponentStatus = z.infer<typeof EngineComponentStatus>;
|
||||||
|
export const EngineComponentStatus = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
installed: z.boolean(),
|
||||||
|
resource: EngineComponentResource,
|
||||||
|
health: z.enum(['green', 'yellow', 'red', 'unknown']).optional(),
|
||||||
|
errors: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
title: z.string().optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
|
@ -0,0 +1,62 @@
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: Privilege Monitoring Common Schema
|
||||||
|
description: Common schema for Privilege Monitoring
|
||||||
|
version: "1"
|
||||||
|
paths: {}
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
MonitoringEngineDescriptor:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- status
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: "#/components/schemas/EngineStatus"
|
||||||
|
|
||||||
|
EngineStatus:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- installing
|
||||||
|
- started
|
||||||
|
- stopped
|
||||||
|
- updating
|
||||||
|
- error
|
||||||
|
|
||||||
|
EngineComponentStatus:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- installed
|
||||||
|
- resource
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
installed:
|
||||||
|
type: boolean
|
||||||
|
resource:
|
||||||
|
$ref: "#/components/schemas/EngineComponentResource"
|
||||||
|
health:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- green
|
||||||
|
- yellow
|
||||||
|
- red
|
||||||
|
- unknown
|
||||||
|
errors:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
EngineComponentResource:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- privmon_engine
|
||||||
|
- index
|
||||||
|
- task
|
|
@ -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: Init Privilege Monitoring Engine
|
||||||
|
* version: 2023-10-31
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { z } from '@kbn/zod';
|
||||||
|
|
||||||
|
import { MonitoringEngineDescriptor } from '../common.gen';
|
||||||
|
|
||||||
|
export type InitMonitoringEngineResponse = z.infer<typeof InitMonitoringEngineResponse>;
|
||||||
|
export const InitMonitoringEngineResponse = MonitoringEngineDescriptor;
|
|
@ -0,0 +1,20 @@
|
||||||
|
openapi: 3.0.0
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: Init Privilege Monitoring Engine
|
||||||
|
version: "2023-10-31"
|
||||||
|
paths:
|
||||||
|
/api/entity_analytics/monitoring/engine/init:
|
||||||
|
post:
|
||||||
|
x-labels: [ess, serverless]
|
||||||
|
x-codegen-enabled: true
|
||||||
|
operationId: InitMonitoringEngine
|
||||||
|
summary: Initialize the Privilege Monitoring Engine
|
||||||
|
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "../common.schema.yaml#/components/schemas/MonitoringEngineDescriptor"
|
|
@ -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: Health check on Privilege Monitoring
|
||||||
|
* version: 2023-10-31
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from '@kbn/zod';
|
||||||
|
|
||||||
|
export type PrivMonHealthResponse = z.infer<typeof PrivMonHealthResponse>;
|
||||||
|
export const PrivMonHealthResponse = z.object({
|
||||||
|
ok: z.boolean().optional(),
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
openapi: 3.0.0
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: Health check on Privilege Monitoring
|
||||||
|
version: "2023-10-31"
|
||||||
|
paths:
|
||||||
|
/api/entity_analytics/monitoring/privileges/health:
|
||||||
|
get:
|
||||||
|
x-labels: [ess, serverless]
|
||||||
|
x-codegen-enabled: true
|
||||||
|
operationId: PrivMonHealth
|
||||||
|
summary: Health check on Privilege Monitoring
|
||||||
|
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Successful response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ok:
|
||||||
|
type: boolean
|
|
@ -258,6 +258,8 @@ import type {
|
||||||
GetEntityStoreStatusRequestQueryInput,
|
GetEntityStoreStatusRequestQueryInput,
|
||||||
GetEntityStoreStatusResponse,
|
GetEntityStoreStatusResponse,
|
||||||
} from './entity_analytics/entity_store/status.gen';
|
} from './entity_analytics/entity_store/status.gen';
|
||||||
|
import type { InitMonitoringEngineResponse } from './entity_analytics/privilege_monitoring/engine/init.gen';
|
||||||
|
import type { PrivMonHealthResponse } from './entity_analytics/privilege_monitoring/health.gen';
|
||||||
import type { CleanUpRiskEngineResponse } from './entity_analytics/risk_engine/engine_cleanup_route.gen';
|
import type { CleanUpRiskEngineResponse } from './entity_analytics/risk_engine/engine_cleanup_route.gen';
|
||||||
import type {
|
import type {
|
||||||
ConfigureRiskEngineSavedObjectRequestBodyInput,
|
ConfigureRiskEngineSavedObjectRequestBodyInput,
|
||||||
|
@ -1664,6 +1666,18 @@ finalize it.
|
||||||
})
|
})
|
||||||
.catch(catchAxiosErrorFormatAndThrow);
|
.catch(catchAxiosErrorFormatAndThrow);
|
||||||
}
|
}
|
||||||
|
async initMonitoringEngine() {
|
||||||
|
this.log.info(`${new Date().toISOString()} Calling API InitMonitoringEngine`);
|
||||||
|
return this.kbnClient
|
||||||
|
.request<InitMonitoringEngineResponse>({
|
||||||
|
path: '/api/entity_analytics/monitoring/engine/init',
|
||||||
|
headers: {
|
||||||
|
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
.catch(catchAxiosErrorFormatAndThrow);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
|
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
|
||||||
*/
|
*/
|
||||||
|
@ -1907,6 +1921,18 @@ The edit action is idempotent, meaning that if you add a tag to a rule that alre
|
||||||
})
|
})
|
||||||
.catch(catchAxiosErrorFormatAndThrow);
|
.catch(catchAxiosErrorFormatAndThrow);
|
||||||
}
|
}
|
||||||
|
async privMonHealth() {
|
||||||
|
this.log.info(`${new Date().toISOString()} Calling API PrivMonHealth`);
|
||||||
|
return this.kbnClient
|
||||||
|
.request<PrivMonHealthResponse>({
|
||||||
|
path: '/api/entity_analytics/monitoring/privileges/health',
|
||||||
|
headers: {
|
||||||
|
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
|
||||||
|
},
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
.catch(catchAxiosErrorFormatAndThrow);
|
||||||
|
}
|
||||||
async readAlertsIndex() {
|
async readAlertsIndex() {
|
||||||
this.log.info(`${new Date().toISOString()} Calling API ReadAlertsIndex`);
|
this.log.info(`${new Date().toISOString()} Calling API ReadAlertsIndex`);
|
||||||
return this.kbnClient
|
return this.kbnClient
|
||||||
|
|
|
@ -220,6 +220,11 @@ export const allowedExperimentalValues = Object.freeze({
|
||||||
*/
|
*/
|
||||||
serviceEntityStoreEnabled: true,
|
serviceEntityStoreEnabled: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables Privilege Monitoring
|
||||||
|
*/
|
||||||
|
privilegeMonitoringEnabled: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables the siem migrations feature
|
* Disables the siem migrations feature
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -306,6 +306,35 @@ paths:
|
||||||
summary: List asset criticality records
|
summary: List asset criticality records
|
||||||
tags:
|
tags:
|
||||||
- Security Entity Analytics API
|
- Security Entity Analytics API
|
||||||
|
/api/entity_analytics/monitoring/engine/init:
|
||||||
|
post:
|
||||||
|
operationId: InitMonitoringEngine
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MonitoringEngineDescriptor'
|
||||||
|
description: Successful response
|
||||||
|
summary: Initialize the Privilege Monitoring Engine
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
|
/api/entity_analytics/monitoring/privileges/health:
|
||||||
|
get:
|
||||||
|
operationId: PrivMonHealth
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ok:
|
||||||
|
type: boolean
|
||||||
|
description: Successful response
|
||||||
|
summary: Health check on Privilege Monitoring
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
/api/entity_store/enable:
|
/api/entity_store/enable:
|
||||||
post:
|
post:
|
||||||
operationId: InitEntityStore
|
operationId: InitEntityStore
|
||||||
|
@ -1342,6 +1371,14 @@ components:
|
||||||
type: string
|
type: string
|
||||||
Metadata:
|
Metadata:
|
||||||
$ref: '#/components/schemas/TransformStatsMetadata'
|
$ref: '#/components/schemas/TransformStatsMetadata'
|
||||||
|
MonitoringEngineDescriptor:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/EngineStatus'
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- status
|
||||||
RiskEngineScheduleNowErrorResponse:
|
RiskEngineScheduleNowErrorResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -306,6 +306,35 @@ paths:
|
||||||
summary: List asset criticality records
|
summary: List asset criticality records
|
||||||
tags:
|
tags:
|
||||||
- Security Entity Analytics API
|
- Security Entity Analytics API
|
||||||
|
/api/entity_analytics/monitoring/engine/init:
|
||||||
|
post:
|
||||||
|
operationId: InitMonitoringEngine
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MonitoringEngineDescriptor'
|
||||||
|
description: Successful response
|
||||||
|
summary: Initialize the Privilege Monitoring Engine
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
|
/api/entity_analytics/monitoring/privileges/health:
|
||||||
|
get:
|
||||||
|
operationId: PrivMonHealth
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ok:
|
||||||
|
type: boolean
|
||||||
|
description: Successful response
|
||||||
|
summary: Health check on Privilege Monitoring
|
||||||
|
tags:
|
||||||
|
- Security Entity Analytics API
|
||||||
/api/entity_store/enable:
|
/api/entity_store/enable:
|
||||||
post:
|
post:
|
||||||
operationId: InitEntityStore
|
operationId: InitEntityStore
|
||||||
|
@ -1342,6 +1371,14 @@ components:
|
||||||
type: string
|
type: string
|
||||||
Metadata:
|
Metadata:
|
||||||
$ref: '#/components/schemas/TransformStatsMetadata'
|
$ref: '#/components/schemas/TransformStatsMetadata'
|
||||||
|
MonitoringEngineDescriptor:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/EngineStatus'
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- status
|
||||||
RiskEngineScheduleNowErrorResponse:
|
RiskEngineScheduleNowErrorResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -44,6 +44,7 @@ import { packageServiceMock } from '@kbn/fleet-plugin/server/services/epm/packag
|
||||||
import type { EndpointInternalFleetServicesInterface } from '../../../../endpoint/services/fleet';
|
import type { EndpointInternalFleetServicesInterface } from '../../../../endpoint/services/fleet';
|
||||||
import { siemMigrationsServiceMock } from '../../../siem_migrations/__mocks__/mocks';
|
import { siemMigrationsServiceMock } from '../../../siem_migrations/__mocks__/mocks';
|
||||||
import { AssetInventoryDataClientMock } from '../../../asset_inventory/asset_inventory_data_client.mock';
|
import { AssetInventoryDataClientMock } from '../../../asset_inventory/asset_inventory_data_client.mock';
|
||||||
|
import { privilegeMonitorDataClientMock } from '../../../entity_analytics/privilege_monitoring/privilege_monitoring_data_client.mock';
|
||||||
|
|
||||||
export const createMockClients = () => {
|
export const createMockClients = () => {
|
||||||
const core = coreMock.createRequestHandlerContext();
|
const core = coreMock.createRequestHandlerContext();
|
||||||
|
@ -76,6 +77,7 @@ export const createMockClients = () => {
|
||||||
riskScoreDataClient: riskScoreDataClientMock.create(),
|
riskScoreDataClient: riskScoreDataClientMock.create(),
|
||||||
assetCriticalityDataClient: assetCriticalityDataClientMock.create(),
|
assetCriticalityDataClient: assetCriticalityDataClientMock.create(),
|
||||||
entityStoreDataClient: entityStoreDataClientMock.create(),
|
entityStoreDataClient: entityStoreDataClientMock.create(),
|
||||||
|
privilegeMonitorDataClient: privilegeMonitorDataClientMock.create(),
|
||||||
|
|
||||||
internalFleetServices: {
|
internalFleetServices: {
|
||||||
packages: packageServiceMock.createClient(),
|
packages: packageServiceMock.createClient(),
|
||||||
|
@ -170,6 +172,7 @@ const createSecuritySolutionRequestContextMock = (
|
||||||
getDataViewsService: jest.fn(),
|
getDataViewsService: jest.fn(),
|
||||||
getEntityStoreApiKeyManager: jest.fn(),
|
getEntityStoreApiKeyManager: jest.fn(),
|
||||||
getEntityStoreDataClient: jest.fn(() => clients.entityStoreDataClient),
|
getEntityStoreDataClient: jest.fn(() => clients.entityStoreDataClient),
|
||||||
|
getPrivilegeMonitoringDataClient: jest.fn(() => clients.privilegeMonitorDataClient),
|
||||||
getSiemRuleMigrationsClient: jest.fn(() => clients.siemRuleMigrationsClient),
|
getSiemRuleMigrationsClient: jest.fn(() => clients.siemRuleMigrationsClient),
|
||||||
getInferenceClient: jest.fn(() => clients.getInferenceClient()),
|
getInferenceClient: jest.fn(() => clients.getInferenceClient()),
|
||||||
getAssetInventoryClient: jest.fn(() => clients.assetInventoryDataClient),
|
getAssetInventoryClient: jest.fn(() => clients.assetInventoryDataClient),
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* 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 const PrivilegeMonitoringEngineActions = {
|
||||||
|
INIT: 'init',
|
||||||
|
START: 'start',
|
||||||
|
STOP: 'stop',
|
||||||
|
CREATE: 'create',
|
||||||
|
DELETE: 'delete',
|
||||||
|
EXECUTE: 'execute',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PrivilegeMonitoringEngineActions =
|
||||||
|
(typeof PrivilegeMonitoringEngineActions)[keyof typeof PrivilegeMonitoringEngineActions];
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* 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 { KibanaRequest } from '@kbn/core-http-server';
|
||||||
|
import type { CoreStart } from '@kbn/core-lifecycle-server';
|
||||||
|
import type { Logger } from '@kbn/logging';
|
||||||
|
import type { SecurityPluginStart } from '@kbn/security-plugin-types-server';
|
||||||
|
import type { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||||
|
import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request';
|
||||||
|
|
||||||
|
import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server';
|
||||||
|
import type { SavedObjectsType } from '@kbn/core/server';
|
||||||
|
import { getPrivmonEncryptedSavedObjectId } from './saved_object';
|
||||||
|
import { privilegeMonitoringRuntimePrivileges } from './privileges';
|
||||||
|
|
||||||
|
export interface ApiKeyManager {
|
||||||
|
generate: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiKeyManagerDependencies {
|
||||||
|
core: CoreStart;
|
||||||
|
logger: Logger;
|
||||||
|
security: SecurityPluginStart;
|
||||||
|
encryptedSavedObjects?: EncryptedSavedObjectsPluginStart;
|
||||||
|
request?: KibanaRequest;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getApiKeyManager = (deps: ApiKeyManagerDependencies) => {
|
||||||
|
return {
|
||||||
|
generate: generate(deps),
|
||||||
|
getApiKey: getApiKey(deps),
|
||||||
|
getClientFromApiKey: getClientFromApiKey(deps),
|
||||||
|
getRequestFromApiKey,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const generate = async (deps: ApiKeyManagerDependencies) => {
|
||||||
|
const { core, encryptedSavedObjects, request, namespace } = deps;
|
||||||
|
if (!encryptedSavedObjects) {
|
||||||
|
throw new Error(
|
||||||
|
'Unable to create API key. Ensure encrypted Saved Object client is enabled in this environment.'
|
||||||
|
);
|
||||||
|
} else if (!request) {
|
||||||
|
throw new Error('Unable to create API key due to invalid request');
|
||||||
|
} else {
|
||||||
|
const apiKey = await generateAPIKey(request, deps);
|
||||||
|
|
||||||
|
const soClient = core.savedObjects.getScopedClient(request, {
|
||||||
|
includedHiddenTypes: [PrivilegeMonitoringApiKeyType.name],
|
||||||
|
});
|
||||||
|
|
||||||
|
await soClient.create(PrivilegeMonitoringApiKeyType.name, apiKey, {
|
||||||
|
id: getPrivmonEncryptedSavedObjectId(namespace),
|
||||||
|
overwrite: true,
|
||||||
|
managed: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getApiKey = async (deps: ApiKeyManagerDependencies) => {
|
||||||
|
if (!deps.encryptedSavedObjects) {
|
||||||
|
throw Error(
|
||||||
|
'Unable to retrieve API key. Ensure encrypted Saved Object client is enabled in this environment.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const encryptedSavedObjectsClient = deps.encryptedSavedObjects.getClient({
|
||||||
|
includedHiddenTypes: [PrivilegeMonitoringApiKeyType.name],
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<PrivilegeMonitoringAPIKey>(
|
||||||
|
PrivilegeMonitoringApiKeyType.name,
|
||||||
|
getPrivmonEncryptedSavedObjectId(deps.namespace)
|
||||||
|
)
|
||||||
|
).attributes;
|
||||||
|
} catch (err) {
|
||||||
|
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRequestFromApiKey = async (apiKey: PrivilegeMonitoringAPIKey) => {
|
||||||
|
return getFakeKibanaRequest({
|
||||||
|
id: apiKey.id,
|
||||||
|
api_key: apiKey.apiKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const getClientFromApiKey =
|
||||||
|
(deps: ApiKeyManagerDependencies) => async (apiKey: PrivilegeMonitoringAPIKey) => {
|
||||||
|
const fakeRequest = getFakeKibanaRequest({
|
||||||
|
id: apiKey.id,
|
||||||
|
api_key: apiKey.apiKey,
|
||||||
|
});
|
||||||
|
const clusterClient = deps.core.elasticsearch.client.asScoped(fakeRequest);
|
||||||
|
const soClient = deps.core.savedObjects.getScopedClient(fakeRequest, {
|
||||||
|
includedHiddenTypes: [PrivilegeMonitoringApiKeyType.name],
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
clusterClient,
|
||||||
|
soClient,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateAPIKey = async (
|
||||||
|
req: KibanaRequest,
|
||||||
|
deps: ApiKeyManagerDependencies
|
||||||
|
): Promise<PrivilegeMonitoringAPIKey | undefined> => {
|
||||||
|
const apiKey = await deps.security.authc.apiKeys.grantAsInternalUser(req, {
|
||||||
|
name: 'Privilege Monitoring API key',
|
||||||
|
role_descriptors: {
|
||||||
|
privmon_admin: privilegeMonitoringRuntimePrivileges([]),
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
description: 'API key used to manage the resources in the privilege monitoring engine',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (apiKey !== null) {
|
||||||
|
return {
|
||||||
|
id: apiKey.id,
|
||||||
|
name: apiKey.name,
|
||||||
|
apiKey: apiKey.api_key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SO_PRIVILEGE_MONITORING_API_KEY_TYPE = 'privmon-api-key';
|
||||||
|
|
||||||
|
export const PrivilegeMonitoringApiKeyType: SavedObjectsType = {
|
||||||
|
name: SO_PRIVILEGE_MONITORING_API_KEY_TYPE,
|
||||||
|
hidden: true,
|
||||||
|
namespaceType: 'multiple-isolated',
|
||||||
|
mappings: {
|
||||||
|
dynamic: false,
|
||||||
|
properties: {
|
||||||
|
apiKey: { type: 'binary' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
management: {
|
||||||
|
importableAndExportable: false,
|
||||||
|
displayName: 'Privilege Monitoring API key',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PrivilegeMonitoringAPIKey {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
apiKey: string;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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 { PRIVILEGE_MONITORING_INTERNAL_INDICES_PATTERN } from '../constants';
|
||||||
|
import { privilegeMonitoringTypeName } from '../saved_object/privilege_monitoring_type';
|
||||||
|
|
||||||
|
export const privilegeMonitoringRuntimePrivileges = (sourceIndices: string[]) => ({
|
||||||
|
cluster: ['manage_ingest_pipelines', 'manage_index_templates'],
|
||||||
|
index: [
|
||||||
|
{
|
||||||
|
names: [PRIVILEGE_MONITORING_INTERNAL_INDICES_PATTERN],
|
||||||
|
privileges: ['create_index', 'delete_index', 'index', 'create_doc', 'auto_configure', 'read'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: [...sourceIndices, PRIVILEGE_MONITORING_INTERNAL_INDICES_PATTERN],
|
||||||
|
privileges: ['read', 'view_index_metadata'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application: [
|
||||||
|
{
|
||||||
|
application: 'kibana-.kibana',
|
||||||
|
privileges: [`saved_object:${privilegeMonitoringTypeName}/*`],
|
||||||
|
resources: ['*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* 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 { v5 as uuidv5 } from 'uuid';
|
||||||
|
|
||||||
|
const PRIVMON_API_KEY_SO_ID = '19540C97-E35C-485B-8566-FB86EC8455E4';
|
||||||
|
|
||||||
|
export const getPrivmonEncryptedSavedObjectId = (space: string) => {
|
||||||
|
return uuidv5(space, PRIVMON_API_KEY_SO_ID);
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* 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 const SCOPE = ['securitySolution'];
|
||||||
|
export const TYPE = 'entity_analytics:monitoring:privileges:engine';
|
||||||
|
export const VERSION = '1.0.0';
|
||||||
|
export const TIMEOUT = '10m';
|
||||||
|
export const INTERVAL = '10m';
|
||||||
|
|
||||||
|
export const PRIVILEGE_MONITORING_ENGINE_STATUS = {
|
||||||
|
INSTALLING: 'installing',
|
||||||
|
STARTED: 'started',
|
||||||
|
STOPPED: 'stopped',
|
||||||
|
ERROR: 'error',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Base constants
|
||||||
|
export const PRIVMON_BASE_PREFIX = 'privmon' as const;
|
||||||
|
export const PRIVILEGE_MONITORING_INTERNAL_INDICES_PATTERN = `.${PRIVMON_BASE_PREFIX}*` as const;
|
|
@ -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 { 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}`;
|
||||||
|
// Not required in phase 0.
|
||||||
|
export const getPrivilegedMonitorGroupsIndex = (namespace: string) =>
|
||||||
|
`${privilegedMonitorBaseIndexName}.groups-${namespace}`;
|
||||||
|
|
||||||
|
export type MappingProperties = NonNullable<MappingTypeMapping['properties']>;
|
||||||
|
|
||||||
|
export const PRIVILEGED_MONITOR_USERS_INDEX_MAPPING: MappingProperties = {
|
||||||
|
'event.ingested': {
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
'@timestamp': {
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
'user.name': {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
'labels.is_privileged': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PRIVILEGED_MONITOR_GROUPS_INDEX_MAPPING: MappingProperties = {
|
||||||
|
'event.ingested': {
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
'@timestamp': {
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
'group.name': {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
indexPattern: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
nameMatcher: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
'labels.is_privileged': {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateUserIndexMappings = (): MappingTypeMapping => ({
|
||||||
|
properties: PRIVILEGED_MONITOR_USERS_INDEX_MAPPING,
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* 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 { PrivilegeMonitoringDataClient } from './privilege_monitoring_data_client';
|
||||||
|
|
||||||
|
const createPrivilegeMonitorDataClientMock = () =>
|
||||||
|
({
|
||||||
|
init: jest.fn(),
|
||||||
|
} as unknown as jest.Mocked<PrivilegeMonitoringDataClient>);
|
||||||
|
|
||||||
|
export const privilegeMonitorDataClientMock = { create: createPrivilegeMonitorDataClientMock };
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
elasticsearchServiceMock,
|
||||||
|
savedObjectsClientMock,
|
||||||
|
loggingSystemMock,
|
||||||
|
} from '@kbn/core/server/mocks';
|
||||||
|
import { PrivilegeMonitoringDataClient } from './privilege_monitoring_data_client';
|
||||||
|
import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server/plugin';
|
||||||
|
import { PrivilegeMonitoringEngineActions } from './auditing/actions';
|
||||||
|
import { EngineComponentResourceEnum } from '../../../../common/api/entity_analytics/privilege_monitoring/common.gen';
|
||||||
|
|
||||||
|
import { startPrivilegeMonitoringTask as mockStartPrivilegeMonitoringTask } from './tasks/privilege_monitoring_task';
|
||||||
|
import type { AuditLogger } from '@kbn/core/server';
|
||||||
|
|
||||||
|
jest.mock('./tasks/privilege_monitoring_task', () => {
|
||||||
|
return {
|
||||||
|
startPrivilegeMonitoringTask: jest.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('./saved_object/privilege_monitoring', () => {
|
||||||
|
return {
|
||||||
|
PrivilegeMonitoringEngineDescriptorClient: jest.fn().mockImplementation(() => ({
|
||||||
|
init: jest.fn().mockResolvedValue({ status: 'success' }),
|
||||||
|
update: jest.fn(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
describe('Privilege Monitoring Data Client', () => {
|
||||||
|
const mockSavedObjectClient = savedObjectsClientMock.create();
|
||||||
|
const clusterClientMock = elasticsearchServiceMock.createScopedClusterClient();
|
||||||
|
const loggerMock = loggingSystemMock.createLogger();
|
||||||
|
const auditMock = { log: jest.fn().mockReturnValue(undefined) };
|
||||||
|
loggerMock.debug = jest.fn();
|
||||||
|
|
||||||
|
const defaultOpts = {
|
||||||
|
logger: loggerMock,
|
||||||
|
clusterClient: clusterClientMock,
|
||||||
|
namespace: 'default',
|
||||||
|
soClient: mockSavedObjectClient,
|
||||||
|
kibanaVersion: '8.0.0',
|
||||||
|
taskManager: {} as TaskManagerStartContract,
|
||||||
|
auditLogger: auditMock as unknown as AuditLogger,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dataClient: PrivilegeMonitoringDataClient;
|
||||||
|
const mockDescriptor = { status: 'success' };
|
||||||
|
const mockCreateOrUpdateIndex = jest.fn().mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
dataClient = new PrivilegeMonitoringDataClient(defaultOpts);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('init', () => {
|
||||||
|
it('should initialize the privilege monitoring engine successfully', async () => {
|
||||||
|
dataClient.createOrUpdateIndex = mockCreateOrUpdateIndex;
|
||||||
|
const result = await dataClient.init();
|
||||||
|
|
||||||
|
expect(mockCreateOrUpdateIndex).toHaveBeenCalled();
|
||||||
|
expect(mockStartPrivilegeMonitoringTask).toHaveBeenCalled();
|
||||||
|
expect(loggerMock.debug).toHaveBeenCalledTimes(1);
|
||||||
|
expect(auditMock.log).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockDescriptor);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if taskManager is not available', async () => {
|
||||||
|
const { taskManager, ...optsWithoutTaskManager } = defaultOpts;
|
||||||
|
dataClient = new PrivilegeMonitoringDataClient(optsWithoutTaskManager);
|
||||||
|
await expect(dataClient.init()).rejects.toThrow('Task Manager is not available');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log a message if index already exists', async () => {
|
||||||
|
const error = {
|
||||||
|
meta: {
|
||||||
|
body: {
|
||||||
|
error: {
|
||||||
|
type: 'resource_already_exists_exception',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
dataClient.createOrUpdateIndex = jest.fn().mockRejectedValue(error);
|
||||||
|
|
||||||
|
Object.defineProperty(dataClient, 'engineClient', {
|
||||||
|
value: {
|
||||||
|
init: jest.fn().mockResolvedValue({ status: 'success' }),
|
||||||
|
update: jest.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await dataClient.init();
|
||||||
|
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledWith('Privilege monitoring index already exists');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unexpected errors and update engine status', async () => {
|
||||||
|
const fakeError = new Error('Something went wrong');
|
||||||
|
dataClient.createOrUpdateIndex = jest.fn().mockRejectedValue(fakeError);
|
||||||
|
|
||||||
|
const mockAudit = jest.fn();
|
||||||
|
const mockLog = jest.fn();
|
||||||
|
|
||||||
|
Object.defineProperty(dataClient, 'audit', { value: mockAudit });
|
||||||
|
Object.defineProperty(dataClient, 'log', { value: mockLog });
|
||||||
|
|
||||||
|
await dataClient.init();
|
||||||
|
|
||||||
|
expect(mockLog).toHaveBeenCalledWith(
|
||||||
|
'error',
|
||||||
|
expect.stringContaining('Error initializing privilege monitoring engine')
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockAudit).toHaveBeenCalledWith(
|
||||||
|
PrivilegeMonitoringEngineActions.INIT,
|
||||||
|
EngineComponentResourceEnum.privmon_engine,
|
||||||
|
'Failed to initialize privilege monitoring engine',
|
||||||
|
expect.any(Error)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('audit', () => {
|
||||||
|
it('should log audit events successfully', async () => {
|
||||||
|
// TODO: implement once we have more auditing
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors during audit logging', async () => {
|
||||||
|
// TODO: implement once we have more auditing
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
Logger,
|
||||||
|
ElasticsearchClient,
|
||||||
|
SavedObjectsClientContract,
|
||||||
|
AuditLogger,
|
||||||
|
IScopedClusterClient,
|
||||||
|
AnalyticsServiceSetup,
|
||||||
|
AuditEvent,
|
||||||
|
} from '@kbn/core/server';
|
||||||
|
|
||||||
|
import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
|
||||||
|
import moment from 'moment';
|
||||||
|
import type { InitMonitoringEngineResponse } from '../../../../common/api/entity_analytics/privilege_monitoring/engine/init.gen';
|
||||||
|
import {
|
||||||
|
EngineComponentResourceEnum,
|
||||||
|
type EngineComponentResource,
|
||||||
|
} from '../../../../common/api/entity_analytics/privilege_monitoring/common.gen';
|
||||||
|
import type { ApiKeyManager } from './auth/api_key';
|
||||||
|
import { startPrivilegeMonitoringTask } from './tasks/privilege_monitoring_task';
|
||||||
|
import { createOrUpdateIndex } from '../utils/create_or_update_index';
|
||||||
|
import { generateUserIndexMappings, getPrivilegedMonitorUsersIndex } from './indices';
|
||||||
|
import { PrivilegeMonitoringEngineDescriptorClient } from './saved_object/privilege_monitoring';
|
||||||
|
import { PRIVILEGE_MONITORING_ENGINE_STATUS } from './constants';
|
||||||
|
import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../audit';
|
||||||
|
import { PrivilegeMonitoringEngineActions } from './auditing/actions';
|
||||||
|
import {
|
||||||
|
PRIVMON_ENGINE_INITIALIZATION_EVENT,
|
||||||
|
PRIVMON_ENGINE_RESOURCE_INIT_FAILURE_EVENT,
|
||||||
|
} from '../../telemetry/event_based/events';
|
||||||
|
|
||||||
|
interface PrivilegeMonitoringClientOpts {
|
||||||
|
logger: Logger;
|
||||||
|
clusterClient: IScopedClusterClient;
|
||||||
|
namespace: string;
|
||||||
|
soClient: SavedObjectsClientContract;
|
||||||
|
taskManager?: TaskManagerStartContract;
|
||||||
|
auditLogger?: AuditLogger;
|
||||||
|
kibanaVersion: string;
|
||||||
|
telemetry?: AnalyticsServiceSetup;
|
||||||
|
apiKeyManager?: ApiKeyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrivilegeMonitoringDataClient {
|
||||||
|
private apiKeyGenerator?: ApiKeyManager;
|
||||||
|
private esClient: ElasticsearchClient;
|
||||||
|
private engineClient: PrivilegeMonitoringEngineDescriptorClient;
|
||||||
|
|
||||||
|
constructor(private readonly opts: PrivilegeMonitoringClientOpts) {
|
||||||
|
this.esClient = opts.clusterClient.asCurrentUser;
|
||||||
|
this.apiKeyGenerator = opts.apiKeyManager;
|
||||||
|
this.engineClient = new PrivilegeMonitoringEngineDescriptorClient({
|
||||||
|
soClient: opts.soClient,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(): Promise<InitMonitoringEngineResponse> {
|
||||||
|
if (!this.opts.taskManager) {
|
||||||
|
throw new Error('Task Manager is not available');
|
||||||
|
}
|
||||||
|
const setupStartTime = moment().utc().toISOString();
|
||||||
|
|
||||||
|
this.audit(
|
||||||
|
PrivilegeMonitoringEngineActions.INIT,
|
||||||
|
EngineComponentResourceEnum.privmon_engine,
|
||||||
|
'Initializing privilege monitoring engine'
|
||||||
|
);
|
||||||
|
|
||||||
|
const descriptor = await this.engineClient.init();
|
||||||
|
this.log('debug', `Initialized privileged monitoring engine saved object`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.createOrUpdateIndex().catch((e) => {
|
||||||
|
if (e.meta.body.error.type === 'resource_already_exists_exception') {
|
||||||
|
this.opts.logger.info('Privilege monitoring index already exists');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.apiKeyGenerator) {
|
||||||
|
await this.apiKeyGenerator.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
await startPrivilegeMonitoringTask({
|
||||||
|
logger: this.opts.logger,
|
||||||
|
namespace: this.opts.namespace,
|
||||||
|
taskManager: this.opts.taskManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setupEndTime = moment().utc().toISOString();
|
||||||
|
const duration = moment(setupEndTime).diff(moment(setupStartTime), 'seconds');
|
||||||
|
this.opts.telemetry?.reportEvent(PRIVMON_ENGINE_INITIALIZATION_EVENT.eventType, {
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.log('error', `Error initializing privilege monitoring engine: ${e}`);
|
||||||
|
this.audit(
|
||||||
|
PrivilegeMonitoringEngineActions.INIT,
|
||||||
|
EngineComponentResourceEnum.privmon_engine,
|
||||||
|
'Failed to initialize privilege monitoring engine',
|
||||||
|
e
|
||||||
|
);
|
||||||
|
|
||||||
|
this.opts.telemetry?.reportEvent(PRIVMON_ENGINE_RESOURCE_INIT_FAILURE_EVENT.eventType, {
|
||||||
|
error: e.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.engineClient.update({
|
||||||
|
status: PRIVILEGE_MONITORING_ENGINE_STATUS.ERROR,
|
||||||
|
error: {
|
||||||
|
message: e.message,
|
||||||
|
stack: e.stack,
|
||||||
|
action: 'init',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createOrUpdateIndex() {
|
||||||
|
await createOrUpdateIndex({
|
||||||
|
esClient: this.esClient,
|
||||||
|
logger: this.opts.logger,
|
||||||
|
options: {
|
||||||
|
index: this.getIndex(),
|
||||||
|
mappings: generateUserIndexMappings(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getIndex() {
|
||||||
|
return getPrivilegedMonitorUsersIndex(this.opts.namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
private log(level: Exclude<keyof Logger, 'get' | 'log' | 'isLevelEnabled'>, msg: string) {
|
||||||
|
this.opts.logger[level](
|
||||||
|
`[Privileged Monitoring Engine][namespace: ${this.opts.namespace}] ${msg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private audit(
|
||||||
|
action: PrivilegeMonitoringEngineActions,
|
||||||
|
resource: EngineComponentResource,
|
||||||
|
msg: string,
|
||||||
|
error?: Error
|
||||||
|
) {
|
||||||
|
// NOTE: Excluding errors, all auditing events are currently WRITE events, meaning the outcome is always UNKNOWN.
|
||||||
|
// This may change in the future, depending on the audit action.
|
||||||
|
const outcome = error ? AUDIT_OUTCOME.FAILURE : AUDIT_OUTCOME.UNKNOWN;
|
||||||
|
|
||||||
|
const type =
|
||||||
|
action === PrivilegeMonitoringEngineActions.CREATE
|
||||||
|
? AUDIT_TYPE.CREATION
|
||||||
|
: PrivilegeMonitoringEngineActions.DELETE
|
||||||
|
? AUDIT_TYPE.DELETION
|
||||||
|
: AUDIT_TYPE.CHANGE;
|
||||||
|
|
||||||
|
const category = AUDIT_CATEGORY.DATABASE;
|
||||||
|
|
||||||
|
const message = error ? `${msg}: ${error.message}` : msg;
|
||||||
|
const event: AuditEvent = {
|
||||||
|
message: `[Privilege Monitoring] ${message}`,
|
||||||
|
event: {
|
||||||
|
action: `${action}_${resource}`,
|
||||||
|
category,
|
||||||
|
outcome,
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.opts.auditLogger?.log(event);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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 { PrivMonHealthResponse } from '../../../../../common/api/entity_analytics/privilege_monitoring/health.gen';
|
||||||
|
import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
|
||||||
|
import type { EntityAnalyticsRoutesDeps } from '../../types';
|
||||||
|
|
||||||
|
export const healthCheckPrivilegeMonitoringRoute = (
|
||||||
|
router: EntityAnalyticsRoutesDeps['router'],
|
||||||
|
logger: Logger,
|
||||||
|
config: EntityAnalyticsRoutesDeps['config']
|
||||||
|
) => {
|
||||||
|
router.versioned
|
||||||
|
.get({
|
||||||
|
access: 'public',
|
||||||
|
path: '/api/entity_analytics/monitoring/privileges/health',
|
||||||
|
security: {
|
||||||
|
authz: {
|
||||||
|
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addVersion(
|
||||||
|
{
|
||||||
|
version: API_VERSIONS.public.v1,
|
||||||
|
validate: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
async (context, request, response): Promise<IKibanaResponse<PrivMonHealthResponse>> => {
|
||||||
|
const siemResponse = buildSiemResponse(response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return response.ok({ body: { ok: true } });
|
||||||
|
} catch (e) {
|
||||||
|
const error = transformError(e);
|
||||||
|
logger.error(`Error checking privilege monitoring health: ${error.message}`);
|
||||||
|
return siemResponse.error({
|
||||||
|
statusCode: error.statusCode,
|
||||||
|
body: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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 { InitMonitoringEngineResponse } from '../../../../../common/api/entity_analytics/privilege_monitoring/engine/init.gen';
|
||||||
|
import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
|
||||||
|
import type { EntityAnalyticsRoutesDeps } from '../../types';
|
||||||
|
|
||||||
|
export const initPrivilegeMonitoringEngineRoute = (
|
||||||
|
router: EntityAnalyticsRoutesDeps['router'],
|
||||||
|
logger: Logger,
|
||||||
|
config: EntityAnalyticsRoutesDeps['config']
|
||||||
|
) => {
|
||||||
|
router.versioned
|
||||||
|
.post({
|
||||||
|
access: 'public',
|
||||||
|
path: '/api/entity_analytics/monitoring/engine/init',
|
||||||
|
security: {
|
||||||
|
authz: {
|
||||||
|
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addVersion(
|
||||||
|
{
|
||||||
|
version: API_VERSIONS.public.v1,
|
||||||
|
validate: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
async (
|
||||||
|
context,
|
||||||
|
request,
|
||||||
|
response
|
||||||
|
): Promise<IKibanaResponse<InitMonitoringEngineResponse>> => {
|
||||||
|
const siemResponse = buildSiemResponse(response);
|
||||||
|
const secSol = await context.securitySolution;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = await secSol.getPrivilegeMonitoringDataClient().init();
|
||||||
|
return response.ok({ body });
|
||||||
|
} catch (e) {
|
||||||
|
const error = transformError(e);
|
||||||
|
logger.error(`Error initializing privilege monitoring engine: ${error.message}`);
|
||||||
|
return siemResponse.error({
|
||||||
|
statusCode: error.statusCode,
|
||||||
|
body: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* 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 { EntityAnalyticsRoutesDeps } from '../../types';
|
||||||
|
import { healthCheckPrivilegeMonitoringRoute } from './health';
|
||||||
|
|
||||||
|
import { initPrivilegeMonitoringEngineRoute } from './init';
|
||||||
|
|
||||||
|
export const registerPrivilegeMonitoringRoutes = ({
|
||||||
|
router,
|
||||||
|
logger,
|
||||||
|
getStartServices,
|
||||||
|
config,
|
||||||
|
}: EntityAnalyticsRoutesDeps) => {
|
||||||
|
initPrivilegeMonitoringEngineRoute(router, logger, config);
|
||||||
|
healthCheckPrivilegeMonitoringRoute(router, logger, config);
|
||||||
|
};
|
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
SavedObject,
|
||||||
|
SavedObjectsClientContract,
|
||||||
|
SavedObjectsFindResponse,
|
||||||
|
} from '@kbn/core/server';
|
||||||
|
import { PrivilegeMonitoringEngineDescriptorClient } from './privilege_monitoring';
|
||||||
|
import { privilegeMonitoringTypeName } from './privilege_monitoring_type';
|
||||||
|
import { PRIVILEGE_MONITORING_ENGINE_STATUS } from '../constants';
|
||||||
|
|
||||||
|
describe('PrivilegeMonitoringEngineDescriptorClient', () => {
|
||||||
|
let soClient: jest.Mocked<SavedObjectsClientContract>;
|
||||||
|
let client: PrivilegeMonitoringEngineDescriptorClient;
|
||||||
|
const namespace = 'test-namespace';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
soClient = {
|
||||||
|
create: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
get: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
} as unknown as jest.Mocked<SavedObjectsClientContract>;
|
||||||
|
|
||||||
|
client = new PrivilegeMonitoringEngineDescriptorClient({ soClient, namespace });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct saved object ID', () => {
|
||||||
|
expect(client.getSavedObjectId()).toBe(`privilege-monitoring-${namespace}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize a new descriptor if none exists', async () => {
|
||||||
|
soClient.find.mockResolvedValue({
|
||||||
|
total: 0,
|
||||||
|
saved_objects: [],
|
||||||
|
} as unknown as SavedObjectsFindResponse<unknown, unknown>);
|
||||||
|
soClient.create.mockResolvedValue({
|
||||||
|
id: `privilege-monitoring-${namespace}`,
|
||||||
|
type: privilegeMonitoringTypeName,
|
||||||
|
attributes: { status: 'installing' as unknown },
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.init();
|
||||||
|
|
||||||
|
expect(soClient.create).toHaveBeenCalledWith(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
{ status: PRIVILEGE_MONITORING_ENGINE_STATUS.INSTALLING },
|
||||||
|
{ id: `privilege-monitoring-${namespace}` }
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ status: 'installing' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update an existing descriptor if one exists', async () => {
|
||||||
|
const existingDescriptor = {
|
||||||
|
total: 1,
|
||||||
|
saved_objects: [{ attributes: { status: 'started', apiKey: 'old-key' } }],
|
||||||
|
} as SavedObjectsFindResponse<unknown, unknown>;
|
||||||
|
|
||||||
|
soClient.find.mockResolvedValue(
|
||||||
|
existingDescriptor as unknown as SavedObjectsFindResponse<unknown, unknown>
|
||||||
|
);
|
||||||
|
soClient.update.mockResolvedValue({
|
||||||
|
id: `privilege-monitoring-${namespace}`,
|
||||||
|
type: privilegeMonitoringTypeName,
|
||||||
|
attributes: { status: 'installing' as unknown, apiKey: '' as unknown },
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.init();
|
||||||
|
|
||||||
|
expect(soClient.update).toHaveBeenCalledWith(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
`privilege-monitoring-${namespace}`,
|
||||||
|
{ status: PRIVILEGE_MONITORING_ENGINE_STATUS.INSTALLING, apiKey: '', error: undefined },
|
||||||
|
{ refresh: 'wait_for' }
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ status: 'installing', apiKey: '' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the descriptor', async () => {
|
||||||
|
soClient.update.mockResolvedValue({
|
||||||
|
id: `privilege-monitoring-${namespace}`,
|
||||||
|
type: privilegeMonitoringTypeName,
|
||||||
|
attributes: { status: 'started' as unknown },
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.update({ status: 'started' });
|
||||||
|
|
||||||
|
expect(soClient.update).toHaveBeenCalledWith(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
`privilege-monitoring-${namespace}`,
|
||||||
|
{ status: 'started' },
|
||||||
|
{ refresh: 'wait_for' }
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ status: 'started' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the status', async () => {
|
||||||
|
soClient.update.mockResolvedValue({
|
||||||
|
id: `privilege-monitoring-${namespace}`,
|
||||||
|
type: privilegeMonitoringTypeName,
|
||||||
|
attributes: { status: 'started' as unknown },
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.updateStatus('started');
|
||||||
|
|
||||||
|
expect(soClient.update).toHaveBeenCalledWith(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
`privilege-monitoring-${namespace}`,
|
||||||
|
{ status: 'started' },
|
||||||
|
{ refresh: 'wait_for' }
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ status: 'started' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find descriptors', async () => {
|
||||||
|
const findResponse = {
|
||||||
|
total: 1,
|
||||||
|
saved_objects: [{ attributes: { status: 'started', apiKey: 'key' } }],
|
||||||
|
};
|
||||||
|
soClient.find.mockResolvedValue(findResponse as SavedObjectsFindResponse<unknown, unknown>);
|
||||||
|
|
||||||
|
const result = await client.find();
|
||||||
|
|
||||||
|
expect(soClient.find).toHaveBeenCalledWith({
|
||||||
|
type: privilegeMonitoringTypeName,
|
||||||
|
namespaces: [namespace],
|
||||||
|
});
|
||||||
|
expect(result).toEqual(findResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a descriptor', async () => {
|
||||||
|
const getResponse = {
|
||||||
|
id: `privilege-monitoring-${namespace}`,
|
||||||
|
type: privilegeMonitoringTypeName,
|
||||||
|
attributes: { status: 'started' as unknown, apiKey: 'key' as unknown },
|
||||||
|
references: [],
|
||||||
|
};
|
||||||
|
soClient.get.mockResolvedValue(getResponse as unknown as SavedObject<unknown>);
|
||||||
|
|
||||||
|
const result = await client.get();
|
||||||
|
|
||||||
|
expect(soClient.get).toHaveBeenCalledWith(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
`privilege-monitoring-${namespace}`
|
||||||
|
);
|
||||||
|
expect(result).toEqual(getResponse.attributes);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a descriptor', async () => {
|
||||||
|
await client.delete();
|
||||||
|
|
||||||
|
expect(soClient.delete).toHaveBeenCalledWith(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
`privilege-monitoring-${namespace}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* 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 { SavedObjectsClientContract, SavedObjectsFindResponse } from '@kbn/core/server';
|
||||||
|
|
||||||
|
import type { MonitoringEngineDescriptor } from '../../../../../common/api/entity_analytics/privilege_monitoring/common.gen';
|
||||||
|
import { privilegeMonitoringTypeName } from './privilege_monitoring_type';
|
||||||
|
import { PRIVILEGE_MONITORING_ENGINE_STATUS } from '../constants';
|
||||||
|
|
||||||
|
interface PrivilegeMonitoringEngineDescriptorDependencies {
|
||||||
|
soClient: SavedObjectsClientContract;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PrivilegedMonitoringEngineDescriptor {
|
||||||
|
status: MonitoringEngineDescriptor['status'];
|
||||||
|
error?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrivilegeMonitoringEngineDescriptorClient {
|
||||||
|
constructor(private readonly deps: PrivilegeMonitoringEngineDescriptorDependencies) {}
|
||||||
|
|
||||||
|
getSavedObjectId() {
|
||||||
|
return `privilege-monitoring-${this.deps.namespace}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const engineDescriptor = await this.find();
|
||||||
|
if (engineDescriptor.total === 1) {
|
||||||
|
return this.updateExistingDescriptor(engineDescriptor);
|
||||||
|
}
|
||||||
|
const { attributes } = await this.deps.soClient.create<PrivilegedMonitoringEngineDescriptor>(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
{
|
||||||
|
status: PRIVILEGE_MONITORING_ENGINE_STATUS.INSTALLING,
|
||||||
|
},
|
||||||
|
{ id: this.getSavedObjectId() }
|
||||||
|
);
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateExistingDescriptor(
|
||||||
|
engineDescriptor: SavedObjectsFindResponse<PrivilegedMonitoringEngineDescriptor, unknown>
|
||||||
|
) {
|
||||||
|
const old = engineDescriptor.saved_objects[0].attributes;
|
||||||
|
const update = {
|
||||||
|
...old,
|
||||||
|
error: undefined,
|
||||||
|
status: PRIVILEGE_MONITORING_ENGINE_STATUS.INSTALLING,
|
||||||
|
apiKey: '',
|
||||||
|
};
|
||||||
|
await this.deps.soClient.update<PrivilegedMonitoringEngineDescriptor>(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
this.getSavedObjectId(),
|
||||||
|
update,
|
||||||
|
{ refresh: 'wait_for' }
|
||||||
|
);
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(engine: Partial<PrivilegedMonitoringEngineDescriptor>) {
|
||||||
|
const id = this.getSavedObjectId();
|
||||||
|
const { attributes } = await this.deps.soClient.update<PrivilegedMonitoringEngineDescriptor>(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
id,
|
||||||
|
engine,
|
||||||
|
{ refresh: 'wait_for' }
|
||||||
|
);
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateStatus(status: MonitoringEngineDescriptor['status']) {
|
||||||
|
return this.update({ status });
|
||||||
|
}
|
||||||
|
|
||||||
|
async find() {
|
||||||
|
return this.deps.soClient.find<PrivilegedMonitoringEngineDescriptor>({
|
||||||
|
type: privilegeMonitoringTypeName,
|
||||||
|
namespaces: [this.deps.namespace],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async get() {
|
||||||
|
const id = this.getSavedObjectId();
|
||||||
|
const { attributes } = await this.deps.soClient.get<PrivilegedMonitoringEngineDescriptor>(
|
||||||
|
privilegeMonitoringTypeName,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete() {
|
||||||
|
const id = this.getSavedObjectId();
|
||||||
|
return this.deps.soClient.delete(privilegeMonitoringTypeName, id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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 { SavedObjectsModelVersion } from '@kbn/core-saved-objects-server';
|
||||||
|
import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
|
||||||
|
import type { SavedObjectsType } from '@kbn/core/server';
|
||||||
|
|
||||||
|
export const privilegeMonitoringTypeName = 'privilege-monitoring-status';
|
||||||
|
|
||||||
|
export const privilegeMonitoringTypeNameMappings: SavedObjectsType['mappings'] = {
|
||||||
|
dynamic: false,
|
||||||
|
properties: {
|
||||||
|
status: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const version1: SavedObjectsModelVersion = {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'mappings_addition',
|
||||||
|
addedMappings: {
|
||||||
|
status: { type: 'keyword' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const privilegeMonitoringType: SavedObjectsType = {
|
||||||
|
name: privilegeMonitoringTypeName,
|
||||||
|
indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX,
|
||||||
|
hidden: false,
|
||||||
|
namespaceType: 'multiple-isolated',
|
||||||
|
mappings: privilegeMonitoringTypeNameMappings,
|
||||||
|
modelVersions: { 1: version1 },
|
||||||
|
};
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* 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 { Logger, AnalyticsServiceSetup } from '@kbn/core/server';
|
||||||
|
import type {
|
||||||
|
ConcreteTaskInstance,
|
||||||
|
TaskManagerSetupContract,
|
||||||
|
TaskManagerStartContract,
|
||||||
|
TaskRunCreatorFunction,
|
||||||
|
} from '@kbn/task-manager-plugin/server';
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
import type { ExperimentalFeatures } from '../../../../../common';
|
||||||
|
import type { EntityAnalyticsRoutesDeps } from '../../types';
|
||||||
|
|
||||||
|
import { TYPE, VERSION, TIMEOUT, SCOPE, INTERVAL } from '../constants';
|
||||||
|
import {
|
||||||
|
defaultState,
|
||||||
|
stateSchemaByVersion,
|
||||||
|
type LatestTaskStateSchema as PrivilegeMonitoringTaskState,
|
||||||
|
} from './state';
|
||||||
|
|
||||||
|
interface RegisterParams {
|
||||||
|
getStartServices: EntityAnalyticsRoutesDeps['getStartServices'];
|
||||||
|
logger: Logger;
|
||||||
|
telemetry: AnalyticsServiceSetup;
|
||||||
|
taskManager: TaskManagerSetupContract | undefined;
|
||||||
|
experimentalFeatures: ExperimentalFeatures;
|
||||||
|
kibanaVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RunParams {
|
||||||
|
isCancelled: () => boolean;
|
||||||
|
logger: Logger;
|
||||||
|
telemetry: AnalyticsServiceSetup;
|
||||||
|
experimentalFeatures: ExperimentalFeatures;
|
||||||
|
taskInstance: ConcreteTaskInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StartParams {
|
||||||
|
logger: Logger;
|
||||||
|
namespace: string;
|
||||||
|
taskManager: TaskManagerStartContract;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTaskName = (): string => TYPE;
|
||||||
|
|
||||||
|
const getTaskId = (namespace: string): string => `${TYPE}:${namespace}:${VERSION}`;
|
||||||
|
|
||||||
|
export const registerPrivilegeMonitoringTask = ({
|
||||||
|
getStartServices,
|
||||||
|
logger,
|
||||||
|
telemetry,
|
||||||
|
taskManager,
|
||||||
|
kibanaVersion,
|
||||||
|
experimentalFeatures,
|
||||||
|
}: RegisterParams): void => {
|
||||||
|
if (!taskManager) {
|
||||||
|
logger.info(
|
||||||
|
'[Privilege Monitoring] Task Manager is unavailable; skipping privilege monitoring task registration.'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskManager.registerTaskDefinitions({
|
||||||
|
[getTaskName()]: {
|
||||||
|
title: 'Entity Analytics Privilege Monitoring',
|
||||||
|
timeout: TIMEOUT,
|
||||||
|
stateSchemaByVersion,
|
||||||
|
createTaskRunner: createPrivilegeMonitoringTaskRunnerFactory({
|
||||||
|
logger,
|
||||||
|
telemetry,
|
||||||
|
experimentalFeatures,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPrivilegeMonitoringTaskRunnerFactory =
|
||||||
|
(deps: {
|
||||||
|
logger: Logger;
|
||||||
|
telemetry: AnalyticsServiceSetup;
|
||||||
|
experimentalFeatures: ExperimentalFeatures;
|
||||||
|
}): TaskRunCreatorFunction =>
|
||||||
|
({ taskInstance }) => {
|
||||||
|
let cancelled = false;
|
||||||
|
const isCancelled = () => cancelled;
|
||||||
|
return {
|
||||||
|
run: async () =>
|
||||||
|
runPrivilegeMonitoringTask({
|
||||||
|
isCancelled,
|
||||||
|
logger: deps.logger,
|
||||||
|
telemetry: deps.telemetry,
|
||||||
|
taskInstance,
|
||||||
|
experimentalFeatures: deps.experimentalFeatures,
|
||||||
|
}),
|
||||||
|
cancel: async () => {
|
||||||
|
cancelled = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const runPrivilegeMonitoringTask = async ({
|
||||||
|
isCancelled,
|
||||||
|
logger,
|
||||||
|
telemetry,
|
||||||
|
taskInstance,
|
||||||
|
experimentalFeatures,
|
||||||
|
}: RunParams): Promise<{
|
||||||
|
state: PrivilegeMonitoringTaskState;
|
||||||
|
}> => {
|
||||||
|
const state = taskInstance.state as PrivilegeMonitoringTaskState;
|
||||||
|
const taskStartTime = moment().utc().toISOString();
|
||||||
|
const updatedState = {
|
||||||
|
lastExecutionTimestamp: taskStartTime,
|
||||||
|
namespace: state.namespace,
|
||||||
|
runs: state.runs + 1,
|
||||||
|
};
|
||||||
|
if (isCancelled()) {
|
||||||
|
logger.info('[Privilege Monitoring] Task was cancelled.');
|
||||||
|
return { state: updatedState };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info('[Privilege Monitoring] Running privilege monitoring task');
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[Privilege Monitoring] Error running privilege monitoring task', e);
|
||||||
|
}
|
||||||
|
return { state: updatedState };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startPrivilegeMonitoringTask = async ({
|
||||||
|
logger,
|
||||||
|
namespace,
|
||||||
|
taskManager,
|
||||||
|
}: StartParams) => {
|
||||||
|
const taskId = getTaskId(namespace);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await taskManager.ensureScheduled({
|
||||||
|
id: taskId,
|
||||||
|
taskType: getTaskName(),
|
||||||
|
scope: SCOPE,
|
||||||
|
schedule: {
|
||||||
|
interval: INTERVAL,
|
||||||
|
},
|
||||||
|
state: { ...defaultState, namespace },
|
||||||
|
params: { version: VERSION },
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Scheduling privilege monitoring task with id ${taskId}`);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(
|
||||||
|
`[Privilege Monitoring] [task ${taskId}]: error scheduling task, received ${e.message}`
|
||||||
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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 { schema, type TypeOf } from '@kbn/config-schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: Do not modify the existing versioned schema(s) below, instead define a new version (ex: 2, 3, 4).
|
||||||
|
* This is required to support zero-downtime upgrades and rollbacks. See https://github.com/elastic/kibana/issues/155764.
|
||||||
|
*
|
||||||
|
* As you add a new schema version, don't forget to change latestTaskStateSchema variable to reference the latest schema.
|
||||||
|
* For example, changing stateSchemaByVersion[1].schema to stateSchemaByVersion[2].schema.
|
||||||
|
*/
|
||||||
|
export const stateSchemaByVersion = {
|
||||||
|
1: {
|
||||||
|
up: (state: Record<string, unknown>) => ({
|
||||||
|
lastExecutionTimestamp: state.lastExecutionTimestamp || undefined,
|
||||||
|
runs: state.runs || 0,
|
||||||
|
namespace: typeof state.namespace === 'string' ? state.namespace : 'default',
|
||||||
|
}),
|
||||||
|
schema: schema.object({
|
||||||
|
lastExecutionTimestamp: schema.maybe(schema.string()),
|
||||||
|
namespace: schema.string(),
|
||||||
|
runs: schema.number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const latestTaskStateSchema = stateSchemaByVersion[1].schema;
|
||||||
|
export type LatestTaskStateSchema = TypeOf<typeof latestTaskStateSchema>;
|
||||||
|
|
||||||
|
export const defaultState: LatestTaskStateSchema = {
|
||||||
|
lastExecutionTimestamp: undefined,
|
||||||
|
namespace: 'default',
|
||||||
|
runs: 0,
|
||||||
|
};
|
|
@ -10,7 +10,7 @@ import { registerRiskScoreRoutes } from './risk_score/routes';
|
||||||
import { registerRiskEngineRoutes } from './risk_engine/routes';
|
import { registerRiskEngineRoutes } from './risk_engine/routes';
|
||||||
import type { EntityAnalyticsRoutesDeps } from './types';
|
import type { EntityAnalyticsRoutesDeps } from './types';
|
||||||
import { registerEntityStoreRoutes } from './entity_store/routes';
|
import { registerEntityStoreRoutes } from './entity_store/routes';
|
||||||
|
import { registerPrivilegeMonitoringRoutes } from './privilege_monitoring/routes/register_privilege_monitoring_routes';
|
||||||
export const registerEntityAnalyticsRoutes = (routeDeps: EntityAnalyticsRoutesDeps) => {
|
export const registerEntityAnalyticsRoutes = (routeDeps: EntityAnalyticsRoutesDeps) => {
|
||||||
registerAssetCriticalityRoutes(routeDeps);
|
registerAssetCriticalityRoutes(routeDeps);
|
||||||
registerRiskScoreRoutes(routeDeps);
|
registerRiskScoreRoutes(routeDeps);
|
||||||
|
@ -18,4 +18,7 @@ export const registerEntityAnalyticsRoutes = (routeDeps: EntityAnalyticsRoutesDe
|
||||||
if (!routeDeps.config.experimentalFeatures.entityStoreDisabled) {
|
if (!routeDeps.config.experimentalFeatures.entityStoreDisabled) {
|
||||||
registerEntityStoreRoutes(routeDeps);
|
registerEntityStoreRoutes(routeDeps);
|
||||||
}
|
}
|
||||||
|
if (routeDeps.config.experimentalFeatures.privilegeMonitoringEnabled) {
|
||||||
|
registerPrivilegeMonitoringRoutes(routeDeps);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -221,6 +221,34 @@ export const ENTITY_STORE_USAGE_EVENT: EventTypeOpts<{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PRIVMON_ENGINE_INITIALIZATION_EVENT: EventTypeOpts<{
|
||||||
|
duration: number;
|
||||||
|
}> = {
|
||||||
|
eventType: 'privmon_engine_initialization',
|
||||||
|
schema: {
|
||||||
|
duration: {
|
||||||
|
type: 'long',
|
||||||
|
_meta: {
|
||||||
|
description: 'Duration (in seconds) of the privilege monitoring engine initialization',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PRIVMON_ENGINE_RESOURCE_INIT_FAILURE_EVENT: EventTypeOpts<{
|
||||||
|
error: string;
|
||||||
|
}> = {
|
||||||
|
eventType: 'privmon_engine_resource_init_failure',
|
||||||
|
schema: {
|
||||||
|
error: {
|
||||||
|
type: 'keyword',
|
||||||
|
_meta: {
|
||||||
|
description: 'Error message for a resource initialization failure',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const ALERT_SUPPRESSION_EVENT: EventTypeOpts<{
|
export const ALERT_SUPPRESSION_EVENT: EventTypeOpts<{
|
||||||
suppressionAlertsCreated: number;
|
suppressionAlertsCreated: number;
|
||||||
suppressionAlertsSuppressed: number;
|
suppressionAlertsSuppressed: number;
|
||||||
|
@ -1271,6 +1299,8 @@ export const events = [
|
||||||
ENTITY_ENGINE_RESOURCE_INIT_FAILURE_EVENT,
|
ENTITY_ENGINE_RESOURCE_INIT_FAILURE_EVENT,
|
||||||
ENTITY_ENGINE_INITIALIZATION_EVENT,
|
ENTITY_ENGINE_INITIALIZATION_EVENT,
|
||||||
ENTITY_STORE_USAGE_EVENT,
|
ENTITY_STORE_USAGE_EVENT,
|
||||||
|
PRIVMON_ENGINE_INITIALIZATION_EVENT,
|
||||||
|
PRIVMON_ENGINE_RESOURCE_INIT_FAILURE_EVENT,
|
||||||
TELEMETRY_DATA_STREAM_EVENT,
|
TELEMETRY_DATA_STREAM_EVENT,
|
||||||
TELEMETRY_ILM_POLICY_EVENT,
|
TELEMETRY_ILM_POLICY_EVENT,
|
||||||
TELEMETRY_ILM_STATS_EVENT,
|
TELEMETRY_ILM_STATS_EVENT,
|
||||||
|
|
|
@ -132,6 +132,7 @@ import { scheduleEntityAnalyticsMigration } from './lib/entity_analytics/migrati
|
||||||
import { SiemMigrationsService } from './lib/siem_migrations/siem_migrations_service';
|
import { SiemMigrationsService } from './lib/siem_migrations/siem_migrations_service';
|
||||||
import { TelemetryConfigProvider } from '../common/telemetry_config/telemetry_config_provider';
|
import { TelemetryConfigProvider } from '../common/telemetry_config/telemetry_config_provider';
|
||||||
import { TelemetryConfigWatcher } from './endpoint/lib/policy/telemetry_watch';
|
import { TelemetryConfigWatcher } from './endpoint/lib/policy/telemetry_watch';
|
||||||
|
import { registerPrivilegeMonitoringTask } from './lib/entity_analytics/privilege_monitoring/tasks/privilege_monitoring_task';
|
||||||
|
|
||||||
export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract';
|
export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract';
|
||||||
|
|
||||||
|
@ -267,6 +268,15 @@ export class Plugin implements ISecuritySolutionPlugin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerPrivilegeMonitoringTask({
|
||||||
|
getStartServices: core.getStartServices,
|
||||||
|
taskManager: plugins.taskManager,
|
||||||
|
logger: this.logger,
|
||||||
|
telemetry: core.analytics,
|
||||||
|
kibanaVersion: pluginContext.env.packageInfo.version,
|
||||||
|
experimentalFeatures,
|
||||||
|
});
|
||||||
|
|
||||||
const requestContextFactory = new RequestContextFactory({
|
const requestContextFactory = new RequestContextFactory({
|
||||||
config,
|
config,
|
||||||
logger,
|
logger,
|
||||||
|
|
|
@ -37,6 +37,7 @@ import type {
|
||||||
SecuritySolutionApiRequestHandlerContext,
|
SecuritySolutionApiRequestHandlerContext,
|
||||||
SecuritySolutionRequestHandlerContext,
|
SecuritySolutionRequestHandlerContext,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { PrivilegeMonitoringDataClient } from './lib/entity_analytics/privilege_monitoring/privilege_monitoring_data_client';
|
||||||
|
|
||||||
export interface IRequestContextFactory {
|
export interface IRequestContextFactory {
|
||||||
create(
|
create(
|
||||||
|
@ -249,7 +250,22 @@ export class RequestContextFactory implements IRequestContextFactory {
|
||||||
auditLogger: getAuditLogger(),
|
auditLogger: getAuditLogger(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
getPrivilegeMonitoringDataClient: memoize(() => {
|
||||||
|
// TODO:add soClient with ApiKeyType as with getEntityStoreDataClient
|
||||||
|
return new PrivilegeMonitoringDataClient({
|
||||||
|
logger: options.logger,
|
||||||
|
clusterClient: coreContext.elasticsearch.client,
|
||||||
|
namespace: getSpaceId(),
|
||||||
|
soClient: coreContext.savedObjects.client,
|
||||||
|
taskManager: startPlugins.taskManager,
|
||||||
|
auditLogger: getAuditLogger(),
|
||||||
|
kibanaVersion: options.kibanaVersion,
|
||||||
|
telemetry: core.analytics,
|
||||||
|
// TODO: add apiKeyManager
|
||||||
|
});
|
||||||
|
}),
|
||||||
getEntityStoreDataClient: memoize(() => {
|
getEntityStoreDataClient: memoize(() => {
|
||||||
|
// why are we defining this here, but other places we do it inline?
|
||||||
const clusterClient = coreContext.elasticsearch.client;
|
const clusterClient = coreContext.elasticsearch.client;
|
||||||
const logger = options.logger;
|
const logger = options.logger;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { type as signalsMigrationType } from './lib/detection_engine/migrations/
|
||||||
import { manifestType, unifiedManifestType } from './endpoint/lib/artifacts/saved_object_mappings';
|
import { manifestType, unifiedManifestType } from './endpoint/lib/artifacts/saved_object_mappings';
|
||||||
import { riskEngineConfigurationType } from './lib/entity_analytics/risk_engine/saved_object';
|
import { riskEngineConfigurationType } from './lib/entity_analytics/risk_engine/saved_object';
|
||||||
import { entityEngineDescriptorType } from './lib/entity_analytics/entity_store/saved_object';
|
import { entityEngineDescriptorType } from './lib/entity_analytics/entity_store/saved_object';
|
||||||
|
import { privilegeMonitoringType } from './lib/entity_analytics/privilege_monitoring/saved_object/privilege_monitoring_type';
|
||||||
|
|
||||||
const types = [
|
const types = [
|
||||||
noteType,
|
noteType,
|
||||||
|
@ -29,6 +30,7 @@ const types = [
|
||||||
signalsMigrationType,
|
signalsMigrationType,
|
||||||
riskEngineConfigurationType,
|
riskEngineConfigurationType,
|
||||||
entityEngineDescriptorType,
|
entityEngineDescriptorType,
|
||||||
|
privilegeMonitoringType,
|
||||||
protectionUpdatesNoteType,
|
protectionUpdatesNoteType,
|
||||||
promptType,
|
promptType,
|
||||||
];
|
];
|
||||||
|
|
|
@ -39,7 +39,9 @@ import type { IDetectionRulesClient } from './lib/detection_engine/rule_manageme
|
||||||
import type { EntityStoreDataClient } from './lib/entity_analytics/entity_store/entity_store_data_client';
|
import type { EntityStoreDataClient } from './lib/entity_analytics/entity_store/entity_store_data_client';
|
||||||
import type { SiemRuleMigrationsClient } from './lib/siem_migrations/rules/siem_rule_migrations_service';
|
import type { SiemRuleMigrationsClient } from './lib/siem_migrations/rules/siem_rule_migrations_service';
|
||||||
import type { AssetInventoryDataClient } from './lib/asset_inventory/asset_inventory_data_client';
|
import type { AssetInventoryDataClient } from './lib/asset_inventory/asset_inventory_data_client';
|
||||||
|
import type { PrivilegeMonitoringDataClient } from './lib/entity_analytics/privilege_monitoring/privilege_monitoring_data_client';
|
||||||
import type { ApiKeyManager } from './lib/entity_analytics/entity_store/auth/api_key';
|
import type { ApiKeyManager } from './lib/entity_analytics/entity_store/auth/api_key';
|
||||||
|
|
||||||
export { AppClient };
|
export { AppClient };
|
||||||
|
|
||||||
export interface SecuritySolutionApiRequestHandlerContext {
|
export interface SecuritySolutionApiRequestHandlerContext {
|
||||||
|
@ -64,6 +66,7 @@ export interface SecuritySolutionApiRequestHandlerContext {
|
||||||
getRiskScoreDataClient: () => RiskScoreDataClient;
|
getRiskScoreDataClient: () => RiskScoreDataClient;
|
||||||
getAssetCriticalityDataClient: () => AssetCriticalityDataClient;
|
getAssetCriticalityDataClient: () => AssetCriticalityDataClient;
|
||||||
getEntityStoreDataClient: () => EntityStoreDataClient;
|
getEntityStoreDataClient: () => EntityStoreDataClient;
|
||||||
|
getPrivilegeMonitoringDataClient: () => PrivilegeMonitoringDataClient;
|
||||||
getSiemRuleMigrationsClient: () => SiemRuleMigrationsClient;
|
getSiemRuleMigrationsClient: () => SiemRuleMigrationsClient;
|
||||||
getInferenceClient: () => InferenceClient;
|
getInferenceClient: () => InferenceClient;
|
||||||
getAssetInventoryClient: () => AssetInventoryDataClient;
|
getAssetInventoryClient: () => AssetInventoryDataClient;
|
||||||
|
|
|
@ -1184,6 +1184,13 @@ finalize it.
|
||||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||||
.send(props.body as object);
|
.send(props.body as object);
|
||||||
},
|
},
|
||||||
|
initMonitoringEngine(kibanaSpace: string = 'default') {
|
||||||
|
return supertest
|
||||||
|
.post(routeWithNamespace('/api/entity_analytics/monitoring/engine/init', kibanaSpace))
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
|
||||||
|
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
|
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
|
||||||
*/
|
*/
|
||||||
|
@ -1363,6 +1370,13 @@ The edit action is idempotent, meaning that if you add a tag to a rule that alre
|
||||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||||
.send(props.body as object);
|
.send(props.body as object);
|
||||||
},
|
},
|
||||||
|
privMonHealth(kibanaSpace: string = 'default') {
|
||||||
|
return supertest
|
||||||
|
.get(routeWithNamespace('/api/entity_analytics/monitoring/privileges/health', kibanaSpace))
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
|
||||||
|
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
|
||||||
|
},
|
||||||
readAlertsIndex(kibanaSpace: string = 'default') {
|
readAlertsIndex(kibanaSpace: string = 'default') {
|
||||||
return supertest
|
return supertest
|
||||||
.get(routeWithNamespace('/api/detection_engine/index', kibanaSpace))
|
.get(routeWithNamespace('/api/detection_engine/index', kibanaSpace))
|
||||||
|
|
|
@ -142,6 +142,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
'endpoint:complete-external-response-actions',
|
'endpoint:complete-external-response-actions',
|
||||||
'endpoint:metadata-check-transforms-task',
|
'endpoint:metadata-check-transforms-task',
|
||||||
'endpoint:user-artifact-packager',
|
'endpoint:user-artifact-packager',
|
||||||
|
'entity_analytics:monitoring:privileges:engine',
|
||||||
'entity_store:data_view:refresh',
|
'entity_store:data_view:refresh',
|
||||||
'entity_store:field_retention:enrichment',
|
'entity_store:field_retention:enrichment',
|
||||||
'fleet:automatic-agent-upgrade-task',
|
'fleet:automatic-agent-upgrade-task',
|
||||||
|
|
|
@ -105,6 +105,13 @@
|
||||||
"entity_analytics:entity_store:server:ess": "npm run initialize-server:ea:trial_complete entity_store ess",
|
"entity_analytics:entity_store:server:ess": "npm run initialize-server:ea:trial_complete entity_store ess",
|
||||||
"entity_analytics:entity_store:runner:ess": "npm run run-tests:ea:trial_complete entity_store ess essEnv --",
|
"entity_analytics:entity_store:runner:ess": "npm run run-tests:ea:trial_complete entity_store ess essEnv --",
|
||||||
|
|
||||||
|
"entity_analytics:monitoring:server:serverless": "npm run initialize-server:ea:trial_complete monitoring serverless",
|
||||||
|
"entity_analytics:monitoring:runner:serverless": "npm run run-tests:ea:trial_complete monitoring serverless serverlessEnv",
|
||||||
|
"entity_analytics:monitoring:qa:serverless": "npm run run-tests:ea:trial_complete monitoring serverless qaPeriodicEnv",
|
||||||
|
"entity_analytics:monitoring:qa:serverless:release": "npm run run-tests:ea:trial_complete monitoring serverless qaEnv",
|
||||||
|
"entity_analytics:monitoring:server:ess": "npm run initialize-server:ea:trial_complete monitoring ess",
|
||||||
|
"entity_analytics:monitoring:runner:ess": "npm run run-tests:ea:trial_complete monitoring ess essEnv --",
|
||||||
|
|
||||||
"edr_workflows:artifacts:server:serverless": "npm run initialize-server:edr-workflows artifacts serverless",
|
"edr_workflows:artifacts:server:serverless": "npm run initialize-server:edr-workflows artifacts serverless",
|
||||||
"edr_workflows:artifacts:runner:serverless": "npm run run-tests:edr-workflows artifacts serverless serverlessEnv",
|
"edr_workflows:artifacts:runner:serverless": "npm run run-tests:edr-workflows artifacts serverless serverlessEnv",
|
||||||
"edr_workflows:artifacts:qa:serverless": "npm run run-tests:edr-workflows artifacts serverless qaPeriodicEnv",
|
"edr_workflows:artifacts:qa:serverless": "npm run run-tests:edr-workflows artifacts serverless qaPeriodicEnv",
|
||||||
|
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FtrConfigProviderContext } from '@kbn/test';
|
||||||
|
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
|
const functionalConfig = await readConfigFile(
|
||||||
|
require.resolve('../../../../../config/ess/config.base.trial')
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...functionalConfig.getAll(),
|
||||||
|
kbnTestServer: {
|
||||||
|
...functionalConfig.get('kbnTestServer'),
|
||||||
|
serverArgs: [
|
||||||
|
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||||
|
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
|
||||||
|
'privilegeMonitoringEnabled',
|
||||||
|
])}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
testFiles: [require.resolve('..')],
|
||||||
|
junit: {
|
||||||
|
reportName:
|
||||||
|
'Entity Analytics - Privilege Monitoring Integration Tests - ESS Env - Trial License',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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 { createTestConfig } from '../../../../../config/serverless/config.base';
|
||||||
|
|
||||||
|
export default createTestConfig({
|
||||||
|
kbnTestServerArgs: [
|
||||||
|
`--xpack.securitySolution.enableExperimental=${JSON.stringify(['privilegeMonitoringEnabled'])}`,
|
||||||
|
`--xpack.securitySolutionServerless.productTypes=${JSON.stringify([
|
||||||
|
{ product_line: 'security', product_tier: 'complete' },
|
||||||
|
{ product_line: 'endpoint', product_tier: 'complete' },
|
||||||
|
{ product_line: 'cloud', product_tier: 'complete' },
|
||||||
|
])}`,
|
||||||
|
],
|
||||||
|
testFiles: [require.resolve('..')],
|
||||||
|
junit: {
|
||||||
|
reportName:
|
||||||
|
'Entity Analytics - Privilege Monitoring Integration Tests - Serverless Env - Complete Tier',
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
export default ({ getService }: FtrProviderContext) => {
|
||||||
|
const api = getService('securitySolutionApi');
|
||||||
|
const supertest = getService('supertest');
|
||||||
|
const log = getService('log');
|
||||||
|
|
||||||
|
describe('@ess @serverless @skipInServerlessMKI Entity Privilege Monitoring APIs', () => {
|
||||||
|
const dataView = dataViewRouteHelpersFactory(supertest);
|
||||||
|
before(async () => {
|
||||||
|
await dataView.create('security-solution');
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await dataView.delete('security-solution');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('health', () => {
|
||||||
|
it('should be healthy', async () => {
|
||||||
|
log.info(`Checking health of privilege monitoring`);
|
||||||
|
const res = await api.privMonHealth();
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
log.error(`Health check failed`);
|
||||||
|
log.error(JSON.stringify(res.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(res.status).eql(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||||
|
describe('Entity Analytics - Privilege Monitoring', function () {
|
||||||
|
loadTestFile(require.resolve('./engine'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -374,6 +374,18 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
"saved_object:entity-engine-status/delete",
|
"saved_object:entity-engine-status/delete",
|
||||||
"saved_object:entity-engine-status/bulk_delete",
|
"saved_object:entity-engine-status/bulk_delete",
|
||||||
"saved_object:entity-engine-status/share_to_space",
|
"saved_object:entity-engine-status/share_to_space",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_get",
|
||||||
|
"saved_object:privilege-monitoring-status/get",
|
||||||
|
"saved_object:privilege-monitoring-status/find",
|
||||||
|
"saved_object:privilege-monitoring-status/open_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/close_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/create",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_create",
|
||||||
|
"saved_object:privilege-monitoring-status/update",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_update",
|
||||||
|
"saved_object:privilege-monitoring-status/delete",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_delete",
|
||||||
|
"saved_object:privilege-monitoring-status/share_to_space",
|
||||||
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
||||||
"saved_object:policy-settings-protection-updates-note/get",
|
"saved_object:policy-settings-protection-updates-note/get",
|
||||||
"saved_object:policy-settings-protection-updates-note/find",
|
"saved_object:policy-settings-protection-updates-note/find",
|
||||||
|
@ -1218,6 +1230,18 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
"saved_object:entity-engine-status/delete",
|
"saved_object:entity-engine-status/delete",
|
||||||
"saved_object:entity-engine-status/bulk_delete",
|
"saved_object:entity-engine-status/bulk_delete",
|
||||||
"saved_object:entity-engine-status/share_to_space",
|
"saved_object:entity-engine-status/share_to_space",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_get",
|
||||||
|
"saved_object:privilege-monitoring-status/get",
|
||||||
|
"saved_object:privilege-monitoring-status/find",
|
||||||
|
"saved_object:privilege-monitoring-status/open_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/close_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/create",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_create",
|
||||||
|
"saved_object:privilege-monitoring-status/update",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_update",
|
||||||
|
"saved_object:privilege-monitoring-status/delete",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_delete",
|
||||||
|
"saved_object:privilege-monitoring-status/share_to_space",
|
||||||
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
||||||
"saved_object:policy-settings-protection-updates-note/get",
|
"saved_object:policy-settings-protection-updates-note/get",
|
||||||
"saved_object:policy-settings-protection-updates-note/find",
|
"saved_object:policy-settings-protection-updates-note/find",
|
||||||
|
@ -1840,6 +1864,11 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
"saved_object:entity-engine-status/find",
|
"saved_object:entity-engine-status/find",
|
||||||
"saved_object:entity-engine-status/open_point_in_time",
|
"saved_object:entity-engine-status/open_point_in_time",
|
||||||
"saved_object:entity-engine-status/close_point_in_time",
|
"saved_object:entity-engine-status/close_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_get",
|
||||||
|
"saved_object:privilege-monitoring-status/get",
|
||||||
|
"saved_object:privilege-monitoring-status/find",
|
||||||
|
"saved_object:privilege-monitoring-status/open_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/close_point_in_time",
|
||||||
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
||||||
"saved_object:policy-settings-protection-updates-note/get",
|
"saved_object:policy-settings-protection-updates-note/get",
|
||||||
"saved_object:policy-settings-protection-updates-note/find",
|
"saved_object:policy-settings-protection-updates-note/find",
|
||||||
|
@ -2209,6 +2238,11 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
"saved_object:entity-engine-status/find",
|
"saved_object:entity-engine-status/find",
|
||||||
"saved_object:entity-engine-status/open_point_in_time",
|
"saved_object:entity-engine-status/open_point_in_time",
|
||||||
"saved_object:entity-engine-status/close_point_in_time",
|
"saved_object:entity-engine-status/close_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/bulk_get",
|
||||||
|
"saved_object:privilege-monitoring-status/get",
|
||||||
|
"saved_object:privilege-monitoring-status/find",
|
||||||
|
"saved_object:privilege-monitoring-status/open_point_in_time",
|
||||||
|
"saved_object:privilege-monitoring-status/close_point_in_time",
|
||||||
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
"saved_object:policy-settings-protection-updates-note/bulk_get",
|
||||||
"saved_object:policy-settings-protection-updates-note/get",
|
"saved_object:policy-settings-protection-updates-note/get",
|
||||||
"saved_object:policy-settings-protection-updates-note/find",
|
"saved_object:policy-settings-protection-updates-note/find",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue