diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/basic_license_check_rule_details.cy.ts similarity index 58% rename from x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_ess_basic.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/basic_license_check_rule_details.cy.ts index 6223ac017281d..176af020ac052 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_ess_basic.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/basic_license_check_rule_details.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getNewThreatIndicatorRule } from '../../../../objects/rule'; +import { getEqlRule, getNewThreatIndicatorRule } from '../../../../objects/rule'; import { SUPPRESS_FOR_DETAILS, @@ -13,6 +13,7 @@ import { SUPPRESS_MISSING_FIELD, DEFINITION_DETAILS, ALERT_SUPPRESSION_INSUFFICIENT_LICENSING_ICON, + DETAILS_TITLE, } from '../../../../screens/rule_details'; import { startBasicLicense } from '../../../../tasks/api_calls/licensing'; @@ -28,19 +29,19 @@ import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; const SUPPRESS_BY_FIELDS = ['myhash.mysha256', 'source.ip.keyword']; describe( - 'Detection rules, Indicator Match, Alert Suppression', + 'Alert Suppression license check - Rule Details', { tags: ['@ess'], }, () => { - describe('Create rule form', () => { - beforeEach(() => { - deleteAlertsAndRules(); - login(); - visit(CREATE_RULE_URL); - startBasicLicense(); - }); + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + startBasicLicense(); + }); + describe('Indicator match', () => { it('shows upselling message on rule details with suppression on basic license', () => { const rule = getNewThreatIndicatorRule(); @@ -71,5 +72,40 @@ describe( }); }); }); + + describe('EQL rule', () => { + it('shows an upselling message on rule suppression details', () => { + const rule = getEqlRule(); + + createRule({ + ...rule, + alert_suppression: { + group_by: SUPPRESS_BY_FIELDS, + duration: { value: 360, unit: 's' }, + missing_fields_strategy: 'doNotSuppress', + }, + }).then((createdRule) => { + visit(ruleDetailsUrl(createdRule.body.id)); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '360s'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + + // suppression functionality should be under Tech Preview + cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).contains('Technical Preview'); + }); + + // Platinum license is required for configuration to apply + cy.get(ALERT_SUPPRESSION_INSUFFICIENT_LICENSING_ICON).eq(2).trigger('mouseover'); + cy.get(TOOLTIP).contains( + 'Alert suppression is configured but will not be applied due to insufficient licensing' + ); + }); + }); + }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/basic_license_check_rule_form.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/basic_license_check_rule_form.cy.ts new file mode 100644 index 0000000000000..3ebe8230a3d04 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/basic_license_check_rule_form.cy.ts @@ -0,0 +1,79 @@ +/* + * 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 { + THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, + ALERT_SUPPRESSION_DURATION_VALUE_INPUT, + MACHINE_LEARNING_TYPE, + ALERT_SUPPRESSION_DURATION_UNIT_INPUT, + ALERT_SUPPRESSION_FIELDS_INPUT, + ALERT_SUPPRESSION_FIELDS, +} from '../../../../screens/create_new_rule'; + +import { + selectIndicatorMatchType, + selectNewTermsRuleType, + selectThresholdRuleType, + selectEsqlRuleType, + openSuppressionFieldsTooltipAndCheckLicense, + selectEqlRuleType, +} from '../../../../tasks/create_new_rule'; +import { startBasicLicense } from '../../../../tasks/api_calls/licensing'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; +import { TOOLTIP } from '../../../../screens/common'; + +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; + +describe( + 'Alert Suppression basic license check - Rule Form', + { + tags: ['@ess'], + }, + () => { + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + startBasicLicense(); + }); + + it('cannot create rule with rule execution suppression on basic license for all rules with enabled suppression', () => { + // Default query rule + openSuppressionFieldsTooltipAndCheckLicense(); + + selectIndicatorMatchType(); + openSuppressionFieldsTooltipAndCheckLicense(); + + selectNewTermsRuleType(); + openSuppressionFieldsTooltipAndCheckLicense(); + + selectEsqlRuleType(); + openSuppressionFieldsTooltipAndCheckLicense(); + + selectEqlRuleType(); + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.disabled'); + cy.get(ALERT_SUPPRESSION_FIELDS).trigger('mouseover'); + + // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this + cy.get(TOOLTIP).contains('Platinum license'); + + // ML Rules require Platinum license + cy.get(MACHINE_LEARNING_TYPE).get('button').should('be.disabled'); + + selectThresholdRuleType(); + cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).should('be.disabled'); + cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).parent().trigger('mouseover'); + // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this + cy.get(TOOLTIP).contains('Platinum license'); + + cy.get(ALERT_SUPPRESSION_DURATION_VALUE_INPUT).should('be.disabled'); + cy.get(ALERT_SUPPRESSION_DURATION_UNIT_INPUT).should('be.disabled'); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/custom_query_rule.cy.ts new file mode 100644 index 0000000000000..529f01a9aafd5 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/custom_query_rule.cy.ts @@ -0,0 +1,85 @@ +/* + * 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 { getNewRule } from '../../../../objects/rule'; +import { + DEFINITION_DETAILS, + SUPPRESS_FOR_DETAILS, + SUPPRESS_BY_DETAILS, + SUPPRESS_MISSING_FIELD, + DETAILS_TITLE, +} from '../../../../screens/rule_details'; +import { ALERT_SUPPRESSION_FIELDS } from '../../../../screens/create_new_rule'; + +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { + fillAboutRuleMinimumAndContinue, + createRuleWithoutEnabling, + fillAlertSuppressionFields, + skipScheduleRuleAction, + continueFromDefineStep, + fillCustomQueryInput, +} from '../../../../tasks/create_new_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { getDetails } from '../../../../tasks/rule_details'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; + +describe('Custom Query Rule - Alert suppression', { tags: ['@ess', '@serverless'] }, () => { + const rule = getNewRule(); + + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + }); + + const SUPPRESS_BY_FIELDS = ['source.ip']; + + it('creates rule with suppression', () => { + fillCustomQueryInput('*'); + fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); + // alert suppression fields input should not have Technical Preview label + cy.get(ALERT_SUPPRESSION_FIELDS).should('not.contain.text', 'Technical Preview'); + continueFromDefineStep(); + + // ensures details preview works correctly + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Suppress and group alerts for events with missing fields' + ); + + // suppression functionality should be in GA + cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).should( + 'not.contain.text', + 'Technical Preview' + ); + }); + + fillAboutRuleMinimumAndContinue(rule); + skipScheduleRuleAction(); + createRuleWithoutEnabling(); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Suppress and group alerts for events with missing fields' + ); + + // suppression functionality should be in GA + cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).should( + 'not.contain.text', + 'Technical Preview' + ); + }); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/eql_rule.cy.ts similarity index 98% rename from x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/eql_rule.cy.ts index 8f4b6a10faeb8..9f9abceaad182 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/eql_rule.cy.ts @@ -36,7 +36,7 @@ const SUPPRESS_BY_FIELDS = ['agent.type']; // Skip in MKI due to flake describe( - 'Detection Rule Creation - EQL Rules - With Alert Suppression', + 'Detection EQL Rules - Alert suppression', { tags: ['@ess', '@skipInServerlessMKI'], }, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_sequence.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/eql_sequence_rule.cy.ts similarity index 94% rename from x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_sequence.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/eql_sequence_rule.cy.ts index 2cf8526a2f9c3..ffc65fdddbf8b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_sequence.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/eql_sequence_rule.cy.ts @@ -35,11 +35,9 @@ import { const SUPPRESS_BY_FIELDS = ['agent.type']; describe( - 'Detection Rule Creation - EQL Rules - With Alert Suppression', + 'EQL Rules - Alert suppression', { - // skipped in MKI as it depends on feature flag alertSuppressionForEsqlRuleEnabled - // alertSuppressionForEsqlRuleEnabled feature flag is also enabled in a global config - tags: ['@ess', '@skipInServerlessMKI'], + tags: ['@ess', '@serverless'], env: { kbnServerArgs: [ `--xpack.securitySolution.enableExperimental=${JSON.stringify([ diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/esql_rule.cy.ts new file mode 100644 index 0000000000000..a6ef61db06a1d --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/esql_rule.cy.ts @@ -0,0 +1,107 @@ +/* + * 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 { getEsqlRule } from '../../../../objects/rule'; +import { + DEFINITION_DETAILS, + SUPPRESS_BY_DETAILS, + SUPPRESS_FOR_DETAILS, + SUPPRESS_MISSING_FIELD, +} from '../../../../screens/rule_details'; +import { getDetails } from '../../../../tasks/rule_details'; +import { + selectEsqlRuleType, + fillEsqlQueryBar, + createRuleWithoutEnabling, + fillAlertSuppressionFields, + selectAlertSuppressionPerInterval, + setAlertSuppressionDuration, + selectDoNotSuppressForMissingFields, + continueFromDefineStep, + fillAboutRuleMinimumAndContinue, + skipScheduleRuleAction, + interceptEsqlQueryFieldsRequest, +} from '../../../../tasks/create_new_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; + +import { CREATE_RULE_URL } from '../../../../urls/navigation'; + +// https://github.com/cypress-io/cypress/issues/22113 +// issue is inside monaco editor, used in ES|QL query input +// calling it after visiting page in each tests, seems fixes the issue +// the only other alternative is patching ResizeObserver, which is something I would like to avoid +const workaroundForResizeObserver = () => + cy.on('uncaught:exception', (err) => { + if (err.message.includes('ResizeObserver loop limit exceeded')) { + return false; + } + }); + +describe( + 'Detection ES|QL - Alert suppression', + { + tags: ['@ess', '@serverless'], + }, + () => { + const rule = getEsqlRule(); + + beforeEach(() => { + login(); + visit(CREATE_RULE_URL); + }); + + it('shows custom ES|QL field in investigation fields autocomplete and saves it in rule', function () { + const CUSTOM_ESQL_FIELD = '_custom_agent_name'; + const SUPPRESS_BY_FIELDS = [CUSTOM_ESQL_FIELD, 'agent.type']; + + const queryWithCustomFields = [ + `from auditbeat* metadata _id, _version, _index`, + `eval ${CUSTOM_ESQL_FIELD} = agent.name`, + `drop agent.*`, + ].join(' | '); + + workaroundForResizeObserver(); + + selectEsqlRuleType(); + + interceptEsqlQueryFieldsRequest(queryWithCustomFields, 'esqlSuppressionFieldsRequest'); + fillEsqlQueryBar(queryWithCustomFields); + + cy.wait('@esqlSuppressionFieldsRequest'); + fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); + selectAlertSuppressionPerInterval(); + setAlertSuppressionDuration(2, 'h'); + selectDoNotSuppressForMissingFields(); + continueFromDefineStep(); + + // ensures details preview works correctly + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '2h'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + }); + + fillAboutRuleMinimumAndContinue(rule); + skipScheduleRuleAction(); + createRuleWithoutEnabling(); + + // ensures rule details displayed correctly after rule created + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '2h'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/essentials_license_check_rule_form.cy.ts similarity index 96% rename from x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/essentials_license_check_rule_form.cy.ts index 013ed1d6f4f88..707f476a588da 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/essentials_license_check_rule_form.cy.ts @@ -21,7 +21,7 @@ import { import { CREATE_RULE_URL } from '../../../../urls/navigation'; describe( - 'Detection rules, Alert Suppression for Essentials tier', + 'Alert Suppression - Essentials tier license check', { tags: ['@serverless'], env: { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/indicator_match_rule.cy.ts similarity index 98% rename from x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/indicator_match_rule.cy.ts index d0539683e5a64..576a22bbd075d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/indicator_match_rule.cy.ts @@ -36,7 +36,7 @@ import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; const SUPPRESS_BY_FIELDS = ['myhash.mysha256', 'source.ip.keyword']; describe( - 'Detection rules, Indicator Match, Alert Suppression', + 'Indicator Match - Alert suppression', { tags: ['@ess', '@serverless'], }, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule_suppression.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/machine_learning_rule.cy.ts similarity index 99% rename from x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule_suppression.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/machine_learning_rule.cy.ts index 3ce0003dccf90..8eaac1fcb72c0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule_suppression.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/machine_learning_rule.cy.ts @@ -40,7 +40,7 @@ import { getDetails } from '../../../../tasks/rule_details'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; describe( - 'Machine Learning Detection Rules - Creation', + 'Machine Learning Detection Rules - Alert suppression', { tags: ['@ess', '@serverless'], }, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/new_terms_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/new_terms_rule.cy.ts new file mode 100644 index 0000000000000..08148bde1f69e --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/new_terms_rule.cy.ts @@ -0,0 +1,86 @@ +/* + * 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 { getNewTermsRule } from '../../../../objects/rule'; + +import { + DEFINITION_DETAILS, + SUPPRESS_BY_DETAILS, + SUPPRESS_FOR_DETAILS, + SUPPRESS_MISSING_FIELD, +} from '../../../../screens/rule_details'; + +import { getDetails } from '../../../../tasks/rule_details'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { + fillDefineNewTermsRule, + selectNewTermsRuleType, + fillAlertSuppressionFields, + fillAboutRuleMinimumAndContinue, + createRuleWithoutEnabling, + skipScheduleRuleAction, + continueFromDefineStep, + selectAlertSuppressionPerInterval, + setAlertSuppressionDuration, + selectDoNotSuppressForMissingFields, +} from '../../../../tasks/create_new_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; + +describe( + 'New Terms Rule - Alert suppression', + { + tags: ['@ess', '@serverless'], + }, + () => { + const rule = getNewTermsRule(); + + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + selectNewTermsRuleType(); + }); + + it('With time interval suppression', () => { + const SUPPRESS_BY_FIELDS = ['agent.hostname', 'agent.type']; + + fillDefineNewTermsRule(rule); + + // fill suppress by fields and select non-default suppression options + fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); + selectAlertSuppressionPerInterval(); + setAlertSuppressionDuration(45, 'm'); + selectDoNotSuppressForMissingFields(); + continueFromDefineStep(); + + // ensures details preview works correctly + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + }); + + fillAboutRuleMinimumAndContinue(rule); + skipScheduleRuleAction(); + createRuleWithoutEnabling(); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/threshold_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/threshold_rule.cy.ts new file mode 100644 index 0000000000000..a3de0166c72c8 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/alert_suppression/threshold_rule.cy.ts @@ -0,0 +1,65 @@ +/* + * 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 { getNewThresholdRule } from '../../../../objects/rule'; +import { DEFINITION_DETAILS, SUPPRESS_FOR_DETAILS } from '../../../../screens/rule_details'; +import { goToRuleDetailsOf } from '../../../../tasks/alerts_detection_rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; + +import { + createRuleWithoutEnabling, + fillAboutRuleMinimumAndContinue, + enablesAndPopulatesThresholdSuppression, + skipScheduleRuleAction, + selectThresholdRuleType, + fillDefineThresholdRule, + continueFromDefineStep, +} from '../../../../tasks/create_new_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { getDetails } from '../../../../tasks/rule_details'; +import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; + +describe( + 'Threshold Rule - Alert suppression', + { + tags: ['@ess', '@serverless'], + }, + () => { + const rule = getNewThresholdRule(); + + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + }); + + it('Creates a new threshold rule with suppression enabled', () => { + selectThresholdRuleType(); + + fillDefineThresholdRule(rule); + enablesAndPopulatesThresholdSuppression(5, 'h'); + continueFromDefineStep(); + + // ensures duration displayed on define step in preview mode + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '5h'); + }); + + fillAboutRuleMinimumAndContinue(rule); + skipScheduleRuleAction(); + createRuleWithoutEnabling(); + openRuleManagementPageViaBreadcrumbs(); + goToRuleDetailsOf(rule.name); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '5h'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts index f7f343c11b674..3b7b054ec01f1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts @@ -43,10 +43,13 @@ import { fillThreatSubtechnique, fillThreatTechnique, importSavedQuery, + waitForAlertsToPopulate, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; import { visit } from '../../../../tasks/navigation'; +import { waitForTheRuleToBeExecuted } from '../../../../tasks/rule_details'; +import { ALERTS_COUNT, ALERT_GRID_CELL } from '../../../../screens/alerts'; // This test is meant to test touching all the common various components in rule creation // to ensure we don't miss any changes that maybe affect one of these more obscure UI components @@ -111,5 +114,13 @@ describe('Common rule creation flows', { tags: ['@ess', '@serverless'] }, () => cy.get(DESCRIPTION_SETUP_GUIDE_BUTTON).click(); cy.get(DESCRIPTION_SETUP_GUIDE_CONTENT).should('contain', 'test setup markdown'); // Markdown formatting should be removed + + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); + + cy.get(ALERTS_COUNT) + .invoke('text') + .should('match', /^[1-9].+$/); + cy.get(ALERT_GRID_CELL).contains(ruleFields.ruleName); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts deleted file mode 100644 index 99f9e1c145796..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, - ALERT_SUPPRESSION_DURATION_VALUE_INPUT, - MACHINE_LEARNING_TYPE, - ALERT_SUPPRESSION_DURATION_UNIT_INPUT, -} from '../../../../screens/create_new_rule'; - -import { - selectIndicatorMatchType, - selectNewTermsRuleType, - selectThresholdRuleType, - selectEsqlRuleType, - openSuppressionFieldsTooltipAndCheckLicense, -} from '../../../../tasks/create_new_rule'; -import { startBasicLicense } from '../../../../tasks/api_calls/licensing'; -import { login } from '../../../../tasks/login'; -import { visit } from '../../../../tasks/navigation'; -import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { TOOLTIP } from '../../../../screens/common'; - -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; - -describe( - 'Detection rules, Common flows Alert Suppression', - { - tags: ['@ess'], - }, - () => { - describe('Create rule form', () => { - beforeEach(() => { - deleteAlertsAndRules(); - login(); - visit(CREATE_RULE_URL); - startBasicLicense(); - }); - - it('can not create rule with rule execution suppression on basic license for all rules with enabled suppression', () => { - // Default query rule - openSuppressionFieldsTooltipAndCheckLicense(); - - selectIndicatorMatchType(); - openSuppressionFieldsTooltipAndCheckLicense(); - - selectNewTermsRuleType(); - openSuppressionFieldsTooltipAndCheckLicense(); - - selectEsqlRuleType(); - openSuppressionFieldsTooltipAndCheckLicense(); - - // ML Rules require Platinum license - cy.get(MACHINE_LEARNING_TYPE).get('button').should('be.disabled'); - - selectThresholdRuleType(); - cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).should('be.disabled'); - cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).parent().trigger('mouseover'); - // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this - cy.get(TOOLTIP).contains('Platinum license'); - - cy.get(ALERT_SUPPRESSION_DURATION_VALUE_INPUT).should('be.disabled'); - cy.get(ALERT_SUPPRESSION_DURATION_UNIT_INPUT).should('be.disabled'); - }); - }); - } -); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts index 4bb45e03a15fa..354fd97815ccc 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts @@ -6,15 +6,7 @@ */ import { getNewRule } from '../../../../objects/rule'; -import { - RULE_NAME_HEADER, - DEFINITION_DETAILS, - SUPPRESS_FOR_DETAILS, - SUPPRESS_BY_DETAILS, - SUPPRESS_MISSING_FIELD, - DETAILS_TITLE, -} from '../../../../screens/rule_details'; -import { ALERT_SUPPRESSION_FIELDS } from '../../../../screens/create_new_rule'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; import { GLOBAL_SEARCH_BAR_FILTER_ITEM } from '../../../../screens/search_bar'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; @@ -25,100 +17,41 @@ import { createRuleWithoutEnabling, fillDefineCustomRule, openAddFilterPopover, - fillAlertSuppressionFields, - skipScheduleRuleAction, - continueFromDefineStep, - fillCustomQueryInput, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; -import { getDetails } from '../../../../tasks/rule_details'; import { fillAddFilterForm } from '../../../../tasks/search_bar'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; -describe('Create custom query rule', { tags: ['@ess', '@serverless'] }, () => { +describe('Custom query rule - Rule Creation', { tags: ['@ess', '@serverless'] }, () => { const rule = getNewRule(); beforeEach(() => { deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); }); - describe('Custom detection rules creation', () => { - beforeEach(() => { - deleteAlertsAndRules(); - login(); - visit(CREATE_RULE_URL); - }); - - it('Creates and enables a rule', function () { - fillDefineCustomRuleAndContinue(rule); - fillAboutRuleMinimumAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createRuleWithoutEnabling(); - - cy.log('Asserting we have a new rule created'); - cy.get(RULE_NAME_HEADER).should('contain', rule.name); - }); - - // FLAKEY - see https://github.com/elastic/kibana/issues/182891 - it('Adds filter on define step', { tags: ['@skipInServerless'] }, () => { - visit(CREATE_RULE_URL); - fillDefineCustomRule(rule); - openAddFilterPopover(); - fillAddFilterForm({ - key: 'host.name', - operator: 'exists', - }); - // Check that newly added filter exists - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('have.text', 'host.name: exists'); - }); + it('Creates and enables a rule', function () { + fillDefineCustomRuleAndContinue(rule); + fillAboutRuleMinimumAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createRuleWithoutEnabling(); - // https://github.com/elastic/kibana/issues/187277 - describe('Alert suppression', { tags: ['@skipInServerlessMKI'] }, () => { - const SUPPRESS_BY_FIELDS = ['source.ip']; - - it('creates rule with suppression', () => { - fillCustomQueryInput('*'); - fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); - // alert suppression fields input should not have Technical Preview label - cy.get(ALERT_SUPPRESSION_FIELDS).should('not.contain.text', 'Technical Preview'); - continueFromDefineStep(); - - // ensures details preview works correctly - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); - getDetails(SUPPRESS_MISSING_FIELD).should( - 'have.text', - 'Suppress and group alerts for events with missing fields' - ); - - // suppression functionality should be in GA - cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).should( - 'not.contain.text', - 'Technical Preview' - ); - }); - - fillAboutRuleMinimumAndContinue(rule); - skipScheduleRuleAction(); - createRuleWithoutEnabling(); - - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); - getDetails(SUPPRESS_MISSING_FIELD).should( - 'have.text', - 'Suppress and group alerts for events with missing fields' - ); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); + }); - // suppression functionality should be in GA - cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).should( - 'not.contain.text', - 'Technical Preview' - ); - }); - }); + // FLAKEY - see https://github.com/elastic/kibana/issues/182891 + it('Adds filter on define step', { tags: ['@skipInServerless'] }, () => { + visit(CREATE_RULE_URL); + fillDefineCustomRule(rule); + openAddFilterPopover(); + fillAddFilterForm({ + key: 'host.name', + operator: 'exists', }); + // Check that newly added filter exists + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('have.text', 'host.name: exists'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule_data_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule_data_view.cy.ts index 50f43149b9b3e..741f90994d211 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule_data_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule_data_view.cy.ts @@ -5,89 +5,36 @@ * 2.0. */ -import { formatMitreAttackDescription, getHumanizedDuration } from '../../../../helpers/rules'; import { getDataViewRule } from '../../../../objects/rule'; -import { ALERTS_COUNT, ALERT_GRID_CELL } from '../../../../screens/alerts'; -import { - CUSTOM_RULES_BTN, - RISK_SCORE, - RULE_NAME, - RULE_SWITCH, - SEVERITY, -} from '../../../../screens/alerts_detection_rules'; -import { - ABOUT_CONTINUE_BTN, - RULE_DESCRIPTION_INPUT, - RULE_NAME_INPUT, -} from '../../../../screens/create_new_rule'; - -import { - ADDITIONAL_LOOK_BACK_DETAILS, - ABOUT_DETAILS, - ABOUT_INVESTIGATION_NOTES, - ABOUT_RULE_DESCRIPTION, - CUSTOM_QUERY_DETAILS, - DEFINITION_DETAILS, - FALSE_POSITIVES_DETAILS, - removeExternalLinkText, - INDEX_PATTERNS_DETAILS, - INVESTIGATION_NOTES_MARKDOWN, - INVESTIGATION_NOTES_TOGGLE, - MITRE_ATTACK_DETAILS, - REFERENCE_URLS_DETAILS, - RISK_SCORE_DETAILS, - RULE_NAME_HEADER, - RULE_TYPE_DETAILS, - RUNS_EVERY_DETAILS, - SCHEDULE_DETAILS, - SEVERITY_DETAILS, - TAGS_DETAILS, - TIMELINE_TEMPLATE_DETAILS, - DATA_VIEW_DETAILS, - EDIT_RULE_SETTINGS_LINK, - INTERVAL_ABBR_VALUE, -} from '../../../../screens/rule_details'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; import { GLOBAL_SEARCH_BAR_FILTER_ITEM } from '../../../../screens/search_bar'; -import { - getRulesManagementTableRows, - goToRuleDetailsOf, -} from '../../../../tasks/alerts_detection_rules'; import { deleteAlertsAndRules, deleteDataView, postDataView, } from '../../../../tasks/api_calls/common'; import { - createAndEnableRule, createRuleWithoutEnabling, fillAboutRuleAndContinue, fillDefineCustomRule, fillDefineCustomRuleAndContinue, fillScheduleRuleAndContinue, openAddFilterPopover, - waitForAlertsToPopulate, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; -import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; -import { getDetails, waitForTheRuleToBeExecuted } from '../../../../tasks/rule_details'; import { fillAddFilterForm } from '../../../../tasks/search_bar'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; -// Skipping in MKI due to flake -describe('Custom query rules', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { - describe('Custom detection rules creation with data views', () => { +describe( + 'Custom query rules with data views - Rule Creation', + { tags: ['@ess', '@serverless'] }, + () => { const rule = getDataViewRule(); - const expectedUrls = rule.references?.join(''); - const expectedFalsePositives = rule.false_positives?.join(''); - const expectedTags = rule.tags?.join(''); - const mitreAttack = rule.threat; - const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); - const expectedNumberOfRules = 1; beforeEach(() => { if (rule.data_view_id != null) { @@ -95,6 +42,7 @@ describe('Custom query rules', { tags: ['@ess', '@serverless', '@skipInServerles } deleteAlertsAndRules(); login(); + visit(CREATE_RULE_URL); }); afterEach(() => { @@ -104,91 +52,16 @@ describe('Custom query rules', { tags: ['@ess', '@serverless', '@skipInServerles }); it('Creates and enables a new rule', function () { - visit(CREATE_RULE_URL); fillDefineCustomRuleAndContinue(rule); fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - getRulesManagementTableRows().should('have.length', expectedNumberOfRules); - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.risk_score); - cy.get(SEVERITY).should('have.text', 'High'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'High'); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); - }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(DATA_VIEW_DETAILS).should('have.text', rule.data_view_id); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - }); - cy.get(DEFINITION_DETAILS).should('not.contain', INDEX_PATTERNS_DETAILS); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${rule.interval}`); - const humanizedDuration = getHumanizedDuration( - rule.from ?? 'now-6m', - rule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${humanizedDuration}`); - }); - - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); - - cy.get(ALERTS_COUNT) - .invoke('text') - .should('match', /^[1-9].+$/); - cy.get(ALERT_GRID_CELL).contains(rule.name); - }); - - it('Creates and edits a new rule with a data view', function () { - visit(CREATE_RULE_URL); - fillDefineCustomRuleAndContinue(rule); - cy.get(RULE_NAME_INPUT).clear(); - cy.get(RULE_NAME_INPUT).type(rule.name); - cy.get(RULE_DESCRIPTION_INPUT).clear(); - cy.get(RULE_DESCRIPTION_INPUT).type(rule.description); - - cy.get(ABOUT_CONTINUE_BTN).should('exist').click(); - fillScheduleRuleAndContinue(rule); createRuleWithoutEnabling(); - openRuleManagementPageViaBreadcrumbs(); - goToRuleDetailsOf(rule.name); - - cy.get(EDIT_RULE_SETTINGS_LINK).click(); - - cy.get(RULE_NAME_HEADER).should('contain', 'Edit rule settings'); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); }); it('Adds filter on define step', () => { - visit(CREATE_RULE_URL); fillDefineCustomRule(rule); openAddFilterPopover(); fillAddFilterForm({ @@ -198,5 +71,5 @@ describe('Custom query rules', { tags: ['@ess', '@serverless', '@skipInServerles // Check that newly added filter exists cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('have.text', 'host.name: exists'); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_saved_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_saved_query_rule.cy.ts index 48f2bd4002f3b..bb2481a6699bc 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_saved_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_saved_query_rule.cy.ts @@ -5,259 +5,81 @@ * 2.0. */ -import { getNewRule, getSavedQueryRule } from '../../../../objects/rule'; +import { getSavedQueryRule } from '../../../../objects/rule'; -import { - DEFINE_CONTINUE_BUTTON, - LOAD_QUERY_DYNAMICALLY_CHECKBOX, - QUERY_BAR, -} from '../../../../screens/create_new_rule'; +import { DEFINE_CONTINUE_BUTTON, QUERY_BAR } from '../../../../screens/create_new_rule'; import { TOASTER } from '../../../../screens/alerts_detection_rules'; -import { - RULE_NAME_HEADER, - SAVED_QUERY_NAME_DETAILS, - SAVED_QUERY_DETAILS, - SAVED_QUERY_FILTERS_DETAILS, - DEFINE_RULE_PANEL_PROGRESS, - CUSTOM_QUERY_DETAILS, -} from '../../../../screens/rule_details'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; -import { editFirstRule, goToRuleDetailsOf } from '../../../../tasks/alerts_detection_rules'; import { createSavedQuery, deleteSavedQueries } from '../../../../tasks/api_calls/saved_queries'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { - createAndEnableRule, fillAboutRuleAndContinue, fillScheduleRuleAndContinue, selectAndLoadSavedQuery, getCustomQueryInput, checkLoadQueryDynamically, - uncheckLoadQueryDynamically, + createRuleWithoutEnabling, } from '../../../../tasks/create_new_rule'; -import { saveEditedRule, visitEditRulePage } from '../../../../tasks/edit_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; -import { - assertDetailsNotExist, - getDetails, - visitRuleDetailsPage, -} from '../../../../tasks/rule_details'; +import { visitRuleDetailsPage } from '../../../../tasks/rule_details'; import { createRule } from '../../../../tasks/api_calls/rules'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management'; -import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; const savedQueryName = 'custom saved query'; const savedQueryQuery = 'process.name: test'; const savedQueryFilterKey = 'testAgent.value'; -describe('Saved query rules', { tags: ['@ess', '@serverless'] }, () => { - describe('Custom saved_query detection rule creation', () => { - beforeEach(() => { - login(); - deleteAlertsAndRules(); - deleteSavedQueries(); - }); - - it('Creates saved query rule', function () { - const rule = getSavedQueryRule(); - createSavedQuery(savedQueryName, savedQueryQuery, savedQueryFilterKey); - visit(CREATE_RULE_URL); - - selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); - - // edit loaded saved query - getCustomQueryInput() - .type(' AND random query') - .should('have.value', [savedQueryQuery, ' AND random query'].join('')); - - // when clicking load query dynamically checkbox, saved query should be shown in query input and input should be disabled - checkLoadQueryDynamically(); - getCustomQueryInput().should('have.value', savedQueryQuery).should('be.disabled'); - cy.get(QUERY_BAR).should('contain', savedQueryFilterKey); - - cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click(); - - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - cy.intercept('POST', '/api/detection_engine/rules').as('savedQueryRule'); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - cy.wait('@savedQueryRule').then(({ response }) => { - // created rule should have saved_query type - cy.wrap(response?.body.type).should('equal', 'saved_query'); - }); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - - cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); - - getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); - getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); - getDetails(SAVED_QUERY_FILTERS_DETAILS).should('contain', savedQueryFilterKey); - }); - - context('Non existent saved query', () => { - const FAILED_TO_LOAD_ERROR = 'Failed to load the saved query'; - - describe('on rule details page', () => { - beforeEach(() => { - createRule( - getSavedQueryRule({ - saved_id: 'non-existent', - query: undefined, - }) - ).then((rule) => visitRuleDetailsPage(rule.body.id)); - }); - - it('Shows error toast on details page when saved query can not be loaded', function () { - cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); - }); - }); - - describe('on rule editing page', () => { - beforeEach(() => { - createRule( - getSavedQueryRule({ - saved_id: 'non-existent', - query: undefined, - }) - ).then((rule) => visitEditRulePage(rule.body.id)); - }); - - it('Shows validation error on rule edit when saved query can not be loaded', function () { - cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); - }); - - // https://github.com/elastic/kibana/issues/187623 - it( - 'Allows to update saved_query rule with non-existent query', - { tags: ['@skipInServerlessMKI'] }, - () => { - cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('exist'); - - cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); - saveEditedRule(); - - cy.wait('@editedRule').then(({ response }) => { - // updated rule type shouldn't change - cy.wrap(response?.body.type).should('equal', 'saved_query'); - }); - - cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); - - assertDetailsNotExist(SAVED_QUERY_NAME_DETAILS); - assertDetailsNotExist(SAVED_QUERY_DETAILS); - } - ); - }); - }); - - context('Editing', () => { - it('Allows to update query rule as saved_query rule type', () => { - createSavedQuery(savedQueryName, savedQueryQuery); - createRule(getNewRule()).then((rule) => visitEditRulePage(rule.body.id)); - - selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); - checkLoadQueryDynamically(); - - cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); - saveEditedRule(); - - cy.wait('@editedRule').then(({ response }) => { - // updated rule should be saved as saved_query type once Load query dynamically checkbox was checked - cy.wrap(response?.body.type).should('equal', 'saved_query'); - }); - - cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); - - getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); - getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); - }); - - it('Allows to update saved_query rule as query rule type', () => { - const expectedCustomTestQuery = 'random test query'; - createSavedQuery(savedQueryName, savedQueryQuery).then((response) => { - cy.log(JSON.stringify(response.body, null, 2)); - createRule(getSavedQueryRule({ saved_id: response.body.id, query: undefined })).then( - (rule) => visitEditRulePage(rule.body.id) - ); - }); - - // query input should be disabled and has value of saved query - getCustomQueryInput().should('have.value', savedQueryQuery).should('be.disabled'); - - // after unchecking Load Query Dynamically checkbox, query input becomes enabled, type custom query - uncheckLoadQueryDynamically(); - getCustomQueryInput().should('be.enabled').clear().type(expectedCustomTestQuery); - - cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); - saveEditedRule(); +describe('Saved query rules - Rule Creation', { tags: ['@ess', '@serverless'] }, () => { + beforeEach(() => { + login(); + deleteAlertsAndRules(); + deleteSavedQueries(); + }); - cy.wait('@editedRule').then(({ response }) => { - // updated rule should be saved as query type once Load query dynamically checkbox was unchecked - cy.wrap(response?.body.type).should('equal', 'query'); - }); + it('Creates saved query rule', function () { + const rule = getSavedQueryRule(); + createSavedQuery(savedQueryName, savedQueryQuery, savedQueryFilterKey); + visit(CREATE_RULE_URL); - getDetails(CUSTOM_QUERY_DETAILS).should('contain', expectedCustomTestQuery); - }); + selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); - it('Allows to update saved_query rule with non-existent query by adding custom query', () => { - const expectedCustomTestQuery = 'random test query'; - createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })).then((rule) => - visitEditRulePage(rule.body.id) - ); + // edit loaded saved query + getCustomQueryInput() + .type(' AND random query') + .should('have.value', [savedQueryQuery, ' AND random query'].join('')); - uncheckLoadQueryDynamically(); + // when clicking load query dynamically checkbox, saved query should be shown in query input and input should be disabled + checkLoadQueryDynamically(); + getCustomQueryInput().should('have.value', savedQueryQuery).should('be.disabled'); + cy.get(QUERY_BAR).should('contain', savedQueryFilterKey); - // type custom query, ensure Load dynamically checkbox is absent, as rule can't be saved win non valid saved query - getCustomQueryInput().type(expectedCustomTestQuery); - cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('not.visible'); + cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click(); - cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); - saveEditedRule(); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createRuleWithoutEnabling(); - cy.wait('@editedRule').then(({ response }) => { - // updated rule should be saved as query type once Load query dynamically checkbox was unchecked - cy.wrap(response?.body.type).should('equal', 'query'); - }); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); + }); - getDetails(CUSTOM_QUERY_DETAILS).should('contain', expectedCustomTestQuery); + context('Non existent saved query', () => { + const FAILED_TO_LOAD_ERROR = 'Failed to load the saved query'; + + describe('on rule details page', () => { + beforeEach(() => { + createRule( + getSavedQueryRule({ + saved_id: 'non-existent', + query: undefined, + }) + ).then((rule) => visitRuleDetailsPage(rule.body.id)); }); - it('Allows to update saved_query rule with non-existent query by selecting another saved query', () => { - createSavedQuery(savedQueryName, savedQueryQuery); - createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })).then((rule) => - visitEditRulePage(rule.body.id) - ); - - visit(RULES_MANAGEMENT_URL); - - editFirstRule(); - uncheckLoadQueryDynamically(); - - // select another saved query, edit query input, which later should be dismissed once Load query dynamically checkbox checked - selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); - getCustomQueryInput().type('AND this part wil be dismissed'); - - checkLoadQueryDynamically(); - getCustomQueryInput().should('have.value', savedQueryQuery); - - cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); - saveEditedRule(); - - cy.wait('@editedRule').then(({ response }) => { - // updated rule type shouldn't change - cy.wrap(response?.body.type).should('equal', 'saved_query'); - }); - - cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); - - getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); - getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); + it('Shows error toast on details page when saved query can not be loaded', function () { + cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule.cy.ts index e01e5d8e708a2..5c4eeacc722ac 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule.cy.ts @@ -5,50 +5,14 @@ * 2.0. */ -import { formatMitreAttackDescription, getHumanizedDuration } from '../../../../helpers/rules'; -import { getEqlRule, getEqlSequenceRule, getIndexPatterns } from '../../../../objects/rule'; +import { getEqlRule, getEqlSequenceRule } from '../../../../objects/rule'; -import { ALERTS_COUNT, ALERT_DATA_GRID } from '../../../../screens/alerts'; -import { - CUSTOM_RULES_BTN, - RISK_SCORE, - RULES_MANAGEMENT_TABLE, - RULE_NAME, - RULE_SWITCH, - SEVERITY, -} from '../../../../screens/alerts_detection_rules'; -import { - ABOUT_DETAILS, - ABOUT_INVESTIGATION_NOTES, - ABOUT_RULE_DESCRIPTION, - ADDITIONAL_LOOK_BACK_DETAILS, - EQL_QUERY_DETAILS, - DEFINITION_DETAILS, - FALSE_POSITIVES_DETAILS, - removeExternalLinkText, - INDEX_PATTERNS_DETAILS, - INVESTIGATION_NOTES_MARKDOWN, - INVESTIGATION_NOTES_TOGGLE, - MITRE_ATTACK_DETAILS, - REFERENCE_URLS_DETAILS, - RISK_SCORE_DETAILS, - RULE_NAME_HEADER, - RULE_TYPE_DETAILS, - RUNS_EVERY_DETAILS, - SCHEDULE_DETAILS, - SEVERITY_DETAILS, - TAGS_DETAILS, - TIMELINE_TEMPLATE_DETAILS, - INTERVAL_ABBR_VALUE, -} from '../../../../screens/rule_details'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; -import { getDetails } from '../../../../tasks/rule_details'; -import { expectNumberOfRules, goToRuleDetailsOf } from '../../../../tasks/alerts_detection_rules'; -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { continueFromDefineStep, - createAndEnableRule, createRuleWithNonBlockingErrors, + createRuleWithoutEnabling, fillAboutRuleAndContinue, fillDefineEqlRuleAndContinue, fillScheduleRuleAndContinue, @@ -56,12 +20,9 @@ import { getIndexPatternClearButton, getRuleIndexInput, selectEqlRuleType, - waitForAlertsToPopulate, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; -import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; -import { CREATE_RULE_URL } from '../../../../urls/navigation'; import { EQL_OPTIONS_POPOVER_TRIGGER, EQL_OPTIONS_TIMESTAMP_INPUT, @@ -70,132 +31,40 @@ import { EQL_QUERY_VALIDATION_ERROR_CONTENT, RULES_CREATION_FORM, } from '../../../../screens/create_new_rule'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; -// Skip in MKI due to flake -describe('EQL rules', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { +describe('EQL Rule - Rule Creation', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { - login(); deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); }); - describe('Detection rules, EQL', () => { + it('Creates a new EQL rule', function () { const rule = getEqlRule(); - const expectedUrls = rule.references?.join(''); - const expectedFalsePositives = rule.false_positives?.join(''); - const expectedTags = rule.tags?.join(''); - const mitreAttack = rule.threat; - const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); - const expectedNumberOfRules = 1; - const expectedNumberOfAlerts = '1 alert'; - - it('Creates and enables a new EQL rule', function () { - visit(CREATE_RULE_URL); - selectEqlRuleType(); - fillDefineEqlRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + selectEqlRuleType(); + fillDefineEqlRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createRuleWithoutEnabling(); - expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); - - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.risk_score); - cy.get(SEVERITY).should('have.text', 'High'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'High'); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); - }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); - getDetails(EQL_QUERY_DETAILS).should('have.text', rule.query); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Event Correlation'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${rule.interval}`); - const humanizedDuration = getHumanizedDuration( - rule.from ?? 'now-6m', - rule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${humanizedDuration}`); - }); - - waitForAlertsToPopulate(); - - cy.get(ALERTS_COUNT).should('have.text', expectedNumberOfAlerts); - cy.get(ALERT_DATA_GRID) - .invoke('text') - .then((text) => { - expect(text).contains(rule.name); - expect(text).contains(rule.severity); - expect(text).contains(rule.risk_score); - }); - }); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); }); - describe('Detection rules, sequence EQL', () => { - const expectedNumberOfSequenceAlerts = '2 alerts'; - + it('Creates a new EQL rule with a sequence', () => { const rule = getEqlSequenceRule(); - before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); - }); - - after(() => { - cy.task('esArchiverUnload', { archiveName: 'auditbeat_multiple' }); - }); - - it( - 'Creates and enables a new EQL rule with a sequence', - { - tags: ['@skipInServerlessMKI'], - }, - function () { - login(); - visit(CREATE_RULE_URL); - selectEqlRuleType(); - fillDefineEqlRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - goToRuleDetailsOf(rule.name); - waitForAlertsToPopulate(); + selectEqlRuleType(); + fillDefineEqlRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createRuleWithoutEnabling(); - cy.get(ALERTS_COUNT).should('have.text', expectedNumberOfSequenceAlerts); - cy.get(ALERT_DATA_GRID) - .invoke('text') - .then((text) => { - cy.log('ALERT_DATA_GRID', text); - expect(text).contains(rule.name); - expect(text).contains(rule.severity); - }); - } - ); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); }); describe('with source data requiring EQL overrides', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_ess_basic.cy.ts deleted file mode 100644 index a9ebc451c3e08..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_ess_basic.cy.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { getEqlRule } from '../../../../objects/rule'; - -import { login } from '../../../../tasks/login'; -import { visit } from '../../../../tasks/navigation'; -import { getDetails } from '../../../../tasks/rule_details'; -import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -import { selectEqlRuleType } from '../../../../tasks/create_new_rule'; -import { - ALERT_SUPPRESSION_FIELDS_INPUT, - ALERT_SUPPRESSION_FIELDS, -} from '../../../../screens/create_new_rule'; -import { - DEFINITION_DETAILS, - SUPPRESS_FOR_DETAILS, - SUPPRESS_BY_DETAILS, - SUPPRESS_MISSING_FIELD, - DETAILS_TITLE, - ALERT_SUPPRESSION_INSUFFICIENT_LICENSING_ICON, -} from '../../../../screens/rule_details'; -import { startBasicLicense } from '../../../../tasks/api_calls/licensing'; -import { createRule } from '../../../../tasks/api_calls/rules'; -import { TOOLTIP } from '../../../../screens/common'; -import { ruleDetailsUrl } from '../../../../urls/rule_details'; - -const SUPPRESS_BY_FIELDS = ['agent.type']; - -describe( - 'Detection Rule Creation - EQL Rules - With Alert Suppression - Basic License', - { - tags: ['@ess'], - }, - () => { - beforeEach(() => { - deleteAlertsAndRules(); - login(); - visit(CREATE_RULE_URL); - startBasicLicense(); - }); - after(() => { - cy.task('esArchiverUnload', { archiveName: 'auditbeat_multiple' }); - }); - - it('cannot create a rule with "per rule execution" suppression durations', () => { - selectEqlRuleType(); - - cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.disabled'); - cy.get(ALERT_SUPPRESSION_FIELDS).trigger('mouseover'); - - // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this - cy.get(TOOLTIP).contains('Platinum license'); - }); - - it('shows an upselling message on rule suppression details', () => { - const rule = getEqlRule(); - - createRule({ - ...rule, - alert_suppression: { - group_by: SUPPRESS_BY_FIELDS, - duration: { value: 360, unit: 's' }, - missing_fields_strategy: 'doNotSuppress', - }, - }).then((createdRule) => { - visit(ruleDetailsUrl(createdRule.body.id)); - - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '360s'); - getDetails(SUPPRESS_MISSING_FIELD).should( - 'have.text', - 'Do not suppress alerts for events with missing fields' - ); - - // suppression functionality should be under Tech Preview - cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).contains('Technical Preview'); - }); - - // Platinum license is required for configuration to apply - cy.get(ALERT_SUPPRESSION_INSUFFICIENT_LICENSING_ICON).eq(2).trigger('mouseover'); - cy.get(TOOLTIP).contains( - 'Alert suppression is configured but will not be applied due to insufficient licensing' - ); - }); - }); - } -); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_serverless_essentials.cy.ts deleted file mode 100644 index 6c0241f94c1f5..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/eql_rule_suppression_serverless_essentials.cy.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { getEqlRule } from '../../../../objects/rule'; - -import { login } from '../../../../tasks/login'; -import { visit } from '../../../../tasks/navigation'; -import { getDetails } from '../../../../tasks/rule_details'; -import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -import { - fillAlertSuppressionFields, - fillAboutRuleMinimumAndContinue, - createRuleWithoutEnabling, - skipScheduleRuleAction, - continueFromDefineStep, - fillDefineEqlRule, - selectEqlRuleType, -} from '../../../../tasks/create_new_rule'; - -import { DEFINITION_DETAILS, SUPPRESS_BY_DETAILS } from '../../../../screens/rule_details'; - -const SUPPRESS_BY_FIELDS = ['agent.type']; - -describe( - 'Detection Rule Creation - EQL Rules - With Alert Suppression - Serverless Essentials License', - { - tags: ['@serverless'], - env: { - ftrConfig: { - productTypes: [ - { product_line: 'security', product_tier: 'essentials' }, - { product_line: 'endpoint', product_tier: 'essentials' }, - ], - }, - }, - }, - () => { - const rule = getEqlRule(); - before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); - }); - beforeEach(() => { - deleteAlertsAndRules(); - login(); - }); - after(() => { - cy.task('esArchiverUnload', { archiveName: 'auditbeat_multiple' }); - }); - - describe('with non-sequence queries', () => { - it('creates a rule with a "per rule execution" suppression duration', () => { - visit(CREATE_RULE_URL); - selectEqlRuleType(); - fillDefineEqlRule(rule); - - // selecting only suppression fields, the rest options would be default - fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); - continueFromDefineStep(); - - fillAboutRuleMinimumAndContinue(rule); - skipScheduleRuleAction(); - createRuleWithoutEnabling(); - - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - }); - }); - }); - } -); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts index 0045a79ff4394..0f5407b4f2545 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts @@ -16,10 +16,6 @@ import { RULE_NAME_HEADER, RULE_TYPE_DETAILS, RULE_NAME_OVERRIDE_DETAILS, - DEFINITION_DETAILS, - SUPPRESS_BY_DETAILS, - SUPPRESS_FOR_DETAILS, - SUPPRESS_MISSING_FIELD, } from '../../../../screens/rule_details'; import { ESQL_QUERY_BAR } from '../../../../screens/create_new_rule'; @@ -41,14 +37,6 @@ import { fillRuleName, fillDescription, getAboutContinueButton, - fillAlertSuppressionFields, - selectAlertSuppressionPerInterval, - setAlertSuppressionDuration, - selectDoNotSuppressForMissingFields, - continueFromDefineStep, - fillAboutRuleMinimumAndContinue, - skipScheduleRuleAction, - interceptEsqlQueryFieldsRequest, createRuleWithNonBlockingErrors, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; @@ -68,7 +56,7 @@ const workaroundForResizeObserver = () => }); describe( - 'Detection ES|QL rules, creation', + 'Detection ES|QL rules - Rule Creation', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'], }, @@ -241,60 +229,5 @@ describe( cy.get(INVESTIGATION_FIELDS_VALUE_ITEM).should('have.text', CUSTOM_ESQL_FIELD); }); }); - - describe('Alert suppression', () => { - beforeEach(() => { - login(); - visit(CREATE_RULE_URL); - }); - it('shows custom ES|QL field in investigation fields autocomplete and saves it in rule', function () { - const CUSTOM_ESQL_FIELD = '_custom_agent_name'; - const SUPPRESS_BY_FIELDS = [CUSTOM_ESQL_FIELD, 'agent.type']; - - const queryWithCustomFields = [ - `from auditbeat* metadata _id, _version, _index`, - `eval ${CUSTOM_ESQL_FIELD} = agent.name`, - `drop agent.*`, - ].join(' | '); - - workaroundForResizeObserver(); - - selectEsqlRuleType(); - - interceptEsqlQueryFieldsRequest(queryWithCustomFields, 'esqlSuppressionFieldsRequest'); - fillEsqlQueryBar(queryWithCustomFields); - - cy.wait('@esqlSuppressionFieldsRequest'); - fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); - selectAlertSuppressionPerInterval(); - setAlertSuppressionDuration(2, 'h'); - selectDoNotSuppressForMissingFields(); - continueFromDefineStep(); - - // ensures details preview works correctly - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '2h'); - getDetails(SUPPRESS_MISSING_FIELD).should( - 'have.text', - 'Do not suppress alerts for events with missing fields' - ); - }); - - fillAboutRuleMinimumAndContinue(rule); - skipScheduleRuleAction(); - createRuleWithoutEnabling(); - - // ensures rule details displayed correctly after rule created - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '2h'); - getDetails(SUPPRESS_MISSING_FIELD).should( - 'have.text', - 'Do not suppress alerts for events with missing fields' - ); - }); - }); - }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts index 89788eb689a1e..261a0046f8463 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts @@ -118,461 +118,465 @@ import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"'; // Skipping in MKI due to flake -describe('indicator match', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { - describe('Detection rules, Indicator Match', () => { - const expectedUrls = getNewThreatIndicatorRule().references?.join(''); - const expectedFalsePositives = getNewThreatIndicatorRule().false_positives?.join(''); - const expectedTags = getNewThreatIndicatorRule().tags?.join(''); - const mitreAttack = getNewThreatIndicatorRule().threat; - const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); - const expectedNumberOfRules = 1; - const expectedNumberOfAlerts = '1 alert'; - - beforeEach(() => { - cy.task('esArchiverLoad', { archiveName: 'threat_indicator' }); - cy.task('esArchiverLoad', { archiveName: 'suspicious_source_event' }); - deleteAlertsAndRules(); - login(); - }); +describe( + 'Indicator Match - Rule Creation', + { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, + () => { + describe('Detection rules, Indicator Match', () => { + const expectedUrls = getNewThreatIndicatorRule().references?.join(''); + const expectedFalsePositives = getNewThreatIndicatorRule().false_positives?.join(''); + const expectedTags = getNewThreatIndicatorRule().tags?.join(''); + const mitreAttack = getNewThreatIndicatorRule().threat; + const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); + const expectedNumberOfRules = 1; + const expectedNumberOfAlerts = '1 alert'; + + beforeEach(() => { + cy.task('esArchiverLoad', { archiveName: 'threat_indicator' }); + cy.task('esArchiverLoad', { archiveName: 'suspicious_source_event' }); + deleteAlertsAndRules(); + login(); + }); - describe('Creating new indicator match rules', () => { - describe('Index patterns', () => { - beforeEach(() => { - visit(CREATE_RULE_URL); - selectIndicatorMatchType(); - }); + describe('Creating new indicator match rules', () => { + describe('Index patterns', () => { + beforeEach(() => { + visit(CREATE_RULE_URL); + selectIndicatorMatchType(); + }); - it('Contains a predefined index pattern', () => { - getRuleIndexInput().should('have.text', getIndexPatterns().join('')); - }); + it('Contains a predefined index pattern', () => { + getRuleIndexInput().should('have.text', getIndexPatterns().join('')); + }); - it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('not.exist'); - }); + it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('not.exist'); + }); - it('Shows invalidation text when you try to continue without filling it out', () => { - getIndexPatternClearButton().click(); - getIndicatorIndicatorIndex().type(`{backspace}{enter}`); - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('exist'); + it('Shows invalidation text when you try to continue without filling it out', () => { + getIndexPatternClearButton().click(); + getIndicatorIndicatorIndex().type(`{backspace}{enter}`); + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('exist'); + }); }); - }); - describe('Indicator index patterns', () => { - beforeEach(() => { - visit(CREATE_RULE_URL); - selectIndicatorMatchType(); - }); + describe('Indicator index patterns', () => { + beforeEach(() => { + visit(CREATE_RULE_URL); + selectIndicatorMatchType(); + }); - it('Contains a predefined index pattern', () => { - getIndicatorIndicatorIndex().should('have.text', getThreatIndexPatterns().join('')); - }); + it('Contains a predefined index pattern', () => { + getIndicatorIndicatorIndex().should('have.text', getThreatIndexPatterns().join('')); + }); - it('Does NOT show invalidation text on initial page load', () => { - getIndexPatternInvalidationText().should('not.exist'); - }); + it('Does NOT show invalidation text on initial page load', () => { + getIndexPatternInvalidationText().should('not.exist'); + }); - it('Shows invalidation text if you try to continue without filling it out', () => { - getIndicatorIndicatorIndex().type(`{backspace}{enter}`); - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('exist'); + it('Shows invalidation text if you try to continue without filling it out', () => { + getIndicatorIndicatorIndex().type(`{backspace}{enter}`); + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('exist'); + }); }); - }); - describe('custom query input', () => { - beforeEach(() => { - visit(CREATE_RULE_URL); - selectIndicatorMatchType(); - }); + describe('custom query input', () => { + beforeEach(() => { + visit(CREATE_RULE_URL); + selectIndicatorMatchType(); + }); - it('Has a default set of *:*', () => { - getCustomQueryInput().should('have.text', '*:*'); - }); + it('Has a default set of *:*', () => { + getCustomQueryInput().should('have.text', '*:*'); + }); - it('Shows invalidation text if text is removed', () => { - getCustomQueryInput().type('{selectall}{del}'); - getCustomQueryInvalidationText().should('exist'); + it('Shows invalidation text if text is removed', () => { + getCustomQueryInput().type('{selectall}{del}'); + getCustomQueryInvalidationText().should('exist'); + }); }); - }); - describe('indicator query input', () => { - beforeEach(() => { - visit(CREATE_RULE_URL); - selectIndicatorMatchType(); - }); + describe('indicator query input', () => { + beforeEach(() => { + visit(CREATE_RULE_URL); + selectIndicatorMatchType(); + }); - it(`Has a default set of ${DEFAULT_THREAT_MATCH_QUERY}`, () => { - getCustomIndicatorQueryInput().should('have.text', DEFAULT_THREAT_MATCH_QUERY); - }); + it(`Has a default set of ${DEFAULT_THREAT_MATCH_QUERY}`, () => { + getCustomIndicatorQueryInput().should('have.text', DEFAULT_THREAT_MATCH_QUERY); + }); - it('Shows invalidation text if text is removed', () => { - getCustomIndicatorQueryInput().type('{selectall}{del}'); - getThreatMatchQueryInvalidationText().should('exist'); + it('Shows invalidation text if text is removed', () => { + getCustomIndicatorQueryInput().type('{selectall}{del}'); + getThreatMatchQueryInvalidationText().should('exist'); + }); }); - }); - // FLAKY: https://github.com/elastic/kibana/issues/182669 - describe.skip('Indicator mapping', () => { - beforeEach(() => { - const rule = getNewThreatIndicatorRule(); - visit(CREATE_RULE_URL); - selectIndicatorMatchType(); - if (rule.index) { - fillIndexAndIndicatorIndexPattern(rule.index, rule.threat_index); - } - }); + // FLAKY: https://github.com/elastic/kibana/issues/182669 + describe.skip('Indicator mapping', () => { + beforeEach(() => { + const rule = getNewThreatIndicatorRule(); + visit(CREATE_RULE_URL); + selectIndicatorMatchType(); + if (rule.index) { + fillIndexAndIndicatorIndexPattern(rule.index, rule.threat_index); + } + }); - it('Does NOT show invalidation text on initial page load', () => { - getIndicatorInvalidationText().should('not.exist'); - }); + it('Does NOT show invalidation text on initial page load', () => { + getIndicatorInvalidationText().should('not.exist'); + }); - it('Shows invalidation text when you try to press continue without filling anything out', () => { - getDefineContinueButton().click(); - getIndicatorAtLeastOneInvalidationText().should('exist'); - }); + it('Shows invalidation text when you try to press continue without filling anything out', () => { + getDefineContinueButton().click(); + getIndicatorAtLeastOneInvalidationText().should('exist'); + }); - it('Shows invalidation text when the "AND" button is pressed and both the mappings are blank', () => { - getIndicatorAndButton().click(); - getIndicatorInvalidationText().should('exist'); - }); + it('Shows invalidation text when the "AND" button is pressed and both the mappings are blank', () => { + getIndicatorAndButton().click(); + getIndicatorInvalidationText().should('exist'); + }); - it('Shows invalidation text when the "OR" button is pressed and both the mappings are blank', () => { - getIndicatorOrButton().click(); - getIndicatorInvalidationText().should('exist'); - }); + it('Shows invalidation text when the "OR" button is pressed and both the mappings are blank', () => { + getIndicatorOrButton().click(); + getIndicatorInvalidationText().should('exist'); + }); - it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('not.exist'); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('not.exist'); - }); - it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value', - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - validColumns: 'indicatorField', + it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value', + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + validColumns: 'indicatorField', + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('exist'); - }); - it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: 'non-existent-value', - validColumns: 'indexField', + it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: 'non-existent-value', + validColumns: 'indexField', + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('exist'); - }); - it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'agent.name', - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - validColumns: 'indicatorField', - }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().find('input').should('have.value', 'agent.name'); - getIndicatorMappingComboField() - .find('input') - .should('have.value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); + it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'agent.name', + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + validColumns: 'indicatorField', + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().find('input').should('have.value', 'agent.name'); + getIndicatorMappingComboField() + .find('input') + .should('have.value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); + }); - it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: 'non-existent-value', - validColumns: 'indexField', - }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: 'second-non-existent-value', - validColumns: 'indexField', - }); - getIndicatorDeleteButton().click(); - getIndicatorMappingComboField() - .find('input') - .should('have.value', 'second-non-existent-value'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); + it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: 'non-existent-value', + validColumns: 'indexField', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: 'second-non-existent-value', + validColumns: 'indexField', + }); + getIndicatorDeleteButton().click(); + getIndicatorMappingComboField() + .find('input') + .should('have.value', 'second-non-existent-value'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); + }); - it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value', - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - validColumns: 'indicatorField', - }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'second-non-existent-value', - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - validColumns: 'indicatorField', - }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField() - .find('input') - .should('have.value', 'second-non-existent-value'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); + it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value', + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + validColumns: 'indicatorField', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'second-non-existent-value', + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + validColumns: 'indicatorField', + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField() + .find('input') + .should('have.value', 'second-non-existent-value'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); + }); - it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => { - fillIndicatorMatchRow({ - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField() - .find('input') - .should('value', '') - .should('have.attr', 'placeholder', 'Search'); - getIndicatorMappingComboField() - .find('input') - .should('value', '') - .should('have.attr', 'placeholder', 'Search'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); + it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => { + fillIndicatorMatchRow({ + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField() + .find('input') + .should('value', '') + .should('have.attr', 'placeholder', 'Search'); + getIndicatorMappingComboField() + .find('input') + .should('value', '') + .should('have.attr', 'placeholder', 'Search'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); + }); - it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => { - fillIndicatorMatchRow({ - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'non-existent-value', - indicatorIndexField: 'non-existent-value', - validColumns: 'none', - }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 3, - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - }); - getIndicatorDeleteButton(2).click(); - getIndicatorIndexComboField(1) - .find('input') - .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].field); - getIndicatorMappingComboField(1) - .find('input') - .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); - getIndicatorIndexComboField(2) - .find('input') - .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].field); - getIndicatorMappingComboField(2) - .find('input') - .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); - getIndicatorIndexComboField(3).should('not.exist'); - getIndicatorMappingComboField(3).should('not.exist'); + it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => { + fillIndicatorMatchRow({ + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'non-existent-value', + indicatorIndexField: 'non-existent-value', + validColumns: 'none', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 3, + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + }); + getIndicatorDeleteButton(2).click(); + getIndicatorIndexComboField(1) + .find('input') + .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].field); + getIndicatorMappingComboField(1) + .find('input') + .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); + getIndicatorIndexComboField(2) + .find('input') + .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].field); + getIndicatorMappingComboField(2) + .find('input') + .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); + getIndicatorIndexComboField(3).should('not.exist'); + getIndicatorMappingComboField(3).should('not.exist'); + }); + + it('Can add two OR rows and delete the second row. The first row has invalid data and the second row has valid data. The first row is deleted and the second row shifts up correctly.', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value-one', + indicatorIndexField: 'non-existent-value-two', + validColumns: 'none', + }); + getIndicatorOrButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, + indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField() + .find('input') + .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].field); + getIndicatorMappingComboField() + .find('input') + .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); + }); }); - it('Can add two OR rows and delete the second row. The first row has invalid data and the second row has valid data. The first row is deleted and the second row shifts up correctly.', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value-one', - indicatorIndexField: 'non-existent-value-two', - validColumns: 'none', - }); - getIndicatorOrButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].field, - indicatorIndexField: getNewThreatIndicatorRule().threat_mapping[0].entries[0].value, - }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField() - .find('input') - .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].field); - getIndicatorMappingComboField() - .find('input') - .should('value', getNewThreatIndicatorRule().threat_mapping[0].entries[0].value); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); + describe('Schedule', () => { + it('IM rule has 1h time interval and lookback by default', () => { + visit(CREATE_RULE_URL); + selectIndicatorMatchType(); + fillDefineIndicatorMatchRuleAndContinue(getNewThreatIndicatorRule()); + fillAboutRuleAndContinue(getNewThreatIndicatorRule()); + + cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', '1'); + cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', 'h'); + cy.get(SCHEDULE_LOOKBACK_AMOUNT_INPUT).invoke('val').should('eql', '5'); + cy.get(SCHEDULE_LOOKBACK_UNITS_INPUT).invoke('val').should('eql', 'm'); + }); }); }); - describe('Schedule', () => { - it('IM rule has 1h time interval and lookback by default', () => { + describe('Generating signals', () => { + it('Creates and enables a new Indicator Match rule', () => { + const rule = getNewThreatIndicatorRule(); visit(CREATE_RULE_URL); selectIndicatorMatchType(); - fillDefineIndicatorMatchRuleAndContinue(getNewThreatIndicatorRule()); - fillAboutRuleAndContinue(getNewThreatIndicatorRule()); + fillDefineIndicatorMatchRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createAndEnableRule(); + openRuleManagementPageViaBreadcrumbs(); + + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + + expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); + + cy.get(RULE_NAME).should('have.text', rule.name); + cy.get(RISK_SCORE).should('have.text', rule.risk_score); + cy.get(SEVERITY).should('have.text', 'Critical'); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetailsOf(rule.name); + + cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', 'Critical'); + getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); + getDetails(INDICATOR_PREFIX_OVERRIDE).should('have.text', rule.threat_indicator_path); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); + getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); + getDetails(TAGS_DETAILS).should('have.text', expectedTags); + }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click(); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); + + cy.get(DEFINITION_DETAILS).within(() => { + if (rule.index) { + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', rule.index.join('')); + } + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*'); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + getDetails(INDICATOR_INDEX_PATTERNS).should('have.text', rule.threat_index.join('')); + getDetails(INDICATOR_MAPPING).should( + 'have.text', + `${rule.threat_mapping[0].entries[0].field} MATCHES ${rule.threat_mapping[0].entries[0].value}` + ); + getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*'); + }); - cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', '1'); - cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', 'h'); - cy.get(SCHEDULE_LOOKBACK_AMOUNT_INPUT).invoke('val').should('eql', '5'); - cy.get(SCHEDULE_LOOKBACK_UNITS_INPUT).invoke('val').should('eql', 'm'); - }); - }); - }); + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS) + .find(INTERVAL_ABBR_VALUE) + .should('have.text', `${rule.interval}`); + const humanizedDuration = getHumanizedDuration( + rule.from ?? 'now-6m', + rule.interval ?? '5m' + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS) + .find(INTERVAL_ABBR_VALUE) + .should('have.text', `${humanizedDuration}`); + }); - describe('Generating signals', () => { - it('Creates and enables a new Indicator Match rule', () => { - const rule = getNewThreatIndicatorRule(); - visit(CREATE_RULE_URL); - selectIndicatorMatchType(); - fillDefineIndicatorMatchRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); - - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.risk_score); - cy.get(SEVERITY).should('have.text', 'Critical'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'Critical'); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); - getDetails(INDICATOR_PREFIX_OVERRIDE).should('have.text', rule.threat_indicator_path); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); - }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - - cy.get(DEFINITION_DETAILS).within(() => { - if (rule.index) { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', rule.index.join('')); - } - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*'); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(INDICATOR_INDEX_PATTERNS).should('have.text', rule.threat_index.join('')); - getDetails(INDICATOR_MAPPING).should( - 'have.text', - `${rule.threat_mapping[0].entries[0].field} MATCHES ${rule.threat_mapping[0].entries[0].value}` - ); - getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*'); - }); + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${rule.interval}`); - const humanizedDuration = getHumanizedDuration( - rule.from ?? 'now-6m', - rule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${humanizedDuration}`); + cy.get(ALERTS_COUNT).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_RISK_SCORE).first().should('have.text', rule.risk_score); }); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); + it('Investigate alert in timeline', () => { + loadPrepackagedTimelineTemplates(); + createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })).then( + (rule) => visitRuleDetailsPage(rule.body.id) + ); - cy.get(ALERTS_COUNT).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_RISK_SCORE).first().should('have.text', rule.risk_score); - }); + waitForAlertsToPopulate(); + investigateFirstAlertInTimeline(); - it('Investigate alert in timeline', () => { - loadPrepackagedTimelineTemplates(); - createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })).then( - (rule) => visitRuleDetailsPage(rule.body.id) - ); - - waitForAlertsToPopulate(); - investigateFirstAlertInTimeline(); - - cy.get(PROVIDER_BADGE).should('have.length', 3); - cy.get(PROVIDER_BADGE).should( - 'have.text', - `threat.enrichments.matched.atomic: "${ - indicatorRuleMatchingDoc.atomic - }"threat.enrichments.matched.type: "indicator_match_rule"threat.enrichments.matched.field: "${ - getNewThreatIndicatorRule().threat_mapping[0].entries[0].field - }"` - ); - - cy.get(INDICATOR_MATCH_ROW_RENDER).should( - 'have.text', - `${getNewThreatIndicatorRule().threat_mapping[0].entries[0].field}matched${ - indicatorRuleMatchingDoc.atomic - }indicator_match_ruleprovided` + ` byAbuseCH malware` - ); - }); - }); + cy.get(PROVIDER_BADGE).should('have.length', 3); + cy.get(PROVIDER_BADGE).should( + 'have.text', + `threat.enrichments.matched.atomic: "${ + indicatorRuleMatchingDoc.atomic + }"threat.enrichments.matched.type: "indicator_match_rule"threat.enrichments.matched.field: "${ + getNewThreatIndicatorRule().threat_mapping[0].entries[0].field + }"` + ); - describe('Duplicates the indicator rule', () => { - const TESTED_RULE_DATA = getNewThreatIndicatorRule({ - name: 'Indicator rule duplicate test', - rule_id: 'rule_testing', - enabled: false, + cy.get(INDICATOR_MATCH_ROW_RENDER).should( + 'have.text', + `${getNewThreatIndicatorRule().threat_mapping[0].entries[0].field}matched${ + indicatorRuleMatchingDoc.atomic + }indicator_match_ruleprovided` + ` byAbuseCH malware` + ); + }); }); - describe('on rule editing page', () => { - beforeEach(() => { - createRule(TESTED_RULE_DATA); - visit(RULES_MANAGEMENT_URL); - disableAutoRefresh(); + describe('Duplicates the indicator rule', () => { + const TESTED_RULE_DATA = getNewThreatIndicatorRule({ + name: 'Indicator rule duplicate test', + rule_id: 'rule_testing', + enabled: false, }); - it('Allows the rule to be duplicated from the table', () => { - duplicateFirstRule(); - goBackToRuleDetails(); - goBackToRulesTable(); - checkDuplicatedRule(TESTED_RULE_DATA.name); - }); + describe('on rule editing page', () => { + beforeEach(() => { + createRule(TESTED_RULE_DATA); + visit(RULES_MANAGEMENT_URL); + disableAutoRefresh(); + }); - it("Allows the rule to be duplicated from the table's bulk actions", () => { - selectAllRules(); - duplicateSelectedRulesWithExceptions(); - checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`); - }); - }); + it('Allows the rule to be duplicated from the table', () => { + duplicateFirstRule(); + goBackToRuleDetails(); + goBackToRulesTable(); + checkDuplicatedRule(TESTED_RULE_DATA.name); + }); - describe('on rule details page', () => { - beforeEach(() => { - createRule(getNewThreatIndicatorRule(TESTED_RULE_DATA)).then((rule) => - visitRuleDetailsPage(rule.body.id) - ); + it("Allows the rule to be duplicated from the table's bulk actions", () => { + selectAllRules(); + duplicateSelectedRulesWithExceptions(); + checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`); + }); }); - it('Allows the rule to be duplicated', () => { - duplicateRuleFromMenu(); - goBackToRuleDetails(); - goBackToRulesTable(); - checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`); + describe('on rule details page', () => { + beforeEach(() => { + createRule(getNewThreatIndicatorRule(TESTED_RULE_DATA)).then((rule) => + visitRuleDetailsPage(rule.body.id) + ); + }); + + it('Allows the rule to be duplicated', () => { + duplicateRuleFromMenu(); + goBackToRuleDetails(); + goBackToRulesTable(); + checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`); + }); }); }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_serverless_essentials.cy.ts deleted file mode 100644 index 239c1ba4e9682..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_serverless_essentials.cy.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getNewThreatIndicatorRule } from '../../../../objects/rule'; - -import { DEFINITION_DETAILS, SUPPRESS_BY_DETAILS } from '../../../../screens/rule_details'; - -import { - fillDefineIndicatorMatchRule, - fillAlertSuppressionFields, - selectIndicatorMatchType, - fillAboutRuleMinimumAndContinue, - createRuleWithoutEnabling, - skipScheduleRuleAction, - continueFromDefineStep, -} from '../../../../tasks/create_new_rule'; - -import { login } from '../../../../tasks/login'; -import { visit } from '../../../../tasks/navigation'; -import { getDetails } from '../../../../tasks/rule_details'; -import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; - -const SUPPRESS_BY_FIELDS = ['myhash.mysha256', 'source.ip.keyword']; - -describe( - 'Detection rules, Indicator Match, Alert Suppression', - { - tags: ['@serverless'], - env: { - ftrConfig: { - productTypes: [ - { product_line: 'security', product_tier: 'essentials' }, - { product_line: 'endpoint', product_tier: 'essentials' }, - ], - }, - }, - }, - () => { - const rule = getNewThreatIndicatorRule(); - beforeEach(() => { - cy.task('esArchiverLoad', { archiveName: 'threat_indicator' }); - cy.task('esArchiverLoad', { archiveName: 'suspicious_source_event' }); - deleteAlertsAndRules(); - login(); - }); - - it('creates rule with per rule execution suppression for essentials license', () => { - visit(CREATE_RULE_URL); - selectIndicatorMatchType(); - fillDefineIndicatorMatchRule(rule); - - // selecting only suppression fields, the rest options would be default - fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); - continueFromDefineStep(); - - fillAboutRuleMinimumAndContinue(rule); - skipScheduleRuleAction(); - createRuleWithoutEnabling(); - - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - }); - }); - } -); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule.cy.ts index 50ca504e7a125..2077d881e5f84 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule.cy.ts @@ -5,43 +5,12 @@ * 2.0. */ -import { formatMitreAttackDescription, getHumanizedDuration } from '../../../../helpers/rules'; import { getMachineLearningRule } from '../../../../objects/rule'; -import { - RISK_SCORE, - RULES_MANAGEMENT_TABLE, - RULE_NAME, - RULE_SWITCH, - SEVERITY, -} from '../../../../screens/alerts_detection_rules'; -import { - ABOUT_DETAILS, - ABOUT_RULE_DESCRIPTION, - ADDITIONAL_LOOK_BACK_DETAILS, - ANOMALY_SCORE_DETAILS, - DEFINITION_DETAILS, - FALSE_POSITIVES_DETAILS, - removeExternalLinkText, - MACHINE_LEARNING_JOB_ID, - // MACHINE_LEARNING_JOB_STATUS, - MITRE_ATTACK_DETAILS, - REFERENCE_URLS_DETAILS, - RISK_SCORE_DETAILS, - RULE_NAME_HEADER, - RULE_TYPE_DETAILS, - RUNS_EVERY_DETAILS, - SCHEDULE_DETAILS, - SEVERITY_DETAILS, - TAGS_DETAILS, - TIMELINE_TEMPLATE_DETAILS, - INTERVAL_ABBR_VALUE, -} from '../../../../screens/rule_details'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; -import { getDetails } from '../../../../tasks/rule_details'; -import { expectNumberOfRules, goToRuleDetailsOf } from '../../../../tasks/alerts_detection_rules'; import { - createAndEnableRule, + createRuleWithoutEnabling, fillAboutRuleAndContinue, fillDefineMachineLearningRuleAndContinue, fillScheduleRuleAndContinue, @@ -49,22 +18,11 @@ import { } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; -import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; import { forceStopAndCloseJob } from '../../../../support/machine_learning'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -// https://github.com/elastic/kibana/issues/187622 -describe('Machine Learning rules', { tags: ['@ess', '@serverless, @skipInServerlessMKI'] }, () => { - const expectedUrls = (getMachineLearningRule().references ?? []).join(''); - const expectedFalsePositives = (getMachineLearningRule().false_positives ?? []).join(''); - const expectedTags = (getMachineLearningRule().tags ?? []).join(''); - const expectedMitre = formatMitreAttackDescription(getMachineLearningRule().threat ?? []); - const expectedJobText = [ - 'Unusual Linux Network Activity', - 'Anomalous Process for a Linux Population', - ].join(''); - +describe('Machine Learning - Rule Creation', { tags: ['@ess', '@serverless'] }, () => { before(() => { const machineLearningJobIds = ([] as string[]).concat( getMachineLearningRule().machine_learning_job_id @@ -79,60 +37,15 @@ describe('Machine Learning rules', { tags: ['@ess', '@serverless, @skipInServerl visit(CREATE_RULE_URL); }); - it('Creates and enables a new ml rule', () => { + it('Creates a new ml rule', () => { const mlRule = getMachineLearningRule(); selectMachineLearningRuleType(); fillDefineMachineLearningRuleAndContinue(mlRule); fillAboutRuleAndContinue(mlRule); fillScheduleRuleAndContinue(mlRule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - expectNumberOfRules(RULES_MANAGEMENT_TABLE, 1); - - cy.get(RULE_NAME).should('have.text', mlRule.name); - cy.get(RISK_SCORE).should('have.text', mlRule.risk_score); - cy.get(SEVERITY).should('have.text', 'Critical'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(mlRule.name); + createRuleWithoutEnabling(); - cy.get(RULE_NAME_HEADER).should('contain', `${mlRule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', mlRule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'Critical'); - getDetails(RISK_SCORE_DETAILS).should('have.text', mlRule.risk_score); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); - }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(ANOMALY_SCORE_DETAILS).should('have.text', mlRule.anomaly_threshold); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Machine Learning'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - // With the #1912 ML rule improvement changes we enable jobs on rule creation. - // Though, in cypress jobs enabling does not work reliably and job can be started or stopped. - // Thus, we disable next check till we fix the issue with enabling jobs in cypress. - // Relevant ticket: https://github.com/elastic/security-team/issues/5389 - // cy.get(MACHINE_LEARNING_JOB_STATUS).should('have.text', 'StoppedStopped'); - cy.get(MACHINE_LEARNING_JOB_ID).should('have.text', expectedJobText); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${mlRule.interval}`); - const humanizedDuration = getHumanizedDuration( - mlRule.from ?? 'now-6m', - mlRule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${humanizedDuration}`); - }); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', mlRule.name); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts index 996cf764c736c..beaac5cd14d8b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts @@ -5,197 +5,44 @@ * 2.0. */ -import { formatMitreAttackDescription, getHumanizedDuration } from '../../../../helpers/rules'; -import { getIndexPatterns, getNewTermsRule } from '../../../../objects/rule'; +import { getNewTermsRule } from '../../../../objects/rule'; -import { ALERT_DATA_GRID } from '../../../../screens/alerts'; -import { - CUSTOM_RULES_BTN, - RISK_SCORE, - RULES_MANAGEMENT_TABLE, - RULE_NAME, - RULE_SWITCH, - SEVERITY, -} from '../../../../screens/alerts_detection_rules'; -import { - ABOUT_DETAILS, - ABOUT_INVESTIGATION_NOTES, - ABOUT_RULE_DESCRIPTION, - ADDITIONAL_LOOK_BACK_DETAILS, - CUSTOM_QUERY_DETAILS, - DEFINITION_DETAILS, - FALSE_POSITIVES_DETAILS, - removeExternalLinkText, - INDEX_PATTERNS_DETAILS, - INVESTIGATION_NOTES_MARKDOWN, - INVESTIGATION_NOTES_TOGGLE, - MITRE_ATTACK_DETAILS, - REFERENCE_URLS_DETAILS, - RISK_SCORE_DETAILS, - RULE_NAME_HEADER, - RULE_TYPE_DETAILS, - RUNS_EVERY_DETAILS, - SCHEDULE_DETAILS, - SEVERITY_DETAILS, - TAGS_DETAILS, - TIMELINE_TEMPLATE_DETAILS, - NEW_TERMS_HISTORY_WINDOW_DETAILS, - NEW_TERMS_FIELDS_DETAILS, - SUPPRESS_BY_DETAILS, - SUPPRESS_FOR_DETAILS, - SUPPRESS_MISSING_FIELD, - INTERVAL_ABBR_VALUE, -} from '../../../../screens/rule_details'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; -import { getDetails } from '../../../../tasks/rule_details'; -import { expectNumberOfRules, goToRuleDetailsOf } from '../../../../tasks/alerts_detection_rules'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { - createAndEnableRule, fillAboutRuleAndContinue, - fillDefineNewTermsRule, fillDefineNewTermsRuleAndContinue, fillScheduleRuleAndContinue, selectNewTermsRuleType, - waitForAlertsToPopulate, - fillAlertSuppressionFields, - fillAboutRuleMinimumAndContinue, createRuleWithoutEnabling, - skipScheduleRuleAction, - continueFromDefineStep, - selectAlertSuppressionPerInterval, - setAlertSuppressionDuration, - selectDoNotSuppressForMissingFields, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; -// Skipped in MKI due to flake describe( - 'New Terms rules', + 'New Terms Rule - Rule Creation', { - tags: ['@ess', '@serverless', '@skipInServerlessMKI'], + tags: ['@ess', '@serverless'], }, () => { - describe('Detection rules, New Terms', () => { - const rule = getNewTermsRule(); - const expectedUrls = rule.references?.join(''); - const expectedFalsePositives = rule.false_positives?.join(''); - const expectedTags = rule.tags?.join(''); - const mitreAttack = rule.threat; - const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); - const expectedNumberOfRules = 1; - - beforeEach(() => { - deleteAlertsAndRules(); - login(); - visit(CREATE_RULE_URL); - selectNewTermsRuleType(); - }); - - it('Creates and enables a new terms rule', function () { - fillDefineNewTermsRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); - - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.risk_score); - cy.get(SEVERITY).should('have.text', 'High'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'High'); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); - }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'New Terms'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(NEW_TERMS_FIELDS_DETAILS).should('have.text', 'host.name'); - getDetails(NEW_TERMS_HISTORY_WINDOW_DETAILS).should('have.text', '51000h'); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${rule.interval}`); - const humanizedDuration = getHumanizedDuration( - rule.from ?? 'now-6m', - rule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${humanizedDuration}`); - }); - - waitForAlertsToPopulate(); - - cy.get(ALERT_DATA_GRID) - .invoke('text') - .then((text) => { - expect(text).contains(rule.name); - expect(text).contains(rule.severity); - expect(text).contains(rule.risk_score); - }); - }); - - it('Creates rule rule with time interval suppression', () => { - const SUPPRESS_BY_FIELDS = ['agent.hostname', 'agent.type']; - - fillDefineNewTermsRule(rule); - - // fill suppress by fields and select non-default suppression options - fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); - selectAlertSuppressionPerInterval(); - setAlertSuppressionDuration(45, 'm'); - selectDoNotSuppressForMissingFields(); - continueFromDefineStep(); - - // ensures details preview works correctly - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); - getDetails(SUPPRESS_MISSING_FIELD).should( - 'have.text', - 'Do not suppress alerts for events with missing fields' - ); - }); + const rule = getNewTermsRule(); + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + selectNewTermsRuleType(); + }); - fillAboutRuleMinimumAndContinue(rule); - skipScheduleRuleAction(); - createRuleWithoutEnabling(); + it('Creates a new terms rule', function () { + fillDefineNewTermsRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createRuleWithoutEnabling(); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); - getDetails(SUPPRESS_MISSING_FIELD).should( - 'have.text', - 'Do not suppress alerts for events with missing fields' - ); - }); - }); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/override.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/override.cy.ts index b23c68b341a96..961c2237f1fbf 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/override.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/override.cy.ts @@ -5,157 +5,35 @@ * 2.0. */ -import { formatMitreAttackDescription, getHumanizedDuration } from '../../../../helpers/rules'; -import { - getIndexPatterns, - getNewOverrideRule, - getSeveritiesOverride, -} from '../../../../objects/rule'; - -import { ALERT_GRID_CELL, ALERTS_COUNT } from '../../../../screens/alerts'; - -import { - CUSTOM_RULES_BTN, - RISK_SCORE, - RULES_MANAGEMENT_TABLE, - RULE_NAME, - RULE_SWITCH, - SEVERITY, -} from '../../../../screens/alerts_detection_rules'; -import { - ABOUT_INVESTIGATION_NOTES, - ABOUT_DETAILS, - ABOUT_RULE_DESCRIPTION, - ADDITIONAL_LOOK_BACK_DETAILS, - CUSTOM_QUERY_DETAILS, - DEFINITION_DETAILS, - DETAILS_DESCRIPTION, - DETAILS_TITLE, - FALSE_POSITIVES_DETAILS, - removeExternalLinkText, - INDEX_PATTERNS_DETAILS, - INVESTIGATION_NOTES_MARKDOWN, - INVESTIGATION_NOTES_TOGGLE, - MITRE_ATTACK_DETAILS, - REFERENCE_URLS_DETAILS, - RISK_SCORE_DETAILS, - RISK_SCORE_OVERRIDE_DETAILS, - RULE_NAME_HEADER, - RULE_NAME_OVERRIDE_DETAILS, - RULE_TYPE_DETAILS, - RUNS_EVERY_DETAILS, - SCHEDULE_DETAILS, - SEVERITY_DETAILS, - TAGS_DETAILS, - TIMELINE_TEMPLATE_DETAILS, - TIMESTAMP_OVERRIDE_DETAILS, - INTERVAL_ABBR_VALUE, -} from '../../../../screens/rule_details'; - +import { getNewOverrideRule } from '../../../../objects/rule'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -import { expectNumberOfRules, goToRuleDetailsOf } from '../../../../tasks/alerts_detection_rules'; import { - createAndEnableRule, + createRuleWithoutEnabling, fillAboutRuleWithOverrideAndContinue, fillDefineCustomRuleAndContinue, fillScheduleRuleAndContinue, - waitForAlertsToPopulate, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; -import { getDetails } from '../../../../tasks/rule_details'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; -describe('Rules override', { tags: ['@ess', '@serverless'] }, () => { +describe('Rule Overrides - Rule Creation', { tags: ['@ess', '@serverless'] }, () => { const rule = getNewOverrideRule(); - const expectedUrls = rule.references?.join(''); - const expectedFalsePositives = rule.false_positives?.join(''); - const expectedTags = rule.tags?.join(''); - const mitreAttack = rule.threat; - const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); beforeEach(() => { - login(); deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); }); - it('Creates and enables a new custom rule with override option', function () { - visit(CREATE_RULE_URL); + it('Creates a new custom rule with override options', function () { fillDefineCustomRuleAndContinue(rule); fillAboutRuleWithOverrideAndContinue(rule); fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - expectNumberOfRules(RULES_MANAGEMENT_TABLE, 1); - - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.risk_score); - cy.get(SEVERITY).should('have.text', 'High'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'High'); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); - getDetails(RISK_SCORE_OVERRIDE_DETAILS).should( - 'have.text', - `${rule.risk_score_mapping?.[0].field}kibana.alert.risk_score` - ); - getDetails(RULE_NAME_OVERRIDE_DETAILS).should('have.text', rule.rule_name_override); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); - }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - getDetails(TIMESTAMP_OVERRIDE_DETAILS).should('have.text', rule.timestamp_override); - cy.contains(DETAILS_TITLE, 'Severity override') - .invoke('index', DETAILS_TITLE) // get index relative to other titles, not all siblings - .then((severityOverrideIndex) => { - rule.severity_mapping?.forEach((severity, i) => { - cy.get(DETAILS_DESCRIPTION) - .eq(severityOverrideIndex + i) - .should( - 'have.text', - `${severity.field}:${severity.value}${getSeveritiesOverride()[i]}` - ); - }); - }); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${rule.interval}`); - const humanizedDuration = getHumanizedDuration(rule.from ?? 'now-6m', rule.interval ?? '5m'); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${humanizedDuration}`); - }); - - waitForAlertsToPopulate(); + createRuleWithoutEnabling(); - cy.get(ALERTS_COUNT) - .invoke('text') - .should('match', /^[1-9].+$/); // Any number of alerts - cy.get(ALERT_GRID_CELL).contains('winlogbeat'); - cy.get(ALERT_GRID_CELL).contains('high'); - cy.get(ALERT_GRID_CELL).contains('80'); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts index 8af755c6ed328..2a1214e8c919a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts @@ -5,80 +5,28 @@ * 2.0. */ -import { formatMitreAttackDescription, getHumanizedDuration } from '../../../../helpers/rules'; -import { getIndexPatterns, getNewThresholdRule } from '../../../../objects/rule'; - -import { ALERTS_COUNT, ALERT_GRID_CELL } from '../../../../screens/alerts'; - -import { - CUSTOM_RULES_BTN, - RISK_SCORE, - RULES_MANAGEMENT_TABLE, - RULE_NAME, - RULE_SWITCH, - SEVERITY, -} from '../../../../screens/alerts_detection_rules'; -import { - ABOUT_DETAILS, - ABOUT_INVESTIGATION_NOTES, - ABOUT_RULE_DESCRIPTION, - ADDITIONAL_LOOK_BACK_DETAILS, - CUSTOM_QUERY_DETAILS, - FALSE_POSITIVES_DETAILS, - DEFINITION_DETAILS, - removeExternalLinkText, - INDEX_PATTERNS_DETAILS, - INVESTIGATION_NOTES_MARKDOWN, - INVESTIGATION_NOTES_TOGGLE, - MITRE_ATTACK_DETAILS, - REFERENCE_URLS_DETAILS, - RISK_SCORE_DETAILS, - RULE_NAME_HEADER, - RULE_TYPE_DETAILS, - RUNS_EVERY_DETAILS, - SCHEDULE_DETAILS, - SEVERITY_DETAILS, - TAGS_DETAILS, - THRESHOLD_DETAILS, - TIMELINE_TEMPLATE_DETAILS, - SUPPRESS_FOR_DETAILS, - INTERVAL_ABBR_VALUE, -} from '../../../../screens/rule_details'; -import { expectNumberOfRules, goToRuleDetailsOf } from '../../../../tasks/alerts_detection_rules'; +import { getNewThresholdRule } from '../../../../objects/rule'; +import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { - createAndEnableRule, createRuleWithoutEnabling, - fillAboutRuleMinimumAndContinue, - enablesAndPopulatesThresholdSuppression, - skipScheduleRuleAction, fillAboutRuleAndContinue, fillDefineThresholdRuleAndContinue, fillScheduleRuleAndContinue, selectThresholdRuleType, - waitForAlertsToPopulate, - fillDefineThresholdRule, - continueFromDefineStep, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; -import { getDetails, assertDetailsNotExist } from '../../../../tasks/rule_details'; -import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; describe( - 'Threshold rules', + 'Threshold Rule - Rule Creation', { tags: ['@ess', '@serverless'], }, () => { const rule = getNewThresholdRule(); - const expectedUrls = rule.references?.join(''); - const expectedFalsePositives = rule.false_positives?.join(''); - const expectedTags = rule.tags?.join(''); - const mitreAttack = rule.threat; - const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); beforeEach(() => { deleteAlertsAndRules(); @@ -91,87 +39,10 @@ describe( fillDefineThresholdRuleAndContinue(rule); fillAboutRuleAndContinue(rule); fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - expectNumberOfRules(RULES_MANAGEMENT_TABLE, 1); - - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.risk_score); - cy.get(SEVERITY).should('have.text', 'High'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'High'); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); - }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(THRESHOLD_DETAILS).should( - 'have.text', - `Results aggregated by ${rule.threshold.field} >= ${rule.threshold.value}` - ); - assertDetailsNotExist(SUPPRESS_FOR_DETAILS); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${rule.interval}`); - const humanizedDuration = getHumanizedDuration( - rule.from ?? 'now-6m', - rule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS) - .find(INTERVAL_ABBR_VALUE) - .should('have.text', `${humanizedDuration}`); - }); - - waitForAlertsToPopulate(); - - cy.get(ALERTS_COUNT).should(($count) => expect(+$count.text().split(' ')[0]).to.be.lt(100)); - cy.get(ALERT_GRID_CELL).contains(rule.name); - }); - - it('Creates a new threshold rule with suppression enabled', () => { - selectThresholdRuleType(); - - fillDefineThresholdRule(rule); - enablesAndPopulatesThresholdSuppression(5, 'h'); - continueFromDefineStep(); - - // ensures duration displayed on define step in preview mode - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '5h'); - }); - - fillAboutRuleMinimumAndContinue(rule); - skipScheduleRuleAction(); createRuleWithoutEnabling(); - openRuleManagementPageViaBreadcrumbs(); - goToRuleDetailsOf(rule.name); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '5h'); - }); + cy.log('Asserting we have a new rule created'); + cy.get(RULE_NAME_HEADER).should('contain', rule.name); }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/custom_saved_query.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/custom_saved_query.cy.ts new file mode 100644 index 0000000000000..0d6c4be9b0c4c --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/custom_saved_query.cy.ts @@ -0,0 +1,186 @@ +/* + * 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 { getNewRule, getSavedQueryRule } from '../../../../objects/rule'; + +import { LOAD_QUERY_DYNAMICALLY_CHECKBOX } from '../../../../screens/create_new_rule'; +import { TOASTER } from '../../../../screens/alerts_detection_rules'; +import { + SAVED_QUERY_NAME_DETAILS, + SAVED_QUERY_DETAILS, + DEFINE_RULE_PANEL_PROGRESS, + CUSTOM_QUERY_DETAILS, +} from '../../../../screens/rule_details'; + +import { editFirstRule } from '../../../../tasks/alerts_detection_rules'; +import { createSavedQuery, deleteSavedQueries } from '../../../../tasks/api_calls/saved_queries'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { + selectAndLoadSavedQuery, + getCustomQueryInput, + checkLoadQueryDynamically, + uncheckLoadQueryDynamically, +} from '../../../../tasks/create_new_rule'; +import { saveEditedRule, visitEditRulePage } from '../../../../tasks/edit_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { assertDetailsNotExist, getDetails } from '../../../../tasks/rule_details'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management'; + +const savedQueryName = 'custom saved query'; +const savedQueryQuery = 'process.name: test'; + +describe('Saved query rules, rule edit', { tags: ['@ess', '@serverless'] }, () => { + beforeEach(() => { + login(); + deleteAlertsAndRules(); + deleteSavedQueries(); + }); + + it('Allows to update query rule as saved_query rule type', () => { + createSavedQuery(savedQueryName, savedQueryQuery); + createRule(getNewRule()).then((rule) => visitEditRulePage(rule.body.id)); + + selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); + checkLoadQueryDynamically(); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule should be saved as saved_query type once Load query dynamically checkbox was checked + cy.wrap(response?.body.type).should('equal', 'saved_query'); + }); + + cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + + getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); + getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); + }); + + it('Allows to update saved_query rule as query rule type', () => { + const expectedCustomTestQuery = 'random test query'; + createSavedQuery(savedQueryName, savedQueryQuery).then((response) => { + cy.log(JSON.stringify(response.body, null, 2)); + createRule(getSavedQueryRule({ saved_id: response.body.id, query: undefined })).then((rule) => + visitEditRulePage(rule.body.id) + ); + }); + + // query input should be disabled and has value of saved query + getCustomQueryInput().should('have.value', savedQueryQuery).should('be.disabled'); + + // after unchecking Load Query Dynamically checkbox, query input becomes enabled, type custom query + uncheckLoadQueryDynamically(); + getCustomQueryInput().should('be.enabled').clear().type(expectedCustomTestQuery); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule should be saved as query type once Load query dynamically checkbox was unchecked + cy.wrap(response?.body.type).should('equal', 'query'); + }); + + getDetails(CUSTOM_QUERY_DETAILS).should('contain', expectedCustomTestQuery); + }); + + it('Allows to update saved_query rule with non-existent query by adding custom query', () => { + const expectedCustomTestQuery = 'random test query'; + createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })).then((rule) => + visitEditRulePage(rule.body.id) + ); + + uncheckLoadQueryDynamically(); + + // type custom query, ensure Load dynamically checkbox is absent, as rule can't be saved win non valid saved query + getCustomQueryInput().type(expectedCustomTestQuery); + cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('not.visible'); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule should be saved as query type once Load query dynamically checkbox was unchecked + cy.wrap(response?.body.type).should('equal', 'query'); + }); + + getDetails(CUSTOM_QUERY_DETAILS).should('contain', expectedCustomTestQuery); + }); + + it('Allows to update saved_query rule with non-existent query by selecting another saved query', () => { + createSavedQuery(savedQueryName, savedQueryQuery); + createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })).then((rule) => + visitEditRulePage(rule.body.id) + ); + + visit(RULES_MANAGEMENT_URL); + + editFirstRule(); + uncheckLoadQueryDynamically(); + + // select another saved query, edit query input, which later should be dismissed once Load query dynamically checkbox checked + selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); + getCustomQueryInput().type('AND this part wil be dismissed'); + + checkLoadQueryDynamically(); + getCustomQueryInput().should('have.value', savedQueryQuery); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule type shouldn't change + cy.wrap(response?.body.type).should('equal', 'saved_query'); + }); + + cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + + getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); + getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); + }); + + context('Non existent saved query', () => { + const FAILED_TO_LOAD_ERROR = 'Failed to load the saved query'; + + beforeEach(() => { + createRule( + getSavedQueryRule({ + saved_id: 'non-existent', + query: undefined, + }) + ).then((rule) => visitEditRulePage(rule.body.id)); + }); + + it('Shows validation error on rule edit when saved query can not be loaded', function () { + cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); + }); + + // https://github.com/elastic/kibana/issues/187623 + it( + 'Allows to update saved_query rule with non-existent query', + { tags: ['@skipInServerlessMKI'] }, + () => { + cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('exist'); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule type shouldn't change + cy.wrap(response?.body.type).should('equal', 'saved_query'); + }); + + cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + + assertDetailsNotExist(SAVED_QUERY_NAME_DETAILS); + assertDetailsNotExist(SAVED_QUERY_DETAILS); + } + ); + }); +});