Skip to content

Commit

Permalink
Copies any foreign signals to a original_signals section within signals
Browse files Browse the repository at this point in the history
  • Loading branch information
FrankHassanabad committed Oct 30, 2020
1 parent f5b1fae commit f96873b
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
objectPairIntersection,
objectArrayIntersection,
} from './build_bulk_body';
import { SignalHit } from './types';
import { SignalHit, SignalSourceHit } from './types';
import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock';

describe('buildBulkBody', () => {
Expand Down Expand Up @@ -441,6 +441,206 @@ describe('buildBulkBody', () => {
};
expect(fakeSignalSourceHit).toEqual(expected);
});

test('bulk body builds "original_signal" if it exists already as a numeric', () => {
const sampleParams = sampleRuleAlertParams();
const sampleDoc = sampleDocNoSortId();
delete sampleDoc._source.source;
const doc = ({
...sampleDoc,
_source: {
...sampleDoc._source,
signal: 123,
},
} as unknown) as SignalSourceHit;
const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody({
doc,
ruleParams: sampleParams,
id: sampleRuleGuid,
name: 'rule-name',
actions: [],
createdAt: '2020-01-28T15:58:34.810Z',
updatedAt: '2020-01-28T15:59:14.004Z',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
tags: ['some fake tag 1', 'some fake tag 2'],
throttle: 'no_actions',
});
const expected: Omit<SignalHit, '@timestamp'> & { someKey: string } = {
someKey: 'someValue',
event: {
kind: 'signal',
},
signal: {
original_signal: 123,
parent: {
id: sampleIdGuid,
type: 'event',
index: 'myFakeSignalIndex',
depth: 0,
},
parents: [
{
id: sampleIdGuid,
type: 'event',
index: 'myFakeSignalIndex',
depth: 0,
},
],
ancestors: [
{
id: sampleIdGuid,
type: 'event',
index: 'myFakeSignalIndex',
depth: 0,
},
],
original_time: '2020-04-20T21:27:45+0000',
status: 'open',
rule: {
actions: [],
author: ['Elastic'],
building_block_type: 'default',
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
rule_id: 'rule-1',
false_positives: [],
max_signals: 10000,
risk_score: 50,
risk_score_mapping: [],
output_index: '.siem-signals',
description: 'Detecting root and admin users',
from: 'now-6m',
immutable: false,
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
interval: '5m',
language: 'kuery',
license: 'Elastic License',
name: 'rule-name',
query: 'user.name: root or user.name: admin',
references: ['http://google.com'],
severity: 'high',
severity_mapping: [],
tags: ['some fake tag 1', 'some fake tag 2'],
threat: [],
type: 'query',
to: 'now',
note: '',
enabled: true,
created_by: 'elastic',
updated_by: 'elastic',
version: 1,
updated_at: fakeSignalSourceHit.signal.rule?.updated_at,
created_at: fakeSignalSourceHit.signal.rule?.created_at,
throttle: 'no_actions',
exceptions_list: getListArrayMock(),
},
depth: 1,
},
};
expect(fakeSignalSourceHit).toEqual(expected);
});

test('bulk body builds "original_signal" if it exists already as an object', () => {
const sampleParams = sampleRuleAlertParams();
const sampleDoc = sampleDocNoSortId();
delete sampleDoc._source.source;
const doc = ({
...sampleDoc,
_source: {
...sampleDoc._source,
signal: { child_1: { child_2: 'nested data' } },
},
} as unknown) as SignalSourceHit;
const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody({
doc,
ruleParams: sampleParams,
id: sampleRuleGuid,
name: 'rule-name',
actions: [],
createdAt: '2020-01-28T15:58:34.810Z',
updatedAt: '2020-01-28T15:59:14.004Z',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
tags: ['some fake tag 1', 'some fake tag 2'],
throttle: 'no_actions',
});
const expected: Omit<SignalHit, '@timestamp'> & { someKey: string } = {
someKey: 'someValue',
event: {
kind: 'signal',
},
signal: {
original_signal: { child_1: { child_2: 'nested data' } },
parent: {
id: sampleIdGuid,
type: 'event',
index: 'myFakeSignalIndex',
depth: 0,
},
parents: [
{
id: sampleIdGuid,
type: 'event',
index: 'myFakeSignalIndex',
depth: 0,
},
],
ancestors: [
{
id: sampleIdGuid,
type: 'event',
index: 'myFakeSignalIndex',
depth: 0,
},
],
original_time: '2020-04-20T21:27:45+0000',
status: 'open',
rule: {
actions: [],
author: ['Elastic'],
building_block_type: 'default',
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
rule_id: 'rule-1',
false_positives: [],
max_signals: 10000,
risk_score: 50,
risk_score_mapping: [],
output_index: '.siem-signals',
description: 'Detecting root and admin users',
from: 'now-6m',
immutable: false,
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
interval: '5m',
language: 'kuery',
license: 'Elastic License',
name: 'rule-name',
query: 'user.name: root or user.name: admin',
references: ['http://google.com'],
severity: 'high',
severity_mapping: [],
tags: ['some fake tag 1', 'some fake tag 2'],
threat: [],
type: 'query',
to: 'now',
note: '',
enabled: true,
created_by: 'elastic',
updated_by: 'elastic',
version: 1,
updated_at: fakeSignalSourceHit.signal.rule?.updated_at,
created_at: fakeSignalSourceHit.signal.rule?.created_at,
throttle: 'no_actions',
exceptions_list: getListArrayMock(),
},
depth: 1,
},
};
expect(fakeSignalSourceHit).toEqual(expected);
});
});

