diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts index bdc76cc5f0a59..a594d7f3ffc2f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts @@ -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); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts index 77a4ae274e6e4..727c7257b6682 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts @@ -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}` ); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts index 5027fe09a8d3e..7a4117f0d58a3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts @@ -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'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts index 310efde996da3..4812dcc2a626a 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts @@ -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'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts index 7c3ba64536faf..4ffab681d4aaa 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts @@ -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); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts index 764d8d0b688ac..2c0507ca38157 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts @@ -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'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts index 701efb2706bf3..68973fdc56941 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts @@ -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(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_actions.cy.ts new file mode 100644 index 0000000000000..b0f78cc76881c --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_actions.cy.ts @@ -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); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts index 4e2b42c7b7ee1..59efa1cced40c 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts @@ -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}` ); }); diff --git a/x-pack/plugins/security_solution/cypress/objects/connector.ts b/x-pack/plugins/security_solution/cypress/objects/connector.ts index 5c2abeab06026..39c93e6f80383 100644 --- a/x-pack/plugins/security_solution/cypress/objects/connector.ts +++ b/x-pack/plugins/security_solution/cypress/objects/connector.ts @@ -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', }); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 7b9d543e480ee..245e220c340c6 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -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 = ( diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index e46e8a75a4341..96928ff49da40 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -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']"; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/elasticsearch.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/elasticsearch.ts new file mode 100644 index 0000000000000..7e2cef6654034 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/elasticsearch.ts @@ -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 } + ); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 80fb77013acbb..1909030b726d3 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -40,17 +40,21 @@ export const createCustomRule = ( rule: CustomRule, ruleId = 'rule_testing', interval = '100m' -): Cypress.Chainable> => - cy.request({ +): Cypress.Chainable> => { + 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> => - cy.request({ +): Cypress.Chainable> => { + 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: [], diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index cd9525e95b0b2..4b6ad24c59f15 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -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 = () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 2b7e8e0b3375e..8f59172220717 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -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 }); + 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 }); - 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.referenceUrls) { + fillReferenceUrls(rule.referenceUrls); + } - cy.get(ADVANCED_SETTINGS_BTN).click({ force: true }); + if (rule.falsePositivesExamples) { + fillFalsePositiveExamples(rule.falsePositivesExamples); + } - 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.mitre) { + fillMitre(rule.mitre); + } + if (rule.note) { + fillNote(rule.note); + } +}; - 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 }); - }); +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); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx index 135d4783da822..643e1b69a513d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx @@ -148,6 +148,7 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent = ({ return ( 0 && inputTargetValue !== undefined}