From 5d1b5104fcdb96e799131a12cc54fa7b8cf7edc7 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Wed, 20 Oct 2021 03:16:31 +0200 Subject: [PATCH] Fix adding prepackaged rules via fleet plugin (#114467) Co-authored-by: Garrett Spong Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- api_docs/security_solution.json | 12 +- .../server/alert_data_client/alerts_client.ts | 6 +- .../alerts_client_factory.test.ts | 5 +- .../alerts_client_factory.ts | 6 +- .../tests/bulk_update.test.ts | 5 +- .../tests/find_alerts.test.ts | 5 +- .../alert_data_client/tests/get.test.ts | 5 +- .../alert_data_client/tests/update.test.ts | 5 +- x-pack/plugins/rule_registry/server/index.ts | 2 +- x-pack/plugins/rule_registry/server/mocks.ts | 9 +- x-pack/plugins/rule_registry/server/plugin.ts | 8 +- .../rule_data_plugin_service.mock.ts | 18 +- .../rule_data_plugin_service.ts | 89 ++++--- .../security_solution/server/config.mock.ts | 62 +++++ .../security_solution/server/config.ts | 24 +- .../endpoint/endpoint_app_context_services.ts | 81 +++---- .../server/endpoint/errors.ts | 6 + .../server/endpoint/mocks.ts | 18 +- .../endpoint/routes/actions/audit_log.test.ts | 2 + .../endpoint/routes/actions/isolation.test.ts | 4 + .../endpoint/routes/actions/status.test.ts | 2 + .../endpoint/routes/metadata/metadata.test.ts | 3 + .../endpoint/routes/policy/handlers.test.ts | 3 + .../fleet_integration.test.ts | 15 +- .../fleet_integration/fleet_integration.ts | 28 +-- .../handlers/install_prepackaged_rules.ts | 36 +-- .../plugins/security_solution/server/index.ts | 6 +- .../routes/__mocks__/index.ts | 33 +-- .../routes/__mocks__/request_context.ts | 131 +++++++---- .../routes/__mocks__/request_responses.ts | 28 ++- .../routes/index/create_index_route.ts | 42 +--- .../privileges/read_privileges_route.test.ts | 6 +- .../rules/add_prepackaged_rules_route.test.ts | 54 ++--- .../rules/add_prepackaged_rules_route.ts | 67 ++---- .../rules/create_rules_bulk_route.test.ts | 8 +- .../routes/rules/create_rules_route.test.ts | 8 +- .../routes/rules/import_rules_route.test.ts | 8 +- .../routes/signals/open_close_signals.test.ts | 4 +- .../server/lib/framework/types.ts | 5 +- .../create_timelines/helpers.test.ts | 17 +- .../server/lib/timeline/utils/common.ts | 7 +- .../security_solution/server/plugin.ts | 222 +++++++----------- .../server/plugin_contract.ts | 102 ++++++++ .../server/request_context_factory.mock.ts | 23 ++ .../server/request_context_factory.ts | 90 +++++++ .../security_solution/server/routes/index.ts | 6 +- .../plugins/security_solution/server/types.ts | 29 ++- 47 files changed, 794 insertions(+), 561 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/config.mock.ts create mode 100644 x-pack/plugins/security_solution/server/plugin_contract.ts create mode 100644 x-pack/plugins/security_solution/server/request_context_factory.mock.ts create mode 100644 x-pack/plugins/security_solution/server/request_context_factory.ts diff --git a/api_docs/security_solution.json b/api_docs/security_solution.json index b16ae8334f1b0..e159e936e8f42 100644 --- a/api_docs/security_solution.json +++ b/api_docs/security_solution.json @@ -772,17 +772,17 @@ "interfaces": [ { "parentPluginId": "securitySolution", - "id": "def-server.AppRequestContext", + "id": "def-server.SecuritySolutionApiRequestHandlerContext", "type": "Interface", "tags": [], - "label": "AppRequestContext", + "label": "SecuritySolutionApiRequestHandlerContext", "description": [], "path": "x-pack/plugins/security_solution/server/types.ts", "deprecated": false, "children": [ { "parentPluginId": "securitySolution", - "id": "def-server.AppRequestContext.getAppClient", + "id": "def-server.SecuritySolutionApiRequestHandlerContext.getAppClient", "type": "Function", "tags": [], "label": "getAppClient", @@ -804,7 +804,7 @@ }, { "parentPluginId": "securitySolution", - "id": "def-server.AppRequestContext.getSpaceId", + "id": "def-server.SecuritySolutionApiRequestHandlerContext.getSpaceId", "type": "Function", "tags": [], "label": "getSpaceId", @@ -819,7 +819,7 @@ }, { "parentPluginId": "securitySolution", - "id": "def-server.AppRequestContext.getExecutionLogClient", + "id": "def-server.SecuritySolutionApiRequestHandlerContext.getExecutionLogClient", "type": "Function", "tags": [], "label": "getExecutionLogClient", @@ -31438,4 +31438,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index d35d18d3b5958..16447e6b0f539 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -41,7 +41,7 @@ import { SPACE_IDS, } from '../../common/technical_rule_data_field_names'; import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; -import { Dataset, RuleDataPluginService } from '../rule_data_plugin_service'; +import { Dataset, IRuleDataService } from '../rule_data_plugin_service'; const getEsQueryConfig: typeof getEsQueryConfigTyped = getEsQueryConfigNonTyped; const getSafeSortIds: typeof getSafeSortIdsTyped = getSafeSortIdsNonTyped; @@ -71,7 +71,7 @@ export interface ConstructorOptions { authorization: PublicMethodsOf; auditLogger?: AuditLogger; esClient: ElasticsearchClient; - ruleDataService: RuleDataPluginService; + ruleDataService: IRuleDataService; } export interface UpdateOptions { @@ -116,7 +116,7 @@ export class AlertsClient { private readonly authorization: PublicMethodsOf; private readonly esClient: ElasticsearchClient; private readonly spaceId: string | undefined; - private readonly ruleDataService: RuleDataPluginService; + private readonly ruleDataService: IRuleDataService; constructor(options: ConstructorOptions) { this.logger = options.logger; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts index 41ef5e4edb0d1..276ea070d6f87 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts @@ -13,8 +13,7 @@ import { loggingSystemMock } from 'src/core/server/mocks'; import { securityMock } from '../../../security/server/mocks'; import { AuditLogger } from '../../../security/server'; import { alertingAuthorizationMock } from '../../../alerting/server/authorization/alerting_authorization.mock'; -import { ruleDataPluginServiceMock } from '../rule_data_plugin_service/rule_data_plugin_service.mock'; -import { RuleDataPluginService } from '../rule_data_plugin_service'; +import { ruleDataServiceMock } from '../rule_data_plugin_service/rule_data_plugin_service.mock'; jest.mock('./alerts_client'); @@ -26,7 +25,7 @@ const alertsClientFactoryParams: AlertsClientFactoryProps = { getAlertingAuthorization: (_: KibanaRequest) => alertingAuthMock, securityPluginSetup, esClient: {} as ElasticsearchClient, - ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, + ruleDataService: ruleDataServiceMock.create(), }; const fakeRequest = { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts index c1ff6d5d56ea9..8225394c2dba7 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts @@ -9,7 +9,7 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient, KibanaRequest, Logger } from 'src/core/server'; import { AlertingAuthorization } from '../../../alerting/server'; import { SecurityPluginSetup } from '../../../security/server'; -import { RuleDataPluginService } from '../rule_data_plugin_service'; +import { IRuleDataService } from '../rule_data_plugin_service'; import { AlertsClient } from './alerts_client'; export interface AlertsClientFactoryProps { @@ -17,7 +17,7 @@ export interface AlertsClientFactoryProps { esClient: ElasticsearchClient; getAlertingAuthorization: (request: KibanaRequest) => PublicMethodsOf; securityPluginSetup: SecurityPluginSetup | undefined; - ruleDataService: RuleDataPluginService | null; + ruleDataService: IRuleDataService | null; } export class AlertsClientFactory { @@ -28,7 +28,7 @@ export class AlertsClientFactory { request: KibanaRequest ) => PublicMethodsOf; private securityPluginSetup!: SecurityPluginSetup | undefined; - private ruleDataService!: RuleDataPluginService | null; + private ruleDataService!: IRuleDataService | null; public initialize(options: AlertsClientFactoryProps) { /** diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts index 2be1f6875cd7e..8868d7959621d 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts @@ -19,8 +19,7 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { AuditLogger } from '../../../../security/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; -import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; -import { RuleDataPluginService } from '../../rule_data_plugin_service'; +import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); @@ -33,7 +32,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, + ruleDataService: ruleDataServiceMock.create(), }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts index b94a3b96312e4..5f9a20c14ea5b 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts @@ -18,8 +18,7 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { AuditLogger } from '../../../../security/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; -import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; -import { RuleDataPluginService } from '../../rule_data_plugin_service'; +import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); @@ -32,7 +31,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, + ruleDataService: ruleDataServiceMock.create(), }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts index 320e9f8a5fb1c..eaf6c0089ce12 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts @@ -19,8 +19,7 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { AuditLogger } from '../../../../security/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; -import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; -import { RuleDataPluginService } from '../../rule_data_plugin_service'; +import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); @@ -33,7 +32,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, + ruleDataService: ruleDataServiceMock.create(), }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts index 922011dcb5271..85527e26a9cd3 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts @@ -18,8 +18,7 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { AuditLogger } from '../../../../security/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; -import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; -import { RuleDataPluginService } from '../../rule_data_plugin_service'; +import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); @@ -32,7 +31,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, + ruleDataService: ruleDataServiceMock.create(), }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/index.ts b/x-pack/plugins/rule_registry/server/index.ts index 5331ab86be982..d6c5b61706415 100644 --- a/x-pack/plugins/rule_registry/server/index.ts +++ b/x-pack/plugins/rule_registry/server/index.ts @@ -12,7 +12,7 @@ import { PluginInitializerContext } from 'src/core/server'; import { RuleRegistryPlugin } from './plugin'; export type { RuleRegistryPluginSetupContract, RuleRegistryPluginStartContract } from './plugin'; -export { RuleDataPluginService } from './rule_data_plugin_service'; +export { IRuleDataService, RuleDataPluginService } from './rule_data_plugin_service'; export { RuleDataClient } from './rule_data_client'; export { IRuleDataClient } from './rule_data_client/types'; export type { diff --git a/x-pack/plugins/rule_registry/server/mocks.ts b/x-pack/plugins/rule_registry/server/mocks.ts index e9ec25ddcdaba..023de6aa6029c 100644 --- a/x-pack/plugins/rule_registry/server/mocks.ts +++ b/x-pack/plugins/rule_registry/server/mocks.ts @@ -7,12 +7,17 @@ import { alertsClientMock } from './alert_data_client/alerts_client.mock'; import { createRuleDataClientMock } from './rule_data_client/rule_data_client.mock'; -import { ruleDataPluginServiceMock } from './rule_data_plugin_service/rule_data_plugin_service.mock'; +import { + ruleDataServiceMock, + RuleDataServiceMock, +} from './rule_data_plugin_service/rule_data_plugin_service.mock'; import { createLifecycleAlertServicesMock } from './utils/lifecycle_alert_services_mock'; export const ruleRegistryMocks = { createLifecycleAlertServices: createLifecycleAlertServicesMock, - createRuleDataPluginService: ruleDataPluginServiceMock.create, + createRuleDataService: ruleDataServiceMock.create, createRuleDataClient: createRuleDataClientMock, createAlertsClientMock: alertsClientMock, }; + +export { RuleDataServiceMock }; diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index b68f3eeb10669..334216ce41361 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -20,7 +20,7 @@ import { PluginStartContract as AlertingStart } from '../../alerting/server'; import { SecurityPluginSetup } from '../../security/server'; import { RuleRegistryPluginConfig } from './config'; -import { RuleDataPluginService } from './rule_data_plugin_service'; +import { IRuleDataService, RuleDataService } from './rule_data_plugin_service'; import { AlertsClientFactory } from './alert_data_client/alerts_client_factory'; import { AlertsClient } from './alert_data_client/alerts_client'; import { RacApiRequestHandlerContext, RacRequestHandlerContext } from './types'; @@ -35,7 +35,7 @@ export interface RuleRegistryPluginStartDependencies { } export interface RuleRegistryPluginSetupContract { - ruleDataService: RuleDataPluginService; + ruleDataService: IRuleDataService; } export interface RuleRegistryPluginStartContract { @@ -57,7 +57,7 @@ export class RuleRegistryPlugin private readonly logger: Logger; private readonly kibanaVersion: string; private readonly alertsClientFactory: AlertsClientFactory; - private ruleDataService: RuleDataPluginService | null; + private ruleDataService: IRuleDataService | null; private security: SecurityPluginSetup | undefined; constructor(initContext: PluginInitializerContext) { @@ -100,7 +100,7 @@ export class RuleRegistryPlugin } }; - this.ruleDataService = new RuleDataPluginService({ + this.ruleDataService = new RuleDataService({ logger, kibanaVersion, isWriteEnabled: isWriteEnabled(this.config, this.legacyConfig), diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.mock.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.mock.ts index c50a982741b0c..43e727e79b76b 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.mock.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.mock.ts @@ -5,13 +5,10 @@ * 2.0. */ -import type { PublicMethodsOf } from '@kbn/utility-types'; -import { RuleDataPluginService } from './rule_data_plugin_service'; +import { IRuleDataService } from './rule_data_plugin_service'; -type Schema = PublicMethodsOf; - -const createRuleDataPluginService = () => { - const mocked: jest.Mocked = { +export const ruleDataServiceMock = { + create: (): jest.Mocked => ({ getResourcePrefix: jest.fn(), getResourceName: jest.fn(), isWriteEnabled: jest.fn(), @@ -19,10 +16,9 @@ const createRuleDataPluginService = () => { initializeIndex: jest.fn(), findIndexByName: jest.fn(), findIndicesByFeature: jest.fn(), - }; - return mocked; + }), }; -export const ruleDataPluginServiceMock = { - create: createRuleDataPluginService, -}; +export const RuleDataServiceMock = jest + .fn, []>() + .mockImplementation(ruleDataServiceMock.create); diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts index 0617bc0a820ac..c5ec38ec8534e 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts @@ -17,6 +17,59 @@ import { Dataset, IndexOptions } from './index_options'; import { ResourceInstaller } from './resource_installer'; import { joinWithDash } from './utils'; +/** + * A service for creating and using Elasticsearch indices for alerts-as-data. + */ +export interface IRuleDataService { + /** + * Returns a prefix used in the naming scheme of index aliases, templates + * and other Elasticsearch resources that this service creates + * for alerts-as-data indices. + */ + getResourcePrefix(): string; + + /** + * Prepends a relative resource name with the resource prefix. + * @returns Full name of the resource. + * @example 'security.alerts' => '.alerts-security.alerts' + */ + getResourceName(relativeName: string): string; + + /** + * If write is enabled, everything works as usual. + * If it's disabled, writing to all alerts-as-data indices will be disabled, + * and also Elasticsearch resources associated with the indices will not be + * installed. + */ + isWriteEnabled(): boolean; + + /** + * Installs common Elasticsearch resources used by all alerts-as-data indices. + */ + initializeService(): void; + + /** + * Initializes alerts-as-data index and starts index bootstrapping right away. + * @param indexOptions Index parameters: names and resources. + * @returns Client for reading and writing data to this index. + */ + initializeIndex(indexOptions: IndexOptions): IRuleDataClient; + + /** + * Looks up the index information associated with the given registration context and dataset. + */ + findIndexByName(registrationContext: string, dataset: Dataset): IndexInfo | null; + + /** + * Looks up the index information associated with the given Kibana "feature". + * Note: features are used in RBAC. + */ + findIndicesByFeature(featureId: ValidFeatureId, dataset?: Dataset): IndexInfo[]; +} + +// TODO: This is a leftover. Remove its usage from the "observability" plugin and delete it. +export type RuleDataPluginService = IRuleDataService; + interface ConstructorOptions { getClusterClient: () => Promise; logger: Logger; @@ -24,10 +77,7 @@ interface ConstructorOptions { isWriteEnabled: boolean; } -/** - * A service for creating and using Elasticsearch indices for alerts-as-data. - */ -export class RuleDataPluginService { +export class RuleDataService implements IRuleDataService { private readonly indicesByBaseName: Map; private readonly indicesByFeatureId: Map; private readonly resourceInstaller: ResourceInstaller; @@ -49,37 +99,18 @@ export class RuleDataPluginService { this.isInitialized = false; } - /** - * Returns a prefix used in the naming scheme of index aliases, templates - * and other Elasticsearch resources that this service creates - * for alerts-as-data indices. - */ public getResourcePrefix(): string { return INDEX_PREFIX; } - /** - * Prepends a relative resource name with the resource prefix. - * @returns Full name of the resource. - * @example 'security.alerts' => '.alerts-security.alerts' - */ public getResourceName(relativeName: string): string { return joinWithDash(this.getResourcePrefix(), relativeName); } - /** - * If write is enabled, everything works as usual. - * If it's disabled, writing to all alerts-as-data indices will be disabled, - * and also Elasticsearch resources associated with the indices will not be - * installed. - */ public isWriteEnabled(): boolean { return this.options.isWriteEnabled; } - /** - * Installs common Elasticsearch resources used by all alerts-as-data indices. - */ public initializeService(): void { // Run the installation of common resources and handle exceptions. this.installCommonResources = this.resourceInstaller @@ -93,11 +124,6 @@ export class RuleDataPluginService { this.isInitialized = true; } - /** - * Initializes alerts-as-data index and starts index bootstrapping right away. - * @param indexOptions Index parameters: names and resources. - * @returns Client for reading and writing data to this index. - */ public initializeIndex(indexOptions: IndexOptions): IRuleDataClient { if (!this.isInitialized) { throw new Error( @@ -156,18 +182,11 @@ export class RuleDataPluginService { }); } - /** - * Looks up the index information associated with the given registration context and dataset. - */ public findIndexByName(registrationContext: string, dataset: Dataset): IndexInfo | null { const baseName = this.getResourceName(`${registrationContext}.${dataset}`); return this.indicesByBaseName.get(baseName) ?? null; } - /** - * Looks up the index information associated with the given Kibana "feature". - * Note: features are used in RBAC. - */ public findIndicesByFeature(featureId: ValidFeatureId, dataset?: Dataset): IndexInfo[] { const foundIndices = this.indicesByFeatureId.get(featureId) ?? []; return dataset ? foundIndices.filter((i) => i.indexOptions.dataset === dataset) : foundIndices; diff --git a/x-pack/plugins/security_solution/server/config.mock.ts b/x-pack/plugins/security_solution/server/config.mock.ts new file mode 100644 index 0000000000000..c1d1e02ca35f4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/config.mock.ts @@ -0,0 +1,62 @@ +/* + * 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 { DEFAULT_SIGNALS_INDEX, SIGNALS_INDEX_KEY } from '../common/constants'; +import { + ExperimentalFeatures, + parseExperimentalConfigValue, +} from '../common/experimental_features'; +import { ConfigType } from './config'; +import { UnderlyingLogClient } from './lib/detection_engine/rule_execution_log/types'; + +export const createMockConfig = (): ConfigType => { + const enableExperimental: string[] = []; + + return { + [SIGNALS_INDEX_KEY]: DEFAULT_SIGNALS_INDEX, + maxRuleImportExportSize: 10000, + maxRuleImportPayloadBytes: 10485760, + maxTimelineImportExportSize: 10000, + maxTimelineImportPayloadBytes: 10485760, + enableExperimental, + endpointResultListDefaultFirstPageIndex: 0, + endpointResultListDefaultPageSize: 10, + packagerTaskInterval: '60s', + alertMergeStrategy: 'missingFields', + alertIgnoreFields: [], + prebuiltRulesFromFileSystem: true, + prebuiltRulesFromSavedObjects: false, + ruleExecutionLog: { + underlyingClient: UnderlyingLogClient.savedObjects, + }, + + kibanaIndex: '.kibana', + experimentalFeatures: parseExperimentalConfigValue(enableExperimental), + }; +}; + +const withExperimentalFeature = ( + config: ConfigType, + feature: keyof ExperimentalFeatures +): ConfigType => { + const enableExperimental = config.enableExperimental.concat(feature); + return { + ...config, + enableExperimental, + experimentalFeatures: parseExperimentalConfigValue(enableExperimental), + }; +}; + +const withRuleRegistryEnabled = (config: ConfigType, isEnabled: boolean): ConfigType => { + return isEnabled ? withExperimentalFeature(config, 'ruleRegistryEnabled') : config; +}; + +export const configMock = { + createDefault: createMockConfig, + withExperimentalFeature, + withRuleRegistryEnabled, +}; diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index 61cbb5641c5f6..072e23b7a773c 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -9,8 +9,10 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from '../../../../src/core/server'; import { SIGNALS_INDEX_KEY, DEFAULT_SIGNALS_INDEX } from '../common/constants'; import { + ExperimentalFeatures, getExperimentalAllowedValues, isValidExperimentalValue, + parseExperimentalConfigValue, } from '../common/experimental_features'; import { UnderlyingLogClient } from './lib/detection_engine/rule_execution_log/types'; @@ -134,7 +136,23 @@ export const configSchema = schema.object({ prebuiltRulesFromSavedObjects: schema.boolean({ defaultValue: true }), }); -export const createConfig = (context: PluginInitializerContext) => - context.config.get>(); +export type ConfigSchema = TypeOf; -export type ConfigType = TypeOf; +export type ConfigType = ConfigSchema & { + kibanaIndex: string; + experimentalFeatures: ExperimentalFeatures; +}; + +export const createConfig = (context: PluginInitializerContext): ConfigType => { + const globalConfig = context.config.legacy.get(); + const pluginConfig = context.config.get>(); + + const kibanaIndex = globalConfig.kibana.index; + const experimentalFeatures = parseExperimentalConfigValue(pluginConfig.enableExperimental); + + return { + ...pluginConfig, + kibanaIndex, + experimentalFeatures, + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 5a47c8a616c00..caea18da75ae4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -27,13 +27,18 @@ import { import { ManifestManager } from './services/artifacts'; import { AppClientFactory } from '../client'; import { ConfigType } from '../config'; +import { IRequestContextFactory } from '../request_context_factory'; import { LicenseService } from '../../common/license'; -import { - ExperimentalFeatures, - parseExperimentalConfigValue, -} from '../../common/experimental_features'; +import { ExperimentalFeatures } from '../../common/experimental_features'; import { EndpointMetadataService } from './services/metadata'; -import { EndpointAppContentServicesNotStartedError } from './errors'; +import { + EndpointAppContentServicesNotSetUpError, + EndpointAppContentServicesNotStartedError, +} from './errors'; + +export interface EndpointAppContextServiceSetupContract { + securitySolutionRequestContextFactory: IRequestContextFactory; +} export type EndpointAppContextServiceStartContract = Partial< Pick< @@ -59,40 +64,29 @@ export type EndpointAppContextServiceStartContract = Partial< * of the plugin lifecycle. And stop during the stop phase, if needed. */ export class EndpointAppContextService { - private agentService: AgentService | undefined; - private manifestManager: ManifestManager | undefined; - private packagePolicyService: PackagePolicyServiceInterface | undefined; - private agentPolicyService: AgentPolicyServiceInterface | undefined; - private config: ConfigType | undefined; - private license: LicenseService | undefined; + private setupDependencies: EndpointAppContextServiceSetupContract | null = null; + private startDependencies: EndpointAppContextServiceStartContract | null = null; public security: SecurityPluginStart | undefined; - private cases: CasesPluginStartContract | undefined; - private endpointMetadataService: EndpointMetadataService | undefined; - private experimentalFeatures: ExperimentalFeatures | undefined; + + public setup(dependencies: EndpointAppContextServiceSetupContract) { + this.setupDependencies = dependencies; + } public start(dependencies: EndpointAppContextServiceStartContract) { - this.agentService = dependencies.agentService; - this.packagePolicyService = dependencies.packagePolicyService; - this.agentPolicyService = dependencies.agentPolicyService; - this.manifestManager = dependencies.manifestManager; - this.config = dependencies.config; - this.license = dependencies.licenseService; + if (!this.setupDependencies) { + throw new EndpointAppContentServicesNotSetUpError(); + } + + this.startDependencies = dependencies; this.security = dependencies.security; - this.cases = dependencies.cases; - this.endpointMetadataService = dependencies.endpointMetadataService; - this.experimentalFeatures = parseExperimentalConfigValue(this.config.enableExperimental); - if (this.manifestManager && dependencies.registerIngestCallback) { + if (dependencies.registerIngestCallback && dependencies.manifestManager) { dependencies.registerIngestCallback( 'packagePolicyCreate', getPackagePolicyCreateCallback( dependencies.logger, - this.manifestManager, - dependencies.appClientFactory, - dependencies.config.maxTimelineImportExportSize, - dependencies.config.prebuiltRulesFromFileSystem, - dependencies.config.prebuiltRulesFromSavedObjects, - dependencies.security, + dependencies.manifestManager, + this.setupDependencies.securitySolutionRequestContextFactory, dependencies.alerting, dependencies.licenseService, dependencies.exceptionListsClient @@ -106,7 +100,10 @@ export class EndpointAppContextService { dependencies.registerIngestCallback( 'postPackagePolicyDelete', - getPackagePolicyDeleteCallback(dependencies.exceptionListsClient, this.experimentalFeatures) + getPackagePolicyDeleteCallback( + dependencies.exceptionListsClient, + dependencies.config.experimentalFeatures + ) ); } } @@ -114,43 +111,43 @@ export class EndpointAppContextService { public stop() {} public getExperimentalFeatures(): Readonly | undefined { - return this.experimentalFeatures; + return this.startDependencies?.config.experimentalFeatures; } public getEndpointMetadataService(): EndpointMetadataService { - if (!this.endpointMetadataService) { + if (this.startDependencies == null) { throw new EndpointAppContentServicesNotStartedError(); } - return this.endpointMetadataService; + return this.startDependencies.endpointMetadataService; } public getAgentService(): AgentService | undefined { - return this.agentService; + return this.startDependencies?.agentService; } public getPackagePolicyService(): PackagePolicyServiceInterface | undefined { - return this.packagePolicyService; + return this.startDependencies?.packagePolicyService; } public getAgentPolicyService(): AgentPolicyServiceInterface | undefined { - return this.agentPolicyService; + return this.startDependencies?.agentPolicyService; } public getManifestManager(): ManifestManager | undefined { - return this.manifestManager; + return this.startDependencies?.manifestManager; } public getLicenseService(): LicenseService { - if (!this.license) { + if (this.startDependencies == null) { throw new EndpointAppContentServicesNotStartedError(); } - return this.license; + return this.startDependencies.licenseService; } public async getCasesClient(req: KibanaRequest): Promise { - if (!this.cases) { + if (this.startDependencies?.cases == null) { throw new EndpointAppContentServicesNotStartedError(); } - return this.cases.getCasesClientWithRequest(req); + return this.startDependencies.cases.getCasesClientWithRequest(req); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/errors.ts b/x-pack/plugins/security_solution/server/endpoint/errors.ts index fae15984d9c44..7260d6055b310 100644 --- a/x-pack/plugins/security_solution/server/endpoint/errors.ts +++ b/x-pack/plugins/security_solution/server/endpoint/errors.ts @@ -17,6 +17,12 @@ export class EndpointError extends Error { export class NotFoundError extends EndpointError {} +export class EndpointAppContentServicesNotSetUpError extends EndpointError { + constructor() { + super('EndpointAppContextService has not been set up (EndpointAppContextService.setup())'); + } +} + export class EndpointAppContentServicesNotStartedError extends EndpointError { constructor() { super('EndpointAppContextService has not been started (EndpointAppContextService.start())'); diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 39833b6e995d1..190770f3d860d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -22,6 +22,7 @@ import { AppClientFactory } from '../client'; import { createMockConfig } from '../lib/detection_engine/routes/__mocks__'; import { EndpointAppContextService, + EndpointAppContextServiceSetupContract, EndpointAppContextServiceStartContract, } from './endpoint_app_context_services'; import { ManifestManager } from './services/artifacts/manifest_manager/manifest_manager'; @@ -37,6 +38,7 @@ import { parseExperimentalConfigValue } from '../../common/experimental_features // a restricted path. // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { createCasesClientMock } from '../../../cases/server/client/mocks'; +import { requestContextFactoryMock } from '../request_context_factory.mock'; import { EndpointMetadataService } from './services/metadata'; /** @@ -69,13 +71,25 @@ export const createMockEndpointAppContextService = ( } as unknown as jest.Mocked; }; +/** + * Creates a mocked input contract for the `EndpointAppContextService#setup()` method + */ +export const createMockEndpointAppContextServiceSetupContract = + (): jest.Mocked => { + return { + securitySolutionRequestContextFactory: requestContextFactoryMock.create(), + }; + }; + /** * Creates a mocked input contract for the `EndpointAppContextService#start()` method */ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked => { - const factory = new AppClientFactory(); const config = createMockConfig(); + const factory = new AppClientFactory(); + factory.setup({ getSpaceId: () => 'mockSpace', config }); + const casesClientMock = createCasesClientMock(); const savedObjectsStart = savedObjectsServiceMock.createStartContract(); const agentService = createMockAgentService(); @@ -86,8 +100,6 @@ export const createMockEndpointAppContextServiceStartContract = agentPolicyService ); - factory.setup({ getSpaceId: () => 'mockSpace', config }); - return { agentService, agentPolicyService, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts index 5ce7962000788..c6df8c9183917 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts @@ -25,6 +25,7 @@ import { parseExperimentalConfigValue } from '../../../../common/experimental_fe import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { + createMockEndpointAppContextServiceSetupContract, createMockEndpointAppContextServiceStartContract, createRouteHandlerContext, } from '../../mocks'; @@ -130,6 +131,7 @@ describe('Action Log API', () => { const esClientMock = elasticsearchServiceMock.createScopedClusterClient(); const routerMock = httpServiceMock.createRouter(); endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start(createMockEndpointAppContextServiceStartContract()); registerActionAuditLogRoutes(routerMock, { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts index ee3bc5e1f21e3..a483a33ea4c8d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts @@ -18,6 +18,7 @@ import { parseExperimentalConfigValue } from '../../../../common/experimental_fe import { SecuritySolutionRequestHandlerContext } from '../../../types'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { + createMockEndpointAppContextServiceSetupContract, createMockEndpointAppContextServiceStartContract, createMockPackageService, createRouteHandlerContext, @@ -157,9 +158,12 @@ describe('Host Isolation', () => { keep_policies_up_to_date: false, }) ); + licenseEmitter = new Subject(); licenseService = new LicenseService(); licenseService.start(licenseEmitter); + + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...startContract, licenseService, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts index facd53643bc4f..2f8ba30936f25 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/status.test.ts @@ -21,6 +21,7 @@ import { parseExperimentalConfigValue } from '../../../../common/experimental_fe import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { + createMockEndpointAppContextServiceSetupContract, createMockEndpointAppContextServiceStartContract, createRouteHandlerContext, } from '../../mocks'; @@ -67,6 +68,7 @@ describe('Endpoint Action Status', () => { const esClientMock = elasticsearchServiceMock.createScopedClusterClient(); const routerMock = httpServiceMock.createRouter(); endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start(createMockEndpointAppContextServiceStartContract()); registerActionStatusRoutes(routerMock, { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 3e5050c05814a..7c2e5de928484 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -22,6 +22,7 @@ import { HostInfo, HostResultList, HostStatus } from '../../../../common/endpoin import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; import { registerEndpointRoutes } from './index'; import { + createMockEndpointAppContextServiceSetupContract, createMockEndpointAppContextServiceStartContract, createMockPackageService, createRouteHandlerContext, @@ -134,6 +135,7 @@ describe('test endpoint route', () => { keep_policies_up_to_date: false, }) ); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); mockAgentService = startContract.agentService!; @@ -394,6 +396,7 @@ describe('test endpoint route', () => { keep_policies_up_to_date: false, }) ); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); mockAgentService = startContract.agentService!; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts index 90cda7ceb05d4..f25171c6734c8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts @@ -7,6 +7,7 @@ import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { + createMockEndpointAppContextServiceSetupContract, createMockEndpointAppContextServiceStartContract, createRouteHandlerContext, } from '../../mocks'; @@ -45,6 +46,7 @@ describe('test policy response handler', () => { mockSavedObjectClient = savedObjectsClientMock.create(); mockResponse = httpServerMock.createResponseFactory(); endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start(createMockEndpointAppContextServiceStartContract()); }); @@ -161,6 +163,7 @@ describe('test policy response handler', () => { page: 1, perPage: 1, }; + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...createMockEndpointAppContextServiceStartContract(), ...{ agentService: mockAgentService }, diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 2f31f54143f74..71c093e0781b0 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; + import { httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; import { createNewPackagePolicyMock, deletePackagePolicyMock } from '../../../fleet/common/mocks'; import { @@ -18,7 +20,8 @@ import { getPackagePolicyUpdateCallback, } from './fleet_integration'; import { KibanaRequest } from 'kibana/server'; -import { createMockConfig, requestContextMock } from '../lib/detection_engine/routes/__mocks__'; +import { requestContextMock } from '../lib/detection_engine/routes/__mocks__'; +import { requestContextFactoryMock } from '../request_context_factory.mock'; import { EndpointAppContextServiceStartContract } from '../endpoint/endpoint_app_context_services'; import { createMockEndpointAppContextServiceStartContract } from '../endpoint/mocks'; import { licenseMock } from '../../../licensing/common/licensing.mock'; @@ -42,16 +45,12 @@ import { ExperimentalFeatures, } from '../../common/experimental_features'; import { DeletePackagePoliciesResponse } from '../../../fleet/common'; -import { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; describe('ingest_integration tests ', () => { let endpointAppContextMock: EndpointAppContextServiceStartContract; let req: KibanaRequest; let ctx: SecuritySolutionRequestHandlerContext; const exceptionListClient: ExceptionListClient = getExceptionListClientMock(); - const maxTimelineImportExportSize = createMockConfig().maxTimelineImportExportSize; - const prebuiltRulesFromFileSystem = createMockConfig().prebuiltRulesFromFileSystem; - const prebuiltRulesFromSavedObjects = createMockConfig().prebuiltRulesFromSavedObjects; let licenseEmitter: Subject; let licenseService: LicenseService; const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } }); @@ -88,11 +87,7 @@ describe('ingest_integration tests ', () => { const callback = getPackagePolicyCreateCallback( logger, manifestManager, - endpointAppContextMock.appClientFactory, - maxTimelineImportExportSize, - prebuiltRulesFromFileSystem, - prebuiltRulesFromSavedObjects, - endpointAppContextMock.security, + requestContextFactoryMock.create(), endpointAppContextMock.alerting, licenseService, exceptionListClient diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index 09810a6c88c3d..a53d5d43de524 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -8,7 +8,6 @@ import { KibanaRequest, Logger, RequestHandlerContext } from 'kibana/server'; import { ExceptionListClient } from '../../../lists/server'; import { PluginStartContract as AlertsStartContract } from '../../../alerting/server'; -import { SecurityPluginStart } from '../../../security/server'; import { PostPackagePolicyCreateCallback, PostPackagePolicyDeleteCallback, @@ -18,15 +17,15 @@ import { import { NewPackagePolicy, UpdatePackagePolicy } from '../../../fleet/common'; import { NewPolicyData, PolicyConfig } from '../../common/endpoint/types'; -import { ManifestManager } from '../endpoint/services'; -import { AppClientFactory } from '../client'; +import { ExperimentalFeatures } from '../../common/experimental_features'; import { LicenseService } from '../../common/license'; +import { ManifestManager } from '../endpoint/services'; +import { IRequestContextFactory } from '../request_context_factory'; import { installPrepackagedRules } from './handlers/install_prepackaged_rules'; import { createPolicyArtifactManifest } from './handlers/create_policy_artifact_manifest'; import { createDefaultPolicy } from './handlers/create_default_policy'; import { validatePolicyAgainstLicense } from './handlers/validate_policy_against_license'; import { removePolicyFromTrustedApps } from './handlers/remove_policy_from_trusted_apps'; -import { ExperimentalFeatures } from '../../common/experimental_features'; const isEndpointPackagePolicy = ( packagePolicy: T @@ -40,11 +39,7 @@ const isEndpointPackagePolicy = ( export const getPackagePolicyCreateCallback = ( logger: Logger, manifestManager: ManifestManager, - appClientFactory: AppClientFactory, - maxTimelineImportExportSize: number, - prebuiltRulesFromFileSystem: boolean, - prebuiltRulesFromSavedObjects: boolean, - securityStart: SecurityPluginStart, + securitySolutionRequestContextFactory: IRequestContextFactory, alerts: AlertsStartContract, licenseService: LicenseService, exceptionsClient: ExceptionListClient | undefined @@ -59,20 +54,23 @@ export const getPackagePolicyCreateCallback = ( return newPackagePolicy; } + // In this callback we are handling an HTTP request to the fleet plugin. Since we use + // code from the security_solution plugin to handle it (installPrepackagedRules), + // we need to build the context that is native to security_solution and pass it there. + const securitySolutionContext = await securitySolutionRequestContextFactory.create( + context, + request + ); + // perform these operations in parallel in order to help in not delaying the API response too much const [, manifestValue] = await Promise.all([ // Install Detection Engine prepackaged rules exceptionsClient && installPrepackagedRules({ logger, - appClientFactory, - context, + context: securitySolutionContext, request, - securityStart, alerts, - maxTimelineImportExportSize, - prebuiltRulesFromFileSystem, - prebuiltRulesFromSavedObjects, exceptionsClient, }), diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts index d8adf4ea6a1ca..01368ccb22c62 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts @@ -5,25 +5,18 @@ * 2.0. */ -import { KibanaRequest, Logger, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest, Logger } from 'kibana/server'; import { ExceptionListClient } from '../../../../lists/server'; import { PluginStartContract as AlertsStartContract } from '../../../../alerting/server'; -import { SecurityPluginStart } from '../../../../security/server'; -import { AppClientFactory } from '../../client'; import { createDetectionIndex } from '../../lib/detection_engine/routes/index/create_index_route'; import { createPrepackagedRules } from '../../lib/detection_engine/routes/rules/add_prepackaged_rules_route'; -import { buildFrameworkRequest } from '../../lib/timeline/utils/common'; +import { SecuritySolutionApiRequestHandlerContext } from '../../types'; export interface InstallPrepackagedRulesProps { logger: Logger; - appClientFactory: AppClientFactory; - context: RequestHandlerContext; + context: SecuritySolutionApiRequestHandlerContext; request: KibanaRequest; - securityStart: SecurityPluginStart; alerts: AlertsStartContract; - maxTimelineImportExportSize: number; - prebuiltRulesFromFileSystem: boolean; - prebuiltRulesFromSavedObjects: boolean; exceptionsClient: ExceptionListClient; } @@ -33,29 +26,14 @@ export interface InstallPrepackagedRulesProps { */ export const installPrepackagedRules = async ({ logger, - appClientFactory, context, request, - securityStart, alerts, - maxTimelineImportExportSize, - prebuiltRulesFromFileSystem, - prebuiltRulesFromSavedObjects, exceptionsClient, }: InstallPrepackagedRulesProps): Promise => { - // prep for detection rules creation - const appClient = appClientFactory.create(request); - - // This callback is called by fleet plugin. - // It doesn't have access to SecuritySolutionRequestHandlerContext in runtime. - // Muting the error to have green CI. - // @ts-expect-error - const frameworkRequest = await buildFrameworkRequest(context, securityStart, request); - // Create detection index & rules (if necessary). move past any failure, this is just a convenience try { - // @ts-expect-error - await createDetectionIndex(context, appClient); + await createDetectionIndex(context); } catch (err) { if (err.statusCode !== 409) { // 409 -> detection index already exists, which is fine @@ -68,14 +46,8 @@ export const installPrepackagedRules = async ({ // this checks to make sure index exists first, safe to try in case of failure above // may be able to recover from minor errors await createPrepackagedRules( - // @ts-expect-error context, - appClient, alerts.getRulesClientWithRequest(request), - frameworkRequest, - maxTimelineImportExportSize, - prebuiltRulesFromFileSystem, - prebuiltRulesFromSavedObjects, exceptionsClient ); } catch (err) { diff --git a/x-pack/plugins/security_solution/server/index.ts b/x-pack/plugins/security_solution/server/index.ts index 7e3da726f6ebe..0adcd25f5e246 100644 --- a/x-pack/plugins/security_solution/server/index.ts +++ b/x-pack/plugins/security_solution/server/index.ts @@ -7,7 +7,7 @@ import { PluginInitializerContext, PluginConfigDescriptor } from '../../../../src/core/server'; import { Plugin, PluginSetup, PluginStart } from './plugin'; -import { configSchema, ConfigType } from './config'; +import { configSchema, ConfigSchema, ConfigType } from './config'; import { SIGNALS_INDEX_KEY } from '../common/constants'; import { AppClient } from './types'; @@ -15,7 +15,7 @@ export const plugin = (context: PluginInitializerContext) => { return new Plugin(context); }; -export const config: PluginConfigDescriptor = { +export const config: PluginConfigDescriptor = { exposeToBrowser: { enableExperimental: true, }, @@ -47,5 +47,5 @@ export const config: PluginConfigDescriptor = { export { ConfigType, Plugin, PluginSetup, PluginStart }; export { AppClient }; -export type { AppRequestContext } from './types'; +export type { SecuritySolutionApiRequestHandlerContext } from './types'; export { EndpointError } from './endpoint/errors'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts index 8417115fb1896..cf0ceaff4ec4c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts @@ -5,34 +5,11 @@ * 2.0. */ -import { DEFAULT_SIGNALS_INDEX, SIGNALS_INDEX_KEY } from '../../../../../common/constants'; -import { requestContextMock } from './request_context'; -import { serverMock } from './server'; -import { requestMock } from './request'; -import { responseMock } from './response_factory'; -import { ConfigType } from '../../../../config'; -import { UnderlyingLogClient } from '../../rule_execution_log/types'; - -export { requestMock, requestContextMock, responseMock, serverMock }; - -export const createMockConfig = (): ConfigType => ({ - [SIGNALS_INDEX_KEY]: DEFAULT_SIGNALS_INDEX, - maxRuleImportExportSize: 10000, - maxRuleImportPayloadBytes: 10485760, - maxTimelineImportExportSize: 10000, - maxTimelineImportPayloadBytes: 10485760, - enableExperimental: [], - endpointResultListDefaultFirstPageIndex: 0, - endpointResultListDefaultPageSize: 10, - packagerTaskInterval: '60s', - alertMergeStrategy: 'missingFields', - alertIgnoreFields: [], - prebuiltRulesFromFileSystem: true, - prebuiltRulesFromSavedObjects: false, - ruleExecutionLog: { - underlyingClient: UnderlyingLogClient.savedObjects, - }, -}); +export { requestContextMock } from './request_context'; +export { requestMock } from './request'; +export { responseMock } from './response_factory'; +export { serverMock } from './server'; +export { configMock, createMockConfig } from '../../../../config.mock'; export const mockGetCurrentUser = { user: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index 6039ad6ab6126..2f5f8ac846954 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -5,66 +5,103 @@ * 2.0. */ -import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; -import { - coreMock, - elasticsearchServiceMock, - savedObjectsClientMock, -} from '../../../../../../../../src/core/server/mocks'; +import type { MockedKeys } from '@kbn/utility-types/jest'; +import { coreMock } from 'src/core/server/mocks'; + +import { ActionsApiRequestHandlerContext } from '../../../../../../actions/server'; +import { AlertingApiRequestHandlerContext } from '../../../../../../alerting/server'; import { rulesClientMock } from '../../../../../../alerting/server/mocks'; import { licensingMock } from '../../../../../../licensing/server/mocks'; +import { listMock } from '../../../../../../lists/server/mocks'; +import { ruleRegistryMocks } from '../../../../../../rule_registry/server/mocks'; + import { siemMock } from '../../../../mocks'; +import { createMockConfig } from '../../../../config.mock'; import { ruleExecutionLogClientMock } from '../../rule_execution_log/__mocks__/rule_execution_log_client'; +import { requestMock } from './request'; +import { internalFrameworkRequest } from '../../../framework'; -const createMockClients = () => ({ - rulesClient: rulesClientMock.create(), - licensing: { license: licensingMock.createLicenseMock() }, - clusterClient: elasticsearchServiceMock.createScopedClusterClient(), - savedObjectsClient: savedObjectsClientMock.create(), - ruleExecutionLogClient: ruleExecutionLogClientMock.create(), - appClient: siemMock.createClient(), -}); - -/** - * Adds mocking to the interface so we don't have to cast everywhere - */ -type SecuritySolutionRequestHandlerContextMock = SecuritySolutionRequestHandlerContext & { - core: { - elasticsearch: { - client: { - asCurrentUser: { - updateByQuery: jest.Mock; - search: jest.Mock; - security: { - hasPrivileges: jest.Mock; - }; - }; - }; - }; +import type { + SecuritySolutionApiRequestHandlerContext, + SecuritySolutionRequestHandlerContext, +} from '../../../../types'; + +const createMockClients = () => { + const core = coreMock.createRequestHandlerContext(); + const license = licensingMock.createLicenseMock(); + + return { + core, + clusterClient: core.elasticsearch.client, + savedObjectsClient: core.savedObjects.client, + + licensing: { + ...licensingMock.createRequestHandlerContext({ license }), + license, + }, + lists: { + listClient: listMock.getListClient(), + exceptionListClient: listMock.getExceptionListClient(core.savedObjects.client), + }, + rulesClient: rulesClientMock.create(), + ruleDataService: ruleRegistryMocks.createRuleDataService(), + + config: createMockConfig(), + appClient: siemMock.createClient(), + ruleExecutionLogClient: ruleExecutionLogClientMock.create(), }; }; +type MockClients = ReturnType; + +type SecuritySolutionRequestHandlerContextMock = + MockedKeys & { + core: MockClients['core']; + }; + const createRequestContextMock = ( - clients: ReturnType = createMockClients() + clients: MockClients = createMockClients() ): SecuritySolutionRequestHandlerContextMock => { - const coreContext = coreMock.createRequestHandlerContext(); return { - alerting: { getRulesClient: jest.fn(() => clients.rulesClient) }, - core: { - ...coreContext, - elasticsearch: { - ...coreContext.elasticsearch, - client: clients.clusterClient, - }, - savedObjects: { client: clients.savedObjectsClient }, - }, + core: clients.core, + securitySolution: createSecuritySolutionRequestContextMock(clients), + actions: {} as unknown as jest.Mocked, + alerting: { + getRulesClient: jest.fn(() => clients.rulesClient), + } as unknown as jest.Mocked, licensing: clients.licensing, - securitySolution: { - getAppClient: jest.fn(() => clients.appClient), - getExecutionLogClient: jest.fn(() => clients.ruleExecutionLogClient), - getSpaceId: jest.fn(() => 'default'), + lists: { + getListClient: jest.fn(() => clients.lists.listClient), + getExceptionListClient: jest.fn(() => clients.lists.exceptionListClient), }, - } as unknown as SecuritySolutionRequestHandlerContextMock; + }; +}; + +const createSecuritySolutionRequestContextMock = ( + clients: MockClients +): jest.Mocked => { + const core = clients.core; + const kibanaRequest = requestMock.create(); + + return { + core, + getConfig: jest.fn(() => clients.config), + getFrameworkRequest: jest.fn(() => { + return { + ...kibanaRequest.body, + [internalFrameworkRequest]: kibanaRequest, + context: { core }, + user: { + username: 'mockUser', + }, + }; + }), + getAppClient: jest.fn(() => clients.appClient), + getSpaceId: jest.fn(() => 'default'), + getRuleDataService: jest.fn(() => clients.ruleDataService), + getExecutionLogClient: jest.fn(() => clients.ruleExecutionLogClient), + getExceptionListClient: jest.fn(() => clients.lists.exceptionListClient), + }; }; const createTools = () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 9d1cd3cbca3fb..1520b4da82d8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -5,7 +5,9 @@ * 2.0. */ -import { SavedObjectsFindResponse, SavedObjectsFindResult } from 'kibana/server'; +import type { estypes } from '@elastic/elasticsearch'; +import { SavedObjectsFindResponse, SavedObjectsFindResult } from 'src/core/server'; + import { ActionResult } from '../../../../../../actions/server'; import { SignalSearchResponse } from '../../signals/types'; import { @@ -562,6 +564,28 @@ export const getFindBulkResultStatus = (): FindBulkExecutionLogResponse => ({ ], }); +export const getBasicEmptySearchResponse = (): estypes.SearchResponse => ({ + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { + hits: [], + total: { relation: 'eq', value: 0 }, + max_score: 0, + }, +}); + +export const getBasicNoShardsSearchResponse = (): estypes.SearchResponse => ({ + took: 1, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + hits: { + hits: [], + total: { relation: 'eq', value: 0 }, + max_score: 0, + }, +}); + export const getEmptySignalsResponse = (): SignalSearchResponse => ({ took: 1, timed_out: false, @@ -588,7 +612,7 @@ export const getEmptyEqlSequencesResponse = (): EqlSearchResponse => ({ timed_out: false, }); -export const getSuccessfulSignalUpdateResponse = () => ({ +export const getSuccessfulSignalUpdateResponse = (): estypes.UpdateByQueryResponse => ({ took: 18, timed_out: false, total: 1, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts index 6ec23c32e4976..b011fd3fcd247 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts @@ -16,9 +16,8 @@ import { createBootstrapIndex, } from '@kbn/securitysolution-es-utils'; import type { - AppClient, + SecuritySolutionApiRequestHandlerContext, SecuritySolutionPluginRouter, - SecuritySolutionRequestHandlerContext, } from '../../../../types'; import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; @@ -32,15 +31,8 @@ import signalsPolicy from './signals_policy.json'; import { templateNeedsUpdate } from './check_template_version'; import { getIndexVersion } from './get_index_version'; import { isOutdated } from '../../migrations/helpers'; -import { RuleDataPluginService } from '../../../../../../rule_registry/server'; -import { ConfigType } from '../../../../config'; -import { parseExperimentalConfigValue } from '../../../../../common/experimental_features'; -export const createIndexRoute = ( - router: SecuritySolutionPluginRouter, - ruleDataService: RuleDataPluginService, - config: ConfigType -) => { +export const createIndexRoute = (router: SecuritySolutionPluginRouter) => { router.post( { path: DETECTION_ENGINE_INDEX_URL, @@ -51,14 +43,13 @@ export const createIndexRoute = ( }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const { ruleRegistryEnabled } = parseExperimentalConfigValue(config.enableExperimental); try { const siemClient = context.securitySolution?.getAppClient(); if (!siemClient) { return siemResponse.error({ statusCode: 404 }); } - await createDetectionIndex(context, siemClient, ruleDataService, ruleRegistryEnabled); + await createDetectionIndex(context.securitySolution); return response.ok({ body: { acknowledged: true } }); } catch (err) { const error = transformError(err); @@ -71,30 +62,18 @@ export const createIndexRoute = ( ); }; -class CreateIndexError extends Error { - public readonly statusCode: number; - constructor(message: string, statusCode: number) { - super(message); - this.statusCode = statusCode; - } -} - export const createDetectionIndex = async ( - context: SecuritySolutionRequestHandlerContext, - siemClient: AppClient, - ruleDataService: RuleDataPluginService, - ruleRegistryEnabled: boolean + context: SecuritySolutionApiRequestHandlerContext ): Promise => { + const config = context.getConfig(); const esClient = context.core.elasticsearch.client.asCurrentUser; - const spaceId = siemClient.getSpaceId(); - - if (!siemClient) { - throw new CreateIndexError('', 404); - } - + const siemClient = context.getAppClient(); + const spaceId = context.getSpaceId(); const index = siemClient.getSignalsIndex(); const indexExists = await getIndexExists(esClient, index); + const { ruleRegistryEnabled } = config.experimentalFeatures; + // If using the rule registry implementation, we don't want to create new .siem-signals indices - // only create/update resources if there are existing indices if (ruleRegistryEnabled && !indexExists) { @@ -106,7 +85,10 @@ export const createDetectionIndex = async ( if (!policyExists) { await setPolicy(esClient, index, signalsPolicy); } + + const ruleDataService = context.getRuleDataService(); const aadIndexAliasName = ruleDataService.getResourceName(`security.alerts-${spaceId}`); + if (await templateNeedsUpdate({ alias: index, esClient })) { await esClient.indices.putIndexTemplate({ name: index, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts index 7ffa45e2bf7ee..2c2c65f5f78f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts @@ -19,9 +19,9 @@ describe('read_privileges route', () => { server = serverMock.create(); ({ context } = requestContextMock.createTools()); - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockResolvedValue({ - body: getMockPrivilegesResult(), - }); + context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockResolvedValue( + elasticsearchClientMock.createSuccessTransportRequestPromise(getMockPrivilegesResult()) + ); readPrivilegesRoute(server.router, true); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index 26e09d69d3a45..29ceb74e9ba0c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -10,14 +10,13 @@ import { addPrepackagedRulesRequest, getFindResultWithSingleHit, getAlertMock, + getBasicEmptySearchResponse, + getBasicNoShardsSearchResponse, } from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, createMockConfig, mockGetCurrentUser } from '../__mocks__'; +import { configMock, requestContextMock, serverMock } from '../__mocks__'; import { AddPrepackagedRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { SecurityPluginSetup } from '../../../../../../security/server'; import { addPrepackedRulesRoute, createPrepackagedRules } from './add_prepackaged_rules_route'; import { listMock } from '../../../../../../lists/server/mocks'; -import { siemMock } from '../../../../mocks'; -import { FrameworkRequest } from '../../../framework'; import { ExceptionListClient } from '../../../../../../lists/server'; import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -76,25 +75,21 @@ describe.each([ ['Legacy', false], ['RAC', true], ])('add_prepackaged_rules_route - %s', (_, isRuleRegistryEnabled) => { - const siemMockClient = siemMock.createClient(); let server: ReturnType; let { clients, context } = requestContextMock.createTools(); - let securitySetup: SecurityPluginSetup; let mockExceptionsClient: ExceptionListClient; const testif = isRuleRegistryEnabled ? test.skip : test; + const defaultConfig = context.securitySolution.getConfig(); beforeEach(() => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - securitySetup = { - authc: { - getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), - }, - authz: {}, - } as unknown as SecurityPluginSetup; - mockExceptionsClient = listMock.getExceptionListClient(); + context.securitySolution.getConfig.mockImplementation(() => + configMock.withRuleRegistryEnabled(defaultConfig, isRuleRegistryEnabled) + ); + clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); clients.rulesClient.update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) @@ -110,9 +105,9 @@ describe.each([ }); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - addPrepackedRulesRoute(server.router, createMockConfig(), securitySetup, isRuleRegistryEnabled); + addPrepackedRulesRoute(server.router); }); describe('status codes', () => { @@ -138,7 +133,9 @@ describe.each([ test('it returns a 400 if the index does not exist when rule registry not enabled', async () => { const request = addPrepackagedRulesRequest(); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise( + getBasicNoShardsSearchResponse() + ) ); const response = await server.inject(request, context); @@ -302,39 +299,22 @@ describe.each([ describe('createPrepackagedRules', () => { test('uses exception lists client from context when available', async () => { - context.lists = { - getExceptionListClient: jest.fn(), - getListClient: jest.fn(), - }; - const config = createMockConfig(); - await createPrepackagedRules( - context, - siemMockClient, + context.securitySolution, clients.rulesClient, - {} as FrameworkRequest, - 1200, - config.prebuiltRulesFromFileSystem, - config.prebuiltRulesFromSavedObjects, mockExceptionsClient ); expect(mockExceptionsClient.createEndpointList).not.toHaveBeenCalled(); - expect(context.lists?.getExceptionListClient).toHaveBeenCalled(); + expect(context.securitySolution.getExceptionListClient).toHaveBeenCalled(); }); test('uses passed in exceptions list client when lists client not available in context', async () => { - const { lists, ...myContext } = context; - const config = createMockConfig(); + context.securitySolution.getExceptionListClient.mockImplementation(() => null); await createPrepackagedRules( - myContext, - siemMockClient, + context.securitySolution, clients.rulesClient, - {} as FrameworkRequest, - 1200, - config.prebuiltRulesFromFileSystem, - config.prebuiltRulesFromSavedObjects, mockExceptionsClient ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index ddf4e956beac4..50766af669ce7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -9,9 +9,8 @@ import moment from 'moment'; import { transformError, getIndexExists } from '@kbn/securitysolution-es-utils'; import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { - AppClient, + SecuritySolutionApiRequestHandlerContext, SecuritySolutionPluginRouter, - SecuritySolutionRequestHandlerContext, } from '../../../../types'; import { @@ -21,10 +20,6 @@ import { import { importTimelineResultSchema } from '../../../../../common/types/timeline'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; -import { ConfigType } from '../../../../config'; -import { SetupPlugins } from '../../../../plugin'; -import { buildFrameworkRequest } from '../../../timeline/utils/common'; - import { getLatestPrepackagedRules } from '../../rules/get_prepackaged_rules'; import { installPrepackagedRules } from '../../rules/install_prepacked_rules'; import { updatePrepackagedRules } from '../../rules/update_prepacked_rules'; @@ -35,17 +30,11 @@ import { ruleAssetSavedObjectsClientFactory } from '../../rules/rule_asset/rule_ import { buildSiemResponse } from '../utils'; import { RulesClient } from '../../../../../../alerting/server'; -import { FrameworkRequest } from '../../../framework'; import { ExceptionListClient } from '../../../../../../lists/server'; import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; -export const addPrepackedRulesRoute = ( - router: SecuritySolutionPluginRouter, - config: ConfigType, - security: SetupPlugins['security'], - isRuleRegistryEnabled: boolean -) => { +export const addPrepackedRulesRoute = (router: SecuritySolutionPluginRouter) => { router.put( { path: DETECTION_ENGINE_PREPACKAGED_URL, @@ -63,7 +52,6 @@ export const addPrepackedRulesRoute = ( }, async (context, _, response) => { const siemResponse = buildSiemResponse(response); - const frameworkRequest = await buildFrameworkRequest(context, security, _); try { const rulesClient = context.alerting?.getRulesClient(); @@ -74,15 +62,9 @@ export const addPrepackedRulesRoute = ( } const validated = await createPrepackagedRules( - context, - siemClient, + context.securitySolution, rulesClient, - frameworkRequest, - config.maxTimelineImportExportSize, - config.prebuiltRulesFromFileSystem, - config.prebuiltRulesFromSavedObjects, - undefined, - isRuleRegistryEnabled + undefined ); return response.ok({ body: validated ?? {} }); } catch (err) { @@ -105,22 +87,26 @@ class PrepackagedRulesError extends Error { } export const createPrepackagedRules = async ( - context: SecuritySolutionRequestHandlerContext, - siemClient: AppClient, + context: SecuritySolutionApiRequestHandlerContext, rulesClient: RulesClient, - frameworkRequest: FrameworkRequest, - maxTimelineImportExportSize: ConfigType['maxTimelineImportExportSize'], - prebuiltRulesFromFileSystem: ConfigType['prebuiltRulesFromFileSystem'], - prebuiltRulesFromSavedObjects: ConfigType['prebuiltRulesFromSavedObjects'], - exceptionsClient?: ExceptionListClient, - isRuleRegistryEnabled?: boolean | undefined + exceptionsClient?: ExceptionListClient ): Promise => { + const config = context.getConfig(); + const frameworkRequest = context.getFrameworkRequest(); const esClient = context.core.elasticsearch.client; const savedObjectsClient = context.core.savedObjects.client; - const exceptionsListClient = - context.lists != null ? context.lists.getExceptionListClient() : exceptionsClient; + const siemClient = context.getAppClient(); + const exceptionsListClient = context.getExceptionListClient() ?? exceptionsClient; const ruleAssetsClient = ruleAssetSavedObjectsClientFactory(savedObjectsClient); - const ruleStatusClient = context.securitySolution.getExecutionLogClient(); + const ruleStatusClient = context.getExecutionLogClient(); + + const { + maxTimelineImportExportSize, + prebuiltRulesFromFileSystem, + prebuiltRulesFromSavedObjects, + experimentalFeatures: { ruleRegistryEnabled }, + } = config; + if (!siemClient || !rulesClient) { throw new PrepackagedRulesError('', 404); } @@ -137,12 +123,12 @@ export const createPrepackagedRules = async ( ); const prepackagedRules = await getExistingPrepackagedRules({ rulesClient, - isRuleRegistryEnabled: isRuleRegistryEnabled ?? false, + isRuleRegistryEnabled: ruleRegistryEnabled, }); const rulesToInstall = getRulesToInstall(latestPrepackagedRules, prepackagedRules); const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, prepackagedRules); const signalsIndex = siemClient.getSignalsIndex(); - if (!isRuleRegistryEnabled && (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0)) { + if (!ruleRegistryEnabled && (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0)) { const signalsIndexExists = await getIndexExists(esClient.asCurrentUser, signalsIndex); if (!signalsIndexExists) { throw new PrepackagedRulesError( @@ -153,12 +139,7 @@ export const createPrepackagedRules = async ( } await Promise.all( - installPrepackagedRules( - rulesClient, - rulesToInstall, - signalsIndex, - isRuleRegistryEnabled ?? false - ) + installPrepackagedRules(rulesClient, rulesToInstall, signalsIndex, ruleRegistryEnabled) ); const timeline = await installPrepackagedTimelines( maxTimelineImportExportSize, @@ -172,11 +153,11 @@ export const createPrepackagedRules = async ( await updatePrepackagedRules( rulesClient, savedObjectsClient, - context.securitySolution.getSpaceId(), + context.getSpaceId(), ruleStatusClient, rulesToUpdate, signalsIndex, - isRuleRegistryEnabled ?? false + ruleRegistryEnabled ); const prepackagedRulesOutput: PrePackagedRulesAndTimelinesSchema = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 6f721bb2bb9c5..6dc303d5a266b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -14,6 +14,8 @@ import { getEmptyFindResult, getAlertMock, createBulkMlRuleRequest, + getBasicEmptySearchResponse, + getBasicNoShardsSearchResponse, } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { createRulesBulkRoute } from './create_rules_bulk_route'; @@ -43,7 +45,7 @@ describe.each([ ); // successful creation context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); createRulesBulkRoute(server.router, ml, isRuleRegistryEnabled); }); @@ -93,7 +95,9 @@ describe.each([ test('returns an error object if the index does not exist when rule registry not enabled', async () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise( + getBasicNoShardsSearchResponse() + ) ); const response = await server.inject(getReadBulkRequest(), context); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 59fe5c0ff68a1..010c4b27507bb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -13,6 +13,8 @@ import { getRuleExecutionStatuses, getFindResultWithSingleHit, createMlRuleRequest, + getBasicEmptySearchResponse, + getBasicNoShardsSearchResponse, } from '../__mocks__/request_responses'; import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -44,7 +46,7 @@ describe.each([ clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); // needed to transform: ; context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); createRulesRoute(server.router, ml, isRuleRegistryEnabled); }); @@ -103,7 +105,9 @@ describe.each([ describe('unhappy paths', () => { test('it returns a 400 if the index does not exist when rule registry not enabled', async () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise( + getBasicNoShardsSearchResponse() + ) ); const response = await server.inject(getCreateRequest(), context); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts index aa301bcc0335e..23779afdc5410 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts @@ -12,6 +12,8 @@ import { getEmptyFindResult, getAlertMock, getFindResultWithSingleHit, + getBasicEmptySearchResponse, + getBasicNoShardsSearchResponse, } from '../__mocks__/request_responses'; import { createMockConfig, requestContextMock, serverMock, requestMock } from '../__mocks__'; import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; @@ -52,7 +54,7 @@ describe.each([ getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); importRulesRoute(server.router, config, ml, isRuleRegistryEnabled); }); @@ -133,7 +135,9 @@ describe.each([ test('returns an error if the index does not exist when rule registry not enabled', async () => { clients.appClient.getSignalsIndex.mockReturnValue('mockSignalsIndex'); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } }) + elasticsearchClientMock.createSuccessTransportRequestPromise( + getBasicNoShardsSearchResponse() + ) ); const response = await server.inject(request, context); expect(response.status).toEqual(isRuleRegistryEnabled ? 200 : 400); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts index f6abfc9ebe3d1..07c3bc37e7d72 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -26,11 +26,13 @@ describe('set signal status', () => { beforeEach(() => { server = serverMock.create(); ({ context } = requestContextMock.createTools()); - context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( + + context.core.elasticsearch.client.asCurrentUser.updateByQuery.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise( getSuccessfulSignalUpdateResponse() ) ); + setSignalsStatusRoute(server.router); }); diff --git a/x-pack/plugins/security_solution/server/lib/framework/types.ts b/x-pack/plugins/security_solution/server/lib/framework/types.ts index eceff4b35f74f..3ecd2adf00242 100644 --- a/x-pack/plugins/security_solution/server/lib/framework/types.ts +++ b/x-pack/plugins/security_solution/server/lib/framework/types.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { KibanaRequest } from '../../../../../../src/core/server'; +import { KibanaRequest, RequestHandlerContext } from '../../../../../../src/core/server'; import { AuthenticatedUser } from '../../../../security/common/model'; -import type { SecuritySolutionRequestHandlerContext } from '../../types'; export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); export interface FrameworkRequest extends Pick { [internalFrameworkRequest]: KibanaRequest; - context: SecuritySolutionRequestHandlerContext; + context: RequestHandlerContext; user: AuthenticatedUser | null; } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts index 05f3b5373a8de..8bf5213d6a47f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts @@ -32,17 +32,12 @@ const notes = [ const existingNoteIds = undefined; const isImmutable = true; -jest.mock('moment', () => { - const mockMoment = { - toISOString: jest - .fn() - .mockReturnValueOnce('2020-11-03T11:37:31.655Z') - .mockReturnValue('2020-11-04T11:37:31.655Z'), - subtract: jest.fn(), - }; - mockMoment.subtract.mockReturnValue(mockMoment); - return jest.fn().mockReturnValue(mockMoment); -}); +// System under test uses moment.js under the hood, so we need to mock time. +// Mocking moment via jest.mock('moment') breaks imports of moment in other files. +// Instead, we simply mock Date.now() via jest API and moment starts using it. +// This affects all the tests in this file and doesn't affect tests in other files. +// https://jestjs.io/docs/timer-mocks +jest.useFakeTimers('modern').setSystemTime(new Date('2020-11-04T11:37:31.655Z')); jest.mock('../../../saved_object/timelines', () => ({ persistTimeline: jest.fn().mockResolvedValue({ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts index be086732ddcd0..91f8e4153a63b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts @@ -12,15 +12,14 @@ import { Readable } from 'stream'; import { createListStream } from '@kbn/utils'; import { schema } from '@kbn/config-schema'; -import { KibanaRequest } from 'src/core/server'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { SetupPlugins, StartPlugins } from '../../../plugin'; -import type { SecuritySolutionRequestHandlerContext } from '../../../types'; import { FrameworkRequest } from '../../framework'; export const buildFrameworkRequest = async ( - context: SecuritySolutionRequestHandlerContext, + context: RequestHandlerContext, security: StartPlugins['security'] | SetupPlugins['security'] | undefined, request: KibanaRequest ): Promise => { @@ -30,7 +29,7 @@ export const buildFrameworkRequest = async ( return set( 'user', user, - set( + set( 'context.core.savedObjects.client', savedObjectsClient, request diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0f908d7db8e05..f2aa4927a7688 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -9,45 +9,15 @@ import { Observable } from 'rxjs'; import LRU from 'lru-cache'; import { estypes } from '@elastic/elasticsearch'; -import { - CoreSetup, - CoreStart, - Logger, - Plugin as IPlugin, - PluginInitializerContext, - SavedObjectsClient, -} from '../../../../src/core/server'; -import { - PluginSetup as DataPluginSetup, - PluginStart as DataPluginStart, -} from '../../../../src/plugins/data/server'; -import { - UsageCollectionSetup, - UsageCounter, -} from '../../../../src/plugins/usage_collection/server'; -import { - PluginSetupContract as AlertingSetup, - PluginStartContract as AlertPluginStartContract, -} from '../../alerting/server'; -import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; +import { Logger, SavedObjectsClient } from '../../../../src/core/server'; +import { UsageCounter } from '../../../../src/plugins/usage_collection/server'; -import { PluginStartContract as CasesPluginStartContract } from '../../cases/server'; import { ECS_COMPONENT_TEMPLATE_NAME } from '../../rule_registry/common/assets'; -import { SecurityPluginSetup as SecuritySetup, SecurityPluginStart } from '../../security/server'; -import { - IRuleDataClient, - RuleRegistryPluginSetupContract, - RuleRegistryPluginStartContract, - Dataset, -} from '../../rule_registry/server'; -import { PluginSetupContract as FeaturesSetup } from '../../features/server'; -import { MlPluginSetup as MlSetup } from '../../ml/server'; +import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; +import { IRuleDataClient, Dataset } from '../../rule_registry/server'; import { ListPluginSetup } from '../../lists/server'; -import { EncryptedSavedObjectsPluginSetup as EncryptedSavedObjectsSetup } from '../../encrypted_saved_objects/server'; -import { SpacesPluginSetup as SpacesSetup } from '../../spaces/server'; -import { ILicense, LicensingPluginStart } from '../../licensing/server'; -import { FleetStartContract } from '../../fleet/server'; -import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; +import { ILicense } from '../../licensing/server'; + import { createEqlAlertType, createIndicatorMatchAlertType, @@ -70,7 +40,6 @@ import { SIGNALS_ID, LEGACY_NOTIFICATIONS_ID, QUERY_RULE_TYPE_ID, - DEFAULT_SPACE_ID, INDICATOR_RULE_TYPE_ID, ML_RULE_TYPE_ID, EQL_RULE_TYPE_ID, @@ -89,18 +58,13 @@ import { registerTrustedAppsRoutes } from './endpoint/routes/trusted_apps'; import { securitySolutionSearchStrategyProvider } from './search_strategy/security_solution'; import { TelemetryEventsSender } from './lib/telemetry/sender'; import { TelemetryReceiver } from './lib/telemetry/receiver'; -import { - TelemetryPluginStart, - TelemetryPluginSetup, -} from '../../../../src/plugins/telemetry/server'; import { licenseService } from './lib/license'; import { PolicyWatcher } from './endpoint/lib/policy/license_watch'; -import { parseExperimentalConfigValue } from '../common/experimental_features'; import { migrateArtifactsToFleet } from './endpoint/lib/artifacts/migrate_artifacts_to_fleet'; import aadFieldConversion from './lib/detection_engine/routes/index/signal_aad_mapping.json'; import { alertsFieldMap } from './lib/detection_engine/rule_types/field_maps/alerts'; import { rulesFieldMap } from './lib/detection_engine/rule_types/field_maps/rules'; -import { RuleExecutionLogClient } from './lib/detection_engine/rule_execution_log/rule_execution_log_client'; +import { registerEventLogProvider } from './lib/detection_engine/rule_execution_log/event_log_adapter/register_event_log_provider'; import { getKibanaPrivilegesFeaturePrivileges, getCasesKibanaFeature } from './features'; import { EndpointMetadataService } from './endpoint/services/metadata'; import { CreateRuleOptions } from './lib/detection_engine/rule_types/types'; @@ -109,50 +73,28 @@ import { legacyRulesNotificationAlertType } from './lib/detection_engine/notific // eslint-disable-next-line no-restricted-imports import { legacyIsNotificationAlertExecutor } from './lib/detection_engine/notifications/legacy_types'; import { createSecurityRuleTypeWrapper } from './lib/detection_engine/rule_types/create_security_rule_type_wrapper'; -import { IEventLogClientService, IEventLogService } from '../../event_log/server'; -import { registerEventLogProvider } from './lib/detection_engine/rule_execution_log/event_log_adapter/register_event_log_provider'; -export interface SetupPlugins { - alerting: AlertingSetup; - data: DataPluginSetup; - encryptedSavedObjects?: EncryptedSavedObjectsSetup; - eventLog: IEventLogService; - features: FeaturesSetup; - lists?: ListPluginSetup; - ml?: MlSetup; - ruleRegistry: RuleRegistryPluginSetupContract; - security?: SecuritySetup; - spaces?: SpacesSetup; - taskManager?: TaskManagerSetupContract; - telemetry?: TelemetryPluginSetup; - usageCollection?: UsageCollectionSetup; -} - -export interface StartPlugins { - alerting: AlertPluginStartContract; - cases?: CasesPluginStartContract; - data: DataPluginStart; - eventLog: IEventLogClientService; - fleet?: FleetStartContract; - licensing: LicensingPluginStart; - ruleRegistry: RuleRegistryPluginStartContract; - security: SecurityPluginStart; - taskManager?: TaskManagerStartContract; - telemetry?: TelemetryPluginStart; -} +import { RequestContextFactory } from './request_context_factory'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface PluginSetup {} +import type { + ISecuritySolutionPlugin, + SecuritySolutionPluginSetupDependencies, + SecuritySolutionPluginStartDependencies, + SecuritySolutionPluginCoreSetupDependencies, + SecuritySolutionPluginCoreStartDependencies, + SecuritySolutionPluginSetup, + SecuritySolutionPluginStart, + PluginInitializerContext, +} from './plugin_contract'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface PluginStart {} +export { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract'; -export class Plugin implements IPlugin { - private readonly logger: Logger; +export class Plugin implements ISecuritySolutionPlugin { + private readonly pluginContext: PluginInitializerContext; private readonly config: ConfigType; - private context: PluginInitializerContext; - private appClientFactory: AppClientFactory; - private setupPlugins?: SetupPlugins; + private readonly logger: Logger; + private readonly appClientFactory: AppClientFactory; + private readonly endpointAppContextService = new EndpointAppContextService(); private readonly telemetryReceiver: TelemetryReceiver; private readonly telemetryEventsSender: TelemetryEventsSender; @@ -167,10 +109,11 @@ export class Plugin implements IPlugin({ max: 3, maxAge: 1000 * 60 * 5 }); this.telemetryEventsSender = new TelemetryEventsSender(this.logger); @@ -179,26 +122,47 @@ export class Plugin implements IPlugin, plugins: SetupPlugins) { + public setup( + core: SecuritySolutionPluginCoreSetupDependencies, + plugins: SecuritySolutionPluginSetupDependencies + ): SecuritySolutionPluginSetup { this.logger.debug('plugin setup'); - this.setupPlugins = plugins; - const config = this.config; - const globalConfig = this.context.config.legacy.get(); + const { pluginContext, config, logger, appClientFactory } = this; + const experimentalFeatures = config.experimentalFeatures; + + appClientFactory.setup({ + getSpaceId: plugins.spaces?.spacesService?.getSpaceId, + config, + }); - const experimentalFeatures = parseExperimentalConfigValue(config.enableExperimental); initSavedObjects(core.savedObjects); initUiSettings(core.uiSettings, experimentalFeatures); + + const eventLogService = plugins.eventLog; + registerEventLogProvider(eventLogService); + + const requestContextFactory = new RequestContextFactory({ config, core, plugins }); + const router = core.http.createRouter(); + core.http.registerRouteHandlerContext( + APP_ID, + (context, request) => requestContextFactory.create(context, request) + ); + const endpointContext: EndpointAppContext = { - logFactory: this.context.logger, + logFactory: pluginContext.logger, service: this.endpointAppContextService, config: (): Promise => Promise.resolve(config), experimentalFeatures, }; + this.endpointAppContextService.setup({ + securitySolutionRequestContextFactory: requestContextFactory, + }); + initUsageCollectors({ core, - kibanaIndex: globalConfig.kibana.index, + kibanaIndex: config.kibanaIndex, signalsIndex: config.signalsIndex, ml: plugins.ml, usageCollection: plugins.usageCollection, @@ -206,29 +170,6 @@ export class Plugin implements IPlugin(); - core.http.registerRouteHandlerContext( - APP_ID, - (context, request, response) => ({ - getAppClient: () => this.appClientFactory.create(request), - getSpaceId: () => plugins.spaces?.spacesService?.getSpaceId(request) || DEFAULT_SPACE_ID, - getExecutionLogClient: () => - new RuleExecutionLogClient({ - savedObjectsClient: context.core.savedObjects.client, - eventLogService, - underlyingClient: config.ruleExecutionLog.underlyingClient, - }), - }) - ); - - this.appClientFactory.setup({ - getSpaceId: plugins.spaces?.spacesService?.getSpaceId, - config, - }); - // TODO: Once we are past experimental phase this check can be removed along with legacy registration of rules const isRuleRegistryEnabled = experimentalFeatures.ruleRegistryEnabled; @@ -265,9 +206,9 @@ export class Plugin implements IPlugin; + +export type SecuritySolutionPluginCoreStartDependencies = CoreStart; + +export type ISecuritySolutionPlugin = Plugin< + SecuritySolutionPluginSetup, + SecuritySolutionPluginStart, + SecuritySolutionPluginSetupDependencies, + SecuritySolutionPluginStartDependencies +>; + +export type { + PluginInitializerContext, + // Legacy type identifiers left for compatibility with the rest of the code: + SecuritySolutionPluginSetupDependencies as SetupPlugins, + SecuritySolutionPluginStartDependencies as StartPlugins, + SecuritySolutionPluginSetup as PluginSetup, + SecuritySolutionPluginStart as PluginStart, +}; diff --git a/x-pack/plugins/security_solution/server/request_context_factory.mock.ts b/x-pack/plugins/security_solution/server/request_context_factory.mock.ts new file mode 100644 index 0000000000000..5621ac8fb26ab --- /dev/null +++ b/x-pack/plugins/security_solution/server/request_context_factory.mock.ts @@ -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. + */ + +import { requestContextMock } from './lib/detection_engine/routes/__mocks__'; +import { IRequestContextFactory } from './request_context_factory'; + +export const requestContextFactoryMock = { + create: (): jest.Mocked => ({ + create: jest.fn((context, request) => { + const fullContext = requestContextMock.create(); + const securitySolutionContext = fullContext.securitySolution; + return Promise.resolve(securitySolutionContext); + }), + }), +}; + +export const RequestContextFactoryMock = jest + .fn, []>() + .mockImplementation(requestContextFactoryMock.create); diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts new file mode 100644 index 0000000000000..c2e622bc495c9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -0,0 +1,90 @@ +/* + * 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 { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import { ExceptionListClient } from '../../lists/server'; + +import { DEFAULT_SPACE_ID } from '../common/constants'; +import { AppClientFactory } from './client'; +import { ConfigType } from './config'; +import { RuleExecutionLogClient } from './lib/detection_engine/rule_execution_log/rule_execution_log_client'; +import { buildFrameworkRequest } from './lib/timeline/utils/common'; +import { + SecuritySolutionPluginCoreSetupDependencies, + SecuritySolutionPluginSetupDependencies, +} from './plugin_contract'; +import { SecuritySolutionApiRequestHandlerContext } from './types'; + +export interface IRequestContextFactory { + create( + context: RequestHandlerContext, + request: KibanaRequest + ): Promise; +} + +interface ConstructorOptions { + config: ConfigType; + core: SecuritySolutionPluginCoreSetupDependencies; + plugins: SecuritySolutionPluginSetupDependencies; +} + +export class RequestContextFactory implements IRequestContextFactory { + private readonly appClientFactory: AppClientFactory; + + constructor(private readonly options: ConstructorOptions) { + this.appClientFactory = new AppClientFactory(); + } + + public async create( + context: RequestHandlerContext, + request: KibanaRequest + ): Promise { + const { options, appClientFactory } = this; + const { config, plugins } = options; + const { lists, ruleRegistry, security, spaces } = plugins; + + appClientFactory.setup({ + getSpaceId: plugins.spaces?.spacesService?.getSpaceId, + config, + }); + + const frameworkRequest = await buildFrameworkRequest(context, security, request); + + return { + core: context.core, + + getConfig: () => config, + + getFrameworkRequest: () => frameworkRequest, + + getAppClient: () => appClientFactory.create(request), + + getSpaceId: () => spaces?.spacesService?.getSpaceId(request) || DEFAULT_SPACE_ID, + + getRuleDataService: () => ruleRegistry.ruleDataService, + + getExecutionLogClient: () => + new RuleExecutionLogClient({ + savedObjectsClient: context.core.savedObjects.client, + eventLogService: plugins.eventLog, + underlyingClient: config.ruleExecutionLog.underlyingClient, + }), + + getExceptionListClient: () => { + if (!lists) { + return null; + } + + const username = security?.authc.getCurrentUser(request)?.username || 'elastic'; + return new ExceptionListClient({ + savedObjectsClient: context.core.savedObjects.client, + user: username, + }); + }, + }; + } +} diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 148580d5c4477..9d31684907f86 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -6,7 +6,6 @@ */ import { Logger } from 'src/core/server'; -import { RuleDataPluginService } from '../../../rule_registry/server'; import { SecuritySolutionPluginRouter } from '../types'; @@ -67,7 +66,6 @@ export const initRoutes = ( hasEncryptionKey: boolean, security: SetupPlugins['security'], ml: SetupPlugins['ml'], - ruleDataService: RuleDataPluginService, logger: Logger, isRuleRegistryEnabled: boolean ) => { @@ -85,7 +83,7 @@ export const initRoutes = ( // TODO: pass isRuleRegistryEnabled to all relevant routes - addPrepackedRulesRoute(router, config, security, isRuleRegistryEnabled); + addPrepackedRulesRoute(router); getPrepackagedRulesStatusRoute(router, config, security, isRuleRegistryEnabled); createRulesBulkRoute(router, ml, isRuleRegistryEnabled); updateRulesBulkRoute(router, ml, isRuleRegistryEnabled); @@ -127,7 +125,7 @@ export const initRoutes = ( // Detection Engine index routes that have the REST endpoints of /api/detection_engine/index // All REST index creation, policy management for spaces - createIndexRoute(router, ruleDataService, config); + createIndexRoute(router); readIndexRoute(router, config); deleteIndexRoute(router); diff --git a/x-pack/plugins/security_solution/server/types.ts b/x-pack/plugins/security_solution/server/types.ts index 7822a5b8ba3c5..84643a329573b 100644 --- a/x-pack/plugins/security_solution/server/types.ts +++ b/x-pack/plugins/security_solution/server/types.ts @@ -6,28 +6,35 @@ */ import type { IRouter, RequestHandlerContext } from 'src/core/server'; -import type { ListsApiRequestHandlerContext } from '../../lists/server'; -import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; +import type { ActionsApiRequestHandlerContext } from '../../actions/server'; import type { AlertingApiRequestHandlerContext } from '../../alerting/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; +import type { ListsApiRequestHandlerContext, ExceptionListClient } from '../../lists/server'; +import type { IRuleDataService } from '../../rule_registry/server'; import { AppClient } from './client'; -import { RuleExecutionLogClient } from './lib/detection_engine/rule_execution_log/rule_execution_log_client'; -import type { ActionsApiRequestHandlerContext } from '../../actions/server'; +import { ConfigType } from './config'; +import { IRuleExecutionLogClient } from './lib/detection_engine/rule_execution_log/types'; +import { FrameworkRequest } from './lib/framework'; export { AppClient }; -export interface AppRequestContext { +export interface SecuritySolutionApiRequestHandlerContext extends RequestHandlerContext { + getConfig: () => ConfigType; + getFrameworkRequest: () => FrameworkRequest; getAppClient: () => AppClient; getSpaceId: () => string; - getExecutionLogClient: () => RuleExecutionLogClient; + getRuleDataService: () => IRuleDataService; + getExecutionLogClient: () => IRuleExecutionLogClient; + getExceptionListClient: () => ExceptionListClient | null; } -export type SecuritySolutionRequestHandlerContext = RequestHandlerContext & { - securitySolution: AppRequestContext; - licensing: LicensingApiRequestHandlerContext; - alerting: AlertingApiRequestHandlerContext; +export interface SecuritySolutionRequestHandlerContext extends RequestHandlerContext { + securitySolution: SecuritySolutionApiRequestHandlerContext; actions: ActionsApiRequestHandlerContext; + alerting: AlertingApiRequestHandlerContext; + licensing: LicensingApiRequestHandlerContext; lists?: ListsApiRequestHandlerContext; -}; +} export type SecuritySolutionPluginRouter = IRouter;