From 5b4d3dea8a903cbfad352c645e34f145d65267a7 Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Fri, 26 Feb 2021 07:25:06 -0800 Subject: [PATCH] Add warning for EQL and Threshold rules if exception list contains value list items (#92914) --- .../common/detection_engine/utils.ts | 12 ++++++- .../routes/__mocks__/request_responses.ts | 29 +++++++++++++++++ .../signals/signal_rule_alert_type.test.ts | 32 ++++++++++++++++++- .../signals/signal_rule_alert_type.ts | 13 ++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index 725a2eb9fea7bb..79b912e082fdb1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -5,9 +5,19 @@ * 2.0. */ -import { EntriesArray } from '../shared_imports'; +import { + CreateExceptionListItemSchema, + EntriesArray, + ExceptionListItemSchema, +} from '../shared_imports'; import { Type } from './schemas/common/schemas'; +export const hasLargeValueItem = ( + exceptionItems: Array +) => { + return exceptionItems.some((exceptionItem) => hasLargeValueList(exceptionItem.entries)); +}; + export const hasLargeValueList = (entries: EntriesArray): boolean => { const found = entries.filter(({ type }) => type === 'list'); return found.length > 0; 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 cf6ea572aa8561..649ce9ed643651 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 @@ -440,6 +440,35 @@ export const getMlResult = (): RuleAlertType => { }; }; +export const getThresholdResult = (): RuleAlertType => { + const result = getResult(); + + return { + ...result, + params: { + ...result.params, + type: 'threshold', + threshold: { + field: 'host.ip', + value: 5, + }, + }, + }; +}; + +export const getEqlResult = (): RuleAlertType => { + const result = getResult(); + + return { + ...result, + params: { + ...result.params, + type: 'eql', + query: 'process where true', + }, + }; +}; + export const updateActionResult = (): ActionResult => ({ id: 'result-1', actionTypeId: 'action-id-1', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index cadc6d0c5b7c01..d3d82682cbb4a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -7,7 +7,12 @@ import moment from 'moment'; import { loggingSystemMock } from 'src/core/server/mocks'; -import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; +import { + getResult, + getMlResult, + getThresholdResult, + getEqlResult, +} from '../routes/__mocks__/request_responses'; import { signalRulesAlertType } from './signal_rule_alert_type'; import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { ruleStatusServiceFactory } from './rule_status_service'; @@ -24,6 +29,7 @@ import { getListClientMock } from '../../../../../lists/server/services/lists/li import { getExceptionListClientMock } from '../../../../../lists/server/services/exception_lists/exception_list_client.mock'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; +import { getEntryListMock } from '../../../../../lists/common/schemas/types/entry_list.mock'; jest.mock('./rule_status_saved_objects_client'); jest.mock('./rule_status_service'); @@ -211,6 +217,30 @@ describe('rules_notification_alert_type', () => { ); }); + it('should set a warning when exception list for threshold rule contains value list exceptions', async () => { + (getExceptions as jest.Mock).mockReturnValue([ + getExceptionListItemSchemaMock({ entries: [getEntryListMock()] }), + ]); + payload = getPayload(getThresholdResult(), alertServices); + await alert.executor(payload); + expect(ruleStatusService.warning).toHaveBeenCalled(); + expect(ruleStatusService.warning.mock.calls[0][0]).toContain( + 'Exceptions that use "is in list" or "is not in list" operators are not applied to Threshold rules' + ); + }); + + it('should set a warning when exception list for EQL rule contains value list exceptions', async () => { + (getExceptions as jest.Mock).mockReturnValue([ + getExceptionListItemSchemaMock({ entries: [getEntryListMock()] }), + ]); + payload = getPayload(getEqlResult(), alertServices); + await alert.executor(payload); + expect(ruleStatusService.warning).toHaveBeenCalled(); + expect(ruleStatusService.warning.mock.calls[0][0]).toContain( + 'Exceptions that use "is in list" or "is not in list" operators are not applied to EQL rules' + ); + }); + it('should set a failure status for when rules cannot read ANY provided indices', async () => { (checkPrivileges as jest.Mock).mockResolvedValueOnce({ username: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 2025ba512cb653..14a65bc1eeb7ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -24,6 +24,7 @@ import { isThresholdRule, isEqlRule, isThreatMatchRule, + hasLargeValueItem, } from '../../../../common/detection_engine/utils'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { SetupPlugins } from '../../../plugin'; @@ -365,6 +366,12 @@ export const signalRulesAlertType = ({ }), ]); } else if (isThresholdRule(type) && threshold) { + if (hasLargeValueItem(exceptionItems ?? [])) { + await ruleStatusService.warning( + 'Exceptions that use "is in list" or "is not in list" operators are not applied to Threshold rules' + ); + wroteWarningStatus = true; + } const inputIndex = await getInputIndex(services, version, index); const thresholdFields = Array.isArray(threshold.field) @@ -552,6 +559,12 @@ export const signalRulesAlertType = ({ if (query === undefined) { throw new Error('EQL query rule must have a query defined'); } + if (hasLargeValueItem(exceptionItems ?? [])) { + await ruleStatusService.warning( + 'Exceptions that use "is in list" or "is not in list" operators are not applied to EQL rules' + ); + wroteWarningStatus = true; + } try { const signalIndexVersion = await getIndexVersion(services.callCluster, outputIndex); if (isOutdated({ current: signalIndexVersion, target: MIN_EQL_RULE_INDEX_VERSION })) {