describe('buildSignalFromSequence', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const buildSignalFromEvent = (
const rule = applyOverrides
? buildRuleWithOverrides(ruleSO, event._source)
: buildRuleWithoutOverrides(ruleSO);
const signal = {
const signal: Signal = {
...buildSignal([event], rule),
...additionalSignalFields(event),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/

import { sampleDocNoSortId } from './__mocks__/es_results';
import { buildEventTypeSignal } from './build_event_type_signal';
import { buildEventTypeSignal, isEventTypeSignal } from './build_event_type_signal';
import { BaseSignalHit } from './types';

describe('buildEventTypeSignal', () => {
beforeEach(() => {
Expand Down Expand Up @@ -44,4 +45,57 @@ describe('buildEventTypeSignal', () => {
};
expect(eventType).toEqual(expected);
});

test('It validates a sample doc with no signal type as "false"', () => {
const doc = sampleDocNoSortId();
expect(isEventTypeSignal(doc)).toEqual(false);
});

test('It validates a sample doc with a signal type as "true"', () => {
const doc: BaseSignalHit = ({
...sampleDocNoSortId(),
_source: {
...sampleDocNoSortId()._source,
signal: {
rule: { id: 'id-123' },
},
},
} as unknown) as BaseSignalHit;
expect(isEventTypeSignal(doc)).toEqual(true);
});

test('It validates a numeric signal string as "false"', () => {
const doc: BaseSignalHit = ({
...sampleDocNoSortId(),
_source: {
...sampleDocNoSortId()._source,
signal: 'something',
},
} as unknown) as BaseSignalHit;
expect(isEventTypeSignal(doc)).toEqual(false);
});

test('It validates an empty object as "false"', () => {
const doc: BaseSignalHit = ({
...sampleDocNoSortId(),
_source: {
...sampleDocNoSortId()._source,
signal: {},
},
} as unknown) as BaseSignalHit;
expect(isEventTypeSignal(doc)).toEqual(false);
});

test('It validates an empty rule object as "false"', () => {
const doc: BaseSignalHit = ({
...sampleDocNoSortId(),
_source: {
...sampleDocNoSortId()._source,
signal: {
rule: {},
},
},
} as unknown) as BaseSignalHit;
expect(isEventTypeSignal(doc)).toEqual(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,16 @@ export const buildEventTypeSignal = (doc: BaseSignalHit): object => {
return { kind: 'signal' };
}
};

/**
* Given a document this will return true if that document is a signal
* document. We can't guarantee the code will call this function with a document
* before adding the _source.event.kind = "signal" from "buildEventTypeSignal"
* so we do basic testing to ensure that if the object has the fields of:
* "signal.rule.id" then it will be one of our signals rather than a customer
* overwritten signal.
* @param doc The document which might be a signal or it might be a regular log
*/
export const isEventTypeSignal = (doc: BaseSignalHit): boolean => {
return doc._source.signal?.rule?.id != null && typeof doc._source.signal?.rule?.id === 'string';
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
*/

import { sampleDocNoSortId } from './__mocks__/es_results';
import { buildSignal, buildParent, buildAncestors, additionalSignalFields } from './build_signal';
import { Signal, Ancestor } from './types';
import {
buildSignal,
buildParent,
buildAncestors,
additionalSignalFields,
removeClashes,
} from './build_signal';
import { Signal, Ancestor, BaseSignalHit } from './types';
import {
getRulesSchemaMock,
ANCHOR_DATE,
Expand Down Expand Up @@ -302,4 +308,64 @@ describe('buildSignal', () => {
];
expect(signal).toEqual(expected);
});

describe('removeClashes', () => {
test('it will call renameClashes with a regular doc and not mutate it if it does not have a signal clash', () => {
const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
const output = removeClashes(doc);
expect(output).toBe(doc); // reference check
});

test('it will call renameClashes with a regular doc and not change anything', () => {
const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
const output = removeClashes(doc);
expect(output).toEqual(doc); // deep equal check
});

test('it will remove a "signal" numeric clash', () => {
const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
const doc = ({
...sampleDoc,
_source: {
...sampleDoc._source,
signal: 127,
},
} as unknown) as BaseSignalHit;
const output = removeClashes(doc);
expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'));
});

test('it will remove a "signal" object clash', () => {
const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
const doc = ({
...sampleDoc,
_source: {
...sampleDoc._source,
signal: { child_1: { child_2: 'Test nesting' } },
},
} as unknown) as BaseSignalHit;
const output = removeClashes(doc);
expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'));
});

test('it will not remove a "signal" if that is signal is one of our signals', () => {
const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
const doc = ({
...sampleDoc,
_source: {
...sampleDoc._source,
signal: { rule: { id: '123' } },
},
} as unknown) as BaseSignalHit;
const output = removeClashes(doc);
const expected = {
...sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'),
_source: {
...sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')._source,
signal: { rule: { id: '123' } },
},
};
expect(output).toEqual(expected);
});
});
});
Loading

0 comments on commit f96873b

Please sign in to comment.