mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Detection Engine] Cypress cleanup and simplification (#217276)
## Summary This PR attempts to simplify our Cypress tests to focus in on what exactly we want a test to be doing. Many of our rule creation cypress tests were testing rule creation, rule edit, rule details and more. This results in a lot of flake and us triaging tests that often test things other than what we're ultimately trying to answer. I tried to simplify it so the rule specific tests simply answer - can we create this rule type in the UI? Then there's a single test for checking the entire flow of create rule -> rule details and check for alerts. The FTRs should be ensuring that the rules generate alerts as expected so we don't need to check this for every rule type in cypress. I also moved alert suppression into it's own folder as there is a lot of specific logic to test around that.
This commit is contained in:
parent
b8e78bcd55
commit
81c93ca5d1
27 changed files with 1249 additions and 1961 deletions
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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'],
|
||||
},
|
|
@ -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([
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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: {
|
|
@ -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'],
|
||||
},
|
|
@ -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'],
|
||||
},
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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');
|
||||
});
|
||||
|
||||
// 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'
|
||||
);
|
||||
|
||||
// suppression functionality should be in GA
|
||||
cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).should(
|
||||
'not.contain.text',
|
||||
'Technical Preview'
|
||||
);
|
||||
});
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
describe('Saved query rules - Rule Creation', { tags: ['@ess', '@serverless'] }, () => {
|
||||
beforeEach(() => {
|
||||
login();
|
||||
deleteAlertsAndRules();
|
||||
deleteSavedQueries();
|
||||
});
|
||||
|
||||
it('Creates saved query rule', function () {
|
||||
const rule = getSavedQueryRule();
|
||||
createSavedQuery(savedQueryName, savedQueryQuery, savedQueryFilterKey);
|
||||
visit(CREATE_RULE_URL);
|
||||
it('Creates saved query rule', function () {
|
||||
const rule = getSavedQueryRule();
|
||||
createSavedQuery(savedQueryName, savedQueryQuery, savedQueryFilterKey);
|
||||
visit(CREATE_RULE_URL);
|
||||
|
||||
selectAndLoadSavedQuery(savedQueryName, savedQueryQuery);
|
||||
selectAndLoadSavedQuery(savedQueryName, savedQueryQuery);
|
||||
|
||||
// edit loaded saved query
|
||||
getCustomQueryInput()
|
||||
.type(' AND random query')
|
||||
.should('have.value', [savedQueryQuery, ' AND random query'].join(''));
|
||||
// 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);
|
||||
// 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();
|
||||
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click();
|
||||
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
cy.intercept('POST', '/api/detection_engine/rules').as('savedQueryRule');
|
||||
createAndEnableRule();
|
||||
openRuleManagementPageViaBreadcrumbs();
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createRuleWithoutEnabling();
|
||||
|
||||
cy.wait('@savedQueryRule').then(({ response }) => {
|
||||
// created rule should have saved_query type
|
||||
cy.wrap(response?.body.type).should('equal', 'saved_query');
|
||||
cy.log('Asserting we have a new rule created');
|
||||
cy.get(RULE_NAME_HEADER).should('contain', rule.name);
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
it('Shows error toast on details page when saved query can not be loaded', function () {
|
||||
cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
selectEqlRuleType();
|
||||
fillDefineEqlRuleAndContinue(rule);
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createRuleWithoutEnabling();
|
||||
|
||||
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(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' });
|
||||
});
|
||||
selectEqlRuleType();
|
||||
fillDefineEqlRuleAndContinue(rule);
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createRuleWithoutEnabling();
|
||||
|
||||
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();
|
||||
|
||||
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', () => {
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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(''));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
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();
|
||||
});
|
||||
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('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('Contains a predefined index pattern', () => {
|
||||
getRuleIndexInput().should('have.text', getIndexPatterns().join(''));
|
||||
describe('Indicator index patterns', () => {
|
||||
beforeEach(() => {
|
||||
visit(CREATE_RULE_URL);
|
||||
selectIndicatorMatchType();
|
||||
});
|
||||
|
||||
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('Shows invalidation text if you try to continue without filling it out', () => {
|
||||
getIndicatorIndicatorIndex().type(`{backspace}{enter}`);
|
||||
getDefineContinueButton().click();
|
||||
getIndexPatternInvalidationText().should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => {
|
||||
getDefineContinueButton().click();
|
||||
getIndexPatternInvalidationText().should('not.exist');
|
||||
describe('custom query input', () => {
|
||||
beforeEach(() => {
|
||||
visit(CREATE_RULE_URL);
|
||||
selectIndicatorMatchType();
|
||||
});
|
||||
|
||||
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 when you try to continue without filling it out', () => {
|
||||
getIndexPatternClearButton().click();
|
||||
getIndicatorIndicatorIndex().type(`{backspace}{enter}`);
|
||||
getDefineContinueButton().click();
|
||||
getIndexPatternInvalidationText().should('exist');
|
||||
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('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);
|
||||
}
|
||||
});
|
||||
|
||||
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 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('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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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 "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 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');
|
||||
});
|
||||
});
|
||||
|
||||
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('Indicator index patterns', () => {
|
||||
beforeEach(() => {
|
||||
visit(CREATE_RULE_URL);
|
||||
selectIndicatorMatchType();
|
||||
});
|
||||
|
||||
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('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();
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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('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(() => {
|
||||
describe('Generating signals', () => {
|
||||
it('Creates and enables a new Indicator Match rule', () => {
|
||||
const rule = getNewThreatIndicatorRule();
|
||||
visit(CREATE_RULE_URL);
|
||||
selectIndicatorMatchType();
|
||||
if (rule.index) {
|
||||
fillIndexAndIndicatorIndexPattern(rule.index, rule.threat_index);
|
||||
}
|
||||
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_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).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);
|
||||
});
|
||||
|
||||
it('Does NOT show invalidation text on initial page load', () => {
|
||||
getIndicatorInvalidationText().should('not.exist');
|
||||
});
|
||||
it('Investigate alert in timeline', () => {
|
||||
loadPrepackagedTimelineTemplates();
|
||||
createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })).then(
|
||||
(rule) => visitRuleDetailsPage(rule.body.id)
|
||||
);
|
||||
|
||||
it('Shows invalidation text when you try to press continue without filling anything out', () => {
|
||||
getDefineContinueButton().click();
|
||||
getIndicatorAtLeastOneInvalidationText().should('exist');
|
||||
});
|
||||
waitForAlertsToPopulate();
|
||||
investigateFirstAlertInTimeline();
|
||||
|
||||
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('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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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 "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 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');
|
||||
});
|
||||
});
|
||||
|
||||
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('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(
|
||||
cy.get(PROVIDER_BADGE).should('have.length', 3);
|
||||
cy.get(PROVIDER_BADGE).should(
|
||||
'have.text',
|
||||
`${rule.threat_mapping[0].entries[0].field} MATCHES ${rule.threat_mapping[0].entries[0].value}`
|
||||
`threat.enrichments.matched.atomic: "${
|
||||
indicatorRuleMatchingDoc.atomic
|
||||
}"threat.enrichments.matched.type: "indicator_match_rule"threat.enrichments.matched.field: "${
|
||||
getNewThreatIndicatorRule().threat_mapping[0].entries[0].field
|
||||
}"`
|
||||
);
|
||||
getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*');
|
||||
});
|
||||
|
||||
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).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);
|
||||
});
|
||||
|
||||
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`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Duplicates the indicator rule', () => {
|
||||
const TESTED_RULE_DATA = getNewThreatIndicatorRule({
|
||||
name: 'Indicator rule duplicate test',
|
||||
rule_id: 'rule_testing',
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
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', () => {
|
||||
duplicateFirstRule();
|
||||
goBackToRuleDetails();
|
||||
goBackToRulesTable();
|
||||
checkDuplicatedRule(TESTED_RULE_DATA.name);
|
||||
});
|
||||
|
||||
it("Allows the rule to be duplicated from the table's bulk actions", () => {
|
||||
selectAllRules();
|
||||
duplicateSelectedRulesWithExceptions();
|
||||
checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on rule details page', () => {
|
||||
beforeEach(() => {
|
||||
createRule(getNewThreatIndicatorRule(TESTED_RULE_DATA)).then((rule) =>
|
||||
visitRuleDetailsPage(rule.body.id)
|
||||
cy.get(INDICATOR_MATCH_ROW_RENDER).should(
|
||||
'have.text',
|
||||
`${getNewThreatIndicatorRule().threat_mapping[0].entries[0].field}matched${
|
||||
indicatorRuleMatchingDoc.atomic
|
||||
}indicator_match_ruleprovided` + ` byAbuseCH malware`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Allows the rule to be duplicated', () => {
|
||||
duplicateRuleFromMenu();
|
||||
goBackToRuleDetails();
|
||||
goBackToRulesTable();
|
||||
checkDuplicatedRule(`${TESTED_RULE_DATA.name} [Duplicate]`);
|
||||
describe('Duplicates the indicator rule', () => {
|
||||
const TESTED_RULE_DATA = getNewThreatIndicatorRule({
|
||||
name: 'Indicator rule duplicate test',
|
||||
rule_id: 'rule_testing',
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
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', () => {
|
||||
duplicateFirstRule();
|
||||
goBackToRuleDetails();
|
||||
goBackToRulesTable();
|
||||
checkDuplicatedRule(TESTED_RULE_DATA.name);
|
||||
});
|
||||
|
||||
it("Allows the rule to be duplicated from the table's bulk actions", () => {
|
||||
selectAllRules();
|
||||
duplicateSelectedRulesWithExceptions();
|
||||
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]`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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(''));
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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();
|
||||
createRuleWithoutEnabling();
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
const rule = getNewTermsRule();
|
||||
beforeEach(() => {
|
||||
deleteAlertsAndRules();
|
||||
login();
|
||||
visit(CREATE_RULE_URL);
|
||||
selectNewTermsRuleType();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
deleteAlertsAndRules();
|
||||
login();
|
||||
visit(CREATE_RULE_URL);
|
||||
selectNewTermsRuleType();
|
||||
});
|
||||
it('Creates a new terms rule', function () {
|
||||
fillDefineNewTermsRuleAndContinue(rule);
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createRuleWithoutEnabling();
|
||||
|
||||
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'
|
||||
);
|
||||
});
|
||||
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
||||
cy.log('Asserting we have a new rule created');
|
||||
cy.get(RULE_NAME_HEADER).should('contain', rule.name);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
createRuleWithoutEnabling();
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue