Skip to content

Commit

Permalink
[SIEM] Add rule notifications (elastic#59004)
Browse files Browse the repository at this point in the history
## Summary

Allow defining notifications that will trigger whenever the rule created new signals.

Requires:
- elastic#58395
- elastic#58964
- elastic#60832


![Screenshot 2020-03-02 at 10 19 18](https://user-images.githubusercontent.com/5188868/75662390-4fe8bf00-5c6f-11ea-943f-591367348b91.png)

![Screenshot 2020-03-02 at 10 13 00](https://user-images.githubusercontent.com/5188868/75662421-5e36db00-5c6f-11ea-9317-d158cddf4344.png)


### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)
- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials
- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)
- [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)
- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
  • Loading branch information
patrykkopycinski authored and spong committed Mar 24, 2020
1 parent 66cd8d8 commit 5ea89d2
Show file tree
Hide file tree
Showing 43 changed files with 1,054 additions and 184 deletions.
12 changes: 12 additions & 0 deletions x-pack/legacy/plugins/siem/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ 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`;
export const INTERNAL_NOTIFICATION_ID_KEY = `${INTERNAL_IDENTIFIER}_notification_id`;
export const INTERNAL_NOTIFICATION_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_notification_rule_id`;

/**
* Detection engine routes
Expand Down Expand Up @@ -99,4 +101,14 @@ export const UNAUTHENTICATED_USER = 'Unauthenticated';
*/
export const MINIMUM_ML_LICENSE = 'platinum';

/*
Rule notifications options
*/
export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [
'.email',
'.slack',
'.pagerduty',
'.webhook',
];
export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions';
export const NOTIFICATION_THROTTLE_RULE = 'rule';
7 changes: 5 additions & 2 deletions x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export const CREATE_AND_ACTIVATE_BTN = '[data-test-subj="create-activate"]';

export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]';

export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="continue"]';
export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]';

export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]';

export const FALSE_POSITIVES_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] input';
Expand All @@ -43,7 +45,8 @@ export const RULE_DESCRIPTION_INPUT =
export const RULE_NAME_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleName"] [data-test-subj="input"]';

export const SEVERITY_DROPDOWN = '[data-test-subj="select"]';
export const SEVERITY_DROPDOWN =
'[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]';

export const TAGS_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxSearchInput"]';
2 changes: 2 additions & 0 deletions x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import {
REFERENCE_URLS_INPUT,
RULE_DESCRIPTION_INPUT,
RULE_NAME_INPUT,
SCHEDULE_CONTINUE_BUTTON,
SEVERITY_DROPDOWN,
TAGS_INPUT,
} from '../screens/create_new_rule';

