diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts index c6f818f04fc5d..07acb665ec369 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts @@ -20,10 +20,11 @@ import { ConfigType } from '../../../../config'; import { AlertAttributes } from '../../signals/types'; import { createRuleMock } from './rule'; import { listMock } from '../../../../../../lists/server/mocks'; -import { RuleParams } from '../../schemas/rule_schemas'; +import { QueryRuleParams, RuleParams } from '../../schemas/rule_schemas'; // this is only used in tests // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { createDefaultAlertExecutorOptions } from '../../../../../../rule_registry/server/utils/rule_executor_test_utils'; +import { getCompleteRuleMock } from '../../schemas/rule_schemas.mock'; export const createRuleTypeMocks = ( ruleType: string = 'query', @@ -88,9 +89,15 @@ export const createRuleTypeMocks = ( lists: listMock.createSetup(), logger: loggerMock, ml: mlPluginServerMock.createSetupContract(), - ruleDataClient: ruleRegistryMocks.createRuleDataClient( - '.alerts-security.alerts' - ) as IRuleDataClient, + ruleDataClient: { + ...(ruleRegistryMocks.createRuleDataClient('.alerts-security.alerts') as IRuleDataClient), + getReader: jest.fn((_options?: { namespace?: string }) => ({ + search: jest.fn().mockResolvedValue({ + aggregations: undefined, + }), + getDynamicIndexPattern: jest.fn(), + })), + }, eventLogService: eventLogServiceMock.create(), }, services, @@ -102,6 +109,9 @@ export const createRuleTypeMocks = ( alertId: v4(), state: {}, }), + runOpts: { + completeRule: getCompleteRuleMock(params as QueryRuleParams), + }, services, }); }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.test.ts deleted file mode 100644 index 486a692ba29f4..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; - -import { allowedExperimentalValues } from '../../../../../common/experimental_features'; -import { createEqlAlertType } from './create_eql_alert_type'; -import { createRuleTypeMocks } from '../__mocks__/rule_type'; -import { getEqlRuleParams } from '../../schemas/rule_schemas.mock'; -import { createSecurityRuleTypeWrapper } from '../create_security_rule_type_wrapper'; -import { createMockConfig } from '../../routes/__mocks__'; - -jest.mock('../../rule_execution_log/rule_execution_log_client'); - -describe('Event correlation alerts', () => { - it('does not send an alert when no events found', async () => { - const params = { - ...getEqlRuleParams(), - query: 'any where false', - }; - const { services, dependencies, executor } = createRuleTypeMocks('eql', params); - const securityRuleTypeWrapper = createSecurityRuleTypeWrapper({ - lists: dependencies.lists, - logger: dependencies.logger, - config: createMockConfig(), - ruleDataClient: dependencies.ruleDataClient, - eventLogService: dependencies.eventLogService, - }); - const eqlAlertType = securityRuleTypeWrapper( - createEqlAlertType({ - experimentalFeatures: allowedExperimentalValues, - logger: dependencies.logger, - version: '1.0.0', - }) - ); - dependencies.alerting.registerType(eqlAlertType); - services.scopedClusterClient.asCurrentUser.search.mockReturnValue( - elasticsearchClientMock.createSuccessTransportRequestPromise({ - hits: { - hits: [], - sequences: [], - events: [], - total: { - relation: 'eq', - value: 0, - }, - }, - took: 0, - timed_out: false, - _shards: { - failed: 0, - skipped: 0, - successful: 1, - total: 1, - }, - }) - ); - - await executor({ params }); - expect(dependencies.ruleDataClient.getWriter).not.toBeCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts deleted file mode 100644 index 29054a4022715..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { v4 } from 'uuid'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; - -import { allowedExperimentalValues } from '../../../../../common/experimental_features'; -import { createRuleTypeMocks } from '../__mocks__/rule_type'; -import { createIndicatorMatchAlertType } from './create_indicator_match_alert_type'; -import { sampleDocNoSortId } from '../../signals/__mocks__/es_results'; -import { RuleParams } from '../../schemas/rule_schemas'; -import { createSecurityRuleTypeWrapper } from '../create_security_rule_type_wrapper'; -import { createMockConfig } from '../../routes/__mocks__'; - -jest.mock('../utils/get_list_client', () => ({ - getListClient: jest.fn().mockReturnValue({ - listClient: { - getListItemIndex: jest.fn(), - }, - exceptionsClient: jest.fn(), - }), -})); - -jest.mock('../../rule_execution_log/rule_execution_log_client'); - -describe('Indicator Match Alerts', () => { - const params: Partial = { - from: 'now-1m', - index: ['*'], - threatIndex: ['filebeat-*'], - threatLanguage: 'kuery', - threatMapping: [ - { - entries: [ - { - field: 'file.hash.md5', - type: 'mapping', - value: 'threat.indicator.file.hash.md5', - }, - ], - }, - ], - threatQuery: '*:*', - to: 'now', - type: 'threat_match', - query: '*:*', - language: 'kuery', - }; - const { services, dependencies, executor } = createRuleTypeMocks('threat_match', params); - const securityRuleTypeWrapper = createSecurityRuleTypeWrapper({ - lists: dependencies.lists, - logger: dependencies.logger, - config: createMockConfig(), - ruleDataClient: dependencies.ruleDataClient, - eventLogService: dependencies.eventLogService, - }); - - it('does not send an alert when no events found', async () => { - const indicatorMatchAlertType = securityRuleTypeWrapper( - createIndicatorMatchAlertType({ - experimentalFeatures: allowedExperimentalValues, - logger: dependencies.logger, - version: '1.0.0', - }) - ); - - dependencies.alerting.registerType(indicatorMatchAlertType); - - services.scopedClusterClient.asCurrentUser.search.mockReturnValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise({ - hits: { - hits: [], - sequences: [], - events: [], - total: { - relation: 'eq', - value: 0, - }, - }, - took: 0, - timed_out: false, - _shards: { - failed: 0, - skipped: 0, - successful: 1, - total: 1, - }, - }) - ); - - await executor({ params }); - expect(dependencies.ruleDataClient.getWriter).not.toBeCalled(); - }); - - it('does not send an alert when no enrichments are found', async () => { - const indicatorMatchAlertType = securityRuleTypeWrapper( - createIndicatorMatchAlertType({ - experimentalFeatures: allowedExperimentalValues, - logger: dependencies.logger, - version: '1.0.0', - }) - ); - - dependencies.alerting.registerType(indicatorMatchAlertType); - - services.scopedClusterClient.asCurrentUser.search.mockReturnValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise({ - hits: { - hits: [sampleDocNoSortId(v4()), sampleDocNoSortId(v4()), sampleDocNoSortId(v4())], - total: { - relation: 'eq', - value: 3, - }, - }, - took: 0, - timed_out: false, - _shards: { - failed: 0, - skipped: 0, - successful: 1, - total: 1, - }, - }) - ); - - await executor({ params }); - expect(dependencies.ruleDataClient.getWriter).not.toBeCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts index 6c487da07fda3..c4752751e529a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts @@ -16,7 +16,7 @@ import { CreateRuleOptions, SecurityAlertType } from '../types'; export const createIndicatorMatchAlertType = ( createOptions: CreateRuleOptions ): SecurityAlertType => { - const { experimentalFeatures, logger, version } = createOptions; + const { eventsTelemetry, experimentalFeatures, logger, version } = createOptions; return { id: INDICATOR_RULE_TYPE_ID, name: 'Indicator Match Rule', @@ -68,7 +68,7 @@ export const createIndicatorMatchAlertType = ( bulkCreate, exceptionItems, experimentalFeatures, - eventsTelemetry: undefined, + eventsTelemetry, listClient, logger, completeRule, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.test.ts deleted file mode 100644 index b7a099b10e275..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mlPluginServerMock } from '../../../../../../ml/server/mocks'; - -import { allowedExperimentalValues } from '../../../../../common/experimental_features'; -import { bulkCreateMlSignals } from '../../signals/bulk_create_ml_signals'; - -import { createRuleTypeMocks } from '../__mocks__/rule_type'; -import { createMlAlertType } from './create_ml_alert_type'; - -import { RuleParams } from '../../schemas/rule_schemas'; -import { createSecurityRuleTypeWrapper } from '../create_security_rule_type_wrapper'; -import { createMockConfig } from '../../routes/__mocks__'; - -jest.mock('../../signals/bulk_create_ml_signals'); - -jest.mock('../utils/get_list_client', () => ({ - getListClient: jest.fn().mockReturnValue({ - listClient: { - getListItemIndex: jest.fn(), - }, - exceptionsClient: jest.fn(), - }), -})); - -jest.mock('../../rule_execution_log/rule_execution_log_client'); - -jest.mock('../../signals/filters/filter_events_against_list', () => ({ - filterEventsAgainstList: jest.fn().mockReturnValue({ - _shards: { - failures: [], - }, - hits: { - hits: [ - { - is_interim: false, - }, - ], - }, - }), -})); - -let jobsSummaryMock: jest.Mock; -let mlMock: ReturnType; - -describe('Machine Learning Alerts', () => { - beforeEach(() => { - jobsSummaryMock = jest.fn(); - jobsSummaryMock.mockResolvedValue([ - { - id: 'test-ml-job', - jobState: 'started', - datafeedState: 'started', - }, - ]); - mlMock = mlPluginServerMock.createSetupContract(); - mlMock.jobServiceProvider.mockReturnValue({ - jobsSummary: jobsSummaryMock, - }); - - (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ - success: true, - bulkCreateDuration: 0, - createdItemsCount: 1, - createdItems: [ - { - _id: '897234565234', - _index: 'test-index', - anomalyScore: 23, - }, - ], - errors: [], - }); - }); - - const params: Partial = { - anomalyThreshold: 23, - from: 'now-45m', - machineLearningJobId: ['test-ml-job'], - to: 'now', - type: 'machine_learning', - }; - - it('does not send an alert when no anomalies found', async () => { - jobsSummaryMock.mockResolvedValue([ - { - id: 'test-ml-job', - jobState: 'started', - datafeedState: 'started', - }, - ]); - const { dependencies, executor } = createRuleTypeMocks('machine_learning', params); - const securityRuleTypeWrapper = createSecurityRuleTypeWrapper({ - lists: dependencies.lists, - logger: dependencies.logger, - config: createMockConfig(), - ruleDataClient: dependencies.ruleDataClient, - eventLogService: dependencies.eventLogService, - }); - const mlAlertType = securityRuleTypeWrapper( - createMlAlertType({ - experimentalFeatures: allowedExperimentalValues, - logger: dependencies.logger, - ml: mlMock, - version: '1.0.0', - }) - ); - - dependencies.alerting.registerType(mlAlertType); - - await executor({ params }); - expect(dependencies.ruleDataClient.getWriter).not.toBeCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts index 07eb096d0dd83..a1d6214c6cb65 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts @@ -13,6 +13,14 @@ import { createQueryAlertType } from './create_query_alert_type'; import { createRuleTypeMocks } from '../__mocks__/rule_type'; import { createSecurityRuleTypeWrapper } from '../create_security_rule_type_wrapper'; import { createMockConfig } from '../../routes/__mocks__'; +import { createMockTelemetryEventsSender } from '../../../telemetry/__mocks__'; +import { sampleDocNoSortId } from '../../signals/__mocks__/es_results'; +import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; + +jest.mock('../../signals/utils', () => ({ + ...jest.requireActual('../../signals/utils'), + getExceptions: () => [], +})); jest.mock('../utils/get_list_client', () => ({ getListClient: jest.fn().mockReturnValue({ @@ -24,32 +32,33 @@ jest.mock('../utils/get_list_client', () => ({ jest.mock('../../rule_execution_log/rule_execution_log_client'); describe('Custom Query Alerts', () => { - const { services, dependencies, executor } = createRuleTypeMocks(); + const mocks = createRuleTypeMocks(); + const { dependencies, executor, services } = mocks; + const { alerting, eventLogService, lists, logger, ruleDataClient } = dependencies; const securityRuleTypeWrapper = createSecurityRuleTypeWrapper({ - lists: dependencies.lists, - logger: dependencies.logger, + lists, + logger, config: createMockConfig(), - ruleDataClient: dependencies.ruleDataClient, - eventLogService: dependencies.eventLogService, + ruleDataClient, + eventLogService, }); + const eventsTelemetry = createMockTelemetryEventsSender(true); + + afterEach(() => { + jest.clearAllMocks(); + }); + it('does not send an alert when no events found', async () => { const queryAlertType = securityRuleTypeWrapper( createQueryAlertType({ + eventsTelemetry, experimentalFeatures: allowedExperimentalValues, - logger: dependencies.logger, + logger, version: '1.0.0', }) ); - dependencies.alerting.registerType(queryAlertType); - - const params = { - query: 'dne:42', - index: ['*'], - from: 'now-1m', - to: 'now', - language: 'kuery', - }; + alerting.registerType(queryAlertType); services.scopedClusterClient.asCurrentUser.search.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ @@ -73,7 +82,55 @@ describe('Custom Query Alerts', () => { }) ); + const params = getQueryRuleParams(); + + await executor({ + params, + }); + + expect(ruleDataClient.getWriter().bulk).not.toHaveBeenCalled(); + expect(eventsTelemetry.queueTelemetryEvents).not.toHaveBeenCalled(); + }); + + it('sends an alert when events are found', async () => { + const queryAlertType = securityRuleTypeWrapper( + createQueryAlertType({ + eventsTelemetry, + experimentalFeatures: allowedExperimentalValues, + logger, + version: '1.0.0', + }) + ); + + alerting.registerType(queryAlertType); + + services.scopedClusterClient.asCurrentUser.search.mockReturnValue( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [sampleDocNoSortId()], + sequences: [], + events: [], + total: { + relation: 'eq', + value: 1, + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ); + + const params = getQueryRuleParams(); + await executor({ params }); - expect(dependencies.ruleDataClient.getWriter).not.toBeCalled(); + + expect(ruleDataClient.getWriter().bulk).toHaveBeenCalled(); + expect(eventsTelemetry.queueTelemetryEvents).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index ff032f65ac640..26512187010e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -16,7 +16,7 @@ import { CreateRuleOptions, SecurityAlertType } from '../types'; export const createQueryAlertType = ( createOptions: CreateRuleOptions ): SecurityAlertType => { - const { experimentalFeatures, logger, version } = createOptions; + const { eventsTelemetry, experimentalFeatures, logger, version } = createOptions; return { id: QUERY_RULE_TYPE_ID, name: 'Custom Query Rule', @@ -68,7 +68,7 @@ export const createQueryAlertType = ( bulkCreate, exceptionItems, experimentalFeatures, - eventsTelemetry: undefined, + eventsTelemetry, listClient, logger, completeRule, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.test.ts deleted file mode 100644 index 093ec0af78f59..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { allowedExperimentalValues } from '../../../../../common/experimental_features'; -import { createThresholdAlertType } from './create_threshold_alert_type'; -import { createRuleTypeMocks } from '../__mocks__/rule_type'; -import { getThresholdRuleParams } from '../../schemas/rule_schemas.mock'; -import { createSecurityRuleTypeWrapper } from '../create_security_rule_type_wrapper'; -import { createMockConfig } from '../../routes/__mocks__'; - -jest.mock('../../rule_execution_log/rule_execution_log_client'); - -describe('Threshold Alerts', () => { - it('does not send an alert when no events found', async () => { - const params = getThresholdRuleParams(); - const { dependencies, executor } = createRuleTypeMocks('threshold', params); - const securityRuleTypeWrapper = createSecurityRuleTypeWrapper({ - lists: dependencies.lists, - logger: dependencies.logger, - config: createMockConfig(), - ruleDataClient: dependencies.ruleDataClient, - eventLogService: dependencies.eventLogService, - }); - const thresholdAlertType = securityRuleTypeWrapper( - createThresholdAlertType({ - experimentalFeatures: allowedExperimentalValues, - logger: dependencies.logger, - version: '1.0.0', - }) - ); - dependencies.alerting.registerType(thresholdAlertType); - - await executor({ params }); - expect(dependencies.ruleDataClient.getWriter).not.toBeCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 065d11253c8e6..2338f9dbe065f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -37,6 +37,7 @@ import { import { ExperimentalFeatures } from '../../../../common/experimental_features'; import { IEventLogService } from '../../../../../event_log/server'; import { AlertsFieldMap, RulesFieldMap } from '../../../../common/field_maps'; +import { TelemetryEventsSender } from '../../telemetry/sender'; import { IRuleExecutionLogClient } from '../rule_execution_log'; import { commonParamsCamelToSnake } from '../schemas/rule_converters'; @@ -127,5 +128,6 @@ export interface CreateRuleOptions { experimentalFeatures: ExperimentalFeatures; logger: Logger; ml?: SetupPlugins['ml']; + eventsTelemetry?: TelemetryEventsSender | undefined; version: string; } diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a676ca8779f6a..bccd18eb0498d 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -185,6 +185,7 @@ export class Plugin implements ISecuritySolutionPlugin { experimentalFeatures, logger: this.logger, ml: plugins.ml, + eventsTelemetry: this.telemetryEventsSender, version: pluginContext.env.packageInfo.version, };