Skip to content

Commit

Permalink
[SIEM][Detection Engine] Add rule's notification alert type (#60832) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykkopycinski authored Mar 24, 2020
1 parent 44c77ea commit c2109b8
Show file tree
Hide file tree
Showing 55 changed files with 1,850 additions and 35 deletions.
8 changes: 8 additions & 0 deletions x-pack/legacy/plugins/siem/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,18 @@ export const IP_REPUTATION_LINKS_SETTING_DEFAULT = `[
*/
export const SIGNALS_ID = `${APP_ID}.signals`;

/**
* Id for the notifications alerting type
*/
export const NOTIFICATIONS_ID = `${APP_ID}.notifications`;

/**
* Special internal structure for tags for signals. This is used
* to filter out tags that have internal structures within them.
*/
export const INTERNAL_IDENTIFIER = '__internal';
export const INTERNAL_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_id`;
export const INTERNAL_RULE_ALERT_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_alert_id`;
export const INTERNAL_IMMUTABLE_KEY = `${INTERNAL_IDENTIFIER}_immutable`;

/**
Expand Down Expand Up @@ -87,3 +93,5 @@ export const DETECTION_ENGINE_QUERY_SIGNALS_URL = `${DETECTION_ENGINE_SIGNALS_UR
* Common naming convention for an unauthenticated user
*/
export const UNAUTHENTICATED_USER = 'Unauthenticated';

export const NOTIFICATION_THROTTLE_RULE = 'rule';
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { AlertAction } from '../../../../../../../plugins/alerting/common';
import { RuleAlertAction } from '../types';
import { AlertAction } from '../../../../../plugins/alerting/common';
import { RuleAlertAction } from './types';

export const transformRuleToAlertAction = ({
group,
Expand Down
11 changes: 11 additions & 0 deletions x-pack/legacy/plugins/siem/common/detection_engine/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { AlertAction } from '../../../../../plugins/alerting/common';

export type RuleAlertAction = Omit<AlertAction, 'actionTypeId'> & {
action_type_id: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { addTags } from './add_tags';
import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants';

describe('add_tags', () => {
test('it should add a rule id as an internal structure', () => {
const tags = addTags([], 'rule-1');
expect(tags).toEqual([`${INTERNAL_RULE_ALERT_ID_KEY}:rule-1`]);
});

test('it should not allow duplicate tags to be created', () => {
const tags = addTags(['tag-1', 'tag-1'], 'rule-1');
expect(tags).toEqual(['tag-1', `${INTERNAL_RULE_ALERT_ID_KEY}:rule-1`]);
});

test('it should not allow duplicate internal tags to be created when called two times in a row', () => {
const tags1 = addTags(['tag-1'], 'rule-1');
const tags2 = addTags(tags1, 'rule-1');
expect(tags2).toEqual(['tag-1', `${INTERNAL_RULE_ALERT_ID_KEY}:rule-1`]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants';

export const addTags = (tags: string[] = [], ruleAlertId: string): string[] =>
Array.from(new Set([...tags, `${INTERNAL_RULE_ALERT_ID_KEY}:${ruleAlertId}`]));
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { buildSignalsSearchQuery } from './build_signals_query';

describe('buildSignalsSearchQuery', () => {
it('returns proper query object', () => {
const index = 'index';
const ruleId = 'ruleId-12';
const from = '123123123';
const to = '1123123123';

expect(
buildSignalsSearchQuery({
index,
from,
to,
ruleId,
})
).toEqual({
index,
body: {
query: {
bool: {
filter: [
{
bool: {
should: {
match: {
'signal.rule.rule_id': ruleId,
},
},
minimum_should_match: 1,
},
},
{
range: {
'@timestamp': {
gte: from,
lte: to,
},
},
},
],
},
},
},
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

interface BuildSignalsSearchQuery {
ruleId: string;
index: string;
from: string;
to: string;
}

export const buildSignalsSearchQuery = ({ ruleId, index, from, to }: BuildSignalsSearchQuery) => ({
index,
body: {
query: {
bool: {
filter: [
{
bool: {
should: {
match: {
'signal.rule.rule_id': ruleId,
},
},
minimum_should_match: 1,
},
},
{
range: {
'@timestamp': {
gte: from,
lte: to,
},
},
},
],
},
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { alertsClientMock } from '../../../../../../../plugins/alerting/server/mocks';
import { createNotifications } from './create_notifications';

describe('createNotifications', () => {
let alertsClient: ReturnType<typeof alertsClientMock.create>;

beforeEach(() => {
alertsClient = alertsClientMock.create();
});

it('calls the alertsClient with proper params', async () => {
const ruleAlertId = 'rule-04128c15-0d1b-4716-a4c5-46997ac7f3bd';

await createNotifications({
alertsClient,
actions: [],
ruleAlertId,
enabled: true,
interval: '',
name: '',
tags: [],
});

expect(alertsClient.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
params: expect.objectContaining({
ruleAlertId,
}),
}),
})
);
});

it('calls the alertsClient with transformed actions', async () => {
const action = {
group: 'default',
id: '99403909-ca9b-49ba-9d7a-7e5320e68d05',
params: { message: 'Rule generated {{state.signalsCount}} signals' },
action_type_id: '.slack',
};
await createNotifications({
alertsClient,
actions: [action],
ruleAlertId: 'new-rule-id',
enabled: true,
interval: '',
name: '',
tags: [],
});

expect(alertsClient.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
actions: expect.arrayContaining([
{
group: action.group,
id: action.id,
params: action.params,
actionTypeId: '.slack',
},
]),
}),
})
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { Alert } from '../../../../../../../plugins/alerting/common';
import { APP_ID, NOTIFICATIONS_ID } from '../../../../common/constants';
import { CreateNotificationParams } from './types';
import { addTags } from './add_tags';
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';

export const createNotifications = async ({
alertsClient,
actions,
enabled,
ruleAlertId,
interval,
name,
tags,
}: CreateNotificationParams): Promise<Alert> =>
alertsClient.create({
data: {
name,
tags: addTags(tags, ruleAlertId),
alertTypeId: NOTIFICATIONS_ID,
consumer: APP_ID,
params: {
ruleAlertId,
},
schedule: { interval },
enabled,
actions: actions?.map(transformRuleToAlertAction),
throttle: null,
},
});
Loading

0 comments on commit c2109b8

Please sign in to comment.