mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Entity Analytics] [Entity Store] Refactor entity store enablement (server side) (#199638)
## Summary This PR adds 2 new endpoints regarding enablement of the Entity Store: * `api/entity_store/enable`, which initializes entity engines for both `user` and `host` entities * `api/entity_store/status`, which computes a global store status based on the individual engine status In addition, running initialization of multiple engines in parallel is now allowed. ### How to test 1. Use dev tools to call `POST kbn:/api/entity_store/enable` 2. Check that two engines were created and that the status is `installing` by calling `GET kbn:/api/entity_store/status` 3. Wait a few seconds and keep calling the `status` endpoint. Once initialization finishes, the status should switch to `running` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6e9520aca2
commit
3757e64127
19 changed files with 697 additions and 7 deletions
|
@ -7395,6 +7395,43 @@ paths:
|
|||
tags:
|
||||
- Security Endpoint Management API
|
||||
x-beta: true
|
||||
/api/entity_store/enable:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
fieldHistoryLength:
|
||||
default: 10
|
||||
description: The number of historical values to keep for each field.
|
||||
type: integer
|
||||
filter:
|
||||
type: string
|
||||
indexPattern:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_IndexPattern'
|
||||
description: Schema for the entity store initialization
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor'
|
||||
type: array
|
||||
succeeded:
|
||||
type: boolean
|
||||
description: Successful response
|
||||
summary: Initialize the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
x-beta: true
|
||||
/api/entity_store/engines:
|
||||
get:
|
||||
operationId: ListEntityEngines
|
||||
|
@ -7713,6 +7750,27 @@ paths:
|
|||
tags:
|
||||
- Security Entity Analytics API
|
||||
x-beta: true
|
||||
/api/entity_store/status:
|
||||
get:
|
||||
operationId: GetEntityStoreStatus
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor'
|
||||
type: array
|
||||
status:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_StoreStatus'
|
||||
description: Successful response
|
||||
summary: Get the status of the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
x-beta: true
|
||||
/api/exception_lists:
|
||||
delete:
|
||||
description: Delete an exception list using the `id` or `list_id` field.
|
||||
|
@ -45880,6 +45938,14 @@ components:
|
|||
- index
|
||||
- description
|
||||
- category
|
||||
Security_Entity_Analytics_API_StoreStatus:
|
||||
enum:
|
||||
- not_installed
|
||||
- installing
|
||||
- running
|
||||
- stopped
|
||||
- error
|
||||
type: string
|
||||
Security_Entity_Analytics_API_TaskManagerUnavailableResponse:
|
||||
description: Task manager is unavailable
|
||||
type: object
|
||||
|
|
|
@ -10282,6 +10282,42 @@ paths:
|
|||
summary: Create or update a protection updates note
|
||||
tags:
|
||||
- Security Endpoint Management API
|
||||
/api/entity_store/enable:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
fieldHistoryLength:
|
||||
default: 10
|
||||
description: The number of historical values to keep for each field.
|
||||
type: integer
|
||||
filter:
|
||||
type: string
|
||||
indexPattern:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_IndexPattern'
|
||||
description: Schema for the entity store initialization
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor'
|
||||
type: array
|
||||
succeeded:
|
||||
type: boolean
|
||||
description: Successful response
|
||||
summary: Initialize the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/engines:
|
||||
get:
|
||||
operationId: ListEntityEngines
|
||||
|
@ -10591,6 +10627,26 @@ paths:
|
|||
summary: List Entity Store Entities
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/status:
|
||||
get:
|
||||
operationId: GetEntityStoreStatus
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor'
|
||||
type: array
|
||||
status:
|
||||
$ref: '#/components/schemas/Security_Entity_Analytics_API_StoreStatus'
|
||||
description: Successful response
|
||||
summary: Get the status of the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/exception_lists:
|
||||
delete:
|
||||
description: Delete an exception list using the `id` or `list_id` field.
|
||||
|
@ -53601,6 +53657,14 @@ components:
|
|||
- index
|
||||
- description
|
||||
- category
|
||||
Security_Entity_Analytics_API_StoreStatus:
|
||||
enum:
|
||||
- not_installed
|
||||
- installing
|
||||
- running
|
||||
- stopped
|
||||
- error
|
||||
type: string
|
||||
Security_Entity_Analytics_API_TaskManagerUnavailableResponse:
|
||||
description: Task manager is unavailable
|
||||
type: object
|
||||
|
|
|
@ -39,6 +39,11 @@ export const EngineDescriptor = z.object({
|
|||
error: z.object({}).optional(),
|
||||
});
|
||||
|
||||
export type StoreStatus = z.infer<typeof StoreStatus>;
|
||||
export const StoreStatus = z.enum(['not_installed', 'installing', 'running', 'stopped', 'error']);
|
||||
export type StoreStatusEnum = typeof StoreStatus.enum;
|
||||
export const StoreStatusEnum = StoreStatus.enum;
|
||||
|
||||
export type InspectQuery = z.infer<typeof InspectQuery>;
|
||||
export const InspectQuery = z.object({
|
||||
response: z.array(z.string()),
|
||||
|
|
|
@ -41,6 +41,15 @@ components:
|
|||
- stopped
|
||||
- updating
|
||||
- error
|
||||
|
||||
StoreStatus:
|
||||
type: string
|
||||
enum:
|
||||
- not_installed
|
||||
- installing
|
||||
- running
|
||||
- stopped
|
||||
- error
|
||||
|
||||
IndexPattern:
|
||||
type: string
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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: Enable Entity Store
|
||||
* version: 2023-10-31
|
||||
*/
|
||||
|
||||
import { z } from '@kbn/zod';
|
||||
|
||||
import { IndexPattern, EngineDescriptor, StoreStatus } from './common.gen';
|
||||
|
||||
export type GetEntityStoreStatusResponse = z.infer<typeof GetEntityStoreStatusResponse>;
|
||||
export const GetEntityStoreStatusResponse = z.object({
|
||||
status: StoreStatus.optional(),
|
||||
engines: z.array(EngineDescriptor).optional(),
|
||||
});
|
||||
|
||||
export type InitEntityStoreRequestBody = z.infer<typeof InitEntityStoreRequestBody>;
|
||||
export const InitEntityStoreRequestBody = z.object({
|
||||
/**
|
||||
* The number of historical values to keep for each field.
|
||||
*/
|
||||
fieldHistoryLength: z.number().int().optional().default(10),
|
||||
indexPattern: IndexPattern.optional(),
|
||||
filter: z.string().optional(),
|
||||
});
|
||||
export type InitEntityStoreRequestBodyInput = z.input<typeof InitEntityStoreRequestBody>;
|
||||
|
||||
export type InitEntityStoreResponse = z.infer<typeof InitEntityStoreResponse>;
|
||||
export const InitEntityStoreResponse = z.object({
|
||||
succeeded: z.boolean().optional(),
|
||||
engines: z.array(EngineDescriptor).optional(),
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
openapi: 3.0.0
|
||||
|
||||
info:
|
||||
title: Enable Entity Store
|
||||
version: '2023-10-31'
|
||||
paths:
|
||||
/api/entity_store/enable:
|
||||
post:
|
||||
x-labels: [ess, serverless]
|
||||
x-codegen-enabled: true
|
||||
operationId: InitEntityStore
|
||||
summary: Initialize the Entity Store
|
||||
|
||||
requestBody:
|
||||
description: Schema for the entity store initialization
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
fieldHistoryLength:
|
||||
type: integer
|
||||
description: The number of historical values to keep for each field.
|
||||
default: 10
|
||||
indexPattern:
|
||||
$ref: './common.schema.yaml#/components/schemas/IndexPattern'
|
||||
filter:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
succeeded:
|
||||
type: boolean
|
||||
engines:
|
||||
type: array
|
||||
items:
|
||||
$ref: './common.schema.yaml#/components/schemas/EngineDescriptor'
|
||||
|
||||
/api/entity_store/status:
|
||||
get:
|
||||
x-labels: [ess, serverless]
|
||||
x-codegen-enabled: true
|
||||
operationId: GetEntityStoreStatus
|
||||
summary: Get the status of the Entity Store
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: './common.schema.yaml#/components/schemas/StoreStatus'
|
||||
engines:
|
||||
type: array
|
||||
items:
|
||||
$ref: './common.schema.yaml#/components/schemas/EngineDescriptor'
|
|
@ -231,6 +231,11 @@ import type {
|
|||
InternalUploadAssetCriticalityRecordsResponse,
|
||||
UploadAssetCriticalityRecordsResponse,
|
||||
} from './entity_analytics/asset_criticality/upload_asset_criticality_csv.gen';
|
||||
import type {
|
||||
GetEntityStoreStatusResponse,
|
||||
InitEntityStoreRequestBodyInput,
|
||||
InitEntityStoreResponse,
|
||||
} from './entity_analytics/entity_store/enablement.gen';
|
||||
import type { ApplyEntityEngineDataviewIndicesResponse } from './entity_analytics/entity_store/engine/apply_dataview_indices.gen';
|
||||
import type {
|
||||
DeleteEntityEngineRequestQueryInput,
|
||||
|
@ -1301,6 +1306,18 @@ finalize it.
|
|||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
}
|
||||
async getEntityStoreStatus() {
|
||||
this.log.info(`${new Date().toISOString()} Calling API GetEntityStoreStatus`);
|
||||
return this.kbnClient
|
||||
.request<GetEntityStoreStatusResponse>({
|
||||
path: '/api/entity_store/status',
|
||||
headers: {
|
||||
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
}
|
||||
/**
|
||||
* Get all notes for a given document.
|
||||
*/
|
||||
|
@ -1529,6 +1546,19 @@ finalize it.
|
|||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
}
|
||||
async initEntityStore(props: InitEntityStoreProps) {
|
||||
this.log.info(`${new Date().toISOString()} Calling API InitEntityStore`);
|
||||
return this.kbnClient
|
||||
.request<InitEntityStoreResponse>({
|
||||
path: '/api/entity_store/enable',
|
||||
headers: {
|
||||
[ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
|
||||
},
|
||||
method: 'POST',
|
||||
body: props.body,
|
||||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
}
|
||||
/**
|
||||
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
|
||||
*/
|
||||
|
@ -2290,6 +2320,9 @@ export interface InitEntityEngineProps {
|
|||
params: InitEntityEngineRequestParamsInput;
|
||||
body: InitEntityEngineRequestBodyInput;
|
||||
}
|
||||
export interface InitEntityStoreProps {
|
||||
body: InitEntityStoreRequestBodyInput;
|
||||
}
|
||||
export interface InstallPrepackedTimelinesProps {
|
||||
body: InstallPrepackedTimelinesRequestBodyInput;
|
||||
}
|
||||
|
|
|
@ -267,6 +267,42 @@ paths:
|
|||
summary: List asset criticality records
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/enable:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
fieldHistoryLength:
|
||||
default: 10
|
||||
description: The number of historical values to keep for each field.
|
||||
type: integer
|
||||
filter:
|
||||
type: string
|
||||
indexPattern:
|
||||
$ref: '#/components/schemas/IndexPattern'
|
||||
description: Schema for the entity store initialization
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/EngineDescriptor'
|
||||
type: array
|
||||
succeeded:
|
||||
type: boolean
|
||||
description: Successful response
|
||||
summary: Initialize the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/engines:
|
||||
get:
|
||||
operationId: ListEntityEngines
|
||||
|
@ -576,6 +612,26 @@ paths:
|
|||
summary: List Entity Store Entities
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/status:
|
||||
get:
|
||||
operationId: GetEntityStoreStatus
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/EngineDescriptor'
|
||||
type: array
|
||||
status:
|
||||
$ref: '#/components/schemas/StoreStatus'
|
||||
description: Successful response
|
||||
summary: Get the status of the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/risk_score/engine/dangerously_delete_data:
|
||||
delete:
|
||||
description: >-
|
||||
|
@ -1046,6 +1102,14 @@ components:
|
|||
- index
|
||||
- description
|
||||
- category
|
||||
StoreStatus:
|
||||
enum:
|
||||
- not_installed
|
||||
- installing
|
||||
- running
|
||||
- stopped
|
||||
- error
|
||||
type: string
|
||||
TaskManagerUnavailableResponse:
|
||||
description: Task manager is unavailable
|
||||
type: object
|
||||
|
|
|
@ -267,6 +267,42 @@ paths:
|
|||
summary: List asset criticality records
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/enable:
|
||||
post:
|
||||
operationId: InitEntityStore
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
fieldHistoryLength:
|
||||
default: 10
|
||||
description: The number of historical values to keep for each field.
|
||||
type: integer
|
||||
filter:
|
||||
type: string
|
||||
indexPattern:
|
||||
$ref: '#/components/schemas/IndexPattern'
|
||||
description: Schema for the entity store initialization
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/EngineDescriptor'
|
||||
type: array
|
||||
succeeded:
|
||||
type: boolean
|
||||
description: Successful response
|
||||
summary: Initialize the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/engines:
|
||||
get:
|
||||
operationId: ListEntityEngines
|
||||
|
@ -576,6 +612,26 @@ paths:
|
|||
summary: List Entity Store Entities
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/entity_store/status:
|
||||
get:
|
||||
operationId: GetEntityStoreStatus
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
engines:
|
||||
items:
|
||||
$ref: '#/components/schemas/EngineDescriptor'
|
||||
type: array
|
||||
status:
|
||||
$ref: '#/components/schemas/StoreStatus'
|
||||
description: Successful response
|
||||
summary: Get the status of the Entity Store
|
||||
tags:
|
||||
- Security Entity Analytics API
|
||||
/api/risk_score/engine/dangerously_delete_data:
|
||||
delete:
|
||||
description: >-
|
||||
|
@ -1046,6 +1102,14 @@ components:
|
|||
- index
|
||||
- description
|
||||
- category
|
||||
StoreStatus:
|
||||
enum:
|
||||
- not_installed
|
||||
- installing
|
||||
- running
|
||||
- stopped
|
||||
- error
|
||||
type: string
|
||||
TaskManagerUnavailableResponse:
|
||||
description: Task manager is unavailable
|
||||
type: object
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EngineStatus } from '../../../../common/api/entity_analytics';
|
||||
import type { EngineStatus, StoreStatus } from '../../../../common/api/entity_analytics';
|
||||
|
||||
export const DEFAULT_LOOKBACK_PERIOD = '24h';
|
||||
|
||||
|
@ -17,4 +17,12 @@ export const ENGINE_STATUS: Record<Uppercase<EngineStatus>, EngineStatus> = {
|
|||
ERROR: 'error',
|
||||
};
|
||||
|
||||
export const ENTITY_STORE_STATUS: Record<Uppercase<StoreStatus>, StoreStatus> = {
|
||||
RUNNING: 'running',
|
||||
STOPPED: 'stopped',
|
||||
INSTALLING: 'installing',
|
||||
NOT_INSTALLED: 'not_installed',
|
||||
ERROR: 'error',
|
||||
};
|
||||
|
||||
export const MAX_SEARCH_RESPONSE_SIZE = 10_000;
|
||||
|
|
|
@ -20,17 +20,22 @@ import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
|
|||
import type { DataViewsService } from '@kbn/data-views-plugin/common';
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import type {
|
||||
GetEntityStoreStatusResponse,
|
||||
InitEntityStoreRequestBody,
|
||||
InitEntityStoreResponse,
|
||||
} from '../../../../common/api/entity_analytics/entity_store/enablement.gen';
|
||||
import type { AppClient } from '../../..';
|
||||
import { EntityType } from '../../../../common/api/entity_analytics';
|
||||
import type {
|
||||
Entity,
|
||||
EngineDataviewUpdateResult,
|
||||
InitEntityEngineRequestBody,
|
||||
InitEntityEngineResponse,
|
||||
EntityType,
|
||||
InspectQuery,
|
||||
} from '../../../../common/api/entity_analytics';
|
||||
import { EngineDescriptorClient } from './saved_object/engine_descriptor';
|
||||
import { ENGINE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants';
|
||||
import { ENGINE_STATUS, ENTITY_STORE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants';
|
||||
import { AssetCriticalityEcsMigrationClient } from '../asset_criticality/asset_criticality_migration_client';
|
||||
import { getUnitedEntityDefinition } from './united_entity_definitions';
|
||||
import {
|
||||
|
@ -126,6 +131,44 @@ export class EntityStoreDataClient {
|
|||
});
|
||||
}
|
||||
|
||||
public async enable(
|
||||
{ indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityStoreRequestBody,
|
||||
{ pipelineDebugMode = false }: { pipelineDebugMode?: boolean } = {}
|
||||
): Promise<InitEntityStoreResponse> {
|
||||
if (!this.options.taskManager) {
|
||||
throw new Error('Task Manager is not available');
|
||||
}
|
||||
|
||||
// Immediately defer the initialization to the next tick. This way we don't block on the init preflight checks
|
||||
const run = <T>(fn: () => Promise<T>) =>
|
||||
new Promise<T>((resolve) => setTimeout(() => fn().then(resolve), 0));
|
||||
const promises = Object.values(EntityType.Values).map((entity) =>
|
||||
run(() =>
|
||||
this.init(entity, { indexPattern, filter, fieldHistoryLength }, { pipelineDebugMode })
|
||||
)
|
||||
);
|
||||
|
||||
const engines = await Promise.all(promises);
|
||||
return { engines, succeeded: true };
|
||||
}
|
||||
|
||||
public async status(): Promise<GetEntityStoreStatusResponse> {
|
||||
const { engines, count } = await this.engineClient.list();
|
||||
|
||||
let status = ENTITY_STORE_STATUS.RUNNING;
|
||||
if (count === 0) {
|
||||
status = ENTITY_STORE_STATUS.NOT_INSTALLED;
|
||||
} else if (engines.some((engine) => engine.status === ENGINE_STATUS.ERROR)) {
|
||||
status = ENTITY_STORE_STATUS.ERROR;
|
||||
} else if (engines.every((engine) => engine.status === ENGINE_STATUS.STOPPED)) {
|
||||
status = ENTITY_STORE_STATUS.STOPPED;
|
||||
} else if (engines.some((engine) => engine.status === ENGINE_STATUS.INSTALLING)) {
|
||||
status = ENTITY_STORE_STATUS.INSTALLING;
|
||||
}
|
||||
|
||||
return { engines, status };
|
||||
}
|
||||
|
||||
public async init(
|
||||
entityType: EntityType,
|
||||
{ indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityEngineRequestBody,
|
||||
|
@ -137,7 +180,16 @@ export class EntityStoreDataClient {
|
|||
|
||||
const { config } = this.options;
|
||||
|
||||
await this.riskScoreDataClient.createRiskScoreLatestIndex();
|
||||
await this.riskScoreDataClient.createRiskScoreLatestIndex().catch((e) => {
|
||||
if (e.meta.body.error.type === 'resource_already_exists_exception') {
|
||||
this.options.logger.debug(
|
||||
`Risk score index for ${entityType} already exists, skipping creation.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
const requiresMigration =
|
||||
await this.assetCriticalityMigrationClient.isEcsDataMigrationRequired();
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import type { InitEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen';
|
||||
import { InitEntityStoreRequestBody } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen';
|
||||
import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
|
||||
import type { EntityAnalyticsRoutesDeps } from '../../types';
|
||||
import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources';
|
||||
|
||||
export const enableEntityStoreRoute = (
|
||||
router: EntityAnalyticsRoutesDeps['router'],
|
||||
logger: Logger,
|
||||
config: EntityAnalyticsRoutesDeps['config']
|
||||
) => {
|
||||
router.versioned
|
||||
.post({
|
||||
access: 'public',
|
||||
path: '/api/entity_store/enable',
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.public.v1,
|
||||
validate: {
|
||||
request: {
|
||||
body: buildRouteValidationWithZod(InitEntityStoreRequestBody),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
async (context, request, response): Promise<IKibanaResponse<InitEntityStoreResponse>> => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const secSol = await context.securitySolution;
|
||||
const { pipelineDebugMode } = config.entityAnalytics.entityStore.developer;
|
||||
|
||||
await checkAndInitAssetCriticalityResources(context, logger);
|
||||
|
||||
try {
|
||||
const body: InitEntityStoreResponse = await secSol
|
||||
.getEntityStoreDataClient()
|
||||
.enable(request.body, { pipelineDebugMode });
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
logger.error(`Error initialising entity store: ${error.message}`);
|
||||
return siemResponse.error({
|
||||
statusCode: error.statusCode,
|
||||
body: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -15,6 +15,8 @@ import { listEntityEnginesRoute } from './list';
|
|||
import { entityStoreInternalPrivilegesRoute } from './privileges';
|
||||
import { startEntityEngineRoute } from './start';
|
||||
import { stopEntityEngineRoute } from './stop';
|
||||
import { getEntityStoreStatusRoute } from './status';
|
||||
import { enableEntityStoreRoute } from './enablement';
|
||||
|
||||
export const registerEntityStoreRoutes = ({
|
||||
router,
|
||||
|
@ -22,6 +24,8 @@ export const registerEntityStoreRoutes = ({
|
|||
getStartServices,
|
||||
config,
|
||||
}: EntityAnalyticsRoutesDeps) => {
|
||||
enableEntityStoreRoute(router, logger, config);
|
||||
getEntityStoreStatusRoute(router, logger, config);
|
||||
initEntityEngineRoute(router, logger, config);
|
||||
startEntityEngineRoute(router, logger);
|
||||
stopEntityEngineRoute(router, logger);
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 { GetEntityStoreStatusResponse } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen';
|
||||
import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
|
||||
import type { EntityAnalyticsRoutesDeps } from '../../types';
|
||||
import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources';
|
||||
|
||||
export const getEntityStoreStatusRoute = (
|
||||
router: EntityAnalyticsRoutesDeps['router'],
|
||||
logger: Logger,
|
||||
config: EntityAnalyticsRoutesDeps['config']
|
||||
) => {
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'public',
|
||||
path: '/api/entity_store/status',
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.public.v1,
|
||||
validate: {},
|
||||
},
|
||||
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<GetEntityStoreStatusResponse>> => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const secSol = await context.securitySolution;
|
||||
|
||||
await checkAndInitAssetCriticalityResources(context, logger);
|
||||
|
||||
try {
|
||||
const body: GetEntityStoreStatusResponse = await secSol
|
||||
.getEntityStoreDataClient()
|
||||
.status();
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
logger.error(`Error initialising entity store: ${error.message}`);
|
||||
return siemResponse.error({
|
||||
statusCode: error.statusCode,
|
||||
body: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -106,6 +106,7 @@ import {
|
|||
InitEntityEngineRequestParamsInput,
|
||||
InitEntityEngineRequestBodyInput,
|
||||
} from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/init.gen';
|
||||
import { InitEntityStoreRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/enablement.gen';
|
||||
import { InstallPrepackedTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen';
|
||||
import { ListEntitiesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/entities/list_entities.gen';
|
||||
import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen';
|
||||
|
@ -842,6 +843,13 @@ finalize it.
|
|||
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
|
||||
},
|
||||
getEntityStoreStatus(kibanaSpace: string = 'default') {
|
||||
return supertest
|
||||
.get(routeWithNamespace('/api/entity_store/status', kibanaSpace))
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
|
||||
},
|
||||
/**
|
||||
* Get all notes for a given document.
|
||||
*/
|
||||
|
@ -1030,6 +1038,14 @@ finalize it.
|
|||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.send(props.body as object);
|
||||
},
|
||||
initEntityStore(props: InitEntityStoreProps, kibanaSpace: string = 'default') {
|
||||
return supertest
|
||||
.post(routeWithNamespace('/api/entity_store/enable', kibanaSpace))
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.send(props.body as object);
|
||||
},
|
||||
/**
|
||||
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
|
||||
*/
|
||||
|
@ -1633,6 +1649,9 @@ export interface InitEntityEngineProps {
|
|||
params: InitEntityEngineRequestParamsInput;
|
||||
body: InitEntityEngineRequestBodyInput;
|
||||
}
|
||||
export interface InitEntityStoreProps {
|
||||
body: InitEntityStoreRequestBodyInput;
|
||||
}
|
||||
export interface InstallPrepackedTimelinesProps {
|
||||
body: InstallPrepackedTimelinesRequestBodyInput;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const supertest = getService('supertest');
|
||||
|
||||
const utils = EntityStoreUtils(getService);
|
||||
describe('@ess @skipInServerlessMKI Entity Store Engine APIs', () => {
|
||||
describe('@ess @skipInServerlessMKI Entity Store APIs', () => {
|
||||
const dataView = dataViewRouteHelpersFactory(supertest);
|
||||
|
||||
before(async () => {
|
||||
|
@ -42,6 +42,18 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('enablement', () => {
|
||||
afterEach(async () => {
|
||||
await utils.cleanEngines();
|
||||
});
|
||||
|
||||
it('should enable the entity store, creating both user and host engines', async () => {
|
||||
await utils.enableEntityStore();
|
||||
await utils.expectEngineAssetsExist('user');
|
||||
await utils.expectEngineAssetsExist('host');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get and list', () => {
|
||||
before(async () => {
|
||||
await utils.initEntityEngineForEntityTypesAndWait(['host', 'user']);
|
||||
|
@ -182,6 +194,42 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('status', () => {
|
||||
afterEach(async () => {
|
||||
await utils.cleanEngines();
|
||||
});
|
||||
it('should return "not_installed" when no engines have been initialized', async () => {
|
||||
const { body } = await api.getEntityStoreStatus().expect(200);
|
||||
|
||||
expect(body).to.eql({
|
||||
engines: [],
|
||||
status: 'not_installed',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return "installing" when at least one engine is being initialized', async () => {
|
||||
await utils.enableEntityStore();
|
||||
|
||||
const { body } = await api.getEntityStoreStatus().expect(200);
|
||||
|
||||
expect(body.status).to.eql('installing');
|
||||
expect(body.engines.length).to.eql(2);
|
||||
expect(body.engines[0].status).to.eql('installing');
|
||||
expect(body.engines[1].status).to.eql('installing');
|
||||
});
|
||||
|
||||
it('should return "started" when all engines are started', async () => {
|
||||
await utils.initEntityEngineForEntityTypesAndWait(['host', 'user']);
|
||||
|
||||
const { body } = await api.getEntityStoreStatus().expect(200);
|
||||
|
||||
expect(body.status).to.eql('running');
|
||||
expect(body.engines.length).to.eql(2);
|
||||
expect(body.engines[0].status).to.eql('started');
|
||||
expect(body.engines[1].status).to.eql('started');
|
||||
});
|
||||
});
|
||||
|
||||
describe('apply_dataview_indices', () => {
|
||||
before(async () => {
|
||||
await utils.initEntityEngineForEntityTypesAndWait(['host']);
|
|
@ -10,8 +10,8 @@ import { FtrProviderContext } from '../../../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Entity Analytics - Entity Store', function () {
|
||||
loadTestFile(require.resolve('./entities_list'));
|
||||
loadTestFile(require.resolve('./engine'));
|
||||
loadTestFile(require.resolve('./entity_store'));
|
||||
loadTestFile(require.resolve('./field_retention_operators'));
|
||||
loadTestFile(require.resolve('./engine_nondefault_spaces'));
|
||||
loadTestFile(require.resolve('./entity_store_nondefault_spaces'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -90,6 +90,16 @@ export const EntityStoreUtils = (
|
|||
);
|
||||
};
|
||||
|
||||
const enableEntityStore = async () => {
|
||||
const res = await api.initEntityStore({ body: {} }, namespace);
|
||||
if (res.status !== 200) {
|
||||
log.error(`Failed to enable entity store`);
|
||||
log.error(JSON.stringify(res.body));
|
||||
}
|
||||
expect(res.status).to.eql(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
const expectTransformStatus = async (
|
||||
transformId: string,
|
||||
exists: boolean,
|
||||
|
@ -144,5 +154,6 @@ export const EntityStoreUtils = (
|
|||
expectTransformStatus,
|
||||
expectEngineAssetsExist,
|
||||
expectEngineAssetsDoNotExist,
|
||||
enableEntityStore,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue