[Security Solution] Adds Cypress test for index action in detection rules (#141069)

Co-authored-by: Dmitrii Shevchenko <dmshevch@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Gloria Hornero 2022-09-29 15:44:53 +02:00 committed by GitHub
parent b2ab340425
commit db6a82530b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 534 additions and 197 deletions

View file

@ -6,6 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import type { Mitre } from '../../objects/rule';
import {
getNewRule,
getExistingRule,
@ -13,6 +14,7 @@ import {
getEditedRule,
getNewOverrideRule,
} from '../../objects/rule';
import type { CompleteTimeline } from '../../objects/timeline';
import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts';
import {
@ -46,6 +48,7 @@ import {
FROM_VALIDATION_ERROR,
EMAIL_ACTION_TO_INPUT,
EMAIL_ACTION_SUBJECT_INPUT,
SCHEDULE_CONTINUE_BUTTON,
} from '../../screens/create_new_rule';
import {
ADDITIONAL_LOOK_BACK_DETAILS,
@ -87,7 +90,7 @@ import {
createAndEnableRule,
fillAboutRule,
fillAboutRuleAndContinue,
fillDefineCustomRuleWithImportedQueryAndContinue,
fillDefineCustomRuleAndContinue,
fillEmailConnectorForm,
fillScheduleRuleAndContinue,
goToAboutStepTab,
@ -108,19 +111,21 @@ describe('Custom query rules', () => {
login();
});
describe('Custom detection rules creation', () => {
const expectedUrls = getNewRule().referenceUrls.join('');
const expectedFalsePositives = getNewRule().falsePositivesExamples.join('');
const expectedTags = getNewRule().tags.join('');
const expectedMitre = formatMitreAttackDescription(getNewRule().mitre);
const expectedUrls = getNewRule().referenceUrls?.join('');
const expectedFalsePositives = getNewRule().falsePositivesExamples?.join('');
const expectedTags = getNewRule().tags?.join('');
const mitreAttack = getNewRule().mitre as Mitre[];
const expectedMitre = formatMitreAttackDescription(mitreAttack);
const expectedNumberOfRules = 1;
beforeEach(() => {
const timeline = getNewRule().timeline as CompleteTimeline;
deleteAlertsAndRules();
createTimeline(getNewRule().timeline).then((response) => {
createTimeline(timeline).then((response) => {
cy.wrap({
...getNewRule(),
timeline: {
...getNewRule().timeline,
...timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@ -129,7 +134,7 @@ describe('Custom query rules', () => {
it('Creates and enables a new rule', function () {
visit(RULE_CREATION);
fillDefineCustomRuleWithImportedQueryAndContinue(this.rule);
fillDefineCustomRuleAndContinue(this.rule);
fillAboutRuleAndContinue(this.rule);
fillScheduleRuleAndContinue(this.rule);
@ -144,6 +149,7 @@ describe('Custom query rules', () => {
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', this.rule.name);
cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true });
cy.get(ABOUT_CONTINUE_BTN).should('not.exist');
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
createAndEnableRule();
@ -182,11 +188,11 @@ describe('Custom query rules', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
`${getNewRule().runsEvery.interval}${getNewRule().runsEvery.type}`
`${getNewRule().runsEvery?.interval}${getNewRule().runsEvery?.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
`${getNewRule().lookBack.interval}${getNewRule().lookBack.type}`
`${getNewRule().lookBack?.interval}${getNewRule().lookBack?.type}`
);
});
@ -299,7 +305,7 @@ describe('Custom query rules', () => {
context('Edition', () => {
const rule = getEditedRule();
const expectedEditedtags = rule.tags.join('');
const expectedEditedtags = rule.tags?.join('');
const expectedEditedIndexPatterns =
rule.dataSource.type === 'indexPatterns' &&
rule.dataSource.index &&
@ -349,7 +355,7 @@ describe('Custom query rules', () => {
// expect about step to populate
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name);
cy.get(RULE_DESCRIPTION_INPUT).should('have.text', existingRule.description);
cy.get(TAGS_FIELD).should('have.text', existingRule.tags.join(''));
cy.get(TAGS_FIELD).should('have.text', existingRule.tags?.join(''));
cy.get(SEVERITY_DROPDOWN).should('have.text', existingRule.severity);
cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', existingRule.riskScore);

View file

@ -6,7 +6,9 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import type { Mitre } from '../../objects/rule';
import { getDataViewRule } from '../../objects/rule';
import type { CompleteTimeline } from '../../objects/timeline';
import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts';
import {
@ -50,7 +52,7 @@ import { postDataView } from '../../tasks/common';
import {
createAndEnableRule,
fillAboutRuleAndContinue,
fillDefineCustomRuleWithImportedQueryAndContinue,
fillDefineCustomRuleAndContinue,
fillScheduleRuleAndContinue,
waitForAlertsToPopulate,
waitForTheRuleToBeExecuted,
@ -69,10 +71,11 @@ describe('Custom query rules', () => {
describe('Custom detection rules creation with data views', () => {
const rule = getDataViewRule();
const expectedUrls = rule.referenceUrls.join('');
const expectedFalsePositives = rule.falsePositivesExamples.join('');
const expectedTags = rule.tags.join('');
const expectedMitre = formatMitreAttackDescription(rule.mitre);
const expectedUrls = rule.referenceUrls?.join('');
const expectedFalsePositives = rule.falsePositivesExamples?.join('');
const expectedTags = rule.tags?.join('');
const mitreAttack = rule.mitre as Mitre[];
const expectedMitre = formatMitreAttackDescription(mitreAttack);
const expectedNumberOfRules = 1;
beforeEach(() => {
@ -80,12 +83,13 @@ describe('Custom query rules', () => {
are creating a data view we'll use after and cleanKibana does not delete all the data views created, esArchiverReseKibana does.
We don't use esArchiverReseKibana in all the tests because is a time-consuming method and we don't need to perform an exhaustive
cleaning in all the other tests. */
const timeline = rule.timeline as CompleteTimeline;
esArchiverResetKibana();
createTimeline(rule.timeline).then((response) => {
createTimeline(timeline).then((response) => {
cy.wrap({
...rule,
timeline: {
...rule.timeline,
...timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@ -97,7 +101,7 @@ describe('Custom query rules', () => {
it('Creates and enables a new rule', function () {
visit(RULE_CREATION);
fillDefineCustomRuleWithImportedQueryAndContinue(this.rule);
fillDefineCustomRuleAndContinue(this.rule);
fillAboutRuleAndContinue(this.rule);
fillScheduleRuleAndContinue(this.rule);
createAndEnableRule();
@ -138,11 +142,11 @@ describe('Custom query rules', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
`${getDataViewRule().runsEvery.interval}${getDataViewRule().runsEvery.type}`
`${getDataViewRule().runsEvery?.interval}${getDataViewRule().runsEvery?.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
`${getDataViewRule().lookBack.interval}${getDataViewRule().lookBack.type}`
`${getDataViewRule().lookBack?.interval}${getDataViewRule().lookBack?.type}`
);
});

View file

@ -42,6 +42,7 @@ import { getDetails } from '../../tasks/rule_details';
import { createSavedQueryRule, createCustomRule } from '../../tasks/api_calls/rules';
import { RULE_CREATION, SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
import type { CompleteTimeline } from '../../objects/timeline';
const savedQueryName = 'custom saved query';
const savedQueryQuery = 'process.name: test';
@ -56,11 +57,12 @@ describe('Custom saved_query rules', () => {
beforeEach(() => {
deleteAlertsAndRules();
deleteSavedQueries();
createTimeline(getNewRule().timeline).then((response) => {
const timeline = getNewRule().timeline as CompleteTimeline;
createTimeline(timeline).then((response) => {
cy.wrap({
...getNewRule(),
timeline: {
...getNewRule().timeline,
...timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');

View file

@ -6,6 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import type { Mitre } from '../../objects/rule';
import { getEqlRule, getEqlSequenceRule, getIndexPatterns } from '../../objects/rule';
import { ALERT_DATA_GRID, NUMBER_OF_ALERTS } from '../../screens/alerts';
@ -59,6 +60,7 @@ import { login, visit } from '../../tasks/login';
import { RULE_CREATION } from '../../urls/navigation';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import type { CompleteTimeline } from '../../objects/timeline';
describe('EQL rules', () => {
before(() => {
@ -67,19 +69,21 @@ describe('EQL rules', () => {
deleteAlertsAndRules();
});
describe('Detection rules, EQL', () => {
const expectedUrls = getEqlRule().referenceUrls.join('');
const expectedFalsePositives = getEqlRule().falsePositivesExamples.join('');
const expectedTags = getEqlRule().tags.join('');
const expectedMitre = formatMitreAttackDescription(getEqlRule().mitre);
const expectedUrls = getEqlRule().referenceUrls?.join('');
const expectedFalsePositives = getEqlRule().falsePositivesExamples?.join('');
const expectedTags = getEqlRule().tags?.join('');
const mitreAttack = getEqlRule().mitre as Mitre[];
const expectedMitre = formatMitreAttackDescription(mitreAttack);
const expectedNumberOfRules = 1;
const expectedNumberOfAlerts = '2 alerts';
beforeEach(() => {
createTimeline(getEqlRule().timeline).then((response) => {
const timeline = getEqlRule().timeline as CompleteTimeline;
createTimeline(timeline).then((response) => {
cy.wrap({
...getEqlRule(),
timeline: {
...getEqlRule().timeline,
...timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@ -161,11 +165,12 @@ describe('EQL rules', () => {
esArchiverLoad('auditbeat_big');
});
beforeEach(() => {
createTimeline(getEqlSequenceRule().timeline).then((response) => {
const timeline = getEqlSequenceRule().timeline as CompleteTimeline;
createTimeline(timeline).then((response) => {
cy.wrap({
...getEqlSequenceRule(),
timeline: {
...getEqlSequenceRule().timeline,
...timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');

View file

@ -6,6 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import type { Mitre } from '../../objects/rule';
import {
getIndexPatterns,
getNewThreatIndicatorRule,
@ -109,10 +110,11 @@ const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"';
describe('indicator match', () => {
describe('Detection rules, Indicator Match', () => {
const expectedUrls = getNewThreatIndicatorRule().referenceUrls.join('');
const expectedFalsePositives = getNewThreatIndicatorRule().falsePositivesExamples.join('');
const expectedTags = getNewThreatIndicatorRule().tags.join('');
const expectedMitre = formatMitreAttackDescription(getNewThreatIndicatorRule().mitre);
const expectedUrls = getNewThreatIndicatorRule().referenceUrls?.join('');
const expectedFalsePositives = getNewThreatIndicatorRule().falsePositivesExamples?.join('');
const expectedTags = getNewThreatIndicatorRule().tags?.join('');
const mitreAttack = getNewThreatIndicatorRule().mitre as Mitre[];
const expectedMitre = formatMitreAttackDescription(mitreAttack);
const expectedNumberOfRules = 1;
const expectedNumberOfAlerts = '1 alert';
@ -479,11 +481,11 @@ describe('indicator match', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
`${rule.runsEvery.interval}${rule.runsEvery.type}`
`${rule.runsEvery?.interval}${rule.runsEvery?.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
`${rule.lookBack.interval}${rule.lookBack.type}`
`${rule.lookBack?.interval}${rule.lookBack?.type}`
);
});
@ -492,7 +494,7 @@ describe('indicator match', () => {
cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts);
cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name);
cy.get(ALERT_SEVERITY).first().should('have.text', rule.severity.toLowerCase());
cy.get(ALERT_SEVERITY).first().should('have.text', rule.severity?.toLowerCase());
cy.get(ALERT_RISK_SCORE).first().should('have.text', rule.riskScore);
});

View file

@ -6,6 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import type { Mitre } from '../../objects/rule';
import { getNewTermsRule, getIndexPatterns } from '../../objects/rule';
import { ALERT_DATA_GRID } from '../../screens/alerts';
@ -60,6 +61,7 @@ import {
import { login, visit } from '../../tasks/login';
import { RULE_CREATION } from '../../urls/navigation';
import type { CompleteTimeline } from '../../objects/timeline';
describe('New Terms rules', () => {
before(() => {
@ -67,19 +69,21 @@ describe('New Terms rules', () => {
login();
});
describe('Detection rules, New Terms', () => {
const expectedUrls = getNewTermsRule().referenceUrls.join('');
const expectedFalsePositives = getNewTermsRule().falsePositivesExamples.join('');
const expectedTags = getNewTermsRule().tags.join('');
const expectedMitre = formatMitreAttackDescription(getNewTermsRule().mitre);
const expectedUrls = getNewTermsRule().referenceUrls?.join('');
const expectedFalsePositives = getNewTermsRule().falsePositivesExamples?.join('');
const expectedTags = getNewTermsRule().tags?.join('');
const mitreAttack = getNewTermsRule().mitre as Mitre[];
const expectedMitre = formatMitreAttackDescription(mitreAttack);
const expectedNumberOfRules = 1;
beforeEach(() => {
const timeline = getNewTermsRule().timeline as CompleteTimeline;
deleteAlertsAndRules();
createTimeline(getNewTermsRule().timeline).then((response) => {
createTimeline(timeline).then((response) => {
cy.wrap({
...getNewTermsRule(),
timeline: {
...getNewTermsRule().timeline,
...timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');

View file

@ -6,8 +6,9 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import type { OverrideRule } from '../../objects/rule';
import type { Mitre, OverrideRule } from '../../objects/rule';
import { getIndexPatterns, getNewOverrideRule, getSeveritiesOverride } from '../../objects/rule';
import type { CompleteTimeline } from '../../objects/timeline';
import { NUMBER_OF_ALERTS, ALERT_GRID_CELL } from '../../screens/alerts';
@ -55,7 +56,7 @@ import { cleanKibana } from '../../tasks/common';
import {
createAndEnableRule,
fillAboutRuleWithOverrideAndContinue,
fillDefineCustomRuleWithImportedQueryAndContinue,
fillDefineCustomRuleAndContinue,
fillScheduleRuleAndContinue,
waitForAlertsToPopulate,
waitForTheRuleToBeExecuted,
@ -66,21 +67,23 @@ import { getDetails } from '../../tasks/rule_details';
import { RULE_CREATION } from '../../urls/navigation';
describe('Detection rules, override', () => {
const expectedUrls = getNewOverrideRule().referenceUrls.join('');
const expectedFalsePositives = getNewOverrideRule().falsePositivesExamples.join('');
const expectedTags = getNewOverrideRule().tags.join('');
const expectedMitre = formatMitreAttackDescription(getNewOverrideRule().mitre);
const expectedUrls = getNewOverrideRule().referenceUrls?.join('');
const expectedFalsePositives = getNewOverrideRule().falsePositivesExamples?.join('');
const expectedTags = getNewOverrideRule().tags?.join('');
const mitreAttack = getNewOverrideRule().mitre as Mitre[];
const expectedMitre = formatMitreAttackDescription(mitreAttack);
before(() => {
cleanKibana();
login();
});
beforeEach(() => {
createTimeline(getNewOverrideRule().timeline).then((response) => {
const timeline = getNewOverrideRule().timeline as CompleteTimeline;
createTimeline(timeline).then((response) => {
cy.wrap({
...getNewOverrideRule(),
timeline: {
...getNewOverrideRule().timeline,
...timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@ -89,7 +92,7 @@ describe('Detection rules, override', () => {
it('Creates and enables a new custom rule with override option', function () {
visitWithoutDateRange(RULE_CREATION);
fillDefineCustomRuleWithImportedQueryAndContinue(this.rule);
fillDefineCustomRuleAndContinue(this.rule);
fillAboutRuleWithOverrideAndContinue(this.rule);
fillScheduleRuleAndContinue(this.rule);
createAndEnableRule();

View file

@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getIndexConnector } from '../../objects/connector';
import { getSimpleCustomQueryRule } from '../../objects/rule';
import { goToRuleDetails } from '../../tasks/alerts_detection_rules';
import { deleteIndex, waitForNewDocumentToBeIndexed } from '../../tasks/api_calls/elasticsearch';
import {
cleanKibana,
deleteAlertsAndRules,
deleteConnectors,
deleteDataView,
} from '../../tasks/common';
import {
createAndEnableRule,
fillAboutRuleAndContinue,
fillDefineCustomRuleAndContinue,
fillRuleAction,
fillScheduleRuleAndContinue,
} from '../../tasks/create_new_rule';
import { login, visit } from '../../tasks/login';
import { RULE_CREATION } from '../../urls/navigation';
describe('Rule actions during detection rule creation', () => {
const indexConnector = getIndexConnector();
before(() => {
cleanKibana();
login();
});
beforeEach(() => {
deleteAlertsAndRules();
deleteConnectors();
deleteIndex(indexConnector.index);
deleteDataView(indexConnector.index);
});
const rule = {
...getSimpleCustomQueryRule(),
actions: { throttle: 'rule', connectors: [indexConnector] },
};
const index = rule.actions.connectors[0].index;
const initialNumberOfDocuments = 0;
const expectedJson = JSON.parse(rule.actions.connectors[0].document);
it('Indexes a new document after the index action is triggered ', function () {
visit(RULE_CREATION);
fillDefineCustomRuleAndContinue(rule);
fillAboutRuleAndContinue(rule);
fillScheduleRuleAndContinue(rule);
fillRuleAction(rule);
createAndEnableRule();
goToRuleDetails();
/* When the rule is executed, the action is triggered. We wait for the new document to be indexed */
waitForNewDocumentToBeIndexed(index, initialNumberOfDocuments);
/* We assert that the new indexed document is the one set on the index action */
cy.request({
method: 'GET',
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search`,
headers: { 'kbn-xsrf': 'cypress-creds' },
}).then((response) => {
expect(response.body.hits.hits[0]._source).to.deep.equal(expectedJson);
});
});
});

View file

@ -6,6 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
import type { Mitre } from '../../objects/rule';
import { getIndexPatterns, getNewThresholdRule } from '../../objects/rule';
import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts';
@ -60,13 +61,15 @@ import {
import { login, visitWithoutDateRange } from '../../tasks/login';
import { RULE_CREATION } from '../../urls/navigation';
import type { CompleteTimeline } from '../../objects/timeline';
describe('Detection rules, threshold', () => {
let rule = getNewThresholdRule();
const expectedUrls = getNewThresholdRule().referenceUrls.join('');
const expectedFalsePositives = getNewThresholdRule().falsePositivesExamples.join('');
const expectedTags = getNewThresholdRule().tags.join('');
const expectedMitre = formatMitreAttackDescription(getNewThresholdRule().mitre);
const expectedUrls = getNewThresholdRule().referenceUrls?.join('');
const expectedFalsePositives = getNewThresholdRule().falsePositivesExamples?.join('');
const expectedTags = getNewThresholdRule().tags?.join('');
const mitreAttack = getNewThresholdRule().mitre as Mitre[];
const expectedMitre = formatMitreAttackDescription(mitreAttack);
before(() => {
cleanKibana();
@ -75,9 +78,10 @@ describe('Detection rules, threshold', () => {
beforeEach(() => {
rule = getNewThresholdRule();
const timeline = rule.timeline as CompleteTimeline;
deleteAlertsAndRules();
createTimeline(getNewThresholdRule().timeline).then((response) => {
rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId;
createTimeline(timeline).then((response) => {
timeline.id = response.body.data.persistTimeline.timeline.savedObjectId;
});
visitWithoutDateRange(RULE_CREATION);
});
@ -132,11 +136,11 @@ describe('Detection rules, threshold', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
`${rule.runsEvery.interval}${rule.runsEvery.type}`
`${rule.runsEvery?.interval}${rule.runsEvery?.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
`${rule.lookBack.interval}${rule.lookBack.type}`
`${rule.lookBack?.interval}${rule.lookBack?.type}`
);
});

View file

@ -5,22 +5,42 @@
* 2.0.
*/
export interface EmailConnector {
interface Connector {
name: string;
}
export interface EmailConnector extends Connector {
from: string;
host: string;
port: string;
user: string;
password: string;
service: string;
type: 'email';
}
export interface IndexConnector extends Connector {
index: string;
document: string;
type: 'index';
}
export type Connectors = IndexConnector | EmailConnector;
export const getEmailConnector = (): EmailConnector => ({
name: 'Test connector',
name: 'Test email connector',
from: 'test@example.com',
host: 'example.com',
port: '80',
user: 'username',
password: 'password',
service: 'Other',
type: 'email',
});
export const getIndexConnector = (): IndexConnector => ({
name: 'Test index connector',
index: 'my-index-000001',
document: '{"test": "123"}',
type: 'index',
});

View file

@ -5,11 +5,13 @@
* 2.0.
*/
import type { Throttle } from '@kbn/securitysolution-io-ts-alerting-types';
import { rawRules } from '../../server/lib/detection_engine/rules/prepackaged_rules';
import { getMockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques';
import type { CompleteTimeline } from './timeline';
import { getTimeline, getIndicatorMatchTimelineTemplate } from './timeline';
import type { FullResponseSchema } from '../../common/detection_engine/schemas/request';
import type { Connectors } from './connector';
export const totalNumberOfPrebuiltRules = rawRules.length;
@ -36,6 +38,11 @@ interface Interval {
type: string;
}
export interface Actions {
throttle: Throttle;
connectors: Connectors[];
}
export type RuleDataSource =
| { type: 'indexPatterns'; index: string[] }
| { type: 'dataView'; dataView: string };
@ -46,20 +53,21 @@ export interface CustomRule {
description: string;
dataSource: RuleDataSource;
interval?: string;
severity: string;
riskScore: string;
tags: string[];
severity?: string;
riskScore?: string;
tags?: string[];
timelineTemplate?: string;
referenceUrls: string[];
falsePositivesExamples: string[];
mitre: Mitre[];
note: string;
runsEvery: Interval;
lookBack: Interval;
timeline: CompleteTimeline;
maxSignals: number;
referenceUrls?: string[];
falsePositivesExamples?: string[];
mitre?: Mitre[];
note?: string;
runsEvery?: Interval;
lookBack?: Interval;
timeline?: CompleteTimeline;
maxSignals?: number;
buildingBlockType?: string;
exceptionLists?: Array<{ id: string; list_id: string; type: string; namespace_type: string }>;
actions?: Actions;
}
export interface ThresholdRule extends CustomRule {
@ -231,6 +239,15 @@ export const getNewRule = (): CustomRule => ({
maxSignals: 100,
});
export const getSimpleCustomQueryRule = (): CustomRule => ({
customQuery: 'host.name: *',
dataSource: { index: getIndexPatterns(), type: 'indexPatterns' },
name: 'New Rule Test',
description: 'The new rule description.',
runsEvery: getRunsEvery(),
lookBack: getLookBack(),
});
export const getBuildingBlockRule = (): CustomRule => ({
customQuery: 'host.name: *',
dataSource: { index: getIndexPatterns(), type: 'indexPatterns' },
@ -489,7 +506,7 @@ export const getEditedRule = (): CustomRule => ({
...getExistingRule(),
severity: 'Medium',
description: 'Edited Rule description',
tags: [...getExistingRule().tags, 'edited'],
tags: [...(getExistingRule().tags || []), 'edited'],
});
export const expectedExportedRule = (

View file

@ -42,6 +42,8 @@ export const EMAIL_CONNECTOR_PASSWORD_INPUT = '[data-test-subj="emailPasswordInp
export const EMAIL_CONNECTOR_SERVICE_SELECTOR = '[data-test-subj="emailServiceSelectInput"]';
export const JSON_EDITOR = "[data-test-subj='actionJsonEditor']";
export const ADD_FALSE_POSITIVE_BTN =
'[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text';
@ -58,6 +60,8 @@ export const COMBO_BOX_CLEAR_BTN = '[data-test-subj="comboBoxClearButton"]';
export const COMBO_BOX_INPUT = '[data-test-subj="comboBoxInput"]';
export const COMBO_BOX_SELECTION = '.euiMark';
export const CREATE_AND_ENABLE_BTN = '[data-test-subj="create-enable"]';
export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]';
@ -256,3 +260,7 @@ export const savedQueryByName = (savedQueryName: string) =>
export const APPLY_SELECTED_SAVED_QUERY_BUTTON =
'[data-test-subj="saved-query-management-apply-changes-button"]';
export const INDEX_SELECTOR = "[data-test-subj='.index-siem-ActionTypeSelectOption']";
export const CREATE_CONNECTOR_BTN = "[data-test-subj='createActionConnectorButton-0']";

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const createIndex = (index: string) => {
cy.request({
method: 'PUT',
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}`,
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
};
export const createDocument = (index: string, document: string) => {
cy.request({
method: 'POST',
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_doc`,
headers: { 'kbn-xsrf': 'cypress-creds' },
body: JSON.parse(document),
});
};
export const deleteIndex = (index: string) => {
cy.request({
method: 'DELETE',
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}`,
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
};
export const waitForNewDocumentToBeIndexed = (index: string, initialNumberOfDocuments: number) => {
cy.waitUntil(
() => {
return cy
.request({
method: 'GET',
url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search`,
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
})
.then((response) => {
if (response.status !== 200) {
return false;
} else {
return response.body.hits.hits.length > initialNumberOfDocuments;
}
});
},
{ interval: 500, timeout: 12000 }
);
};

View file

@ -40,17 +40,21 @@ export const createCustomRule = (
rule: CustomRule,
ruleId = 'rule_testing',
interval = '100m'
): Cypress.Chainable<Cypress.Response<unknown>> =>
cy.request({
): Cypress.Chainable<Cypress.Response<unknown>> => {
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
const timeline = rule.timeline != null ? rule.timeline : undefined;
return cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
interval,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'query',
from: 'now-50000h',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
@ -60,29 +64,33 @@ export const createCustomRule = (
enabled: false,
exceptions_list: rule.exceptionLists ?? [],
tags: rule.tags,
...(rule.timeline.id ?? rule.timeline.templateTimelineId
...(timeline?.id ?? timeline?.templateTimelineId
? {
timeline_id: rule.timeline.id ?? rule.timeline.templateTimelineId,
timeline_title: rule.timeline.title,
timeline_id: timeline.id ?? timeline.templateTimelineId,
timeline_title: timeline.title,
}
: {}),
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
};
export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_testing') => {
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
const severity = rule.severity != null ? rule.severity.toLowerCase() : undefined;
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
interval: `${rule.runsEvery?.interval}${rule.runsEvery?.type}`,
from: `now-${rule.lookBack?.interval}${rule.lookBack?.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'eql',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
@ -96,17 +104,20 @@ export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_test
};
export const createThresholdRule = (rule: ThresholdRule, ruleId = 'rule_testing') => {
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
interval: `${rule.runsEvery?.interval}${rule.runsEvery?.type}`,
from: `now-${rule.lookBack?.interval}${rule.lookBack?.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'threshold',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
@ -124,17 +135,20 @@ export const createThresholdRule = (rule: ThresholdRule, ruleId = 'rule_testing'
};
export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing') => {
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
interval: `${rule.runsEvery?.interval}${rule.runsEvery?.type}`,
from: `now-${rule.lookBack?.interval}${rule.lookBack?.type}`,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'new_terms',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
data_view_id: rule.dataSource.type === 'dataView' ? rule.dataSource.dataView : undefined,
@ -151,17 +165,21 @@ export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing')
export const createSavedQueryRule = (
rule: SavedQueryRule,
ruleId = 'saved_query_rule_testing'
): Cypress.Chainable<Cypress.Response<unknown>> =>
cy.request({
): Cypress.Chainable<Cypress.Response<unknown>> => {
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
const timeline = rule.timeline != null ? rule.timeline : undefined;
return cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
interval: rule.interval,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'saved_query',
from: 'now-50000h',
index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : undefined,
@ -171,33 +189,38 @@ export const createSavedQueryRule = (
enabled: false,
exceptions_list: rule.exceptionLists ?? [],
tags: rule.tags,
...(rule.timeline.id ?? rule.timeline.templateTimelineId
...(timeline?.id ?? timeline?.templateTimelineId
? {
timeline_id: rule.timeline.id ?? rule.timeline.templateTimelineId,
timeline_title: rule.timeline.title,
timeline_id: timeline.id ?? timeline.templateTimelineId,
timeline_title: timeline.title,
}
: {}),
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
};
export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') => {
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
const timeline = rule.timeline != null ? rule.timeline : undefined;
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
// Default interval is 1m, our tests config overwrite this to 1s
// See https://github.com/elastic/kibana/pull/125396 for details
interval: '10s',
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'threat_match',
timeline_id: rule.timeline.templateTimelineId,
timeline_title: rule.timeline.title,
timeline_id: timeline?.templateTimelineId,
timeline_title: timeline?.title,
threat_mapping: [
{
entries: [
@ -233,17 +256,20 @@ export const createCustomRuleEnabled = (
interval = '100m',
maxSignals = 500
) => {
const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined;
const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined;
if (rule.dataSource.type === 'indexPatterns') {
cy.request({
method: 'POST',
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
interval,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'query',
from: 'now-50000h',
index: rule.dataSource.index,
@ -264,11 +290,11 @@ export const createCustomRuleEnabled = (
url: 'api/detection_engine/rules',
body: {
rule_id: ruleId,
risk_score: parseInt(rule.riskScore, 10),
risk_score: riskScore,
description: rule.description,
interval,
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
severity,
type: 'query',
from: 'now-50000h',
index: [],

View file

@ -179,6 +179,40 @@ export const deleteCases = () => {
});
};
export const deleteConnectors = () => {
const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`;
cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, {
query: {
bool: {
filter: [
{
match: {
type: 'action',
},
},
],
},
},
});
};
export const deleteSavedQueries = () => {
const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`;
cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, {
query: {
bool: {
filter: [
{
match: {
type: 'query',
},
},
],
},
},
});
};
export const postDataView = (dataSource: string) => {
cy.request({
method: 'POST',
@ -196,6 +230,15 @@ export const postDataView = (dataSource: string) => {
});
};
export const deleteDataView = (dataSource: string) => {
cy.request({
method: 'DELETE',
url: `api/data_views/data_view/${dataSource}`,
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
};
export const scrollToBottom = () => cy.scrollTo('bottom');
export const waitForPageToBeLoaded = () => {

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import type { EmailConnector } from '../objects/connector';
import { getEmailConnector } from '../objects/connector';
import type { EmailConnector, IndexConnector } from '../objects/connector';
import { getIndexConnector, getEmailConnector } from '../objects/connector';
import type {
CustomRule,
MachineLearningRule,
@ -14,6 +14,7 @@ import type {
ThreatIndicatorRule,
ThresholdRule,
NewTermsRule,
Mitre,
} from '../objects/rule';
import { getMachineLearningRule } from '../objects/rule';
import {
@ -106,6 +107,14 @@ import {
NEW_TERMS_HISTORY_SIZE,
NEW_TERMS_HISTORY_TIME_TYPE,
NEW_TERMS_INPUT_AREA,
ACTIONS_THROTTLE_INPUT,
INDEX_SELECTOR,
CREATE_CONNECTOR_BTN,
SAVE_ACTION_CONNECTOR_BTN,
JSON_EDITOR,
CREATE_ACTION_CONNECTOR_BTN,
EMAIL_ACTION_BTN,
COMBO_BOX_SELECTION,
} from '../screens/create_new_rule';
import { TOAST_ERROR } from '../screens/shared';
import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline';
@ -114,7 +123,6 @@ import { refreshPage } from './security_header';
import { EUI_FILTER_SELECT_ITEM } from '../screens/common/controls';
export const createAndEnableRule = () => {
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
cy.get(CREATE_AND_ENABLE_BTN).click({ force: true });
cy.get(CREATE_AND_ENABLE_BTN).should('not.exist');
cy.get(BACK_TO_ALL_RULES_LINK).click({ force: true });
@ -126,34 +134,41 @@ export const fillAboutRule = (
) => {
cy.get(RULE_NAME_INPUT).clear({ force: true }).type(rule.name, { force: true });
cy.get(RULE_DESCRIPTION_INPUT).clear({ force: true }).type(rule.description, { force: true });
cy.get(SEVERITY_DROPDOWN).click({ force: true });
cy.get(`#${rule.severity.toLowerCase()}`).click();
cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${rule.riskScore}`, { force: true });
rule.tags.forEach((tag) => {
cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true });
});
if (rule.severity) {
fillSeverity(rule.severity);
}
if (rule.riskScore) {
fillRiskScore(rule.riskScore);
}
if (rule.tags) {
fillRuleTags(rule.tags);
}
cy.get(ADVANCED_SETTINGS_BTN).click({ force: true });
rule.referenceUrls.forEach((url, index) => {
cy.get(REFERENCE_URLS_INPUT).eq(index).clear({ force: true }).type(url, { force: true });
cy.get(ADD_REFERENCE_URL_BTN).click({ force: true });
});
if (rule.referenceUrls) {
fillReferenceUrls(rule.referenceUrls);
}
rule.falsePositivesExamples.forEach((falsePositive, index) => {
cy.get(FALSE_POSITIVES_INPUT)
.eq(index)
.clear({ force: true })
.type(falsePositive, { force: true });
cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true });
});
if (rule.falsePositivesExamples) {
fillFalsePositiveExamples(rule.falsePositivesExamples);
}
if (rule.mitre) {
fillMitre(rule.mitre);
}
if (rule.note) {
fillNote(rule.note);
}
};
export const fillNote = (note: string) => {
cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }).type(note, { force: true });
};
export const fillMitre = (mitreAttacks: Mitre[]) => {
let techniqueIndex = 0;
let subtechniqueInputIndex = 0;
rule.mitre.forEach((mitre, tacticIndex) => {
mitreAttacks.forEach((mitre, tacticIndex) => {
cy.get(MITRE_ATTACK_TACTIC_DROPDOWN).eq(tacticIndex).click({ force: true });
cy.contains(MITRE_TACTIC, mitre.tactic).click();
@ -175,8 +190,38 @@ export const fillAboutRule = (
cy.get(MITRE_ATTACK_ADD_TACTIC_BUTTON).click({ force: true });
});
};
cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }).type(rule.note, { force: true });
export const fillFalsePositiveExamples = (falsePositives: string[]) => {
falsePositives.forEach((falsePositive, index) => {
cy.get(FALSE_POSITIVES_INPUT)
.eq(index)
.clear({ force: true })
.type(falsePositive, { force: true });
cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true });
});
};
export const fillSeverity = (severity: string) => {
cy.get(SEVERITY_DROPDOWN).click({ force: true });
cy.get(`#${severity.toLowerCase()}`).click();
};
export const fillRiskScore = (riskScore: string) => {
cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${riskScore}`, { force: true });
};
export const fillRuleTags = (tags: string[]) => {
tags.forEach((tag) => {
cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true });
});
};
export const fillReferenceUrls = (referenceUrls: string[]) => {
referenceUrls.forEach((url, index) => {
cy.get(REFERENCE_URLS_INPUT).eq(index).clear({ force: true }).type(url, { force: true });
cy.get(ADD_REFERENCE_URL_BTN).click({ force: true });
});
};
export const fillAboutRuleAndContinue = (
@ -200,8 +245,9 @@ export const fillAboutRuleWithOverrideAndContinue = (rule: OverrideRule) => {
});
});
cy.get(SEVERITY_DROPDOWN).click({ force: true });
cy.get(`#${rule.severity.toLowerCase()}`).click();
if (rule.severity) {
fillSeverity(rule.severity);
}
cy.get(RISK_MAPPING_OVERRIDE_OPTION).click();
cy.get(RISK_OVERRIDE).within(() => {
@ -210,48 +256,24 @@ export const fillAboutRuleWithOverrideAndContinue = (rule: OverrideRule) => {
cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${rule.riskScore}`, { force: true });
rule.tags.forEach((tag) => {
cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true });
});
if (rule.tags) {
fillRuleTags(rule.tags);
}
cy.get(ADVANCED_SETTINGS_BTN).click({ force: true });
rule.referenceUrls.forEach((url, index) => {
cy.get(REFERENCE_URLS_INPUT).eq(index).type(url, { force: true });
cy.get(ADD_REFERENCE_URL_BTN).click({ force: true });
});
rule.falsePositivesExamples.forEach((falsePositive, index) => {
cy.get(FALSE_POSITIVES_INPUT).eq(index).type(falsePositive, { force: true });
cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true });
});
let techniqueIndex = 0;
let subtechniqueInputIndex = 0;
rule.mitre.forEach((mitre, tacticIndex) => {
cy.get(MITRE_ATTACK_TACTIC_DROPDOWN).eq(tacticIndex).click({ force: true });
cy.contains(MITRE_TACTIC, mitre.tactic).click();
mitre.techniques.forEach((technique) => {
cy.get(MITRE_ATTACK_ADD_TECHNIQUE_BUTTON).eq(tacticIndex).click({ force: true });
cy.get(MITRE_ATTACK_TECHNIQUE_DROPDOWN).eq(techniqueIndex).click({ force: true });
cy.contains(MITRE_TACTIC, technique.name).click();
technique.subtechniques.forEach((subtechnique) => {
cy.get(MITRE_ATTACK_ADD_SUBTECHNIQUE_BUTTON).eq(techniqueIndex).click({ force: true });
cy.get(MITRE_ATTACK_SUBTECHNIQUE_DROPDOWN)
.eq(subtechniqueInputIndex)
.click({ force: true });
cy.contains(MITRE_TACTIC, subtechnique).click();
subtechniqueInputIndex++;
});
techniqueIndex++;
});
cy.get(MITRE_ATTACK_ADD_TACTIC_BUTTON).click({ force: true });
});
cy.get(INVESTIGATION_NOTES_TEXTAREA).type(rule.note, { force: true });
if (rule.referenceUrls) {
fillReferenceUrls(rule.referenceUrls);
}
if (rule.falsePositivesExamples) {
fillFalsePositiveExamples(rule.falsePositivesExamples);
}
if (rule.mitre) {
fillMitre(rule.mitre);
}
if (rule.note) {
fillNote(rule.note);
}
cy.get(RULE_NAME_OVERRIDE).within(() => {
cy.get(COMBO_BOX_INPUT).type(`${rule.nameOverride}{enter}`);
@ -264,35 +286,65 @@ export const fillAboutRuleWithOverrideAndContinue = (rule: OverrideRule) => {
getAboutContinueButton().should('exist').click({ force: true });
};
export const fillDefineCustomRuleWithImportedQueryAndContinue = (
rule: CustomRule | OverrideRule
) => {
export const fillCustomQuery = (rule: CustomRule | OverrideRule) => {
if (rule.timeline?.id) {
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
cy.get(TIMELINE(rule.timeline.id)).click();
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
} else {
cy.get(CUSTOM_QUERY_INPUT)
.first()
.type(rule.customQuery || '');
}
};
export const fillDefineCustomRuleAndContinue = (rule: CustomRule | OverrideRule) => {
if (rule.dataSource.type === 'dataView') {
cy.get(DATA_VIEW_OPTION).click();
cy.get(DATA_VIEW_COMBO_BOX).type(`${rule.dataSource.dataView}{enter}`);
}
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
cy.get(TIMELINE(rule.timeline.id)).click();
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
fillCustomQuery(rule);
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
cy.get(CUSTOM_QUERY_INPUT).should('not.exist');
};
export const fillScheduleRuleAndContinue = (rule: CustomRule | MachineLearningRule) => {
cy.get(RUNS_EVERY_INTERVAL).type('{selectall}').type(rule.runsEvery.interval);
cy.get(RUNS_EVERY_TIME_TYPE).select(rule.runsEvery.timeType);
cy.get(LOOK_BACK_INTERVAL).type('{selectAll}').type(rule.lookBack.interval);
cy.get(LOOK_BACK_TIME_TYPE).select(rule.lookBack.timeType);
if (rule.runsEvery) {
cy.get(RUNS_EVERY_INTERVAL).type('{selectall}').type(rule.runsEvery.interval);
cy.get(RUNS_EVERY_TIME_TYPE).select(rule.runsEvery.timeType);
}
if (rule.lookBack) {
cy.get(LOOK_BACK_INTERVAL).type('{selectAll}').type(rule.lookBack.interval);
cy.get(LOOK_BACK_TIME_TYPE).select(rule.lookBack.timeType);
}
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
};
export const fillRuleAction = (rule: CustomRule) => {
if (rule.actions) {
cy.get(ACTIONS_THROTTLE_INPUT).select(rule.actions.throttle);
rule.actions?.connectors.forEach((connector) => {
switch (connector.type) {
case 'index':
cy.get(INDEX_SELECTOR).click();
cy.get(CREATE_CONNECTOR_BTN).click();
fillIndexConnectorForm(connector);
break;
case 'email':
cy.get(EMAIL_ACTION_BTN).click();
cy.get(CREATE_ACTION_CONNECTOR_BTN).click();
fillEmailConnectorForm(connector);
break;
}
});
}
};
export const fillDefineThresholdRule = (rule: ThresholdRule) => {
const thresholdField = 0;
const threshold = 1;
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
cy.get(TIMELINE(rule.timeline.id)).click();
fillCustomQuery(rule);
cy.get(COMBO_BOX_CLEAR_BTN).first().click();
if (rule.dataSource.type === 'indexPatterns') {
@ -318,9 +370,7 @@ export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
const typeThresholdField = ($el: Cypress.ObjectLike) =>
cy.wrap($el).type(rule.thresholdField, { delay: 35 });
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
cy.get(TIMELINE(rule.timeline.id)).click();
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
fillCustomQuery(rule);
cy.get(THRESHOLD_INPUT_AREA)
.find(INPUT)
.then((inputs) => {
@ -360,9 +410,7 @@ export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => {
};
export const fillDefineNewTermsRuleAndContinue = (rule: NewTermsRule) => {
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
cy.get(TIMELINE(rule.timeline.id)).click();
cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery);
fillCustomQuery(rule);
cy.get(NEW_TERMS_INPUT_AREA).find(INPUT).click().type(rule.newTermsFields[0], { delay: 35 });
cy.get(EUI_FILTER_SELECT_ITEM).click({ force: true });
cy.focused().type('{esc}'); // Close combobox dropdown so next inputs can be interacted with
@ -449,6 +497,21 @@ export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConne
cy.get(EMAIL_CONNECTOR_PASSWORD_INPUT).type(connector.password);
};
export const fillIndexConnectorForm = (connector: IndexConnector = getIndexConnector()) => {
cy.get(CONNECTOR_NAME_INPUT).type(connector.name);
cy.get(COMBO_BOX_INPUT).type(connector.index);
cy.get(COMBO_BOX_SELECTION).click({ force: true });
cy.get(SAVE_ACTION_CONNECTOR_BTN).click();
cy.get(SAVE_ACTION_CONNECTOR_BTN).should('not.exist');
cy.get(JSON_EDITOR).should('be.visible');
cy.get(JSON_EDITOR).click();
cy.get(JSON_EDITOR).type(connector.document, {
parseSpecialCharSequences: false,
});
};
/** Returns the indicator index drop down field. Pass in row number, default is 1 */
export const getIndicatorIndexComboField = (row = 1) =>
cy.get(THREAT_COMBO_BOX_INPUT).eq(row * 2 - 2);

View file

@ -148,6 +148,7 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent<Props> = ({
return (
<EuiFormRow
data-test-subj="actionJsonEditor"
fullWidth
error={errors}
isInvalid={errors && errors.length > 0 && inputTargetValue !== undefined}