mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Adds Cypress test for index action in detection rules (#141069)
Co-authored-by: Dmitrii Shevchenko <dmshevch@gmail.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
b2ab340425
commit
db6a82530b
17 changed files with 534 additions and 197 deletions
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { formatMitreAttackDescription } from '../../helpers/rules';
|
||||
import type { Mitre } from '../../objects/rule';
|
||||
import {
|
||||
getNewRule,
|
||||
getExistingRule,
|
||||
|
@ -13,6 +14,7 @@ import {
|
|||
getEditedRule,
|
||||
getNewOverrideRule,
|
||||
} from '../../objects/rule';
|
||||
import type { CompleteTimeline } from '../../objects/timeline';
|
||||
import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts';
|
||||
|
||||
import {
|
||||
|
@ -46,6 +48,7 @@ import {
|
|||
FROM_VALIDATION_ERROR,
|
||||
EMAIL_ACTION_TO_INPUT,
|
||||
EMAIL_ACTION_SUBJECT_INPUT,
|
||||
SCHEDULE_CONTINUE_BUTTON,
|
||||
} from '../../screens/create_new_rule';
|
||||
import {
|
||||
ADDITIONAL_LOOK_BACK_DETAILS,
|
||||
|
@ -87,7 +90,7 @@ import {
|
|||
createAndEnableRule,
|
||||
fillAboutRule,
|
||||
fillAboutRuleAndContinue,
|
||||
fillDefineCustomRuleWithImportedQueryAndContinue,
|
||||
fillDefineCustomRuleAndContinue,
|
||||
fillEmailConnectorForm,
|
||||
fillScheduleRuleAndContinue,
|
||||
goToAboutStepTab,
|
||||
|
@ -108,19 +111,21 @@ describe('Custom query rules', () => {
|
|||
login();
|
||||
});
|
||||
describe('Custom detection rules creation', () => {
|
||||
const expectedUrls = getNewRule().referenceUrls.join('');
|
||||
const expectedFalsePositives = getNewRule().falsePositivesExamples.join('');
|
||||
const expectedTags = getNewRule().tags.join('');
|
||||
const expectedMitre = formatMitreAttackDescription(getNewRule().mitre);
|
||||
const expectedUrls = getNewRule().referenceUrls?.join('');
|
||||
const expectedFalsePositives = getNewRule().falsePositivesExamples?.join('');
|
||||
const expectedTags = getNewRule().tags?.join('');
|
||||
const mitreAttack = getNewRule().mitre as Mitre[];
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack);
|
||||
const expectedNumberOfRules = 1;
|
||||
|
||||
beforeEach(() => {
|
||||
const timeline = getNewRule().timeline as CompleteTimeline;
|
||||
deleteAlertsAndRules();
|
||||
createTimeline(getNewRule().timeline).then((response) => {
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap({
|
||||
...getNewRule(),
|
||||
timeline: {
|
||||
...getNewRule().timeline,
|
||||
...timeline,
|
||||
id: response.body.data.persistTimeline.timeline.savedObjectId,
|
||||
},
|
||||
}).as('rule');
|
||||
|
@ -129,7 +134,7 @@ describe('Custom query rules', () => {
|
|||
|
||||
it('Creates and enables a new rule', function () {
|
||||
visit(RULE_CREATION);
|
||||
fillDefineCustomRuleWithImportedQueryAndContinue(this.rule);
|
||||
fillDefineCustomRuleAndContinue(this.rule);
|
||||
fillAboutRuleAndContinue(this.rule);
|
||||
fillScheduleRuleAndContinue(this.rule);
|
||||
|
||||
|
@ -144,6 +149,7 @@ describe('Custom query rules', () => {
|
|||
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', this.rule.name);
|
||||
cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true });
|
||||
cy.get(ABOUT_CONTINUE_BTN).should('not.exist');
|
||||
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
|
||||
|
||||
createAndEnableRule();
|
||||
|
||||
|
@ -182,11 +188,11 @@ describe('Custom query rules', () => {
|
|||
cy.get(SCHEDULE_DETAILS).within(() => {
|
||||
getDetails(RUNS_EVERY_DETAILS).should(
|
||||
'have.text',
|
||||
`${getNewRule().runsEvery.interval}${getNewRule().runsEvery.type}`
|
||||
`${getNewRule().runsEvery?.interval}${getNewRule().runsEvery?.type}`
|
||||
);
|
||||
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
|
||||
'have.text',
|
||||
`${getNewRule().lookBack.interval}${getNewRule().lookBack.type}`
|
||||
`${getNewRule().lookBack?.interval}${getNewRule().lookBack?.type}`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -299,7 +305,7 @@ describe('Custom query rules', () => {
|
|||
|
||||
context('Edition', () => {
|
||||
const rule = getEditedRule();
|
||||
const expectedEditedtags = rule.tags.join('');
|
||||
const expectedEditedtags = rule.tags?.join('');
|
||||
const expectedEditedIndexPatterns =
|
||||
rule.dataSource.type === 'indexPatterns' &&
|
||||
rule.dataSource.index &&
|
||||
|
@ -349,7 +355,7 @@ describe('Custom query rules', () => {
|
|||
// expect about step to populate
|
||||
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name);
|
||||
cy.get(RULE_DESCRIPTION_INPUT).should('have.text', existingRule.description);
|
||||
cy.get(TAGS_FIELD).should('have.text', existingRule.tags.join(''));
|
||||
cy.get(TAGS_FIELD).should('have.text', existingRule.tags?.join(''));
|
||||
cy.get(SEVERITY_DROPDOWN).should('have.text', existingRule.severity);
|
||||
cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', existingRule.riskScore);
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
*/
|
||||
|
||||
import { formatMitreAttackDescription } from '../../helpers/rules';
|
||||
import type { Mitre } from '../../objects/rule';
|
||||
import { getDataViewRule } from '../../objects/rule';
|
||||
import type { CompleteTimeline } from '../../objects/timeline';
|
||||
import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts';
|
||||
|
||||
import {
|
||||
|
@ -50,7 +52,7 @@ import { postDataView } from '../../tasks/common';
|
|||
import {
|
||||
createAndEnableRule,
|
||||
fillAboutRuleAndContinue,
|
||||
fillDefineCustomRuleWithImportedQueryAndContinue,
|
||||
fillDefineCustomRuleAndContinue,
|
||||
fillScheduleRuleAndContinue,
|
||||
waitForAlertsToPopulate,
|
||||
waitForTheRuleToBeExecuted,
|
||||
|
@ -69,10 +71,11 @@ describe('Custom query rules', () => {
|
|||
|
||||
describe('Custom detection rules creation with data views', () => {
|
||||
const rule = getDataViewRule();
|
||||
const expectedUrls = rule.referenceUrls.join('');
|
||||
const expectedFalsePositives = rule.falsePositivesExamples.join('');
|
||||
const expectedTags = rule.tags.join('');
|
||||
const expectedMitre = formatMitreAttackDescription(rule.mitre);
|
||||
const expectedUrls = rule.referenceUrls?.join('');
|
||||
const expectedFalsePositives = rule.falsePositivesExamples?.join('');
|
||||
const expectedTags = rule.tags?.join('');
|
||||
const mitreAttack = rule.mitre as Mitre[];
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack);
|
||||
const expectedNumberOfRules = 1;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -80,12 +83,13 @@ describe('Custom query rules', () => {
|
|||
are creating a data view we'll use after and cleanKibana does not delete all the data views created, esArchiverReseKibana does.
|
||||
We don't use esArchiverReseKibana in all the tests because is a time-consuming method and we don't need to perform an exhaustive
|
||||
cleaning in all the other tests. */
|
||||
const timeline = rule.timeline as CompleteTimeline;
|
||||
esArchiverResetKibana();
|
||||
createTimeline(rule.timeline).then((response) => {
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap({
|
||||
...rule,
|
||||
timeline: {
|
||||
...rule.timeline,
|
||||
...timeline,
|
||||
id: response.body.data.persistTimeline.timeline.savedObjectId,
|
||||
},
|
||||
}).as('rule');
|
||||
|
@ -97,7 +101,7 @@ describe('Custom query rules', () => {
|
|||
|
||||
it('Creates and enables a new rule', function () {
|
||||
visit(RULE_CREATION);
|
||||
fillDefineCustomRuleWithImportedQueryAndContinue(this.rule);
|
||||
fillDefineCustomRuleAndContinue(this.rule);
|
||||
fillAboutRuleAndContinue(this.rule);
|
||||
fillScheduleRuleAndContinue(this.rule);
|
||||
createAndEnableRule();
|
||||
|
@ -138,11 +142,11 @@ describe('Custom query rules', () => {
|
|||
cy.get(SCHEDULE_DETAILS).within(() => {
|
||||
getDetails(RUNS_EVERY_DETAILS).should(
|
||||
'have.text',
|
||||
`${getDataViewRule().runsEvery.interval}${getDataViewRule().runsEvery.type}`
|
||||
`${getDataViewRule().runsEvery?.interval}${getDataViewRule().runsEvery?.type}`
|
||||
);
|
||||
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
|
||||
'have.text',
|
||||
`${getDataViewRule().lookBack.interval}${getDataViewRule().lookBack.type}`
|
||||
`${getDataViewRule().lookBack?.interval}${getDataViewRule().lookBack?.type}`
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import { getDetails } from '../../tasks/rule_details';
|
|||
import { createSavedQueryRule, createCustomRule } from '../../tasks/api_calls/rules';
|
||||
|
||||
import { RULE_CREATION, SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
|
||||
import type { CompleteTimeline } from '../../objects/timeline';
|
||||
|
||||
const savedQueryName = 'custom saved query';
|
||||
const savedQueryQuery = 'process.name: test';
|
||||
|
@ -56,11 +57,12 @@ describe('Custom saved_query rules', () => {
|
|||
beforeEach(() => {
|
||||
deleteAlertsAndRules();
|
||||
deleteSavedQueries();
|
||||
createTimeline(getNewRule().timeline).then((response) => {
|
||||
const timeline = getNewRule().timeline as CompleteTimeline;
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap({
|
||||
...getNewRule(),
|
||||
timeline: {
|
||||
...getNewRule().timeline,
|
||||
...timeline,
|
||||
id: response.body.data.persistTimeline.timeline.savedObjectId,
|
||||
},
|
||||
}).as('rule');
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { formatMitreAttackDescription } from '../../helpers/rules';
|
||||
import type { Mitre } from '../../objects/rule';
|
||||
import { getEqlRule, getEqlSequenceRule, getIndexPatterns } from '../../objects/rule';
|
||||
|
||||
import { ALERT_DATA_GRID, NUMBER_OF_ALERTS } from '../../screens/alerts';
|
||||
|
@ -59,6 +60,7 @@ import { login, visit } from '../../tasks/login';
|
|||
|
||||
import { RULE_CREATION } from '../../urls/navigation';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
|
||||
import type { CompleteTimeline } from '../../objects/timeline';
|
||||
|
||||
describe('EQL rules', () => {
|
||||
before(() => {
|
||||
|
@ -67,19 +69,21 @@ describe('EQL rules', () => {
|
|||
deleteAlertsAndRules();
|
||||
});
|
||||
describe('Detection rules, EQL', () => {
|
||||
const expectedUrls = getEqlRule().referenceUrls.join('');
|
||||
const expectedFalsePositives = getEqlRule().falsePositivesExamples.join('');
|
||||
const expectedTags = getEqlRule().tags.join('');
|
||||
const expectedMitre = formatMitreAttackDescription(getEqlRule().mitre);
|
||||
const expectedUrls = getEqlRule().referenceUrls?.join('');
|
||||
const expectedFalsePositives = getEqlRule().falsePositivesExamples?.join('');
|
||||
const expectedTags = getEqlRule().tags?.join('');
|
||||
const mitreAttack = getEqlRule().mitre as Mitre[];
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack);
|
||||
const expectedNumberOfRules = 1;
|
||||
const expectedNumberOfAlerts = '2 alerts';
|
||||
|
||||
beforeEach(() => {
|
||||
createTimeline(getEqlRule().timeline).then((response) => {
|
||||
const timeline = getEqlRule().timeline as CompleteTimeline;
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap({
|
||||
...getEqlRule(),
|
||||
timeline: {
|
||||
...getEqlRule().timeline,
|
||||
...timeline,
|
||||
id: response.body.data.persistTimeline.timeline.savedObjectId,
|
||||
},
|
||||
}).as('rule');
|
||||
|
@ -161,11 +165,12 @@ describe('EQL rules', () => {
|
|||
esArchiverLoad('auditbeat_big');
|
||||
});
|
||||
beforeEach(() => {
|
||||
createTimeline(getEqlSequenceRule().timeline).then((response) => {
|
||||
const timeline = getEqlSequenceRule().timeline as CompleteTimeline;
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap({
|
||||
...getEqlSequenceRule(),
|
||||
timeline: {
|
||||
...getEqlSequenceRule().timeline,
|
||||
...timeline,
|
||||
id: response.body.data.persistTimeline.timeline.savedObjectId,
|
||||
},
|
||||
}).as('rule');
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { formatMitreAttackDescription } from '../../helpers/rules';
|
||||
import type { Mitre } from '../../objects/rule';
|
||||
import {
|
||||
getIndexPatterns,
|
||||
getNewThreatIndicatorRule,
|
||||
|
@ -109,10 +110,11 @@ const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"';
|
|||
|
||||
describe('indicator match', () => {
|
||||
describe('Detection rules, Indicator Match', () => {
|
||||
const expectedUrls = getNewThreatIndicatorRule().referenceUrls.join('');
|
||||
const expectedFalsePositives = getNewThreatIndicatorRule().falsePositivesExamples.join('');
|
||||
const expectedTags = getNewThreatIndicatorRule().tags.join('');
|
||||
const expectedMitre = formatMitreAttackDescription(getNewThreatIndicatorRule().mitre);
|
||||
const expectedUrls = getNewThreatIndicatorRule().referenceUrls?.join('');
|
||||
const expectedFalsePositives = getNewThreatIndicatorRule().falsePositivesExamples?.join('');
|
||||
const expectedTags = getNewThreatIndicatorRule().tags?.join('');
|
||||
const mitreAttack = getNewThreatIndicatorRule().mitre as Mitre[];
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack);
|
||||
const expectedNumberOfRules = 1;
|
||||
const expectedNumberOfAlerts = '1 alert';
|
||||
|
||||
|
@ -479,11 +481,11 @@ describe('indicator match', () => {
|
|||
cy.get(SCHEDULE_DETAILS).within(() => {
|
||||
getDetails(RUNS_EVERY_DETAILS).should(
|
||||
'have.text',
|
||||
`${rule.runsEvery.interval}${rule.runsEvery.type}`
|
||||
`${rule.runsEvery?.interval}${rule.runsEvery?.type}`
|
||||
);
|
||||
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
|
||||
'have.text',
|
||||
`${rule.lookBack.interval}${rule.lookBack.type}`
|
||||
`${rule.lookBack?.interval}${rule.lookBack?.type}`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -492,7 +494,7 @@ describe('indicator match', () => {
|
|||
|
||||
cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts);
|
||||
cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name);
|
||||
cy.get(ALERT_SEVERITY).first().should('have.text', rule.severity.toLowerCase());
|
||||
cy.get(ALERT_SEVERITY).first().should('have.text', rule.severity?.toLowerCase());
|
||||
cy.get(ALERT_RISK_SCORE).first().should('have.text', rule.riskScore);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { formatMitreAttackDescription } from '../../helpers/rules';
|
||||
import type { Mitre } from '../../objects/rule';
|
||||
import { getNewTermsRule, getIndexPatterns } from '../../objects/rule';
|
||||
|
||||
import { ALERT_DATA_GRID } from '../../screens/alerts';
|
||||
|
@ -60,6 +61,7 @@ import {
|
|||
import { login, visit } from '../../tasks/login';
|
||||
|
||||
import { RULE_CREATION } from '../../urls/navigation';
|
||||
import type { CompleteTimeline } from '../../objects/timeline';
|
||||
|
||||
describe('New Terms rules', () => {
|
||||
before(() => {
|
||||
|
@ -67,19 +69,21 @@ describe('New Terms rules', () => {
|
|||
login();
|
||||
});
|
||||
describe('Detection rules, New Terms', () => {
|
||||
const expectedUrls = getNewTermsRule().referenceUrls.join('');
|
||||
const expectedFalsePositives = getNewTermsRule().falsePositivesExamples.join('');
|
||||
const expectedTags = getNewTermsRule().tags.join('');
|
||||
const expectedMitre = formatMitreAttackDescription(getNewTermsRule().mitre);
|
||||
const expectedUrls = getNewTermsRule().referenceUrls?.join('');
|
||||
const expectedFalsePositives = getNewTermsRule().falsePositivesExamples?.join('');
|
||||
const expectedTags = getNewTermsRule().tags?.join('');
|
||||
const mitreAttack = getNewTermsRule().mitre as Mitre[];
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack);
|
||||
const expectedNumberOfRules = 1;
|
||||
|
||||
beforeEach(() => {
|
||||
const timeline = getNewTermsRule().timeline as CompleteTimeline;
|
||||
deleteAlertsAndRules();
|
||||
createTimeline(getNewTermsRule().timeline).then((response) => {
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap({
|
||||
...getNewTermsRule(),
|
||||
timeline: {
|
||||
...getNewTermsRule().timeline,
|
||||
...timeline,
|
||||
id: response.body.data.persistTimeline.timeline.savedObjectId,
|
||||
},
|
||||
}).as('rule');
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
*/
|
||||
|
||||
import { formatMitreAttackDescription } from '../../helpers/rules';
|
||||
import type { OverrideRule } from '../../objects/rule';
|
||||
import type { Mitre, OverrideRule } from '../../objects/rule';
|
||||
import { getIndexPatterns, getNewOverrideRule, getSeveritiesOverride } from '../../objects/rule';
|
||||
import type { CompleteTimeline } from '../../objects/timeline';
|
||||
|
||||
import { NUMBER_OF_ALERTS, ALERT_GRID_CELL } from '../../screens/alerts';
|
||||
|
||||
|
@ -55,7 +56,7 @@ import { cleanKibana } from '../../tasks/common';
|
|||
import {
|
||||
createAndEnableRule,
|
||||
fillAboutRuleWithOverrideAndContinue,
|
||||
fillDefineCustomRuleWithImportedQueryAndContinue,
|
||||
fillDefineCustomRuleAndContinue,
|
||||
fillScheduleRuleAndContinue,
|
||||
waitForAlertsToPopulate,
|
||||
waitForTheRuleToBeExecuted,
|
||||
|
@ -66,21 +67,23 @@ import { getDetails } from '../../tasks/rule_details';
|
|||
import { RULE_CREATION } from '../../urls/navigation';
|
||||
|
||||
describe('Detection rules, override', () => {
|
||||
const expectedUrls = getNewOverrideRule().referenceUrls.join('');
|
||||
const expectedFalsePositives = getNewOverrideRule().falsePositivesExamples.join('');
|
||||
const expectedTags = getNewOverrideRule().tags.join('');
|
||||
const expectedMitre = formatMitreAttackDescription(getNewOverrideRule().mitre);
|
||||
const expectedUrls = getNewOverrideRule().referenceUrls?.join('');
|
||||
const expectedFalsePositives = getNewOverrideRule().falsePositivesExamples?.join('');
|
||||
const expectedTags = getNewOverrideRule().tags?.join('');
|
||||
const mitreAttack = getNewOverrideRule().mitre as Mitre[];
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack);
|
||||
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
});
|
||||
beforeEach(() => {
|
||||
createTimeline(getNewOverrideRule().timeline).then((response) => {
|
||||
const timeline = getNewOverrideRule().timeline as CompleteTimeline;
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap({
|
||||
...getNewOverrideRule(),
|
||||
timeline: {
|
||||
...getNewOverrideRule().timeline,
|
||||
...timeline,
|
||||
id: response.body.data.persistTimeline.timeline.savedObjectId,
|
||||
},
|
||||
}).as('rule');
|
||||
|
@ -89,7 +92,7 @@ describe('Detection rules, override', () => {
|
|||
|
||||
it('Creates and enables a new custom rule with override option', function () {
|
||||
visitWithoutDateRange(RULE_CREATION);
|
||||
fillDefineCustomRuleWithImportedQueryAndContinue(this.rule);
|
||||
fillDefineCustomRuleAndContinue(this.rule);
|
||||
fillAboutRuleWithOverrideAndContinue(this.rule);
|
||||
fillScheduleRuleAndContinue(this.rule);
|
||||
createAndEnableRule();
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { getIndexConnector } from '../../objects/connector';
|
||||
import { getSimpleCustomQueryRule } from '../../objects/rule';
|
||||
|
||||
import { goToRuleDetails } from '../../tasks/alerts_detection_rules';
|
||||
import { deleteIndex, waitForNewDocumentToBeIndexed } from '../../tasks/api_calls/elasticsearch';
|
||||
import {
|
||||
cleanKibana,
|
||||
deleteAlertsAndRules,
|
||||
deleteConnectors,
|
||||
deleteDataView,
|
||||
} from '../../tasks/common';
|
||||
import {
|
||||
createAndEnableRule,
|
||||
fillAboutRuleAndContinue,
|
||||
fillDefineCustomRuleAndContinue,
|
||||
fillRuleAction,
|
||||
fillScheduleRuleAndContinue,
|
||||
} from '../../tasks/create_new_rule';
|
||||
import { login, visit } from '../../tasks/login';
|
||||
|
||||
import { RULE_CREATION } from '../../urls/navigation';
|
||||
|
||||
describe('Rule actions during detection rule creation', () => {
|
||||
const indexConnector = getIndexConnector();
|
||||
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
deleteAlertsAndRules();
|
||||
deleteConnectors();
|
||||
deleteIndex(indexConnector.index);
|
||||
deleteDataView(indexConnector.index);
|
||||
});
|
||||
|
||||
const rule = {
|
||||
...getSimpleCustomQueryRule(),
|
||||
actions: { throttle: 'rule', connectors: [indexConnector] },
|
||||
};
|
||||
const index = rule.actions.connectors[0].index;
|
||||
const initialNumberOfDocuments = 0;
|
||||
const expectedJson = JSON.parse(rule.actions.connectors[0].document);
|
||||
|
||||
it('Indexes a new document after the index action is triggered ', function () {
|
||||
visit(RULE_CREATION);
|
||||
fillDefineCustomRuleAndContinue(rule);
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
fillRuleAction(rule);
|
||||
createAndEnableRule();
|
||||
goToRuleDetails();
|
||||
|
||||
/* When the rule is executed, the action is triggered. We wait for the new document to be indexed */
|
||||
waitForNewDocumentToBeIndexed(index, initialNumberOfDocuments);
|
||||
|
||||
/* We assert that the new indexed document is the one set on the index action */
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
}).then((response) => {
|
||||
expect(response.body.hits.hits[0]._source).to.deep.equal(expectedJson);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { formatMitreAttackDescription } from '../../helpers/rules';
|
||||
import type { Mitre } from '../../objects/rule';
|
||||
import { getIndexPatterns, getNewThresholdRule } from '../../objects/rule';
|
||||
|
||||
import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts';
|
||||
|
@ -60,13 +61,15 @@ import {
|
|||
import { login, visitWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { RULE_CREATION } from '../../urls/navigation';
|
||||
import type { CompleteTimeline } from '../../objects/timeline';
|
||||
|
||||
describe('Detection rules, threshold', () => {
|
||||
let rule = getNewThresholdRule();
|
||||
const expectedUrls = getNewThresholdRule().referenceUrls.join('');
|
||||
const expectedFalsePositives = getNewThresholdRule().falsePositivesExamples.join('');
|
||||
const expectedTags = getNewThresholdRule().tags.join('');
|
||||
const expectedMitre = formatMitreAttackDescription(getNewThresholdRule().mitre);
|
||||
const expectedUrls = getNewThresholdRule().referenceUrls?.join('');
|
||||
const expectedFalsePositives = getNewThresholdRule().falsePositivesExamples?.join('');
|
||||
const expectedTags = getNewThresholdRule().tags?.join('');
|
||||
const mitreAttack = getNewThresholdRule().mitre as Mitre[];
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack);
|
||||
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
|
@ -75,9 +78,10 @@ describe('Detection rules, threshold', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
rule = getNewThresholdRule();
|
||||
const timeline = rule.timeline as CompleteTimeline;
|
||||
deleteAlertsAndRules();
|
||||
createTimeline(getNewThresholdRule().timeline).then((response) => {
|
||||
rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId;
|
||||
createTimeline(timeline).then((response) => {
|
||||
timeline.id = response.body.data.persistTimeline.timeline.savedObjectId;
|
||||
});
|
||||
visitWithoutDateRange(RULE_CREATION);
|
||||
});
|
||||
|
@ -132,11 +136,11 @@ describe('Detection rules, threshold', () => {
|
|||
cy.get(SCHEDULE_DETAILS).within(() => {
|
||||
getDetails(RUNS_EVERY_DETAILS).should(
|
||||
'have.text',
|
||||
`${rule.runsEvery.interval}${rule.runsEvery.type}`
|
||||
`${rule.runsEvery?.interval}${rule.runsEvery?.type}`
|
||||
);
|
||||
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
|
||||
'have.text',
|
||||
`${rule.lookBack.interval}${rule.lookBack.type}`
|
||||
`${rule.lookBack?.interval}${rule.lookBack?.type}`
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -5,22 +5,42 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export interface EmailConnector {
|
||||
interface Connector {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface EmailConnector extends Connector {
|
||||
from: string;
|
||||
host: string;
|
||||
port: string;
|
||||
user: string;
|
||||
password: string;
|
||||
service: string;
|
||||
type: 'email';
|
||||
}
|
||||
|
||||
export interface IndexConnector extends Connector {
|
||||
index: string;
|
||||
document: string;
|
||||
type: 'index';
|
||||
}
|
||||
|
||||
export type Connectors = IndexConnector | EmailConnector;
|
||||
|
||||
export const getEmailConnector = (): EmailConnector => ({
|
||||
name: 'Test connector',
|
||||
name: 'Test email connector',
|
||||
from: 'test@example.com',
|
||||
host: 'example.com',
|
||||
port: '80',
|
||||
user: 'username',
|
||||
password: 'password',
|
||||
service: 'Other',
|
||||
type: 'email',
|
||||
});
|
||||
|
||||
export const getIndexConnector = (): IndexConnector => ({
|
||||
name: 'Test index connector',
|
||||
index: 'my-index-000001',
|
||||
document: '{"test": "123"}',
|
||||
type: 'index',
|
||||
});
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Throttle } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { rawRules } from '../../server/lib/detection_engine/rules/prepackaged_rules';
|
||||
import { getMockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques';
|
||||
import type { CompleteTimeline } from './timeline';
|
||||
import { getTimeline, getIndicatorMatchTimelineTemplate } from './timeline';
|
||||
import type { FullResponseSchema } from '../../common/detection_engine/schemas/request';
|
||||
import type { Connectors } from './connector';
|
||||
|
||||
export const totalNumberOfPrebuiltRules = rawRules.length;
|
||||
|
||||
|
@ -36,6 +38,11 @@ interface Interval {
|
|||
type: string;
|
||||
}
|
||||
|
||||
export interface Actions {
|
||||
throttle: Throttle;
|
||||
connectors: Connectors[];
|
||||
}
|
||||
|
||||
export type RuleDataSource =
|
||||
| { type: 'indexPatterns'; index: string[] }
|
||||
| { type: 'dataView'; dataView: string };
|
||||
|
@ -46,20 +53,21 @@ export interface CustomRule {
|
|||
description: string;
|
||||
dataSource: RuleDataSource;
|
||||
interval?: string;
|
||||
severity: string;
|
||||
riskScore: string;
|
||||
tags: string[];
|
||||
severity?: string;
|
||||
riskScore?: string;
|
||||
tags?: string[];
|
||||
timelineTemplate?: string;
|
||||
referenceUrls: string[];
|
||||
falsePositivesExamples: string[];
|
||||
mitre: Mitre[];
|
||||
note: string;
|
||||
runsEvery: Interval;
|
||||
lookBack: Interval;
|
||||
timeline: CompleteTimeline;
|
||||
maxSignals: number;
|
||||
referenceUrls?: string[];
|
||||
falsePositivesExamples?: string[];
|
||||
mitre?: Mitre[];
|
||||
note?: string;
|
||||
runsEvery?: Interval;
|
||||
lookBack?: Interval;
|
||||
timeline?: CompleteTimeline;
|
||||
maxSignals?: number;
|
||||
buildingBlockType?: string;
|
||||
exceptionLists?: Array<{ id: string; list_id: string; type: string; namespace_type: string }>;
|
||||
actions?: Actions;
|
||||
}
|
||||
|
||||
export interface ThresholdRule extends CustomRule {
|
||||
|
@ -231,6 +239,15 @@ export const getNewRule = (): CustomRule => ({
|
|||
maxSignals: 100,
|
||||
});
|
||||
|
||||
export const getSimpleCustomQueryRule = (): CustomRule => ({
|
||||
customQuery: 'host.name: *',
|
||||
dataSource: { index: getIndexPatterns(), type: 'indexPatterns' },
|
||||
name: 'New Rule Test',
|
||||
description: 'The new rule description.',
|
||||
runsEvery: getRunsEvery(),
|
||||
lookBack: getLookBack(),
|
||||
});
|
||||
|
||||
export const getBuildingBlockRule = (): CustomRule => ({
|
||||
customQuery: 'host.name: *',
|
||||
dataSource: { index: getIndexPatterns(), type: 'indexPatterns' },
|
||||
|
@ -489,7 +506,7 @@ export const getEditedRule = (): CustomRule => ({
|
|||
...getExistingRule(),
|
||||
severity: 'Medium',
|
||||
description: 'Edited Rule description',
|
||||
tags: [...getExistingRule().tags, 'edited'],
|
||||
tags: [...(getExistingRule().tags || []), 'edited'],
|
||||
});
|
||||
|
||||
export const expectedExportedRule = (
|
||||
|
|
|
@ -42,6 +42,8 @@ export const EMAIL_CONNECTOR_PASSWORD_INPUT = '[data-test-subj="emailPasswordInp
|
|||
|
||||
export const EMAIL_CONNECTOR_SERVICE_SELECTOR = '[data-test-subj="emailServiceSelectInput"]';
|
||||
|
||||
export const JSON_EDITOR = "[data-test-subj='actionJsonEditor']";
|
||||
|
||||
export const ADD_FALSE_POSITIVE_BTN =
|
||||
'[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text';
|
||||
|
||||
|
@ -58,6 +60,8 @@ export const COMBO_BOX_CLEAR_BTN = '[data-test-subj="comboBoxClearButton"]';
|
|||
|
||||
export const COMBO_BOX_INPUT = '[data-test-subj="comboBoxInput"]';
|
||||
|
||||
export const COMBO_BOX_SELECTION = '.euiMark';
|
||||
|
||||
export const CREATE_AND_ENABLE_BTN = '[data-test-subj="create-enable"]';
|
||||
|
||||
export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]';
|
||||
|
@ -256,3 +260,7 @@ export const savedQueryByName = (savedQueryName: string) =>
|
|||
|
||||
export const APPLY_SELECTED_SAVED_QUERY_BUTTON =
|
||||
'[data-test-subj="saved-query-management-apply-changes-button"]';
|
||||
|
||||
export const INDEX_SELECTOR = "[data-test-subj='.index-siem-ActionTypeSelectOption']";
|
||||
|
||||
export const CREATE_CONNECTOR_BTN = "[data-test-subj='createActionConnectorButton-0']";
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const createIndex = (index: string) => {
|
||||
cy.request({
|
||||
method: 'PUT',
|
||||
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const createDocument = (index: string, document: string) => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_doc`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
body: JSON.parse(document),
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteIndex = (index: string) => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const waitForNewDocumentToBeIndexed = (index: string, initialNumberOfDocuments: number) => {
|
||||
cy.waitUntil(
|
||||
() => {
|
||||
return cy
|
||||
.request({
|
||||
method: 'GET',
|
||||
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
return false;
|
||||
} else {
|
||||
return response.body.hits.hits.length > initialNumberOfDocuments;
|
||||
}
|
||||
});
|
||||
},
|
||||
{ interval: 500, timeout: 12000 }
|
||||
);
|
||||
};
|
|
@ -40,17 +40,21 @@ export const createCustomRule = (
|
|||
rule: CustomRule,
|
||||
ruleId = 'rule_testing',
|
||||
interval = '100m'
|
||||
): Cypress.Chainable<Cypress.Response<unknown>> =>
|
||||
cy.request({
|
||||
): Cypress.Chainable<Cypress.Response<unknown>> => {
|
||||
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
|
||||
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
|
||||
const timeline = rule.timeline != null ? rule.timeline : undefined;
|
||||
|
||||
return cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
description: rule.description,
|
||||
interval,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
severity,
|
||||
type: 'query',
|
||||
from: 'now-50000h',
|
||||
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
|
||||
|
@ -60,29 +64,33 @@ export const createCustomRule = (
|
|||
enabled: false,
|
||||
exceptions_list: rule.exceptionLists ?? [],
|
||||
tags: rule.tags,
|
||||
...(rule.timeline.id ?? rule.timeline.templateTimelineId
|
||||
...(timeline?.id ?? timeline?.templateTimelineId
|
||||
? {
|
||||
timeline_id: rule.timeline.id ?? rule.timeline.templateTimelineId,
|
||||
timeline_title: rule.timeline.title,
|
||||
timeline_id: timeline.id ?? timeline.templateTimelineId,
|
||||
timeline_title: timeline.title,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_testing') => {
|
||||
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
|
||||
const severity = rule.severity != null ? rule.severity.toLowerCase() : undefined;
|
||||
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
description: rule.description,
|
||||
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
|
||||
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
|
||||
interval: `${rule.runsEvery?.interval}${rule.runsEvery?.type}`,
|
||||
from: `now-${rule.lookBack?.interval}${rule.lookBack?.type}`,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
severity,
|
||||
type: 'eql',
|
||||
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
|
||||
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
|
||||
|
@ -96,17 +104,20 @@ export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_test
|
|||
};
|
||||
|
||||
export const createThresholdRule = (rule: ThresholdRule, ruleId = 'rule_testing') => {
|
||||
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
|
||||
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
|
||||
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
description: rule.description,
|
||||
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
|
||||
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
|
||||
interval: `${rule.runsEvery?.interval}${rule.runsEvery?.type}`,
|
||||
from: `now-${rule.lookBack?.interval}${rule.lookBack?.type}`,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
severity,
|
||||
type: 'threshold',
|
||||
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
|
||||
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
|
||||
|
@ -124,17 +135,20 @@ export const createThresholdRule = (rule: ThresholdRule, ruleId = 'rule_testing'
|
|||
};
|
||||
|
||||
export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing') => {
|
||||
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
|
||||
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
|
||||
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
description: rule.description,
|
||||
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
|
||||
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
|
||||
interval: `${rule.runsEvery?.interval}${rule.runsEvery?.type}`,
|
||||
from: `now-${rule.lookBack?.interval}${rule.lookBack?.type}`,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
severity,
|
||||
type: 'new_terms',
|
||||
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
|
||||
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
|
||||
|
@ -151,17 +165,21 @@ export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing')
|
|||
export const createSavedQueryRule = (
|
||||
rule: SavedQueryRule,
|
||||
ruleId = 'saved_query_rule_testing'
|
||||
): Cypress.Chainable<Cypress.Response<unknown>> =>
|
||||
cy.request({
|
||||
): Cypress.Chainable<Cypress.Response<unknown>> => {
|
||||
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
|
||||
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
|
||||
const timeline = rule.timeline != null ? rule.timeline : undefined;
|
||||
|
||||
return cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
description: rule.description,
|
||||
interval: rule.interval,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
severity,
|
||||
type: 'saved_query',
|
||||
from: 'now-50000h',
|
||||
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
|
||||
|
@ -171,33 +189,38 @@ export const createSavedQueryRule = (
|
|||
enabled: false,
|
||||
exceptions_list: rule.exceptionLists ?? [],
|
||||
tags: rule.tags,
|
||||
...(rule.timeline.id ?? rule.timeline.templateTimelineId
|
||||
...(timeline?.id ?? timeline?.templateTimelineId
|
||||
? {
|
||||
timeline_id: rule.timeline.id ?? rule.timeline.templateTimelineId,
|
||||
timeline_title: rule.timeline.title,
|
||||
timeline_id: timeline.id ?? timeline.templateTimelineId,
|
||||
timeline_title: timeline.title,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') => {
|
||||
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
|
||||
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
|
||||
const timeline = rule.timeline != null ? rule.timeline : undefined;
|
||||
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
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(),
|
||||
severity,
|
||||
type: 'threat_match',
|
||||
timeline_id: rule.timeline.templateTimelineId,
|
||||
timeline_title: rule.timeline.title,
|
||||
timeline_id: timeline?.templateTimelineId,
|
||||
timeline_title: timeline?.title,
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
|
@ -233,17 +256,20 @@ export const createCustomRuleEnabled = (
|
|||
interval = '100m',
|
||||
maxSignals = 500
|
||||
) => {
|
||||
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
|
||||
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
|
||||
|
||||
if (rule.dataSource.type === 'indexPatterns') {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
description: rule.description,
|
||||
interval,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
severity,
|
||||
type: 'query',
|
||||
from: 'now-50000h',
|
||||
index: rule.dataSource.index,
|
||||
|
@ -264,11 +290,11 @@ export const createCustomRuleEnabled = (
|
|||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
risk_score: riskScore,
|
||||
description: rule.description,
|
||||
interval,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
severity,
|
||||
type: 'query',
|
||||
from: 'now-50000h',
|
||||
index: [],
|
||||
|
|
|
@ -179,6 +179,40 @@ export const deleteCases = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const deleteConnectors = () => {
|
||||
const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`;
|
||||
cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
match: {
|
||||
type: 'action',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteSavedQueries = () => {
|
||||
const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`;
|
||||
cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
match: {
|
||||
type: 'query',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const postDataView = (dataSource: string) => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
|
@ -196,6 +230,15 @@ export const postDataView = (dataSource: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const deleteDataView = (dataSource: string) => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `api/data_views/data_view/${dataSource}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const scrollToBottom = () => cy.scrollTo('bottom');
|
||||
|
||||
export const waitForPageToBeLoaded = () => {
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EmailConnector } from '../objects/connector';
|
||||
import { getEmailConnector } from '../objects/connector';
|
||||
import type { EmailConnector, IndexConnector } from '../objects/connector';
|
||||
import { getIndexConnector, getEmailConnector } from '../objects/connector';
|
||||
import type {
|
||||
CustomRule,
|
||||
MachineLearningRule,
|
||||
|
@ -14,6 +14,7 @@ import type {
|
|||
ThreatIndicatorRule,
|
||||
ThresholdRule,
|
||||
NewTermsRule,
|
||||
Mitre,
|
||||
} from '../objects/rule';
|
||||
import { getMachineLearningRule } from '../objects/rule';
|
||||
import {
|
||||
|
@ -106,6 +107,14 @@ import {
|
|||
NEW_TERMS_HISTORY_SIZE,
|
||||
NEW_TERMS_HISTORY_TIME_TYPE,
|
||||
NEW_TERMS_INPUT_AREA,
|
||||
ACTIONS_THROTTLE_INPUT,
|
||||
INDEX_SELECTOR,
|
||||
CREATE_CONNECTOR_BTN,
|
||||
SAVE_ACTION_CONNECTOR_BTN,
|
||||
JSON_EDITOR,
|
||||
CREATE_ACTION_CONNECTOR_BTN,
|
||||
EMAIL_ACTION_BTN,
|
||||
COMBO_BOX_SELECTION,
|
||||
} from '../screens/create_new_rule';
|
||||
import { TOAST_ERROR } from '../screens/shared';
|
||||
import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline';
|
||||
|
@ -114,7 +123,6 @@ import { refreshPage } from './security_header';
|
|||
import { EUI_FILTER_SELECT_ITEM } from '../screens/common/controls';
|
||||
|
||||
export const createAndEnableRule = () => {
|
||||
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
|
||||
cy.get(CREATE_AND_ENABLE_BTN).click({ force: true });
|
||||
cy.get(CREATE_AND_ENABLE_BTN).should('not.exist');
|
||||
cy.get(BACK_TO_ALL_RULES_LINK).click({ force: true });
|
||||
|
@ -126,34 +134,41 @@ export const fillAboutRule = (
|
|||
) => {
|
||||
cy.get(RULE_NAME_INPUT).clear({ force: true }).type(rule.name, { force: true });
|
||||
cy.get(RULE_DESCRIPTION_INPUT).clear({ force: true }).type(rule.description, { force: true });
|
||||
|
||||
cy.get(SEVERITY_DROPDOWN).click({ force: true });
|
||||
cy.get(`#${rule.severity.toLowerCase()}`).click();
|
||||
|
||||
cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${rule.riskScore}`, { force: true });
|
||||
|
||||
rule.tags.forEach((tag) => {
|
||||
cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true });
|
||||
});
|
||||
|
||||
if (rule.severity) {
|
||||
fillSeverity(rule.severity);
|
||||
}
|
||||
if (rule.riskScore) {
|
||||
fillRiskScore(rule.riskScore);
|
||||
}
|
||||
if (rule.tags) {
|
||||
fillRuleTags(rule.tags);
|
||||
}
|
||||
cy.get(ADVANCED_SETTINGS_BTN).click({ force: true });
|
||||
|
||||
rule.referenceUrls.forEach((url, index) => {
|
||||
cy.get(REFERENCE_URLS_INPUT).eq(index).clear({ force: true }).type(url, { force: true });
|
||||
cy.get(ADD_REFERENCE_URL_BTN).click({ force: true });
|
||||
});
|
||||
if (rule.referenceUrls) {
|
||||
fillReferenceUrls(rule.referenceUrls);
|
||||
}
|
||||
|
||||
rule.falsePositivesExamples.forEach((falsePositive, index) => {
|
||||
cy.get(FALSE_POSITIVES_INPUT)
|
||||
.eq(index)
|
||||
.clear({ force: true })
|
||||
.type(falsePositive, { force: true });
|
||||
cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true });
|
||||
});
|
||||
if (rule.falsePositivesExamples) {
|
||||
fillFalsePositiveExamples(rule.falsePositivesExamples);
|
||||
}
|
||||
|
||||
if (rule.mitre) {
|
||||
fillMitre(rule.mitre);
|
||||
}
|
||||
if (rule.note) {
|
||||
fillNote(rule.note);
|
||||
}
|
||||
};
|
||||
|
||||
export const fillNote = (note: string) => {
|
||||
cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }).type(note, { force: true });
|
||||
};
|
||||
|
||||
export const fillMitre = (mitreAttacks: Mitre[]) => {
|
||||
let techniqueIndex = 0;
|
||||
let subtechniqueInputIndex = 0;
|
||||
rule.mitre.forEach((mitre, tacticIndex) => {
|
||||
mitreAttacks.forEach((mitre, tacticIndex) => {
|
||||
cy.get(MITRE_ATTACK_TACTIC_DROPDOWN).eq(tacticIndex).click({ force: true });
|
||||
cy.contains(MITRE_TACTIC, mitre.tactic).click();
|
||||
|
||||
|
@ -175,8 +190,38 @@ export const fillAboutRule = (
|
|||
|
||||
cy.get(MITRE_ATTACK_ADD_TACTIC_BUTTON).click({ force: true });
|
||||
});
|
||||
};
|
||||
|
||||
cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }).type(rule.note, { force: true });
|
||||
export const fillFalsePositiveExamples = (falsePositives: string[]) => {
|
||||
falsePositives.forEach((falsePositive, index) => {
|
||||
cy.get(FALSE_POSITIVES_INPUT)
|
||||
.eq(index)
|
||||
.clear({ force: true })
|
||||
.type(falsePositive, { force: true });
|
||||
cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true });
|
||||
});
|
||||
};
|
||||
|
||||
export const fillSeverity = (severity: string) => {
|
||||
cy.get(SEVERITY_DROPDOWN).click({ force: true });
|
||||
cy.get(`#${severity.toLowerCase()}`).click();
|
||||
};
|
||||
|
||||
export const fillRiskScore = (riskScore: string) => {
|
||||
cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${riskScore}`, { force: true });
|
||||
};
|
||||
|
||||
export const fillRuleTags = (tags: string[]) => {
|
||||
tags.forEach((tag) => {
|
||||
cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true });
|
||||
});
|
||||
};
|
||||
|
||||
export const fillReferenceUrls = (referenceUrls: string[]) => {
|
||||
referenceUrls.forEach((url, index) => {
|
||||
cy.get(REFERENCE_URLS_INPUT).eq(index).clear({ force: true }).type(url, { force: true });
|
||||
cy.get(ADD_REFERENCE_URL_BTN).click({ force: true });
|
||||
});
|
||||
};
|
||||
|
||||
export const fillAboutRuleAndContinue = (
|
||||
|
@ -200,8 +245,9 @@ export const fillAboutRuleWithOverrideAndContinue = (rule: OverrideRule) => {
|
|||
});
|
||||
});
|
||||
|
||||
cy.get(SEVERITY_DROPDOWN).click({ force: true });
|
||||
cy.get(`#${rule.severity.toLowerCase()}`).click();
|
||||
if (rule.severity) {
|
||||
fillSeverity(rule.severity);
|
||||
}
|
||||
|
||||
cy.get(RISK_MAPPING_OVERRIDE_OPTION).click();
|
||||
cy.get(RISK_OVERRIDE).within(() => {
|
||||
|
@ -210,48 +256,24 @@ export const fillAboutRuleWithOverrideAndContinue = (rule: OverrideRule) => {
|
|||
|
||||
cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${rule.riskScore}`, { force: true });
|
||||
|
||||
rule.tags.forEach((tag) => {
|
||||
cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true });
|
||||
});
|
||||
if (rule.tags) {
|
||||
fillRuleTags(rule.tags);
|
||||
}
|
||||
|
||||
cy.get(ADVANCED_SETTINGS_BTN).click({ force: true });
|
||||
|
||||
rule.referenceUrls.forEach((url, index) => {
|
||||
cy.get(REFERENCE_URLS_INPUT).eq(index).type(url, { force: true });
|
||||
cy.get(ADD_REFERENCE_URL_BTN).click({ force: true });
|
||||
});
|
||||
|
||||
rule.falsePositivesExamples.forEach((falsePositive, index) => {
|
||||
cy.get(FALSE_POSITIVES_INPUT).eq(index).type(falsePositive, { force: true });
|
||||
cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true });
|
||||
});
|
||||
|
||||
let techniqueIndex = 0;
|
||||
let subtechniqueInputIndex = 0;
|
||||
rule.mitre.forEach((mitre, tacticIndex) => {
|
||||
cy.get(MITRE_ATTACK_TACTIC_DROPDOWN).eq(tacticIndex).click({ force: true });
|
||||
cy.contains(MITRE_TACTIC, mitre.tactic).click();
|
||||
|
||||
mitre.techniques.forEach((technique) => {
|
||||
cy.get(MITRE_ATTACK_ADD_TECHNIQUE_BUTTON).eq(tacticIndex).click({ force: true });
|
||||
cy.get(MITRE_ATTACK_TECHNIQUE_DROPDOWN).eq(techniqueIndex).click({ force: true });
|
||||
cy.contains(MITRE_TACTIC, technique.name).click();
|
||||
|
||||
technique.subtechniques.forEach((subtechnique) => {
|
||||
cy.get(MITRE_ATTACK_ADD_SUBTECHNIQUE_BUTTON).eq(techniqueIndex).click({ force: true });
|
||||
cy.get(MITRE_ATTACK_SUBTECHNIQUE_DROPDOWN)
|
||||
.eq(subtechniqueInputIndex)
|
||||
.click({ force: true });
|
||||
cy.contains(MITRE_TACTIC, subtechnique).click();
|
||||
subtechniqueInputIndex++;
|
||||
});
|
||||
techniqueIndex++;
|
||||
});
|
||||
|
||||
cy.get(MITRE_ATTACK_ADD_TACTIC_BUTTON).click({ force: true });
|
||||
});
|
||||
|
||||
cy.get(INVESTIGATION_NOTES_TEXTAREA).type(rule.note, { force: true });
|
||||
if (rule.referenceUrls) {
|
||||
fillReferenceUrls(rule.referenceUrls);
|
||||
}
|
||||
if (rule.falsePositivesExamples) {
|
||||
fillFalsePositiveExamples(rule.falsePositivesExamples);
|
||||
}
|
||||
if (rule.mitre) {
|
||||
fillMitre(rule.mitre);
|
||||
}
|
||||
if (rule.note) {
|
||||
fillNote(rule.note);
|
||||
}
|
||||
|
||||
cy.get(RULE_NAME_OVERRIDE).within(() => {
|
||||
cy.get(COMBO_BOX_INPUT).type(`${rule.nameOverride}{enter}`);
|
||||
|
@ -264,35 +286,65 @@ export const fillAboutRuleWithOverrideAndContinue = (rule: OverrideRule) => {
|
|||
getAboutContinueButton().should('exist').click({ force: true });
|
||||
};
|
||||
|
||||
export const fillDefineCustomRuleWithImportedQueryAndContinue = (
|
||||
rule: CustomRule | OverrideRule
|
||||
) => {
|
||||
export const fillCustomQuery = (rule: CustomRule | OverrideRule) => {
|
||||
if (rule.timeline?.id) {
|
||||
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
|
||||
cy.get(TIMELINE(rule.timeline.id)).click();
|
||||
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
|
||||
} else {
|
||||
cy.get(CUSTOM_QUERY_INPUT)
|
||||
.first()
|
||||
.type(rule.customQuery || '');
|
||||
}
|
||||
};
|
||||
|
||||
export const fillDefineCustomRuleAndContinue = (rule: CustomRule | OverrideRule) => {
|
||||
if (rule.dataSource.type === 'dataView') {
|
||||
cy.get(DATA_VIEW_OPTION).click();
|
||||
cy.get(DATA_VIEW_COMBO_BOX).type(`${rule.dataSource.dataView}{enter}`);
|
||||
}
|
||||
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
|
||||
cy.get(TIMELINE(rule.timeline.id)).click();
|
||||
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
|
||||
|
||||
fillCustomQuery(rule);
|
||||
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
|
||||
|
||||
cy.get(CUSTOM_QUERY_INPUT).should('not.exist');
|
||||
};
|
||||
|
||||
export const fillScheduleRuleAndContinue = (rule: CustomRule | MachineLearningRule) => {
|
||||
cy.get(RUNS_EVERY_INTERVAL).type('{selectall}').type(rule.runsEvery.interval);
|
||||
cy.get(RUNS_EVERY_TIME_TYPE).select(rule.runsEvery.timeType);
|
||||
cy.get(LOOK_BACK_INTERVAL).type('{selectAll}').type(rule.lookBack.interval);
|
||||
cy.get(LOOK_BACK_TIME_TYPE).select(rule.lookBack.timeType);
|
||||
if (rule.runsEvery) {
|
||||
cy.get(RUNS_EVERY_INTERVAL).type('{selectall}').type(rule.runsEvery.interval);
|
||||
cy.get(RUNS_EVERY_TIME_TYPE).select(rule.runsEvery.timeType);
|
||||
}
|
||||
if (rule.lookBack) {
|
||||
cy.get(LOOK_BACK_INTERVAL).type('{selectAll}').type(rule.lookBack.interval);
|
||||
cy.get(LOOK_BACK_TIME_TYPE).select(rule.lookBack.timeType);
|
||||
}
|
||||
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
|
||||
};
|
||||
|
||||
export const fillRuleAction = (rule: CustomRule) => {
|
||||
if (rule.actions) {
|
||||
cy.get(ACTIONS_THROTTLE_INPUT).select(rule.actions.throttle);
|
||||
rule.actions?.connectors.forEach((connector) => {
|
||||
switch (connector.type) {
|
||||
case 'index':
|
||||
cy.get(INDEX_SELECTOR).click();
|
||||
cy.get(CREATE_CONNECTOR_BTN).click();
|
||||
fillIndexConnectorForm(connector);
|
||||
break;
|
||||
case 'email':
|
||||
cy.get(EMAIL_ACTION_BTN).click();
|
||||
cy.get(CREATE_ACTION_CONNECTOR_BTN).click();
|
||||
fillEmailConnectorForm(connector);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const fillDefineThresholdRule = (rule: ThresholdRule) => {
|
||||
const thresholdField = 0;
|
||||
const threshold = 1;
|
||||
|
||||
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
|
||||
cy.get(TIMELINE(rule.timeline.id)).click();
|
||||
fillCustomQuery(rule);
|
||||
cy.get(COMBO_BOX_CLEAR_BTN).first().click();
|
||||
|
||||
if (rule.dataSource.type === 'indexPatterns') {
|
||||
|
@ -318,9 +370,7 @@ export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
|
|||
const typeThresholdField = ($el: Cypress.ObjectLike) =>
|
||||
cy.wrap($el).type(rule.thresholdField, { delay: 35 });
|
||||
|
||||
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
|
||||
cy.get(TIMELINE(rule.timeline.id)).click();
|
||||
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
|
||||
fillCustomQuery(rule);
|
||||
cy.get(THRESHOLD_INPUT_AREA)
|
||||
.find(INPUT)
|
||||
.then((inputs) => {
|
||||
|
@ -360,9 +410,7 @@ export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => {
|
|||
};
|
||||
|
||||
export const fillDefineNewTermsRuleAndContinue = (rule: NewTermsRule) => {
|
||||
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
|
||||
cy.get(TIMELINE(rule.timeline.id)).click();
|
||||
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
|
||||
fillCustomQuery(rule);
|
||||
cy.get(NEW_TERMS_INPUT_AREA).find(INPUT).click().type(rule.newTermsFields[0], { delay: 35 });
|
||||
cy.get(EUI_FILTER_SELECT_ITEM).click({ force: true });
|
||||
cy.focused().type('{esc}'); // Close combobox dropdown so next inputs can be interacted with
|
||||
|
@ -449,6 +497,21 @@ export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConne
|
|||
cy.get(EMAIL_CONNECTOR_PASSWORD_INPUT).type(connector.password);
|
||||
};
|
||||
|
||||
export const fillIndexConnectorForm = (connector: IndexConnector = getIndexConnector()) => {
|
||||
cy.get(CONNECTOR_NAME_INPUT).type(connector.name);
|
||||
cy.get(COMBO_BOX_INPUT).type(connector.index);
|
||||
|
||||
cy.get(COMBO_BOX_SELECTION).click({ force: true });
|
||||
|
||||
cy.get(SAVE_ACTION_CONNECTOR_BTN).click();
|
||||
cy.get(SAVE_ACTION_CONNECTOR_BTN).should('not.exist');
|
||||
cy.get(JSON_EDITOR).should('be.visible');
|
||||
cy.get(JSON_EDITOR).click();
|
||||
cy.get(JSON_EDITOR).type(connector.document, {
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
};
|
||||
|
||||
/** Returns the indicator index drop down field. Pass in row number, default is 1 */
|
||||
export const getIndicatorIndexComboField = (row = 1) =>
|
||||
cy.get(THREAT_COMBO_BOX_INPUT).eq(row * 2 - 2);
|
||||
|
|
|
@ -148,6 +148,7 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent<Props> = ({
|
|||
|
||||
return (
|
||||
<EuiFormRow
|
||||
data-test-subj="actionJsonEditor"
|
||||
fullWidth
|
||||
error={errors}
|
||||
isInvalid={errors && errors.length > 0 && inputTargetValue !== undefined}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue