[Security Solution] [Platform] Update bulk edit rules to remove data view id from rule if bulk adding index (#136560)

This commit is contained in:
Devin W. Hurley 2022-07-21 04:08:03 -04:00 committed by GitHub
parent 96e284fdf4
commit 0027f63783
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 358 deletions

View file

@ -161,13 +161,15 @@ const IndexPatternsFormComponent = ({
</EuiCallOut>
</EuiFormRow>
)}
<CommonUseField
path="overwriteDataViews"
componentProps={{
idAria: 'bulkEditRulesOverwriteRulesWithDataViews',
'data-test-subj': 'bulkEditRulesOverwriteRulesWithDataViews',
}}
/>
{editAction === BulkActionEditType.add_index_patterns && (
<CommonUseField
path="overwriteDataViews"
componentProps={{
idAria: 'bulkEditRulesOverwriteRulesWithDataViews',
'data-test-subj': 'bulkEditRulesOverwriteRulesWithDataViews',
}}
/>
)}
{overwriteDataViews && (
<EuiFormRow fullWidth>
<EuiCallOut color="warning" size="s" data-test-subj="bulkEditRulesDataViewsWarning">
@ -178,6 +180,16 @@ const IndexPatternsFormComponent = ({
</EuiCallOut>
</EuiFormRow>
)}
{editAction === BulkActionEditType.delete_index_patterns && (
<EuiFormRow fullWidth>
<EuiCallOut color="warning" size="s" data-test-subj="bulkEditRulesDataViewsWarning">
<FormattedMessage
id="xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.deleteIndexPattnersDataViewsOverwriteWarningCallout"
defaultMessage="If you have selected rules which depend on a data view this action will not have any effect on those rules."
/>
</EuiCallOut>
</EuiFormRow>
)}
</BulkEditFormWrapper>
);
};

View file

@ -1,249 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
addItemsToArray,
deleteItemsFromArray,
applyBulkActionEditToRule,
} from './bulk_action_edit';
import { BulkActionEditType } from '../../../../common/detection_engine/schemas/common/schemas';
import type { RuleAlertType } from './types';
describe('bulk_action_edit', () => {
describe('addItemsToArray', () => {
test('should add single item to array', () => {
expect(addItemsToArray(['a', 'b', 'c'], ['d'])).toEqual(['a', 'b', 'c', 'd']);
});
test('should add multiple items to array', () => {
expect(addItemsToArray(['a', 'b', 'c'], ['d', 'e'])).toEqual(['a', 'b', 'c', 'd', 'e']);
});
test('should not allow to add duplicated items', () => {
expect(addItemsToArray(['a', 'c'], ['b', 'c'])).toEqual(['a', 'c', 'b']);
});
});
describe('deleteItemsFromArray', () => {
test('should remove single item from array', () => {
expect(deleteItemsFromArray(['a', 'b', 'c'], ['c'])).toEqual(['a', 'b']);
});
test('should remove multiple items from array', () => {
expect(deleteItemsFromArray(['a', 'b', 'c'], ['b', 'c'])).toEqual(['a']);
});
test('should return array unchanged if items to remove absent in array', () => {
expect(deleteItemsFromArray(['a', 'c'], ['x', 'z'])).toEqual(['a', 'c']);
});
});
describe('applyBulkActionEditToRule', () => {
const getRuleMock = (params = {}) => ({
tags: ['tag1', 'tag2'],
params: { type: 'query', index: ['initial-index-*'], ...params },
});
describe('tags', () => {
test('should add new tags to rule', () => {
const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, {
type: BulkActionEditType.add_tags,
value: ['new_tag'],
});
expect(editedRule.tags).toEqual(['tag1', 'tag2', 'new_tag']);
});
test('should remove tag from rule', () => {
const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, {
type: BulkActionEditType.delete_tags,
value: ['tag1'],
});
expect(editedRule.tags).toEqual(['tag2']);
});
test('should rewrite tags in rule', () => {
const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, {
type: BulkActionEditType.set_tags,
value: ['tag_r_1', 'tag_r_2'],
});
expect(editedRule.tags).toEqual(['tag_r_1', 'tag_r_2']);
});
});
describe('index_patterns', () => {
test('should add new index pattern to rule', () => {
const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, {
type: BulkActionEditType.add_index_patterns,
value: ['my-index-*'],
});
expect(editedRule.params).toHaveProperty('index', ['initial-index-*', 'my-index-*']);
});
test('should remove index pattern from rule', () => {
const editedRule = applyBulkActionEditToRule(
{ params: { index: ['initial-index-*', 'index-2-*'] } } as RuleAlertType,
{
type: BulkActionEditType.delete_index_patterns,
value: ['index-2-*'],
}
);
expect(editedRule.params).toHaveProperty('index', ['initial-index-*']);
});
test('should rewrite index pattern in rule', () => {
const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, {
type: BulkActionEditType.set_index_patterns,
value: ['index'],
});
expect(editedRule.params).toHaveProperty('index', ['index']);
});
test('should throw error on adding index pattern if rule is of machine learning type', () => {
expect(() =>
applyBulkActionEditToRule({ params: { type: 'machine_learning' } } as RuleAlertType, {
type: BulkActionEditType.add_index_patterns,
value: ['my-index-*'],
})
).toThrow(
"Index patterns can't be added. Machine learning rule doesn't have index patterns property"
);
});
test('should throw error on deleting index pattern if rule is of machine learning type', () => {
expect(() =>
applyBulkActionEditToRule({ params: { type: 'machine_learning' } } as RuleAlertType, {
type: BulkActionEditType.delete_index_patterns,
value: ['my-index-*'],
})
).toThrow(
"Index patterns can't be deleted. Machine learning rule doesn't have index patterns property"
);
});
test('should throw error on overwriting index pattern if rule is of machine learning type', () => {
expect(() =>
applyBulkActionEditToRule({ params: { type: 'machine_learning' } } as RuleAlertType, {
type: BulkActionEditType.set_index_patterns,
value: ['my-index-*'],
})
).toThrow(
"Index patterns can't be overwritten. Machine learning rule doesn't have index patterns property"
);
});
test('should throw error if all index patterns are deleted', () => {
expect(() =>
applyBulkActionEditToRule({ params: { index: ['my-index-*'] } } as RuleAlertType, {
type: BulkActionEditType.delete_index_patterns,
value: ['my-index-*'],
})
).toThrow("Can't delete all index patterns. At least one index pattern must be left");
});
test('should throw error if all index patterns are rewritten with empty list', () => {
expect(() =>
applyBulkActionEditToRule({ params: { index: ['my-index-*'] } } as RuleAlertType, {
type: BulkActionEditType.set_index_patterns,
value: [],
})
).toThrow("Index patterns can't be overwritten with empty list");
});
describe('overwriteDataViews', () => {
describe('overwriteDataViews is true', () => {
test('should add new index pattern to rule', () => {
const editedRule = applyBulkActionEditToRule(
getRuleMock({ dataViewId: 'logs-*', index: [] }) as RuleAlertType,
{
type: BulkActionEditType.add_index_patterns,
value: ['my-index-*'],
overwriteDataViews: true,
}
);
expect(editedRule.params).toHaveProperty('index', ['my-index-*']);
expect(editedRule.params).toHaveProperty('dataViewId', undefined);
});
test('should remove index pattern from rule', () => {
const editedRule = applyBulkActionEditToRule(
getRuleMock({ dataViewId: 'logs-*', index: ['index', 'index-2-*'] }) as RuleAlertType,
{
type: BulkActionEditType.delete_index_patterns,
value: ['index-2-*'],
overwriteDataViews: true,
}
);
expect(editedRule.params).toHaveProperty('index', ['index']);
expect(editedRule.params).toHaveProperty('dataViewId', undefined);
});
test('should rewrite index pattern in rule', () => {
const editedRule = applyBulkActionEditToRule(
getRuleMock({ dataViewId: 'logs-*', index: ['index', 'index-1'] }) as RuleAlertType,
{
type: BulkActionEditType.set_index_patterns,
value: ['index'],
overwriteDataViews: true,
}
);
expect(editedRule.params).toHaveProperty('index', ['index']);
expect(editedRule.params).toHaveProperty('dataViewId', undefined);
});
});
describe('overwriteDataViews is false', () => {
test('should NOT remove dataViewId from rule if adding index pattern', () => {
const editedRule = applyBulkActionEditToRule(
getRuleMock({ dataViewId: 'logs-*', index: [] }) as RuleAlertType,
{
type: BulkActionEditType.add_index_patterns,
value: ['my-index-*'],
overwriteDataViews: false,
}
);
expect(editedRule.params).toHaveProperty('dataViewId', 'logs-*');
});
test('should NOT remove dataViewId from rule if deleting index pattern', () => {
const editedRule = applyBulkActionEditToRule(
getRuleMock({ dataViewId: 'logs-*', index: ['index', 'index-2-*'] }) as RuleAlertType,
{
type: BulkActionEditType.delete_index_patterns,
value: ['index-2-*'],
overwriteDataViews: false,
}
);
expect(editedRule.params).toHaveProperty('dataViewId', 'logs-*');
});
test('should NOT remove dataViewId from rule if setting index pattern', () => {
const editedRule = applyBulkActionEditToRule(
getRuleMock({ dataViewId: 'logs-*', index: [] }) as RuleAlertType,
{
type: BulkActionEditType.set_index_patterns,
value: ['index'],
overwriteDataViews: false,
}
);
expect(editedRule.params).toHaveProperty('dataViewId', 'logs-*');
});
});
});
});
describe('timeline', () => {
test('should set timeline', () => {
const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, {
type: BulkActionEditType.set_timeline,
value: {
timeline_id: '91832785-286d-4ebe-b884-1a208d111a70',
timeline_title: 'Test timeline',
},
});
expect(editedRule.params.timelineId).toBe('91832785-286d-4ebe-b884-1a208d111a70');
expect(editedRule.params.timelineTitle).toBe('Test timeline');
});
});
});
});

