[Security Solution][Detections] adds missing bulk edit data view tests (#141915)

## Summary

- addresses https://github.com/elastic/kibana/issues/135201 
- adds Data View cypress and integration tests according to [Data view Bulk Edit test plan](https://docs.google.com/document/d/116x7ITTTJQ6cTiwaGK831_f6Ox7XB3qyLiHxC3Cmf8w/edit#heading=h.j583i3o7bg2g) (internal document)
- integration tests were added earlier in https://github.com/elastic/kibana/pull/138448
This commit is contained in:
Vitalii Dmyterko 2022-09-28 13:58:57 +01:00 committed by GitHub
parent 254b9a525d
commit c7301e51f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 344 additions and 115 deletions

View file

@ -0,0 +1,177 @@
/*
* 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 {
RULES_BULK_EDIT_DATA_VIEWS_WARNING,
RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX,
} from '../../screens/rules_bulk_edit';
import { DATA_VIEW_DETAILS, INDEX_PATTERNS_DETAILS } from '../../screens/rule_details';
import {
waitForRulesTableToBeLoaded,
goToRuleDetails,
selectNumberOfRules,
} from '../../tasks/alerts_detection_rules';
import {
typeIndexPatterns,
waitForBulkEditActionToFinish,
submitBulkEditForm,
checkOverwriteDataViewCheckbox,
checkOverwriteIndexPatternsCheckbox,
openBulkEditAddIndexPatternsForm,
openBulkEditDeleteIndexPatternsForm,
} from '../../tasks/rules_bulk_edit';
import { hasIndexPatterns, getDetails, assertDetailsNotExist } from '../../tasks/rule_details';
import { login, visitWithoutDateRange } from '../../tasks/login';
import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
import {
createCustomRule,
createCustomIndicatorRule,
createEventCorrelationRule,
createThresholdRule,
createNewTermsRule,
createSavedQueryRule,
} from '../../tasks/api_calls/rules';
import { cleanKibana, deleteAlertsAndRules, postDataView } from '../../tasks/common';
import {
getEqlRule,
getNewThreatIndicatorRule,
getNewRule,
getNewThresholdRule,
getNewTermsRule,
} from '../../objects/rule';
import { esArchiverResetKibana } from '../../tasks/es_archiver';
const DATA_VIEW_ID = 'auditbeat';
const expectedIndexPatterns = ['index-1-*', 'index-2-*'];
const expectedNumberOfCustomRulesToBeEdited = 6;
const indexDataSource = { dataView: DATA_VIEW_ID, type: 'dataView' } as const;
const defaultRuleData = {
dataSource: indexDataSource,
};
describe('Detection rules, bulk edit, data view', () => {
before(() => {
cleanKibana();
login();
});
beforeEach(() => {
deleteAlertsAndRules();
esArchiverResetKibana();
postDataView(DATA_VIEW_ID);
createCustomRule({ ...getNewRule(), ...defaultRuleData }, '1');
createEventCorrelationRule({ ...getEqlRule(), ...defaultRuleData }, '2');
createCustomIndicatorRule({ ...getNewThreatIndicatorRule(), ...defaultRuleData }, '3');
createThresholdRule({ ...getNewThresholdRule(), ...defaultRuleData }, '4');
createNewTermsRule({ ...getNewTermsRule(), ...defaultRuleData }, '5');
createSavedQueryRule({ ...getNewRule(), ...defaultRuleData, savedId: 'mocked' }, '6');
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
waitForRulesTableToBeLoaded();
});
it('Add index patterns to custom rules with configured data view', () => {
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
openBulkEditAddIndexPatternsForm();
typeIndexPatterns(expectedIndexPatterns);
submitBulkEditForm();
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
// check if rule still has data view and index patterns field does not exist
goToRuleDetails();
getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID);
assertDetailsNotExist(INDEX_PATTERNS_DETAILS);
});
it('Add index patterns to custom rules with configured data view when data view checkbox is checked', () => {
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
openBulkEditAddIndexPatternsForm();
typeIndexPatterns(expectedIndexPatterns);
// click on data view overwrite checkbox, ensure warning is displayed
cy.get(RULES_BULK_EDIT_DATA_VIEWS_WARNING).should('not.exist');
checkOverwriteDataViewCheckbox();
cy.get(RULES_BULK_EDIT_DATA_VIEWS_WARNING).should('be.visible');
submitBulkEditForm();
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
// check if rule has been updated with index patterns and data view does not exist
goToRuleDetails();
hasIndexPatterns(expectedIndexPatterns.join(''));
assertDetailsNotExist(DATA_VIEW_DETAILS);
});
it('Overwrite index patterns in custom rules with configured data view', () => {
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
openBulkEditAddIndexPatternsForm();
typeIndexPatterns(expectedIndexPatterns);
checkOverwriteIndexPatternsCheckbox();
submitBulkEditForm();
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
// check if rule still has data view and index patterns field does not exist
goToRuleDetails();
getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID);
assertDetailsNotExist(INDEX_PATTERNS_DETAILS);
});
it('Overwrite index patterns in custom rules with configured data view when data view checkbox is checked', () => {
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
openBulkEditAddIndexPatternsForm();
typeIndexPatterns(expectedIndexPatterns);
checkOverwriteIndexPatternsCheckbox();
checkOverwriteDataViewCheckbox();
submitBulkEditForm();
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
// check if rule has been overwritten with index patterns and data view does not exist
goToRuleDetails();
hasIndexPatterns(expectedIndexPatterns.join(''));
assertDetailsNotExist(DATA_VIEW_DETAILS);
});
it('Delete index patterns in custom rules with configured data view', () => {
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
openBulkEditDeleteIndexPatternsForm();
typeIndexPatterns(expectedIndexPatterns);
// in delete form data view checkbox is absent
cy.get(RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX).should('not.exist');
submitBulkEditForm();
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
// check if rule still has data view and index patterns field does not exist
goToRuleDetails();
getDetails(DATA_VIEW_DETAILS).contains(DATA_VIEW_ID);
});
});

View file

@ -32,6 +32,9 @@ export const RULES_BULK_EDIT_INDEX_PATTERNS = '[data-test-subj="bulkEditRulesInd
export const RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX =
'[data-test-subj="bulkEditRulesOverwriteIndexPatterns"]';
export const RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX =
'[data-test-subj="bulkEditRulesOverwriteRulesWithDataViews"]';
export const RULES_BULK_EDIT_TAGS = '[data-test-subj="bulkEditRulesTags"]';
export const RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX =
@ -48,6 +51,9 @@ export const RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR =
export const RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING =
'[data-test-subj="bulkEditRulesTimelineTemplateWarning"]';
export const RULES_BULK_EDIT_DATA_VIEWS_WARNING =
'[data-test-subj="bulkEditRulesDataViewsWarning"]';
export const RULES_BULK_EDIT_SCHEDULES_WARNING = '[data-test-subj="bulkEditRulesSchedulesWarning"]';
export const UPDATE_SCHEDULE_INTERVAL_INPUT =

View file

@ -53,7 +53,8 @@ export const createCustomRule = (
severity: rule.severity.toLocaleLowerCase(),
type: 'query',
from: 'now-50000h',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : '',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
query: rule.customQuery,
language: 'kuery',
enabled: false,
@ -71,83 +72,80 @@ export const createCustomRule = (
});
export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_testing') => {
if (rule.dataSource.type === 'indexPatterns') {
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'eql',
index: rule.dataSource.index,
query: rule.customQuery,
language: 'eql',
enabled: true,
tags: rule.tags,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
});
}
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'eql',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
query: rule.customQuery,
language: 'eql',
enabled: true,
tags: rule.tags,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
});
};
export const createThresholdRule = (rule: ThresholdRule, ruleId = 'rule_testing') => {
if (rule.dataSource.type === 'indexPatterns') {
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'threshold',
index: rule.dataSource.index,
query: rule.customQuery,
threshold: {
field: [rule.thresholdField],
value: parseInt(rule.threshold, 10),
cardinality: [],
},
enabled: true,
tags: rule.tags,
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'threshold',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
query: rule.customQuery,
threshold: {
field: [rule.thresholdField],
value: parseInt(rule.threshold, 10),
cardinality: [],
},
headers: { 'kbn-xsrf': 'cypress-creds' },
});
}
enabled: true,
tags: rule.tags,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
});
};
export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing') => {
if (rule.dataSource.type === 'indexPatterns') {
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'new_terms',
index: rule.dataSource.index,
query: rule.customQuery,
new_terms_fields: rule.newTermsFields,
history_window_start: `now-${rule.historyWindowSize.interval}${rule.historyWindowSize.type}`,
enabled: true,
tags: rule.tags,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
});
}
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'new_terms',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
query: rule.customQuery,
new_terms_fields: rule.newTermsFields,
history_window_start: `now-${rule.historyWindowSize.interval}${rule.historyWindowSize.type}`,
enabled: true,
tags: rule.tags,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
});
};
export const createSavedQueryRule = (
@ -166,7 +164,8 @@ export const createSavedQueryRule = (
severity: rule.severity.toLocaleLowerCase(),
type: 'saved_query',
from: 'now-50000h',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : '',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
saved_id: rule.savedId,
language: 'kuery',
enabled: false,
@ -184,49 +183,48 @@ export const createSavedQueryRule = (
});
export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') => {
if (rule.dataSource.type === 'indexPatterns') {
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
// Default interval is 1m, our tests config overwrite this to 1s
// See https://github.com/elastic/kibana/pull/125396 for details
interval: '10s',
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'threat_match',
timeline_id: rule.timeline.templateTimelineId,
timeline_title: rule.timeline.title,
threat_mapping: [
{
entries: [
{
field: rule.indicatorMappingField,
type: 'mapping',
value: rule.indicatorIndexField,
},
],
},
],
threat_query: '*:*',
threat_language: 'kuery',
threat_filters: [],
threat_index: rule.indicatorIndexPattern,
threat_indicator_path: rule.threatIndicatorPath,
from: 'now-50000h',
index: rule.dataSource.index,
query: rule.customQuery || '*:*',
language: 'kuery',
enabled: true,
tags: rule.tags,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
}
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
// Default interval is 1m, our tests config overwrite this to 1s
// See https://github.com/elastic/kibana/pull/125396 for details
interval: '10s',
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'threat_match',
timeline_id: rule.timeline.templateTimelineId,
timeline_title: rule.timeline.title,
threat_mapping: [
{
entries: [
{
field: rule.indicatorMappingField,
type: 'mapping',
value: rule.indicatorIndexField,
},
],
},
],
threat_query: '*:*',
threat_language: 'kuery',
threat_filters: [],
threat_index: rule.indicatorIndexPattern,
threat_indicator_path: rule.threatIndicatorPath,
from: 'now-50000h',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
query: rule.customQuery || '*:*',
language: 'kuery',
enabled: true,
tags: rule.tags,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
};
export const createCustomRuleEnabled = (

View file

@ -158,6 +158,9 @@ export const goBackToAllRulesTable = () => {
export const getDetails = (title: string | RegExp) =>
cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION);
export const assertDetailsNotExist = (title: string | RegExp) =>
cy.get(DETAILS_TITLE).contains(title).should('not.exist');
export const hasIndexPatterns = (indexPatterns: string) => {
cy.get(DEFINITION_DETAILS).within(() => {
getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns);

View file

@ -30,6 +30,7 @@ import {
APPLY_TIMELINE_RULE_BULK_MENU_ITEM,
RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX,
RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX,
RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX,
RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR,
UPDATE_SCHEDULE_MENU_ITEM,
UPDATE_SCHEDULE_INTERVAL_INPUT,
@ -159,6 +160,14 @@ export const checkOverwriteIndexPatternsCheckbox = () => {
.should('be.checked');
};
export const checkOverwriteDataViewCheckbox = () => {
cy.get(RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX)
.should('have.text', 'Apply changes to rules configured with data views')
.click()
.get('input')
.should('be.checked');
};
export const selectTimelineTemplate = (timelineTitle: string) => {
cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR).click();
cy.get(TIMELINE_SEARCHBOX).type(`${timelineTitle}{enter}`).should('not.exist');

View file

@ -1829,6 +1829,42 @@ export default ({ getService }: FtrProviderContext): void => {
expect(setIndexRule.data_view_id).to.eql(undefined);
});
it('should return error when set an empty index pattern to a rule and overwrite the data view when overwrite_data_views is true', async () => {
const dataViewId = 'index1-*';
const simpleRule = {
...getSimpleRule(),
index: undefined,
data_view_id: dataViewId,
};
const rule = await createRule(supertest, log, simpleRule);
const { body } = await postBulkAction()
.send({
query: '',
action: BulkAction.edit,
[BulkAction.edit]: [
{
type: BulkActionEditType.set_index_patterns,
value: [],
overwrite_data_views: true,
},
],
})
.expect(500);
expect(body.attributes.summary).to.eql({ failed: 1, succeeded: 0, total: 1 });
expect(body.attributes.errors[0]).to.eql({
message: "Mutated params invalid: Index patterns can't be empty",
status_code: 500,
rules: [
{
id: rule.id,
name: rule.name,
},
],
});
});
it('should NOT set an index pattern to a rule and overwrite the data view when overwrite_data_views is false', async () => {
const ruleId = 'ruleId';
const dataViewId = 'index1-*';