export const createAndActivateRule = () => {
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
cy.get(CREATE_AND_ACTIVATE_BTN).click({ force: true });
cy.get(CREATE_AND_ACTIVATE_BTN).should('not.exist');
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('Detections Rules API', () => {
await addRule({ rule: ruleMock, signal: abortCtrl.signal });
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules', {
body:
'{"description":"some desc","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf","language":"kuery","risk_score":75,"name":"Test rule","query":"user.email: \'[email protected]\'","references":[],"severity":"high","tags":["APM"],"to":"now","type":"query","threat":[]}',
'{"description":"some desc","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf","language":"kuery","risk_score":75,"name":"Test rule","query":"user.email: \'[email protected]\'","references":[],"severity":"high","tags":["APM"],"to":"now","type":"query","threat":[],"throttle":null}',
method: 'POST',
signal: abortCtrl.signal,
});
Expand Down Expand Up @@ -291,7 +291,7 @@ describe('Detections Rules API', () => {
await duplicateRules({ rules: rulesMock.data });
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_create', {
body:
'[{"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"version":1},{"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"version":1}]',
'[{"actions":[],"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1},{"actions":[],"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1}]',
method: 'POST',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ export const ruleMock: NewRule = {
to: 'now',
type: 'query',
threat: [],
throttle: null,
};

export const savedRuleMock: Rule = {
actions: [],
created_at: 'mm/dd/yyyyTHH:MM:sssz',
created_by: 'mockUser',
description: 'some desc',
Expand Down Expand Up @@ -65,6 +67,7 @@ export const savedRuleMock: Rule = {
to: 'now',
type: 'query',
threat: [],
throttle: null,
updated_at: 'mm/dd/yyyyTHH:MM:sssz',
updated_by: 'mockUser',
};
Expand All @@ -75,6 +78,7 @@ export const rulesMock: FetchRulesResponse = {
total: 2,
data: [
{
actions: [],
created_at: '2020-02-14T19:49:28.178Z',
updated_at: '2020-02-14T19:49:28.320Z',
created_by: 'elastic',
Expand Down Expand Up @@ -103,9 +107,11 @@ export const rulesMock: FetchRulesResponse = {
to: 'now',
type: 'query',
threat: [],
throttle: null,
version: 1,
},
{
actions: [],
created_at: '2020-02-14T19:49:28.189Z',
updated_at: '2020-02-14T19:49:28.326Z',
created_by: 'elastic',
Expand Down Expand Up @@ -133,6 +139,7 @@ export const rulesMock: FetchRulesResponse = {
to: 'now',
type: 'query',
threat: [],
throttle: null,
version: 1,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ export const RuleTypeSchema = t.keyof({
});
export type RuleType = t.TypeOf<typeof RuleTypeSchema>;

/**
* Params is an "record", since it is a type of AlertActionParams which is action templates.
* @see x-pack/plugins/alerting/common/alert.ts
*/
export const action = t.exact(
t.type({
group: t.string,
id: t.string,
action_type_id: t.string,
params: t.record(t.string, t.any),
})
);

export const NewRuleSchema = t.intersection([
t.type({
description: t.string,
Expand All @@ -24,6 +37,7 @@ export const NewRuleSchema = t.intersection([
type: RuleTypeSchema,
}),
t.partial({
actions: t.array(action),
anomaly_threshold: t.number,
created_by: t.string,
false_positives: t.array(t.string),
Expand All @@ -40,6 +54,7 @@ export const NewRuleSchema = t.intersection([
saved_id: t.string,
tags: t.array(t.string),
threat: t.array(t.unknown),
throttle: t.union([t.string, t.null]),
to: t.string,
updated_by: t.string,
note: t.string,
Expand All @@ -54,9 +69,15 @@ export interface AddRulesProps {
signal: AbortSignal;
}

const MetaRule = t.type({
from: t.string,
});
const MetaRule = t.intersection([
t.type({
from: t.string,
}),
t.partial({
throttle: t.string,
kibanaSiemAppUrl: t.string,
}),
]);

export const RuleSchema = t.intersection([
t.type({
Expand All @@ -81,6 +102,8 @@ export const RuleSchema = t.intersection([
threat: t.array(t.unknown),
updated_at: t.string,
updated_by: t.string,
actions: t.array(action),
throttle: t.union([t.string, t.null]),
}),
t.partial({
anomaly_threshold: t.number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('useRule', () => {
expect(result.current).toEqual([
false,
{
actions: [],
created_at: 'mm/dd/yyyyTHH:MM:sssz',
created_by: 'mockUser',
description: 'some desc',
Expand Down Expand Up @@ -59,6 +60,7 @@ describe('useRule', () => {
severity: 'high',
tags: ['APM'],
threat: [],
throttle: null,
to: 'now',
type: 'query',
updated_at: 'mm/dd/yyyyTHH:MM:sssz',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('useRules', () => {
{
data: [
{
actions: [],
created_at: '2020-02-14T19:49:28.178Z',
created_by: 'elastic',
description:
Expand All @@ -82,13 +83,15 @@ describe('useRules', () => {
severity: 'high',
tags: ['Elastic', 'Endpoint'],
threat: [],
throttle: null,
to: 'now',
type: 'query',
updated_at: '2020-02-14T19:49:28.320Z',
updated_by: 'elastic',
version: 1,
},
{
actions: [],
created_at: '2020-02-14T19:49:28.189Z',
created_by: 'elastic',
description:
Expand All @@ -113,6 +116,7 @@ describe('useRules', () => {
severity: 'medium',
tags: ['Elastic', 'Endpoint'],
threat: [],
throttle: null,
to: 'now',
type: 'query',
updated_at: '2020-02-14T19:49:28.326Z',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { esFilters } from '../../../../../../../../../../src/plugins/data/public';
import { Rule, RuleError } from '../../../../../containers/detection_engine/rules';
import { AboutStepRule, DefineStepRule, ScheduleStepRule } from '../../types';
import { AboutStepRule, ActionsStepRule, DefineStepRule, ScheduleStepRule } from '../../types';
import { FieldValueQueryBar } from '../../components/query_bar';

export const mockQueryBar: FieldValueQueryBar = {
Expand Down Expand Up @@ -40,6 +40,7 @@ export const mockQueryBar: FieldValueQueryBar = {
};

export const mockRule = (id: string): Rule => ({
actions: [],
created_at: '2020-01-10T21:11:45.839Z',
updated_at: '2020-01-10T21:11:45.839Z',
created_by: 'elastic',
Expand Down Expand Up @@ -70,11 +71,13 @@ export const mockRule = (id: string): Rule => ({
to: 'now',
type: 'saved_query',
threat: [],
throttle: null,
note: '# this is some markdown documentation',
version: 1,
});

export const mockRuleWithEverything = (id: string): Rule => ({
actions: [],
created_at: '2020-01-10T21:11:45.839Z',
updated_at: '2020-01-10T21:11:45.839Z',
created_by: 'elastic',
Expand Down Expand Up @@ -142,6 +145,7 @@ export const mockRuleWithEverything = (id: string): Rule => ({
],
},
],
throttle: null,
note: '# this is some markdown documentation',
version: 1,
});
Expand Down Expand Up @@ -175,6 +179,14 @@ export const mockAboutStepRule = (isNew = false): AboutStepRule => ({
note: '# this is some markdown documentation',
});

export const mockActionsStepRule = (isNew = false, enabled = false): ActionsStepRule => ({
isNew,
actions: [],
kibanaSiemAppUrl: 'http://localhost:5601/app/siem',
enabled,
throttle: null,
});

export const mockDefineStepRule = (isNew = false): DefineStepRule => ({
isNew,
ruleType: 'query',
Expand All @@ -188,9 +200,8 @@ export const mockDefineStepRule = (isNew = false): DefineStepRule => ({
},
});

export const mockScheduleStepRule = (isNew = false, enabled = false): ScheduleStepRule => ({
export const mockScheduleStepRule = (isNew = false): ScheduleStepRule => ({
isNew,
enabled,
interval: '5m',
from: '6m',
to: 'now',
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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 { shallow } from 'enzyme';
import React from 'react';
import { NextStep } from './index';

describe('NextStep', () => {
test('renders correctly against snapshot', () => {
const wrapper = shallow(<NextStep onClick={jest.fn()} isDisabled={false} />);
expect(wrapper).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 React from 'react';
import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
import * as RuleI18n from '../../translations';

interface NextStepProps {
onClick: () => Promise<void>;
isDisabled: boolean;
dataTestSubj?: string;
}

export const NextStep = React.memo<NextStepProps>(
({ onClick, isDisabled, dataTestSubj = 'nextStep-continue' }) => (
<>
<EuiHorizontalRule margin="m" />
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>
<EuiButton fill onClick={onClick} isDisabled={isDisabled} data-test-subj={dataTestSubj}>
{RuleI18n.CONTINUE}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</>
)
);

NextStep.displayName = 'NextStep';
Loading

0 comments on commit 5ea89d2

Please sign in to comment.