mirror of
https://github.com/elastic/kibana.git
synced 2025-04-18 23:21: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
|
||||
- x-pack/test_serverless/functional/test_suites/security/config.mki_only.ts
|
||||
|
||||
defaultQueue: 'n2-4-spot'
|
||||
defaultQueue: "n2-4-spot"
|
||||
enabled:
|
||||
- x-pack/test_serverless/api_integration/test_suites/security/config.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/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/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/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
|
||||
|
|
|
@ -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/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/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/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
|
||||
|
|
|
@ -13243,6 +13243,35 @@ paths:
|
|||
summary: Create or update a protection updates note
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
|
@ -62564,6 +62593,14 @@ components:
|
|||
type: string
|
||||
Security_Entity_Analytics_API_Metadata:
|
||||
$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:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -15402,6 +15402,35 @@ paths:
|
|||
summary: Create or update a protection updates note
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
|
@ -72049,6 +72078,14 @@ components:
|
|||
type: string
|
||||
Security_Entity_Analytics_API_Metadata:
|
||||
$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:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -873,6 +873,9 @@
|
|||
"policy-settings-protection-updates-note": [
|
||||
"note"
|
||||
],
|
||||
"privilege-monitoring-status": [
|
||||
"status"
|
||||
],
|
||||
"product-doc-install-status": [
|
||||
"index_name",
|
||||
"installation_status",
|
||||
|
|
|
@ -2913,6 +2913,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"privilege-monitoring-status": {
|
||||
"dynamic": false,
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"product-doc-install-status": {
|
||||
"dynamic": false,
|
||||
"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
|
||||
// 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-saved-query": "6095e288750aa3164dfe186c74bc5195c2bf2bd4",
|
||||
"policy-settings-protection-updates-note": "33924bb246f9e5bcb876109cc83e3c7a28308352",
|
||||
"privilege-monitoring-status": "9b11c4a49e679e2827b0468ba27269a19345c049",
|
||||
"product-doc-install-status": "ca6e96840228e4cc2f11bae24a0797f4f7238c8c",
|
||||
"query": "501bece68f26fe561286a488eabb1a8ab12f1137",
|
||||
"risk-engine-configuration": "bab237d09c2e7189dddddcb1b28f19af69755efb",
|
||||
|
|
|
@ -117,6 +117,7 @@ const previouslyRegisteredTypes = [
|
|||
'osquery-usage-metric',
|
||||
'osquery-manager-usage-metric',
|
||||
'policy-settings-protection-updates-note',
|
||||
'privilege-monitoring-status',
|
||||
'product-doc-install-status',
|
||||
'query',
|
||||
'rules-settings',
|
||||
|
|
|
@ -58,26 +58,35 @@ export const canDisableEntityDiscovery = async (client: ElasticsearchClient) =>
|
|||
);
|
||||
};
|
||||
|
||||
export const entityDefinitionRuntimePrivileges = (sourceIndices: string[]) => ({
|
||||
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: [...sourceIndices, ENTITY_INTERNAL_INDICES_PATTERN],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
application: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: [`saved_object:${SO_ENTITY_DEFINITION_TYPE}/*`],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
export const entityDefinitionRuntimePrivileges = (sourceIndices: string[]) => {
|
||||
return {
|
||||
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: [...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 = {
|
||||
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,
|
||||
GetEntityStoreStatusResponse,
|
||||
} 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 {
|
||||
ConfigureRiskEngineSavedObjectRequestBodyInput,
|
||||
|
@ -1664,6 +1666,18 @@ finalize it.
|
|||
})
|
||||
.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
|
||||
*/
|
||||
|
@ -1907,6 +1921,18 @@ The edit action is idempotent, meaning that if you add a tag to a rule that alre
|
|||
})
|
||||
.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() {
|
||||
this.log.info(`${new Date().toISOString()} Calling API ReadAlertsIndex`);
|
||||
return this.kbnClient
|
||||
|
|
|
@ -220,6 +220,11 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
*/
|
||||
serviceEntityStoreEnabled: true,
|
||||
|
||||
/**
|
||||
* Enables Privilege Monitoring
|
||||
*/
|
||||
privilegeMonitoringEnabled: false,
|
||||
|
||||
/**
|
||||
* Disables the siem migrations feature
|
||||
*/
|
||||
|
|
|
@ -306,6 +306,35 @@ paths:
|
|||
summary: List asset criticality records
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
|
@ -1342,6 +1371,14 @@ components:
|
|||
type: string
|
||||
Metadata:
|
||||
$ref: '#/components/schemas/TransformStatsMetadata'
|
||||
MonitoringEngineDescriptor:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/EngineStatus'
|
||||
required:
|
||||
- type
|
||||
- status
|
||||
RiskEngineScheduleNowErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -306,6 +306,35 @@ paths:
|
|||
summary: List asset criticality records
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
|
@ -1342,6 +1371,14 @@ components:
|
|||
type: string
|
||||
Metadata:
|
||||
$ref: '#/components/schemas/TransformStatsMetadata'
|
||||
MonitoringEngineDescriptor:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/EngineStatus'
|
||||
required:
|
||||
- type
|
||||
- status
|
||||
RiskEngineScheduleNowErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -44,6 +44,7 @@ import { packageServiceMock } from '@kbn/fleet-plugin/server/services/epm/packag
|
|||
import type { EndpointInternalFleetServicesInterface } from '../../../../endpoint/services/fleet';
|
||||
import { siemMigrationsServiceMock } from '../../../siem_migrations/__mocks__/mocks';
|
||||
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 = () => {
|
||||
const core = coreMock.createRequestHandlerContext();
|
||||
|
@ -76,6 +77,7 @@ export const createMockClients = () => {
|
|||
riskScoreDataClient: riskScoreDataClientMock.create(),
|
||||
assetCriticalityDataClient: assetCriticalityDataClientMock.create(),
|
||||
entityStoreDataClient: entityStoreDataClientMock.create(),
|
||||
privilegeMonitorDataClient: privilegeMonitorDataClientMock.create(),
|
||||
|
||||
internalFleetServices: {
|
||||
packages: packageServiceMock.createClient(),
|
||||
|
@ -170,6 +172,7 @@ const createSecuritySolutionRequestContextMock = (
|
|||
getDataViewsService: jest.fn(),
|
||||
getEntityStoreApiKeyManager: jest.fn(),
|
||||
getEntityStoreDataClient: jest.fn(() => clients.entityStoreDataClient),
|
||||
getPrivilegeMonitoringDataClient: jest.fn(() => clients.privilegeMonitorDataClient),
|
||||
getSiemRuleMigrationsClient: jest.fn(() => clients.siemRuleMigrationsClient),
|
||||
getInferenceClient: jest.fn(() => clients.getInferenceClient()),
|
||||
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 type { EntityAnalyticsRoutesDeps } from './types';
|
||||
import { registerEntityStoreRoutes } from './entity_store/routes';
|
||||
|
||||
import { registerPrivilegeMonitoringRoutes } from './privilege_monitoring/routes/register_privilege_monitoring_routes';
|
||||
export const registerEntityAnalyticsRoutes = (routeDeps: EntityAnalyticsRoutesDeps) => {
|
||||
registerAssetCriticalityRoutes(routeDeps);
|
||||
registerRiskScoreRoutes(routeDeps);
|
||||
|
@ -18,4 +18,7 @@ export const registerEntityAnalyticsRoutes = (routeDeps: EntityAnalyticsRoutesDe
|
|||
if (!routeDeps.config.experimentalFeatures.entityStoreDisabled) {
|
||||
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<{
|
||||
suppressionAlertsCreated: number;
|
||||
suppressionAlertsSuppressed: number;
|
||||
|
@ -1271,6 +1299,8 @@ export const events = [
|
|||
ENTITY_ENGINE_RESOURCE_INIT_FAILURE_EVENT,
|
||||
ENTITY_ENGINE_INITIALIZATION_EVENT,
|
||||
ENTITY_STORE_USAGE_EVENT,
|
||||
PRIVMON_ENGINE_INITIALIZATION_EVENT,
|
||||
PRIVMON_ENGINE_RESOURCE_INIT_FAILURE_EVENT,
|
||||
TELEMETRY_DATA_STREAM_EVENT,
|
||||
TELEMETRY_ILM_POLICY_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 { TelemetryConfigProvider } from '../common/telemetry_config/telemetry_config_provider';
|
||||
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';
|
||||
|
||||
|
@ -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({
|
||||
config,
|
||||
logger,
|
||||
|
|
|
@ -37,6 +37,7 @@ import type {
|
|||
SecuritySolutionApiRequestHandlerContext,
|
||||
SecuritySolutionRequestHandlerContext,
|
||||
} from './types';
|
||||
import { PrivilegeMonitoringDataClient } from './lib/entity_analytics/privilege_monitoring/privilege_monitoring_data_client';
|
||||
|
||||
export interface IRequestContextFactory {
|
||||
create(
|
||||
|
@ -249,7 +250,22 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
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(() => {
|
||||
// why are we defining this here, but other places we do it inline?
|
||||
const clusterClient = coreContext.elasticsearch.client;
|
||||
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 { riskEngineConfigurationType } from './lib/entity_analytics/risk_engine/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 = [
|
||||
noteType,
|
||||
|
@ -29,6 +30,7 @@ const types = [
|
|||
signalsMigrationType,
|
||||
riskEngineConfigurationType,
|
||||
entityEngineDescriptorType,
|
||||
privilegeMonitoringType,
|
||||
protectionUpdatesNoteType,
|
||||
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 { SiemRuleMigrationsClient } from './lib/siem_migrations/rules/siem_rule_migrations_service';
|
||||
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';
|
||||
|
||||
export { AppClient };
|
||||
|
||||
export interface SecuritySolutionApiRequestHandlerContext {
|
||||
|
@ -64,6 +66,7 @@ export interface SecuritySolutionApiRequestHandlerContext {
|
|||
getRiskScoreDataClient: () => RiskScoreDataClient;
|
||||
getAssetCriticalityDataClient: () => AssetCriticalityDataClient;
|
||||
getEntityStoreDataClient: () => EntityStoreDataClient;
|
||||
getPrivilegeMonitoringDataClient: () => PrivilegeMonitoringDataClient;
|
||||
getSiemRuleMigrationsClient: () => SiemRuleMigrationsClient;
|
||||
getInferenceClient: () => InferenceClient;
|
||||
getAssetInventoryClient: () => AssetInventoryDataClient;
|
||||
|
|
|
@ -1184,6 +1184,13 @@ finalize it.
|
|||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.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
|
||||
*/
|
||||
|
@ -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')
|
||||
.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') {
|
||||
return supertest
|
||||
.get(routeWithNamespace('/api/detection_engine/index', kibanaSpace))
|
||||
|
|
|
@ -142,6 +142,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'endpoint:complete-external-response-actions',
|
||||
'endpoint:metadata-check-transforms-task',
|
||||
'endpoint:user-artifact-packager',
|
||||
'entity_analytics:monitoring:privileges:engine',
|
||||
'entity_store:data_view:refresh',
|
||||
'entity_store:field_retention:enrichment',
|
||||
'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: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: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",
|
||||
|
|
|
@ -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/bulk_delete",
|
||||
"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/get",
|
||||
"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/bulk_delete",
|
||||
"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/get",
|
||||
"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/open_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/get",
|
||||
"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/open_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/get",
|
||||
"saved_object:policy-settings-protection-updates-note/find",
|
||||
|
|
Loading…
Add table
Reference in a new issue