From 2ebfe602213d495ce4c2363f95723840b922bbf5 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 4 Aug 2020 16:56:56 -0600 Subject: [PATCH 1/4] Fixes severity mapping not matching for Elastic Endpoint Security rule --- .../signals/__mocks__/es_results.ts | 18 ++++++++ .../build_severity_from_mapping.test.ts | 44 ++++++++++++++++++- .../mappings/build_severity_from_mapping.ts | 29 ++++++++++-- 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 513d6a93d1b5b..95ec753c21fd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -109,6 +109,24 @@ export const sampleDocNoSortId = ( sort: [], }); +export const sampleDocSeverity = ( + severity?: Array | string | number | null +): SignalSourceHit => ({ + _index: 'myFakeSignalIndex', + _type: 'doc', + _score: 100, + _version: 1, + _id: sampleIdGuid, + _source: { + someKey: 'someValue', + '@timestamp': '2020-04-20T21:27:45+0000', + event: { + severity: severity ?? 100, + }, + }, + sort: [], +}); + export const sampleEmptyDocSearchResults = (): SignalSearchResponse => ({ took: 10, timed_out: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts index 80950335934f4..746f3a7fc4a91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { sampleDocNoSortId } from '../__mocks__/es_results'; +import { sampleDocNoSortId, sampleDocSeverity } from '../__mocks__/es_results'; import { buildSeverityFromMapping } from './build_severity_from_mapping'; describe('buildSeverityFromMapping', () => { @@ -12,7 +12,7 @@ describe('buildSeverityFromMapping', () => { jest.clearAllMocks(); }); - test('severity defaults to provided if mapping is incomplete', () => { + test('severity defaults to provided if mapping is undefined', () => { const severity = buildSeverityFromMapping({ doc: sampleDocNoSortId(), severity: 'low', @@ -22,5 +22,45 @@ describe('buildSeverityFromMapping', () => { expect(severity).toEqual({ severity: 'low', severityMeta: {} }); }); + test('severity is overridden to highest matched mapping', () => { + const severity = buildSeverityFromMapping({ + doc: sampleDocSeverity(23), + severity: 'low', + severityMapping: [ + { field: 'event.severity', operator: 'equals', value: '23', severity: 'critical' }, + { field: 'event.severity', operator: 'equals', value: '23', severity: 'low' }, + { field: 'event.severity', operator: 'equals', value: '11', severity: 'critical' }, + { field: 'event.severity', operator: 'equals', value: '23', severity: 'medium' }, + ], + }); + + expect(severity).toEqual({ + severity: 'critical', + severityMeta: { + severityOverrideField: 'event.severity', + }, + }); + }); + + test('severity is overridden when field is event.severity and source value is number', () => { + const severity = buildSeverityFromMapping({ + doc: sampleDocSeverity(23), + severity: 'low', + severityMapping: [ + { field: 'event.severity', operator: 'equals', value: '13', severity: 'loew' }, + { field: 'event.severity', operator: 'equals', value: '23', severity: 'medium' }, + { field: 'event.severity', operator: 'equals', value: '33', severity: 'high' }, + { field: 'event.severity', operator: 'equals', value: '43', severity: 'critical' }, + ], + }); + + expect(severity).toEqual({ + severity: 'medium', + severityMeta: { + severityOverrideField: 'event.severity', + }, + }); + }); + // TODO: Enhance... }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts index a3c4f47b491be..25ad809031539 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts @@ -24,6 +24,13 @@ interface BuildSeverityFromMappingReturn { severityMeta: Meta; // TODO: Stricter types } +const severitySortMapping = { + low: 0, + medium: 1, + high: 2, + critical: 3, +}; + export const buildSeverityFromMapping = ({ doc, severity, @@ -31,10 +38,24 @@ export const buildSeverityFromMapping = ({ }: BuildSeverityFromMappingProps): BuildSeverityFromMappingReturn => { if (severityMapping != null && severityMapping.length > 0) { let severityMatch: SeverityMappingItem | undefined; - severityMapping.forEach((mapping) => { - // TODO: Expand by verifying fieldType from index via doc._index - const mappedValue = get(mapping.field, doc._source); - if (mapping.value === mappedValue) { + + // Sort the SeverityMapping from low to high, so last match (highest severity) is used + const severityMappingSorted = severityMapping?.sort( + (a, b) => severitySortMapping[a.severity] - severitySortMapping[b.severity] + ); + + severityMappingSorted.forEach((mapping) => { + const docValue = get(mapping.field, doc._source); + // TODO: Expand by verifying fieldType from index via doc._index + // Till then, explicit parsing of event.severity (long) to number. If not ECS, this could be + // another datatype, but until we can lookup datatype we must assume number for the Elastic + // Endpoint Security rule to function correctly + let parsedMappingValue = mapping.value; + if (mapping.field === 'event.severity') { + parsedMappingValue = Math.floor(Number(parsedMappingValue)); + } + + if (parsedMappingValue === docValue) { severityMatch = { ...mapping }; } }); From 73adc992c469cce67dba1a956f9f6b4d4a234e92 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 4 Aug 2020 17:22:40 -0600 Subject: [PATCH 2/4] Removing unneccessary optional chaining --- .../signals/mappings/build_severity_from_mapping.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts index 25ad809031539..fd681dba10d8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts @@ -40,7 +40,7 @@ export const buildSeverityFromMapping = ({ let severityMatch: SeverityMappingItem | undefined; // Sort the SeverityMapping from low to high, so last match (highest severity) is used - const severityMappingSorted = severityMapping?.sort( + const severityMappingSorted = severityMapping.sort( (a, b) => severitySortMapping[a.severity] - severitySortMapping[b.severity] ); From 6556b2f7f487b179fb8177e024bc70a02cf84343 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 4 Aug 2020 17:39:01 -0600 Subject: [PATCH 3/4] Comments from review --- .../signals/mappings/build_severity_from_mapping.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts index 746f3a7fc4a91..fb1d51364ab39 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts @@ -47,7 +47,7 @@ describe('buildSeverityFromMapping', () => { doc: sampleDocSeverity(23), severity: 'low', severityMapping: [ - { field: 'event.severity', operator: 'equals', value: '13', severity: 'loew' }, + { field: 'event.severity', operator: 'equals', value: '13', severity: 'low' }, { field: 'event.severity', operator: 'equals', value: '23', severity: 'medium' }, { field: 'event.severity', operator: 'equals', value: '33', severity: 'high' }, { field: 'event.severity', operator: 'equals', value: '43', severity: 'critical' }, From a46a61e50639fcca0a584a04bc87c936f0a2775a Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 4 Aug 2020 17:42:15 -0600 Subject: [PATCH 4/4] Fixing type issue --- .../signals/mappings/build_severity_from_mapping.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts index fd681dba10d8d..c0a62a2cc887d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts @@ -50,7 +50,7 @@ export const buildSeverityFromMapping = ({ // Till then, explicit parsing of event.severity (long) to number. If not ECS, this could be // another datatype, but until we can lookup datatype we must assume number for the Elastic // Endpoint Security rule to function correctly - let parsedMappingValue = mapping.value; + let parsedMappingValue: string | number = mapping.value; if (mapping.field === 'event.severity') { parsedMappingValue = Math.floor(Number(parsedMappingValue)); }