Skip to content

Commit

Permalink
[Security Solutions][Detection Engine] Fixes bug with not being able …
Browse files Browse the repository at this point in the history
…to duplicate indicator matches (#92565)

## Summary

Fixes an unreleased regression bug where indicator rules could not be be duplicated.
#90356

- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
  • Loading branch information
FrankHassanabad authored Feb 24, 2021
1 parent 510bc69 commit ebb0435
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,15 @@ import {
} from '../../tasks/alerts';
import {
changeRowsPerPageTo300,
duplicateFirstRule,
duplicateRuleFromMenu,
filterByCustomRules,
goToCreateNewRule,
goToRuleDetails,
waitForRulesTableToBeLoaded,
} from '../../tasks/alerts_detection_rules';
import { cleanKibana } from '../../tasks/common';
import { createCustomIndicatorRule } from '../../tasks/api_calls/rules';
import { cleanKibana, reload } from '../../tasks/common';
import {
createAndActivateRule,
fillAboutRuleAndContinue,
Expand All @@ -92,8 +95,10 @@ import {
waitForAlertsToPopulate,
waitForTheRuleToBeExecuted,
} from '../../tasks/create_new_rule';
import { waitForKibana } from '../../tasks/edit_rule';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { goBackToAllRulesTable } from '../../tasks/rule_details';

import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation';

Expand Down Expand Up @@ -465,5 +470,30 @@ describe('indicator match', () => {
cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore);
});
});

describe('Duplicates the indicator rule', () => {
beforeEach(() => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
goToManageAlertsDetectionRules();
createCustomIndicatorRule(newThreatIndicatorRule);
reload();
});

it('Allows the rule to be duplicated from the table', () => {
waitForKibana();
duplicateFirstRule();
cy.contains(RULE_NAME, `${newThreatIndicatorRule.name} [Duplicate]`);
});

it('Allows the rule to be duplicated from the edit screen', () => {
waitForKibana();
goToRuleDetails();
duplicateRuleFromMenu();
goBackToAllRulesTable();
reload();
cy.contains(RULE_NAME, `${newThreatIndicatorRule.name} [Duplicate]`);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]';

export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]';

export const DUPLICATE_RULE_ACTION_BTN = '[data-test-subj="duplicateRuleAction"]';

export const DUPLICATE_RULE_MENU_PANEL_BTN = '[data-test-subj="rules-details-duplicate-rule"]';

export const REFRESH_BTN = '[data-test-subj="refreshRulesAction"] button';

export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]';

export const ELASTIC_RULES_BTN = '[data-test-subj="showElasticRulesFilterButton"]';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE,
rowsPerPageSelector,
pageSelector,
DUPLICATE_RULE_ACTION_BTN,
DUPLICATE_RULE_MENU_PANEL_BTN,
} from '../screens/alerts_detection_rules';
import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details';

Expand All @@ -45,6 +47,33 @@ export const editFirstRule = () => {
cy.get(EDIT_RULE_ACTION_BTN).click();
};

export const duplicateFirstRule = () => {
cy.get(COLLAPSED_ACTION_BTN).should('be.visible');
cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
cy.get(DUPLICATE_RULE_ACTION_BTN).should('be.visible');
cy.get(DUPLICATE_RULE_ACTION_BTN).click();
};

/**
* Duplicates the rule from the menu and does additional
* pipes and checking that the elements are present on the
* page as well as removed when doing the clicks to help reduce
* flake.
*/
export const duplicateRuleFromMenu = () => {
cy.get(ALL_ACTIONS).should('be.visible');
cy.root()
.pipe(($el) => {
$el.find(ALL_ACTIONS).trigger('click');
return $el.find(DUPLICATE_RULE_MENU_PANEL_BTN);
})
.should(($el) => expect($el).to.be.visible);
// Because of a fade effect and fast clicking this can produce more than one click
cy.get(DUPLICATE_RULE_MENU_PANEL_BTN)
.pipe(($el) => $el.trigger('click'))
.should('not.be.visible');
};

export const deleteFirstRule = () => {
cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
cy.get(DELETE_RULE_ACTION_BTN).click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { CustomRule } from '../../objects/rule';
import { CustomRule, ThreatIndicatorRule } from '../../objects/rule';

export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') =>
cy.request({
Expand All @@ -29,6 +29,44 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') =>
failOnStatusCode: false,
});

export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') =>
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
description: rule.description,
interval: '10s',
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'threat_match',
threat_mapping: [
{
entries: [
{
field: rule.indicatorMapping,
type: 'mapping',
value: rule.indicatorMapping,
},
],
},
],
threat_query: '*:*',
threat_language: 'kuery',
threat_filters: [],
threat_index: ['mock*'],
threat_indicator_path: '',
from: 'now-17520h',
index: ['exceptions-*'],
query: rule.customQuery || '*:*',
language: 'kuery',
enabled: false,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});

export const createCustomRuleActivated = (rule: CustomRule, ruleId = '1') =>
cy.request({
method: 'POST',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import * as H from 'history';
import React, { Dispatch } from 'react';

import { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request';
import {
deleteRules,
duplicateRules,
Expand All @@ -28,6 +29,7 @@ import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../../../common/lib/t

import * as i18n from '../translations';
import { bucketRulesResponse } from './helpers';
import { transformOutput } from '../../../../containers/detection_engine/rules/transforms';

export const editRuleAction = (rule: Rule, history: H.History) => {
history.push(getEditRuleUrl(rule.id));
Expand All @@ -41,7 +43,11 @@ export const duplicateRulesAction = async (
) => {
try {
dispatch({ type: 'loadingRuleIds', ids: ruleIds, actionType: 'duplicate' });
const response = await duplicateRules({ rules });
const response = await duplicateRules({
// We cast this back and forth here as the front end types are not really the right io-ts ones
// and the two types conflict with each other.
rules: rules.map((rule) => transformOutput(rule as CreateRulesSchema) as Rule),
});
const { errors } = bucketRulesResponse(response);
if (errors.length > 0) {
displayErrorToast(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const getActions = (
enabled: (rowItem: Rule) => canEditRuleWithActions(rowItem, actionsPrivileges),
},
{
'data-test-subj': 'duplicateRuleAction',
description: i18n.DUPLICATE_RULE,
icon: 'copy',
name: !actionsPrivileges ? (
Expand Down

0 comments on commit ebb0435

Please sign in to comment.