Skip to content

Commit

Permalink
[SIEM] Fixes Modification of ML Rules (#60662)
Browse files Browse the repository at this point in the history
* Fix updating of ML rules

* Add a regression test for updating ML Rules

* Allow ML Rules to be patched

And adds a regression unit test.

* Allow ML rule params to be imported when overwriting

* Add a basic regression test for creating a rule with ML params

* Prevent users from changing an existing Rule's type
  • Loading branch information
rylnd committed Mar 20, 2020
1 parent c7df9c8 commit 1e87f0a
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import { isMlRule } from '../../helpers';

interface SelectRuleTypeProps {
field: FieldHook;
isReadOnly: boolean;
}

export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ field }) => {
export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ field, isReadOnly = false }) => {
const ruleType = field.value as RuleType;
const setType = useCallback(
(type: RuleType) => {
Expand All @@ -37,6 +38,7 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ field }) => {
description={i18n.QUERY_TYPE_DESCRIPTION}
icon={<EuiIcon size="l" type="search" />}
selectable={{
isDisabled: isReadOnly,
onClick: setQuery,
isSelected: !isMlRule(ruleType),
}}
Expand All @@ -49,6 +51,7 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ field }) => {
isDisabled={!license}
icon={<EuiIcon size="l" type="machineLearningApp" />}
selectable={{
isDisabled: isReadOnly,
onClick: setMl,
isSelected: isMlRule(ruleType),
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,13 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
<>
<StepContentWrapper addPadding={!isUpdateView}>
<Form form={form} data-test-subj="stepDefineRule">
<UseField path="ruleType" component={SelectRuleType} />
<UseField
path="ruleType"
component={SelectRuleType}
componentProps={{
isReadOnly: isUpdateView,
}}
/>
<EuiFormRow fullWidth style={{ display: localIsMlRule ? 'none' : 'flex' }}>
<>
<CommonUseField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,24 @@ export const getResult = (): RuleAlertType => ({
scheduledTaskId: '2dabe330-0702-11ea-8b50-773b89126888',
});

export const getMlResult = (): RuleAlertType => {
const result = getResult();

return {
...result,
params: {
...result.params,
query: undefined,
language: undefined,
filters: undefined,
index: undefined,
type: 'machine_learning',
anomalyThreshold: 44,
machineLearningJobId: 'some_job_id',
},
};
};

export const updateActionResult = (): ActionResult => ({
id: 'result-1',
actionTypeId: 'action-id-1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ export const importRulesRoute = (router: IRouter, config: LegacyServices['config
references,
note,
version,
anomalyThreshold,
machineLearningJobId,
});
resolve({ rule_id: ruleId, status_code: 200 });
} else if (rule != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { actionsClientMock } from '../../../../../../../plugins/actions/server/mocks';
import { getMlResult } from '../routes/__mocks__/request_responses';
import { createRules } from './create_rules';

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

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

it('calls the alertsClient with ML params', async () => {
const params = {
...getMlResult().params,
anomalyThreshold: 55,
machineLearningJobId: 'new_job_id',
};

await createRules({
alertsClient,
actionsClient,
...params,
ruleId: 'new-rule-id',
enabled: true,
interval: '',
name: '',
tags: [],
});

expect(alertsClient.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
params: expect.objectContaining({
anomalyThreshold: 55,
machineLearningJobId: 'new_job_id',
}),
}),
})
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks';
import { alertsClientMock } from '../../../../../../../plugins/alerting/server/mocks';
import { actionsClientMock } from '../../../../../../../plugins/actions/server/mocks';
import { getMlResult } from '../routes/__mocks__/request_responses';
import { patchRules } from './patch_rules';

describe('patchRules', () => {
let actionsClient: ReturnType<typeof actionsClientMock.create>;
let alertsClient: ReturnType<typeof alertsClientMock.create>;
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;

beforeEach(() => {
actionsClient = actionsClientMock.create();
alertsClient = alertsClientMock.create();
savedObjectsClient = savedObjectsClientMock.create();
});

it('calls the alertsClient with ML params', async () => {
alertsClient.get.mockResolvedValue(getMlResult());
const params = {
...getMlResult().params,
anomalyThreshold: 55,
machineLearningJobId: 'new_job_id',
};

await patchRules({
alertsClient,
actionsClient,
savedObjectsClient,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
...params,
});

expect(alertsClient.update).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
params: expect.objectContaining({
anomalyThreshold: 55,
machineLearningJobId: 'new_job_id',
}),
}),
})
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export const patchRules = async ({
version,
throttle,
lists,
anomalyThreshold,
machineLearningJobId,
}: PatchRuleParams): Promise<PartialAlert | null> => {
const rule = await readRules({ alertsClient, ruleId, id });
if (rule == null) {
Expand Down Expand Up @@ -79,6 +81,8 @@ export const patchRules = async ({
throttle,
note,
lists,
anomalyThreshold,
machineLearningJobId,
});

const nextParams = defaults(
Expand Down Expand Up @@ -109,6 +113,8 @@ export const patchRules = async ({
note,
version: calculatedVersion,
lists,
anomalyThreshold,
machineLearningJobId,
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks';
import { alertsClientMock } from '../../../../../../../plugins/alerting/server/mocks';
import { actionsClientMock } from '../../../../../../../plugins/actions/server/mocks';
import { getMlResult } from '../routes/__mocks__/request_responses';
import { updateRules } from './update_rules';

describe('updateRules', () => {
let actionsClient: ReturnType<typeof actionsClientMock.create>;
let alertsClient: ReturnType<typeof alertsClientMock.create>;
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;

beforeEach(() => {
actionsClient = actionsClientMock.create();
alertsClient = alertsClientMock.create();
savedObjectsClient = savedObjectsClientMock.create();
});

it('calls the alertsClient with ML params', async () => {
alertsClient.get.mockResolvedValue(getMlResult());

const params = {
...getMlResult().params,
anomalyThreshold: 55,
machineLearningJobId: 'new_job_id',
};

await updateRules({
alertsClient,
actionsClient,
savedObjectsClient,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
...params,
enabled: true,
interval: '',
name: '',
tags: [],
});

expect(alertsClient.update).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
params: expect.objectContaining({
anomalyThreshold: 55,
machineLearningJobId: 'new_job_id',
}),
}),
})
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export const updateRules = async ({
throttle,
note,
lists,
anomalyThreshold,
machineLearningJobId,
}: UpdateRuleParams): Promise<PartialAlert | null> => {
const rule = await readRules({ alertsClient, ruleId, id });
if (rule == null) {
Expand Down Expand Up @@ -78,6 +80,8 @@ export const updateRules = async ({
version,
throttle,
note,
anomalyThreshold,
machineLearningJobId,
});

// TODO: Remove this and use regular lists once the feature is stable for a release
Expand Down Expand Up @@ -115,6 +119,8 @@ export const updateRules = async ({
references,
note,
version: calculatedVersion,
anomalyThreshold,
machineLearningJobId,
...listsParam,
},
},
Expand Down

0 comments on commit 1e87f0a

Please sign in to comment.