View file

@ -1,102 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { RuleAlertType } from './types';
import type { BulkActionEditPayload } from '../../../../common/detection_engine/schemas/common/schemas';
import { BulkActionEditType } from '../../../../common/detection_engine/schemas/common/schemas';
import { invariant } from '../../../../common/utils/invariant';
import { isMachineLearningParams } from '../signals/utils';
export const addItemsToArray = <T>(arr: T[], items: T[]): T[] =>
Array.from(new Set([...arr, ...items]));
export const deleteItemsFromArray = <T>(arr: T[], items: T[]): T[] => {
const itemsSet = new Set(items);
return arr.filter((item) => !itemsSet.has(item));
};
export const applyBulkActionEditToRule = (
existingRule: RuleAlertType,
action: BulkActionEditPayload
): RuleAlertType => {
const rule = { ...existingRule, params: { ...existingRule.params } };
switch (action.type) {
// tags actions
case BulkActionEditType.add_tags:
rule.tags = addItemsToArray(rule.tags ?? [], action.value);
break;
case BulkActionEditType.delete_tags:
rule.tags = deleteItemsFromArray(rule.tags ?? [], action.value);
break;
case BulkActionEditType.set_tags:
rule.tags = action.value;
break;
// index_patterns actions
// index pattern is not present in machine learning rule type, so we throw error on it
case BulkActionEditType.add_index_patterns:
invariant(
rule.params.type !== 'machine_learning',
"Index patterns can't be added. Machine learning rule doesn't have index patterns property"
);
if (!isMachineLearningParams(rule.params) && action.overwriteDataViews) {
rule.params.dataViewId = undefined;
}
rule.params.index = addItemsToArray(rule.params.index ?? [], action.value);
break;
case BulkActionEditType.delete_index_patterns:
invariant(
rule.params.type !== 'machine_learning',
"Index patterns can't be deleted. Machine learning rule doesn't have index patterns property"
);
if (!isMachineLearningParams(rule.params) && action.overwriteDataViews) {
rule.params.dataViewId = undefined;
}
rule.params.index = deleteItemsFromArray(rule.params.index ?? [], action.value);
invariant(
rule.params.index.length !== 0,
"Can't delete all index patterns. At least one index pattern must be left"
);
break;
case BulkActionEditType.set_index_patterns:
invariant(
rule.params.type !== 'machine_learning',
"Index patterns can't be overwritten. Machine learning rule doesn't have index patterns property"
);
invariant(action.value.length !== 0, "Index patterns can't be overwritten with empty list");
if (!isMachineLearningParams(rule.params) && action.overwriteDataViews) {
rule.params.dataViewId = undefined;
}
rule.params.index = action.value;
break;
// timeline actions
case BulkActionEditType.set_timeline:
const timelineId = action.value.timeline_id.trim() || undefined;
const timelineTitle = timelineId ? action.value.timeline_title : undefined;
rule.params.timelineId = timelineId;
rule.params.timelineTitle = timelineTitle;
break;
}
return rule;
};

