Skip to content

Commit

Permalink
Severity mapping: support numbers and strings in arbitrary (non-ECS) …
Browse files Browse the repository at this point in the history
…fields
  • Loading branch information
banderror committed Nov 22, 2020
1 parent a100b23 commit 80503d9
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { set } from '@elastic/safer-lodash-set';
import {
SignalSourceHit,
SignalSearchResponse,
Expand Down Expand Up @@ -189,21 +190,23 @@ export const sampleDocNoSortId = (
sort: [],
});

export const sampleDocSeverity = (severity?: unknown): SignalSourceHit => ({
_index: 'myFakeSignalIndex',
_type: 'doc',
_score: 100,
_version: 1,
_id: sampleIdGuid,
_source: {
someKey: 'someValue',
'@timestamp': '2020-04-20T21:27:45+0000',
event: {
severity,
export const sampleDocSeverity = (severity?: unknown, fieldName?: string): SignalSourceHit => {
const doc = {
_index: 'myFakeSignalIndex',
_type: 'doc',
_score: 100,
_version: 1,
_id: sampleIdGuid,
_source: {
someKey: 'someValue',
'@timestamp': '2020-04-20T21:27:45+0000',
},
},
sort: [],
});
sort: [],
};

set(doc._source, fieldName ?? 'event.severity', severity);
return doc;
};

export const sampleDocRiskScore = (riskScore?: unknown): SignalSourceHit => ({
_index: 'myFakeSignalIndex',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
BuildSeverityFromMappingReturn,
} from './build_severity_from_mapping';

const ECS_FIELD = 'event.severity';
const ANY_FIELD = 'event.my_custom_severity';

describe('buildSeverityFromMapping', () => {
beforeEach(() => {
jest.clearAllMocks();
Expand All @@ -30,49 +33,92 @@ describe('buildSeverityFromMapping', () => {
});
});

describe('base cases: when mapping to a single field', () => {
// TODO: Discuss at Play Time. Support arbitrary fields and string values.
test.skip(`severity is overridden if there's a match to a string`, () => {
describe('base cases: when mapping to the "event.severity" field from ECS', () => {
test(`severity is overridden if there's a match to a number`, () => {
testIt({
fieldValue: 23,
severityDefault: 'low',
severityMapping: [
{ field: ECS_FIELD, operator: 'equals', value: '13', severity: 'low' },
{ field: ECS_FIELD, operator: 'equals', value: '23', severity: 'medium' },
{ field: ECS_FIELD, operator: 'equals', value: '33', severity: 'high' },
{ field: ECS_FIELD, operator: 'equals', value: '43', severity: 'critical' },
],
expected: overridenSeverityOf('medium'),
});
});

test(`returns the default severity if there's a match to a string (ignores strings)`, () => {
testIt({
fieldValue: 'hackerman',
severityDefault: 'low',
severityMapping: [
{ field: 'event.severity', operator: 'equals', value: 'anything', severity: 'medium' },
{ field: 'event.severity', operator: 'equals', value: 'hackerman', severity: 'critical' },
{ field: ECS_FIELD, operator: 'equals', value: 'hackerman', severity: 'critical' },
],
expected: overridenSeverityOf('critical'),
expected: severityOf('low'),
});
});
});

describe('base cases: when mapping to any other field containing a single value', () => {
test(`severity is overridden if there's a match to a number`, () => {
testIt({
fieldName: ANY_FIELD,
fieldValue: 23,
severityDefault: 'low',
severityMapping: [
{ 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' },
{ field: ANY_FIELD, operator: 'equals', value: '13', severity: 'low' },
{ field: ANY_FIELD, operator: 'equals', value: '23', severity: 'medium' },
{ field: ANY_FIELD, operator: 'equals', value: '33', severity: 'high' },
{ field: ANY_FIELD, operator: 'equals', value: '43', severity: 'critical' },
],
expected: overridenSeverityOf('medium'),
expected: overridenSeverityOf('medium', ANY_FIELD),
});
});

test(`severity is overridden if there's a match to a string`, () => {
testIt({
fieldName: ANY_FIELD,
fieldValue: 'hackerman',
severityDefault: 'low',
severityMapping: [
{ field: ANY_FIELD, operator: 'equals', value: 'anything', severity: 'medium' },
{ field: ANY_FIELD, operator: 'equals', value: 'hackerman', severity: 'critical' },
],
expected: overridenSeverityOf('critical', ANY_FIELD),
});
});
});

describe('base cases: when mapping to an array', () => {
test(`severity is overridden to highest matched mapping`, () => {
test(`severity is overridden to highest matched mapping (works for "event.severity" field)`, () => {
testIt({
fieldValue: [23, 'some string', 43, 33],
severityDefault: 'low',
severityMapping: [
{ 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' },
{ field: ECS_FIELD, operator: 'equals', value: '13', severity: 'low' },
{ field: ECS_FIELD, operator: 'equals', value: '23', severity: 'medium' },
{ field: ECS_FIELD, operator: 'equals', value: '33', severity: 'high' },
{ field: ECS_FIELD, operator: 'equals', value: '43', severity: 'critical' },
],
expected: overridenSeverityOf('critical'),
});
});

test(`severity is overridden to highest matched mapping (works for any custom field)`, () => {
testIt({
fieldName: ANY_FIELD,
fieldValue: ['foo', 'bar', 'baz', 'boo'],
severityDefault: 'low',
severityMapping: [
{ field: ANY_FIELD, operator: 'equals', value: 'bar', severity: 'high' },
{ field: ANY_FIELD, operator: 'equals', value: 'baz', severity: 'critical' },
{ field: ANY_FIELD, operator: 'equals', value: 'foo', severity: 'low' },
{ field: ANY_FIELD, operator: 'equals', value: 'boo', severity: 'medium' },
],
expected: overridenSeverityOf('critical', ANY_FIELD),
});
});
});

describe('edge cases: when mapping the same numerical value to different severities multiple times', () => {
Expand All @@ -81,9 +127,9 @@ describe('buildSeverityFromMapping', () => {
fieldValue: 23,
severityDefault: 'low',
severityMapping: [
{ field: 'event.severity', operator: 'equals', value: '23', severity: 'medium' },
{ field: 'event.severity', operator: 'equals', value: '23', severity: 'critical' },
{ field: 'event.severity', operator: 'equals', value: '23', severity: 'high' },
{ field: ECS_FIELD, operator: 'equals', value: '23', severity: 'medium' },
{ field: ECS_FIELD, operator: 'equals', value: '23', severity: 'critical' },
{ field: ECS_FIELD, operator: 'equals', value: '23', severity: 'high' },
],
expected: overridenSeverityOf('critical'),
});
Expand All @@ -92,15 +138,16 @@ describe('buildSeverityFromMapping', () => {
});

interface TestCase {
fieldName?: string;
fieldValue: unknown;
severityDefault: Severity;
severityMapping: SeverityMappingOrUndefined;
expected: BuildSeverityFromMappingReturn;
}

function testIt({ fieldValue, severityDefault, severityMapping, expected }: TestCase) {
function testIt({ fieldName, fieldValue, severityDefault, severityMapping, expected }: TestCase) {
const result = buildSeverityFromMapping({
eventSource: sampleDocSeverity(fieldValue)._source,
eventSource: sampleDocSeverity(fieldValue, fieldName)._source,
severity: severityDefault,
severityMapping,
});
Expand All @@ -115,11 +162,11 @@ function severityOf(value: Severity) {
};
}

function overridenSeverityOf(value: Severity) {
function overridenSeverityOf(value: Severity, field = ECS_FIELD) {
return {
severity: value,
severityMeta: {
severityOverrideField: 'event.severity',
severityOverrideField: field,
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ function normalizeMappingValue(eventField: string, mappingValue: string): string
function normalizeEventValue(eventField: string, eventValue: SearchTypes): Set<string | number> {
const eventValues = Array.isArray(eventValue) ? eventValue : [eventValue];
const validValues = eventValues.filter((v): v is string | number => isValidValue(eventField, v));
return new Set(validValues);
const finalValues = eventField === ECS_SEVERITY_FIELD ? validValues : validValues.map(String);
return new Set(finalValues);
}

function isValidValue(eventField: string, value: unknown): value is string | number {
Expand Down

0 comments on commit 80503d9

Please sign in to comment.