diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/bulk_edit_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/bulk_edit_rules.spec.ts index 20da46b4a3cf9..dd04d324d3739 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/bulk_edit_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/bulk_edit_rules.spec.ts @@ -74,12 +74,6 @@ const DEFAULT_INDEX_PATTERNS = ['index-1-*', 'index-2-*']; const TAGS = ['cypress-tag-1', 'cypress-tag-2']; const OVERWRITE_INDEX_PATTERNS = ['overwrite-index-1-*', 'overwrite-index-2-*']; -const customRule = { - ...getNewRule(), - index: DEFAULT_INDEX_PATTERNS, - name: RULE_NAME, -}; - const expectedNumberOfCustomRulesToBeEdited = 6; const expectedNumberOfMachineLearningRulesToBeEdited = 1; const numberOfRulesPerPage = 5; @@ -92,7 +86,14 @@ describe('Detection rules, bulk edit', () => { beforeEach(() => { deleteAlertsAndRules(); esArchiverResetKibana(); - createCustomRule(customRule, '1'); + createCustomRule( + { + ...getNewRule(), + name: RULE_NAME, + dataSource: { index: DEFAULT_INDEX_PATTERNS, type: 'indexPatterns' }, + }, + '1' + ); createCustomRule(getExistingRule(), '2'); createCustomRule(getNewOverrideRule(), '3'); createCustomRule(getNewThresholdRule(), '4'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts index 92bd07b8a24ba..ba886993c7433 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts @@ -295,10 +295,13 @@ describe('Custom query rules', () => { }); context('Edition', () => { - const expectedEditedtags = getEditedRule().tags.join(''); + const rule = getEditedRule(); + const expectedEditedtags = rule.tags.join(''); const expectedEditedIndexPatterns = - getEditedRule().index && getEditedRule().index.length - ? getEditedRule().index + rule.dataSource.type === 'indexPatterns' && + rule.dataSource.index && + rule.dataSource.index.length + ? rule.dataSource.index : getIndexPatterns(); before(() => { @@ -325,27 +328,32 @@ describe('Custom query rules', () => { }); it('Allows a rule to be edited', () => { + const existingRule = getExistingRule(); + editFirstRule(); // expect define step to populate - cy.get(CUSTOM_QUERY_INPUT).should('have.value', getExistingRule().customQuery); - if (getExistingRule().index && getExistingRule().index.length > 0) { - cy.get(DEFINE_INDEX_INPUT).should('have.text', getExistingRule().index.join('')); + cy.get(CUSTOM_QUERY_INPUT).should('have.value', existingRule.customQuery); + if ( + existingRule.dataSource.type === 'indexPatterns' && + existingRule.dataSource.index.length > 0 + ) { + cy.get(DEFINE_INDEX_INPUT).should('have.text', existingRule.dataSource.index.join('')); } goToAboutStepTab(); // expect about step to populate - cy.get(RULE_NAME_INPUT).invoke('val').should('eql', getExistingRule().name); - cy.get(RULE_DESCRIPTION_INPUT).should('have.text', getExistingRule().description); - cy.get(TAGS_FIELD).should('have.text', getExistingRule().tags.join('')); - cy.get(SEVERITY_DROPDOWN).should('have.text', getExistingRule().severity); - cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', getExistingRule().riskScore); + 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(SEVERITY_DROPDOWN).should('have.text', existingRule.severity); + cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', existingRule.riskScore); goToScheduleStepTab(); // expect schedule step to populate - const interval = getExistingRule().interval; + const interval = existingRule.interval; const intervalParts = interval != null && interval.match(/[0-9]+|[a-zA-Z]+/g); if (intervalParts) { const [amount, unit] = intervalParts; @@ -381,7 +389,7 @@ describe('Custom query rules', () => { cy.wait('@getRule').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); // ensure that editing rule does not modify max_signals - cy.wrap(response?.body.max_signals).should('eql', getExistingRule().maxSignals); + cy.wrap(response?.body.max_signals).should('eql', existingRule.maxSignals); }); cy.get(RULE_NAME_HEADER).should('contain', `${getEditedRule().name}`); @@ -396,7 +404,7 @@ describe('Custom query rules', () => { cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should( 'have.text', - expectedEditedIndexPatterns.join('') + expectedEditedIndexPatterns?.join('') ); getDetails(CUSTOM_QUERY_DETAILS).should('have.text', getEditedRule().customQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule_data_view.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule_data_view.spec.ts new file mode 100644 index 0000000000000..77a4ae274e6e4 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule_data_view.spec.ts @@ -0,0 +1,158 @@ +/* + * 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 { formatMitreAttackDescription } from '../../helpers/rules'; +import { getDataViewRule } from '../../objects/rule'; +import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts'; + +import { + CUSTOM_RULES_BTN, + RISK_SCORE, + RULE_NAME, + RULES_ROW, + RULES_TABLE, + RULE_SWITCH, + SEVERITY, +} from '../../screens/alerts_detection_rules'; + +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, +} from '../../screens/rule_details'; + +import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; +import { createTimeline } from '../../tasks/api_calls/timelines'; +import { postDataView } from '../../tasks/common'; +import { + createAndEnableRule, + fillAboutRuleAndContinue, + fillDefineCustomRuleWithImportedQueryAndContinue, + fillScheduleRuleAndContinue, + waitForAlertsToPopulate, + waitForTheRuleToBeExecuted, +} from '../../tasks/create_new_rule'; + +import { esArchiverResetKibana } from '../../tasks/es_archiver'; +import { login, visit } from '../../tasks/login'; +import { getDetails } from '../../tasks/rule_details'; + +import { RULE_CREATION } from '../../urls/navigation'; + +describe('Custom query rules', () => { + before(() => { + login(); + }); + + 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 expectedNumberOfRules = 1; + + beforeEach(() => { + /* We don't call cleanKibana method on the before hook, instead we call esArchiverReseKibana on the before each. This is because we + 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. */ + esArchiverResetKibana(); + createTimeline(rule.timeline).then((response) => { + cy.wrap({ + ...rule, + timeline: { + ...rule.timeline, + id: response.body.data.persistTimeline.timeline.savedObjectId, + }, + }).as('rule'); + }); + if (rule.dataSource.type === 'dataView') { + postDataView(rule.dataSource.dataView); + } + }); + + it('Creates and enables a new rule', function () { + visit(RULE_CREATION); + fillDefineCustomRuleWithImportedQueryAndContinue(this.rule); + fillAboutRuleAndContinue(this.rule); + fillScheduleRuleAndContinue(this.rule); + createAndEnableRule(); + + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + + cy.get(RULES_TABLE).find(RULES_ROW).should('have.length', expectedNumberOfRules); + cy.get(RULE_NAME).should('have.text', this.rule.name); + cy.get(RISK_SCORE).should('have.text', this.rule.riskScore); + cy.get(SEVERITY).should('have.text', this.rule.severity); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetails(); + + cy.get(RULE_NAME_HEADER).should('contain', `${this.rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', this.rule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', this.rule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', this.rule.riskScore); + 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({ force: true }); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(DATA_VIEW_DETAILS).should('have.text', this.rule.dataSource.dataView); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery); + 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).should( + 'have.text', + `${getDataViewRule().runsEvery.interval}${getDataViewRule().runsEvery.type}` + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( + 'have.text', + `${getDataViewRule().lookBack.interval}${getDataViewRule().lookBack.type}` + ); + }); + + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); + + cy.get(NUMBER_OF_ALERTS) + .invoke('text') + .should('match', /^[1-9].+$/); + cy.get(ALERT_GRID_CELL).contains(this.rule.name); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index faa907be8810c..7c3ba64536faf 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -205,12 +205,12 @@ describe('indicator match', () => { describe('Indicator mapping', () => { beforeEach(() => { + const rule = getNewThreatIndicatorRule(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); - fillIndexAndIndicatorIndexPattern( - getNewThreatIndicatorRule().index, - getNewThreatIndicatorRule().indicatorIndexPattern - ); + if (rule.dataSource.type === 'indexPatterns') { + fillIndexAndIndicatorIndexPattern(rule.dataSource.index, rule.indicatorIndexPattern); + } }); it('Does NOT show invalidation text on initial page load', () => { @@ -419,11 +419,12 @@ describe('indicator match', () => { }); it('Creates and enables a new Indicator Match rule', () => { + const rule = getNewThreatIndicatorRule(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); - fillDefineIndicatorMatchRuleAndContinue(getNewThreatIndicatorRule()); - fillAboutRuleAndContinue(getNewThreatIndicatorRule()); - fillScheduleRuleAndContinue(getNewThreatIndicatorRule()); + fillDefineIndicatorMatchRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); createAndEnableRule(); cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); @@ -432,22 +433,19 @@ describe('indicator match', () => { cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); }); - cy.get(RULE_NAME).should('have.text', getNewThreatIndicatorRule().name); - cy.get(RISK_SCORE).should('have.text', getNewThreatIndicatorRule().riskScore); - cy.get(SEVERITY).should('have.text', getNewThreatIndicatorRule().severity); + cy.get(RULE_NAME).should('have.text', rule.name); + cy.get(RISK_SCORE).should('have.text', rule.riskScore); + cy.get(SEVERITY).should('have.text', rule.severity); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); goToRuleDetails(); - cy.get(RULE_NAME_HEADER).should('contain', `${getNewThreatIndicatorRule().name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getNewThreatIndicatorRule().description); + 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', getNewThreatIndicatorRule().severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', getNewThreatIndicatorRule().riskScore); - getDetails(INDICATOR_PREFIX_OVERRIDE).should( - 'have.text', - getNewThreatIndicatorRule().threatIndicatorPath - ); + getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); + getDetails(INDICATOR_PREFIX_OVERRIDE).should('have.text', rule.threatIndicatorPath); getDetails(REFERENCE_URLS_DETAILS).should((details) => { expect(removeExternalLinkText(details.text())).equal(expectedUrls); }); @@ -461,22 +459,19 @@ describe('indicator match', () => { cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should( - 'have.text', - getNewThreatIndicatorRule().index.join('') - ); + if (rule.dataSource.type === 'indexPatterns') { + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', rule.dataSource.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', - getNewThreatIndicatorRule().indicatorIndexPattern.join('') + rule.indicatorIndexPattern.join('') ); getDetails(INDICATOR_MAPPING).should( 'have.text', - `${getNewThreatIndicatorRule().indicatorMappingField} MATCHES ${ - getNewThreatIndicatorRule().indicatorIndexField - }` + `${rule.indicatorMappingField} MATCHES ${rule.indicatorIndexField}` ); getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*'); }); @@ -484,15 +479,11 @@ describe('indicator match', () => { cy.get(SCHEDULE_DETAILS).within(() => { getDetails(RUNS_EVERY_DETAILS).should( 'have.text', - `${getNewThreatIndicatorRule().runsEvery.interval}${ - getNewThreatIndicatorRule().runsEvery.type - }` + `${rule.runsEvery.interval}${rule.runsEvery.type}` ); getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( 'have.text', - `${getNewThreatIndicatorRule().lookBack.interval}${ - getNewThreatIndicatorRule().lookBack.type - }` + `${rule.lookBack.interval}${rule.lookBack.type}` ); }); @@ -500,11 +491,9 @@ describe('indicator match', () => { waitForAlertsToPopulate(); cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); - cy.get(ALERT_RULE_NAME).first().should('have.text', getNewThreatIndicatorRule().name); - cy.get(ALERT_SEVERITY) - .first() - .should('have.text', getNewThreatIndicatorRule().severity.toLowerCase()); - cy.get(ALERT_RISK_SCORE).first().should('have.text', getNewThreatIndicatorRule().riskScore); + 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.riskScore); }); it('Investigate alert in timeline', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index 9673de50bbae9..4e2b42c7b7ee1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -6,8 +6,7 @@ */ import { formatMitreAttackDescription } from '../../helpers/rules'; -import type { ThresholdRule } from '../../objects/rule'; -import { getIndexPatterns, getNewRule, getNewThresholdRule } from '../../objects/rule'; +import { getIndexPatterns, getNewThresholdRule } from '../../objects/rule'; import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts'; @@ -20,7 +19,6 @@ import { RULES_TABLE, SEVERITY, } from '../../screens/alerts_detection_rules'; -import { PREVIEW_HEADER_SUBTITLE } from '../../screens/create_new_rule'; import { ABOUT_DETAILS, ABOUT_INVESTIGATION_NOTES, @@ -47,22 +45,14 @@ import { } from '../../screens/rule_details'; import { getDetails } from '../../tasks/rule_details'; -import { goToManageAlertsDetectionRules } from '../../tasks/alerts'; -import { - goToCreateNewRule, - goToRuleDetails, - waitForRulesTableToBeLoaded, -} from '../../tasks/alerts_detection_rules'; -import { createCustomRuleEnabled } from '../../tasks/api_calls/rules'; +import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; import { createTimeline } from '../../tasks/api_calls/timelines'; import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; import { createAndEnableRule, fillAboutRuleAndContinue, fillDefineThresholdRuleAndContinue, - fillDefineThresholdRule, fillScheduleRuleAndContinue, - previewResults, selectThresholdRuleType, waitForAlertsToPopulate, waitForTheRuleToBeExecuted, @@ -156,37 +146,4 @@ describe('Detection rules, threshold', () => { cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text().split(' ')[0]).to.be.lt(100)); cy.get(ALERT_GRID_CELL).contains(rule.name); }); - - it.skip('Preview results of keyword using "host.name"', () => { - rule.index = [...rule.index, '.siem-signals*']; - - createCustomRuleEnabled(getNewRule()); - goToManageAlertsDetectionRules(); - waitForRulesTableToBeLoaded(); - goToCreateNewRule(); - selectThresholdRuleType(); - fillDefineThresholdRule(rule); - previewResults(); - - cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '3 unique hits'); - }); - - it.skip('Preview results of "ip" using "source.ip"', () => { - const previewRule: ThresholdRule = { - ...rule, - thresholdField: 'source.ip', - threshold: '1', - }; - previewRule.index = [...previewRule.index, '.siem-signals*']; - - createCustomRuleEnabled(getNewRule()); - goToManageAlertsDetectionRules(); - waitForRulesTableToBeLoaded(); - goToCreateNewRule(); - selectThresholdRuleType(); - fillDefineThresholdRule(previewRule); - previewResults(); - - cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '10 unique hits'); - }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/add_exception.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/add_exception.spec.ts index 814f29622f51a..ac2fdf96be022 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/add_exception.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/add_exception.spec.ts @@ -40,7 +40,11 @@ describe('Adds rule exception', () => { beforeEach(() => { deleteAlertsAndRules(); createCustomRuleEnabled( - { ...getNewRule(), customQuery: 'agent.name:*', index: ['exceptions*'] }, + { + ...getNewRule(), + customQuery: 'agent.name:*', + dataSource: { index: ['exceptions*'], type: 'indexPatterns' }, + }, 'rule_testing', '1s' ); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_flyout.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_flyout.spec.ts index 9e08313e7e73f..8c2e2af4b8bad 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_flyout.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_flyout.spec.ts @@ -70,7 +70,7 @@ describe('Exceptions flyout', () => { createExceptionList(getExceptionList(), getExceptionList().list_id).then((response) => createCustomRule({ ...getNewRule(), - index: ['exceptions-*'], + dataSource: { index: ['exceptions-*'], type: 'indexPatterns' }, exceptionLists: [ { id: response.body.id, diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 17ffdf52486f5..7fd9e7b5f7625 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -37,11 +37,15 @@ interface Interval { type: string; } +export type RuleDataSource = + | { type: 'indexPatterns'; index: string[] } + | { type: 'dataView'; dataView: string }; + export interface CustomRule { customQuery?: string; name: string; description: string; - index: string[]; + dataSource: RuleDataSource; interval?: string; severity: string; riskScore: string; @@ -176,15 +180,39 @@ const getRunsEvery = (): Interval => ({ type: 's', }); +const getRunsEveryFiveMinutes = (): Interval => ({ + interval: '5', + timeType: 'Minutes', + type: 'm', +}); + const getLookBack = (): Interval => ({ interval: '50000', timeType: 'Hours', type: 'h', }); +export const getDataViewRule = (): CustomRule => ({ + customQuery: 'host.name: *', + dataSource: { dataView: 'auditbeat-2022', type: 'dataView' }, + name: 'New Data View Rule', + description: 'The new rule description.', + severity: 'High', + riskScore: '17', + tags: ['test', 'newRule'], + referenceUrls: ['http://example.com/', 'https://example.com/'], + falsePositivesExamples: ['False1', 'False2'], + mitre: [getMitre1(), getMitre2()], + note: '# test markdown', + runsEvery: getRunsEveryFiveMinutes(), + lookBack: getLookBack(), + timeline: getTimeline(), + maxSignals: 100, +}); + export const getNewRule = (): CustomRule => ({ customQuery: 'host.name: *', - index: getIndexPatterns(), + dataSource: { index: getIndexPatterns(), type: 'indexPatterns' }, name: 'New Rule Test', description: 'The new rule description.', severity: 'High', @@ -202,7 +230,7 @@ export const getNewRule = (): CustomRule => ({ export const getBuildingBlockRule = (): CustomRule => ({ customQuery: 'host.name: *', - index: getIndexPatterns(), + dataSource: { index: getIndexPatterns(), type: 'indexPatterns' }, name: 'Building Block Rule Test', description: 'The new rule description.', severity: 'High', @@ -221,7 +249,7 @@ export const getBuildingBlockRule = (): CustomRule => ({ export const getUnmappedRule = (): CustomRule => ({ customQuery: '*:*', - index: ['unmapped*'], + dataSource: { index: ['unmapped*'], type: 'indexPatterns' }, name: 'Rule with unmapped fields', description: 'The new rule description.', severity: 'High', @@ -239,7 +267,7 @@ export const getUnmappedRule = (): CustomRule => ({ export const getUnmappedCCSRule = (): CustomRule => ({ customQuery: '*:*', - index: [`${ccsRemoteName}:unmapped*`], + dataSource: { index: [`${ccsRemoteName}:unmapped*`], type: 'indexPatterns' }, name: 'Rule with unmapped fields', description: 'The new rule description.', severity: 'High', @@ -259,7 +287,7 @@ export const getExistingRule = (): CustomRule => ({ customQuery: 'host.name: *', name: 'Rule 1', description: 'Description for Rule 1', - index: ['auditbeat-*'], + dataSource: { index: ['auditbeat-*'], type: 'indexPatterns' }, interval: '100m', severity: 'High', riskScore: '19', @@ -278,7 +306,7 @@ export const getExistingRule = (): CustomRule => ({ export const getNewOverrideRule = (): OverrideRule => ({ customQuery: 'host.name: *', - index: getIndexPatterns(), + dataSource: { index: getIndexPatterns(), type: 'indexPatterns' }, name: 'Override Rule', description: 'The new rule description.', severity: 'High', @@ -305,7 +333,7 @@ export const getNewOverrideRule = (): OverrideRule => ({ export const getNewThresholdRule = (): ThresholdRule => ({ customQuery: 'host.name: *', - index: getIndexPatterns(), + dataSource: { index: getIndexPatterns(), type: 'indexPatterns' }, name: 'Threshold Rule', description: 'The new rule description.', severity: 'High', @@ -325,7 +353,7 @@ export const getNewThresholdRule = (): ThresholdRule => ({ export const getNewTermsRule = (): NewTermsRule => ({ customQuery: 'host.name: *', - index: getIndexPatterns(), + dataSource: { index: getIndexPatterns(), type: 'indexPatterns' }, name: 'New Terms Rule', description: 'The new rule description.', severity: 'High', @@ -365,7 +393,7 @@ export const getMachineLearningRule = (): MachineLearningRule => ({ export const getEqlRule = (): CustomRule => ({ customQuery: 'any where process.name == "zsh"', name: 'New EQL Rule', - index: getIndexPatterns(), + dataSource: { index: getIndexPatterns(), type: 'indexPatterns' }, description: 'New EQL rule description.', severity: 'High', riskScore: '17', @@ -383,7 +411,7 @@ export const getEqlRule = (): CustomRule => ({ export const getCCSEqlRule = (): CustomRule => ({ customQuery: 'any where process.name == "run-parts"', name: 'New EQL Rule', - index: [`${ccsRemoteName}:run-parts`], + dataSource: { index: [`${ccsRemoteName}:run-parts`], type: 'indexPatterns' }, description: 'New EQL rule description.', severity: 'High', riskScore: '17', @@ -404,7 +432,7 @@ export const getEqlSequenceRule = (): CustomRule => ({ [any where agent.name == "test.local"]\ [any where host.name == "test.local"]', name: 'New EQL Sequence Rule', - index: getIndexPatterns(), + dataSource: { index: getIndexPatterns(), type: 'indexPatterns' }, description: 'New EQL rule description.', severity: 'High', riskScore: '17', @@ -422,7 +450,7 @@ export const getEqlSequenceRule = (): CustomRule => ({ export const getNewThreatIndicatorRule = (): ThreatIndicatorRule => ({ name: 'Threat Indicator Rule Test', description: 'The threat indicator rule description.', - index: ['suspicious-*'], + dataSource: { index: ['suspicious-*'], type: 'indexPatterns' }, severity: 'Critical', riskScore: '20', tags: ['test', 'threat'], diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 7af92d518db7a..00fb1ad0abe55 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -93,6 +93,11 @@ export const AT_LEAST_ONE_INDEX_PATTERN = 'A minimum of one index pattern is req export const CUSTOM_QUERY_REQUIRED = 'A custom query is required.'; +export const DATA_VIEW_COMBO_BOX = + '[data-test-subj="pick-rule-data-source"] [data-test-subj="comboBoxInput"]'; + +export const DATA_VIEW_OPTION = '[data-test-subj="rule-index-toggle-dataView"]'; + export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]'; export const DEFINE_EDIT_BUTTON = '[data-test-subj="edit-define-rule"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index a541cf5cb2bc6..4aed5286ee1f1 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -22,6 +22,8 @@ export const ANOMALY_SCORE_DETAILS = 'Anomaly score'; export const CUSTOM_QUERY_DETAILS = 'Custom query'; +export const DATA_VIEW_DETAILS = 'Data View'; + export const DEFINITION_DETAILS = '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 88616063c6c36..0ef07195b3309 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -28,7 +28,11 @@ export const createMachineLearningRule = (rule: MachineLearningRule, ruleId = 'm failOnStatusCode: false, }); -export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing', interval = '100m') => +export const createCustomRule = ( + rule: CustomRule, + ruleId = 'rule_testing', + interval = '100m' +): Cypress.Chainable<Cypress.Response<unknown>> => cy.request({ method: 'POST', url: 'api/detection_engine/rules', @@ -41,7 +45,7 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing', inte severity: rule.severity.toLocaleLowerCase(), type: 'query', from: 'now-50000h', - index: rule.index, + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : '', query: rule.customQuery, language: 'kuery', enabled: false, @@ -51,98 +55,107 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing', inte failOnStatusCode: false, }); -export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_testing') => - cy.request({ - method: 'POST', - url: 'api/detection_engine/rules', - body: { - rule_id: ruleId, - risk_score: parseInt(rule.riskScore, 10), - description: rule.description, - interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, - from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, - name: rule.name, - severity: rule.severity.toLocaleLowerCase(), - type: 'eql', - index: rule.index, - query: rule.customQuery, - language: 'eql', - enabled: true, - }, - headers: { 'kbn-xsrf': 'cypress-creds' }, - }); +export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_testing') => { + if (rule.dataSource.type === 'indexPatterns') { + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`, + from: `now-${rule.lookBack.interval}${rule.lookBack.type}`, + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'eql', + index: rule.dataSource.index, + query: rule.customQuery, + language: 'eql', + enabled: true, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); + } +}; -export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') => - cy.request({ - method: 'POST', - url: 'api/detection_engine/rules', - body: { - rule_id: ruleId, - risk_score: parseInt(rule.riskScore, 10), - 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(), - type: 'threat_match', - timeline_id: rule.timeline.templateTimelineId, - timeline_title: rule.timeline.title, - threat_mapping: [ - { - entries: [ - { - field: rule.indicatorMappingField, - type: 'mapping', - value: rule.indicatorIndexField, - }, - ], - }, - ], - threat_query: '*:*', - threat_language: 'kuery', - threat_filters: [], - threat_index: rule.indicatorIndexPattern, - threat_indicator_path: rule.threatIndicatorPath, - from: 'now-50000h', - index: rule.index, - query: rule.customQuery || '*:*', - language: 'kuery', - enabled: true, - }, - headers: { 'kbn-xsrf': 'cypress-creds' }, - failOnStatusCode: false, - }); +export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') => { + if (rule.dataSource.type === 'indexPatterns') { + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + 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(), + type: 'threat_match', + timeline_id: rule.timeline.templateTimelineId, + timeline_title: rule.timeline.title, + threat_mapping: [ + { + entries: [ + { + field: rule.indicatorMappingField, + type: 'mapping', + value: rule.indicatorIndexField, + }, + ], + }, + ], + threat_query: '*:*', + threat_language: 'kuery', + threat_filters: [], + threat_index: rule.indicatorIndexPattern, + threat_indicator_path: rule.threatIndicatorPath, + from: 'now-50000h', + index: rule.dataSource.index, + query: rule.customQuery || '*:*', + language: 'kuery', + enabled: true, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, + }); + } +}; export const createCustomRuleEnabled = ( rule: CustomRule, ruleId = '1', interval = '100m', maxSignals = 500 -) => - cy.request({ - method: 'POST', - url: 'api/detection_engine/rules', - body: { - rule_id: ruleId, - risk_score: parseInt(rule.riskScore, 10), - description: rule.description, - interval, - name: rule.name, - severity: rule.severity.toLocaleLowerCase(), - type: 'query', - from: 'now-50000h', - index: rule.index, - query: rule.customQuery, - language: 'kuery', - enabled: true, - tags: ['rule1'], - max_signals: maxSignals, - building_block_type: rule.buildingBlockType, - }, - headers: { 'kbn-xsrf': 'cypress-creds' }, - failOnStatusCode: false, - }); +) => { + if (rule.dataSource.type === 'indexPatterns') { + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + interval, + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'query', + from: 'now-50000h', + index: rule.dataSource.index, + query: rule.customQuery, + language: 'kuery', + enabled: true, + tags: ['rule1'], + max_signals: maxSignals, + building_block_type: rule.buildingBlockType, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, + }); + } +}; export const deleteCustomRule = (ruleId = '1') => { cy.request({ diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index 437ed1a254ca4..4982053648667 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -179,14 +179,14 @@ export const deleteCases = () => { }); }; -export const postDataView = (indexPattern: string) => { +export const postDataView = (dataSource: string) => { cy.request({ method: 'POST', url: `/api/index_patterns/index_pattern`, body: { index_pattern: { fieldAttrs: '{}', - title: indexPattern, + title: dataSource, timeFieldName: '@timestamp', fields: '{}', }, diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 5e5de7301362b..8d1c1ebc94eb5 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -94,6 +94,8 @@ import { EMAIL_CONNECTOR_PASSWORD_INPUT, EMAIL_CONNECTOR_SERVICE_SELECTOR, PREVIEW_HISTOGRAM, + DATA_VIEW_COMBO_BOX, + DATA_VIEW_OPTION, NEW_TERMS_TYPE, NEW_TERMS_HISTORY_SIZE, NEW_TERMS_HISTORY_TIME_TYPE, @@ -258,6 +260,10 @@ export const fillAboutRuleWithOverrideAndContinue = (rule: OverrideRule) => { export const fillDefineCustomRuleWithImportedQueryAndContinue = ( 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); @@ -282,9 +288,11 @@ export const fillDefineThresholdRule = (rule: ThresholdRule) => { cy.get(TIMELINE(rule.timeline.id)).click(); cy.get(COMBO_BOX_CLEAR_BTN).first().click(); - rule.index.forEach((index) => { - cy.get(COMBO_BOX_INPUT).first().type(`${index}{enter}`); - }); + if (rule.dataSource.type === 'indexPatterns') { + rule.dataSource.index.forEach((index) => { + cy.get(COMBO_BOX_INPUT).first().type(`${index}{enter}`); + }); + } cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery); cy.get(THRESHOLD_INPUT_AREA) @@ -494,7 +502,9 @@ export const getCustomQueryInvalidationText = () => cy.contains(CUSTOM_QUERY_REQ * @param rule The rule to use to fill in everything */ export const fillDefineIndicatorMatchRuleAndContinue = (rule: ThreatIndicatorRule) => { - fillIndexAndIndicatorIndexPattern(rule.index, rule.indicatorIndexPattern); + if (rule.dataSource.type === 'indexPatterns') { + fillIndexAndIndicatorIndexPattern(rule.dataSource.index, rule.indicatorIndexPattern); + } fillIndicatorMatchRow({ indexField: rule.indicatorMappingField, indicatorIndexField: rule.indicatorIndexField,