View file

@ -11,6 +11,7 @@ import type { BulkActionEditForRuleParams } from '../../../../../common/detectio
import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/common/schemas';
import { invariant } from '../../../../../common/utils/invariant';
import { isMachineLearningParams } from '../../signals/utils';
export const addItemsToArray = <T>(arr: T[], items: T[]): T[] =>
Array.from(new Set([...arr, ...items]));
@ -35,6 +36,10 @@ const applyBulkActionEditToRuleParams = (
"Index patterns can't be added. Machine learning rule doesn't have index patterns property"
);
if (!isMachineLearningParams(ruleParams) && action.overwriteDataViews) {
ruleParams.dataViewId = undefined;
}
ruleParams.index = addItemsToArray(ruleParams.index ?? [], action.value);
break;

View file

@ -562,6 +562,42 @@ export default ({ getService }: FtrProviderContext): void => {
expect(deleteIndexRule.index).to.eql(['initial-index-*', 'index2-*']);
});
it('should add an index pattern to a rule and overwrite the data view', async () => {
const ruleId = 'ruleId';
const dataViewId = 'index1-*';
const simpleRule = {
...getSimpleRule(ruleId),
index: undefined,
data_view_id: dataViewId,
};
await createRule(supertest, log, simpleRule);
const { body: setIndexBody } = await postBulkAction()
.send({
query: '',
action: BulkAction.edit,
[BulkAction.edit]: [
{
type: BulkActionEditType.add_index_patterns,
value: ['initial-index-*'],
overwriteDataViews: true,
},
],
})
.expect(200);
expect(setIndexBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
// Check that the updated rule is returned with the response
expect(setIndexBody.attributes.results.updated[0].index).to.eql(['initial-index-*']);
expect(setIndexBody.attributes.results.updated[0].data_view_id).to.eql(undefined);
// Check that the updates have been persisted
const { body: setIndexRule } = await fetchRule(ruleId).expect(200);
expect(setIndexRule.index).to.eql(['initial-index-*']);
});
it('should set timeline values in rule', async () => {
const ruleId = 'ruleId';
const timelineId = '91832785-286d-4ebe-b884-1a208d111a70';