From fffc5f5bce90bcae4ca1791381f2c192baf0fa1f Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 5 Oct 2023 02:09:59 -0400 Subject: [PATCH 01/34] adds initial tests --- .../coverage_overview_dashboard.md | 4 +- .../coverage_overview/rule_source_filter.tsx | 2 +- .../handle_coverage_overview_request.test.ts | 20 ++ .../group1/coverage_overview.ts | 281 ++++++++++++++++++ .../security_and_spaces/group1/index.ts | 1 + .../utils/coverage_overview.ts | 26 ++ .../utils/index.ts | 1 + .../coverage_overview/coverage_overview.cy.ts | 109 +++++++ .../cypress/objects/rule.ts | 2 +- .../cypress/screens/alerts.ts | 20 ++ .../cypress/tasks/alerts.ts | 22 ++ .../cypress/urls/rules_management.ts | 1 + 12 files changed, 485 insertions(+), 4 deletions(-) create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts create mode 100644 x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management/coverage_overview_dashboard.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management/coverage_overview_dashboard.md index 08f23aceda9b9..86170a9e4c405 100644 --- a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management/coverage_overview_dashboard.md +++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management/coverage_overview_dashboard.md @@ -24,9 +24,9 @@ Status: `in progress`. The current test plan matches `Milestone 1 - MVP` of the - **Rule Source**: The filter type defining rule type, current options are `prebuilt`(from elastic prebuilt rules package) and `custom`(created by user) --**Initial filter state**: The filters present on initial page load. Rule activity will be set to `enabled`, rule source will be set to `prebuilt` and `custom` simultaneously. +- **Initial filter state**: The filters present on initial page load. Rule activity will be set to `enabled`, rule source will be set to `prebuilt` and `custom` simultaneously. --**Dashboard containing the rule data**: The normal render of the coverage overview dashboard. Any returned rule data mapped correctly to the tile layout of all the MITRE data in a colored grid +- **Dashboard containing the rule data**: The normal render of the coverage overview dashboard. Any returned rule data mapped correctly to the tile layout of all the MITRE data in a colored grid ### Assumptions diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx index fd1995beb68d7..e55450f0a069a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx @@ -87,7 +87,7 @@ const RuleSourceFilterComponent = ({ `} > { }) ); }); + + describe('filters', () => { + beforeEach(() => { + (findRules as jest.Mock).mockResolvedValue({ + total: 25555, + page: 3, + perPage: 10000, + data: generateRules(1), + }); + }); + it('no filters present', async () => { + await handleCoverageOverviewRequest({ + params: {}, + deps: { + rulesClient: rulesClientMock.create(), + }, + }); + expect(findRules).toHaveBeenCalledWith({}); + }); + }); }); function generateRules(count: number): Rule[] { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts new file mode 100644 index 0000000000000..7464e04c2b6a8 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts @@ -0,0 +1,281 @@ +/* + * 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 { + CoverageOverviewFilter, + CoverageOverviewRuleActivity, + CoverageOverviewRuleSource, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import expect from 'expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRule, + deleteAllRules, + getCoverageOverviewData, + getRuleForSignalTesting, + installPrebuiltRules, +} from '../../utils'; +import { createRuleAssetSavedObject } from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + + const getMockThreatData = (id: string) => [ + { + framework: 'MITRE ATT&CK', + tactic: { id, name: 'test-name', reference: 'test.com' }, + }, + ]; + + let enabledCustomRuleId: string; + let disabledCustomRuleId: string; + let enabledPrebuiltRuleId: string; + let disabledPrebuiltRuleId: string; + + describe('coverage_overview', () => { + beforeEach(async () => { + // create enabled rule + const enabledRule = { + ...getRuleForSignalTesting(['auditbeat-*'], 'enabled-custom-rule'), + threat: getMockThreatData('tactic-1'), + }; + const enabledRuleBody = await createRule(supertest, log, enabledRule); + enabledCustomRuleId = enabledRuleBody.id; + + // create disabled rule + const disabledRule = { + ...getRuleForSignalTesting(['auditbeat-*'], 'disabled-custom-rule', false), + threat: getMockThreatData('tactic-2'), + }; + const disabledRuleBody = await createRule(supertest, log, disabledRule); + disabledCustomRuleId = disabledRuleBody.id; + + // create/install enabled and disabled prebuilt rules + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'enabled-prebuilt-rule', + enabled: true, + threat: getMockThreatData('tactic-3'), + }), + createRuleAssetSavedObject({ + rule_id: 'disabled-prebuilt-rule', + threat: getMockThreatData('tactic-4'), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + if (created[0].enabled) { + enabledPrebuiltRuleId = created[0].id; + disabledPrebuiltRuleId = created[1].id; + } else { + enabledPrebuiltRuleId = created[1].id; + disabledPrebuiltRuleId = created[0].id; + } + }); + + afterEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('works with no rule data present', async () => { + await deleteAllRules(supertest, log); + const response = await getCoverageOverviewData(supertest); + expect(response).toEqual({ + coverage: {}, + unmapped_rule_ids: [], + rules_data: {}, + }); + }); + + it('returns the correct rule data', async () => { + const response = await getCoverageOverviewData(supertest); + expect(response).toEqual({ + coverage: { + 'tactic-1': [enabledCustomRuleId], + 'tactic-2': [disabledCustomRuleId], + 'tactic-3': [enabledPrebuiltRuleId], + 'tactic-4': [disabledPrebuiltRuleId], + }, + unmapped_rule_ids: [], + rules_data: { + [enabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'enabled', + }, + [disabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'disabled', + }, + [enabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'enabled', + }, + [disabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'disabled', + }, + }, + }); + }); + + describe('filters', () => { + it('correctly filters on enabled rules', async () => { + const filter: CoverageOverviewFilter = { activity: [CoverageOverviewRuleActivity.Enabled] }; + const response = await getCoverageOverviewData(supertest, filter); + expect(response).toEqual({ + coverage: { + 'tactic-1': [enabledCustomRuleId], + 'tactic-3': [enabledPrebuiltRuleId], + }, + unmapped_rule_ids: [], + rules_data: { + [enabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'enabled', + }, + [enabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'enabled', + }, + }, + }); + }); + + it('correctly filters on disabled rules', async () => { + const filter: CoverageOverviewFilter = { + activity: [CoverageOverviewRuleActivity.Disabled], + }; + const response = await getCoverageOverviewData(supertest, filter); + expect(response).toEqual({ + coverage: { + 'tactic-2': [disabledCustomRuleId], + 'tactic-4': [disabledPrebuiltRuleId], + }, + unmapped_rule_ids: [], + rules_data: { + [disabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'disabled', + }, + + [disabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'disabled', + }, + }, + }); + }); + + it('correctly filters on custom rules', async () => { + const filter: CoverageOverviewFilter = { + source: [CoverageOverviewRuleSource.Custom], + }; + const response = await getCoverageOverviewData(supertest, filter); + expect(response).toEqual({ + coverage: { + 'tactic-1': [enabledCustomRuleId], + 'tactic-2': [disabledCustomRuleId], + }, + unmapped_rule_ids: [], + rules_data: { + [enabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'enabled', + }, + [disabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'disabled', + }, + }, + }); + }); + + it('correctly filters on prebuilt rules', async () => { + const filter: CoverageOverviewFilter = { + source: [CoverageOverviewRuleSource.Prebuilt], + }; + const response = await getCoverageOverviewData(supertest, filter); + expect(response).toEqual({ + coverage: { + 'tactic-3': [enabledPrebuiltRuleId], + 'tactic-4': [disabledPrebuiltRuleId], + }, + unmapped_rule_ids: [], + rules_data: { + [enabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'enabled', + }, + [disabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'disabled', + }, + }, + }); + }); + + it('correctly filters on multiple fields', async () => { + const filter: CoverageOverviewFilter = { + source: [CoverageOverviewRuleSource.Prebuilt, CoverageOverviewRuleSource.Custom], + activity: [CoverageOverviewRuleActivity.Enabled, CoverageOverviewRuleActivity.Disabled], + }; + const response = await getCoverageOverviewData(supertest, filter); + expect(response).toEqual({ + coverage: { + 'tactic-1': [enabledCustomRuleId], + 'tactic-2': [disabledCustomRuleId], + 'tactic-3': [enabledPrebuiltRuleId], + 'tactic-4': [disabledPrebuiltRuleId], + }, + unmapped_rule_ids: [], + rules_data: { + [enabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'enabled', + }, + [disabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'disabled', + }, + [enabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'enabled', + }, + [disabledPrebuiltRuleId]: { + name: 'Query with a rule id', + activity: 'disabled', + }, + }, + }); + }); + + it('correctly filters on search_term field', async () => { + const filter: CoverageOverviewFilter = { + search_term: 'tactic-1', + }; + const response = await getCoverageOverviewData(supertest, filter); + expect(response).toEqual({ + coverage: { + 'tactic-1': [enabledCustomRuleId], + }, + unmapped_rule_ids: [], + rules_data: { + [enabledCustomRuleId]: { + name: 'Signal Testing Query', + activity: 'enabled', + }, + }, + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts index f181b10e25bbc..8ecd413e93253 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts @@ -30,5 +30,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_rule_exception_references')); loadTestFile(require.resolve('./get_rule_management_filters')); + loadTestFile(require.resolve('./coverage_overview')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts new file mode 100644 index 0000000000000..2d57fdd4f8296 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts @@ -0,0 +1,26 @@ +/* + * 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 type SuperTest from 'supertest'; +import { + CoverageOverviewFilter, + CoverageOverviewResponse, + RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; + +export const getCoverageOverviewData = async ( + supertest: SuperTest.SuperTest, + filter: CoverageOverviewFilter = {} +): Promise => { + const response = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ filter }) + .expect(200); + + return response.body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 4ad3ec1f1a62e..ad5c3944297de 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -8,6 +8,7 @@ export * from './binary_to_string'; export * from './count_down_es'; export * from './count_down_test'; +export * from './coverage_overview'; export * from './create_container_with_endpoint_entries'; export * from './create_container_with_entries'; export * from './create_exception_list'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts new file mode 100644 index 0000000000000..f632f42e6f115 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -0,0 +1,109 @@ +/* + * 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 { getMockThreatData } from '@kbn/security-solution-plugin/public/detections/mitre/mitre_tactics_techniques'; +import { + COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, + COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES, + COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES, +} from '../../../../screens/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { visit } from '../../../../tasks/navigation'; +import { COVERAGE_OVERVIEW_URL } from '../../../../urls/rules_management'; +import { createRuleAssetSavedObject } from '../../../../helpers/rules'; +import { getMitre1, getNewRule } from '../../../../objects/rule'; +import { + createAndInstallMockedPrebuiltRules, + preventPrebuiltRulesPackageInstallation, +} from '../../../../tasks/api_calls/prebuilt_rules'; +import { + cleanKibana, + deleteAlertsAndRules, + deletePrebuiltRulesAssets, +} from '../../../../tasks/common'; +import { login } from '../../../../tasks/login'; +import { + openTechniquePanel, + selectCoverageOverviewActivityFilterOption, + selectCoverageOverviewSourceFilterOption, +} from '../../../../tasks/alerts'; + +const prebuiltRules = [ + createRuleAssetSavedObject({ + name: `Enabled prebuilt rule`, + rule_id: `enabled_prebuilt_rule`, + enabled: true, + threat: [getMitre1()], + }), + createRuleAssetSavedObject({ + name: `Disabled prebuilt rule`, + rule_id: `disabled_prebuilt_rule`, + threat: [getMitre1()], + }), +]; + +describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { + before(() => { + cleanKibana(); + }); + + beforeEach(() => { + login(); + deleteAlertsAndRules(); + deletePrebuiltRulesAssets(); + preventPrebuiltRulesPackageInstallation(); + visit(COVERAGE_OVERVIEW_URL); + createAndInstallMockedPrebuiltRules({ rules: prebuiltRules }); + createRule( + getNewRule({ rule_id: 'enabled_custom_rule', enabled: true, name: 'Enabled custom rule' }) + ); + createRule( + getNewRule({ rule_id: 'disabled_custom_rule', name: 'Disabled custom rule', enabled: false }) + ); + cy.reload(); + }); + + it('technique panel renders correct data on page load', () => { + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); + }); + + it('filtering displays correct data', () => { + // all data filtered in + selectCoverageOverviewActivityFilterOption('Disabled rules'); + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + + // only filtering for disabled and prebuilt rules + selectCoverageOverviewActivityFilterOption('Enabled rules'); + selectCoverageOverviewSourceFilterOption('Custom rules'); + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts index 681eff67d071e..a08c20d204dc2 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts @@ -36,7 +36,7 @@ export const getIndexPatterns = (): string[] => [ export const getThreatIndexPatterns = (): string[] => ['logs-ti_*']; -const getMitre1 = (): Threat => ({ +export const getMitre1 = (): Threat => ({ framework: 'MITRE ATT&CK', tactic: { name: getMockThreatData().tactic.name, diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index 2cd4055dc8dd6..d73fd8b6f7423 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -222,3 +222,23 @@ export const ALERT_RENDERER_HOST_NAME = '[data-test-subj="alertFieldBadge"] [data-test-subj="render-content-host.name"]'; export const HOVER_ACTIONS_CONTAINER = getDataTestSubjectSelector('hover-actions-container'); + +export const COVERAGE_OVERVIEW_TECHNIQUE_PANEL = + '[data-test-subj="coverageOverviewTechniquePanel"]'; + +export const COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES = + '[data-test-subj="coverageOverviewEnabledRulesList"]'; + +export const COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES = + '[data-test-subj="coverageOverviewDisabledRulesList"]'; + +export const COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON = + '[data-test-subj="enableAllDisabledButton"]'; + +export const COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON = + '[data-test-subj="coverageOverviewRuleActivityFilterButton"]'; + +export const COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON = + '[data-test-subj="coverageOverviewRuleSourceFilterButton"]'; + +export const COVERAGE_OVERVIEW_FILTER_LIST = '[data-test-subj="coverageOverviewFilterList"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index fee36ce79faff..800e3643225a9 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -55,6 +55,10 @@ import { ALERT_TABLE_EVENT_RENDERED_VIEW_OPTION, HOVER_ACTIONS_CONTAINER, ALERT_TABLE_GRID_VIEW_OPTION, + COVERAGE_OVERVIEW_TECHNIQUE_PANEL, + COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON, + COVERAGE_OVERVIEW_FILTER_LIST, + COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON, } from '../screens/alerts'; import { LOADING_INDICATOR, REFRESH_BUTTON } from '../screens/security_header'; import { TIMELINE_COLUMN_SPINNER } from '../screens/timeline'; @@ -508,3 +512,21 @@ export const switchAlertTableToGridView = () => { cy.get(ALERT_TABLE_SUMMARY_VIEW_SELECTABLE).should('be.visible').trigger('click'); cy.get(ALERT_TABLE_GRID_VIEW_OPTION).should('be.visible').trigger('click'); }; + +export const openTechniquePanel = (label: string) => { + cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL).contains(label).click(); +}; + +export const selectCoverageOverviewActivityFilterOption = (option: string) => { + cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // open filter popover + cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click(); + cy.get(LOADING_INDICATOR).should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // close filter popover +}; + +export const selectCoverageOverviewSourceFilterOption = (option: string) => { + cy.get(COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON).click(); // open filter popover + cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click(); + cy.get(LOADING_INDICATOR).should('not.exist'); + cy.get(COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON).click(); // close filter popover +}; diff --git a/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts b/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts index 6b0f4bc231247..be3b3fcce83c6 100644 --- a/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts +++ b/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts @@ -7,3 +7,4 @@ export const RULES_MANAGEMENT_URL = '/app/security/rules/management'; export const RULES_MONITORING_URL = '/app/security/rules/monitoring'; +export const COVERAGE_OVERVIEW_URL = '/app/security/rules_coverage_overview'; From 146e1736b6d0ce1b80cdeb41cbc29c32ff681909 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 31 Oct 2023 13:06:34 -0400 Subject: [PATCH 02/34] fixes cypress merge --- .../rule_management/coverage_overview/coverage_overview.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index f632f42e6f115..41fcccddbadfb 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -57,7 +57,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { deletePrebuiltRulesAssets(); preventPrebuiltRulesPackageInstallation(); visit(COVERAGE_OVERVIEW_URL); - createAndInstallMockedPrebuiltRules({ rules: prebuiltRules }); + createAndInstallMockedPrebuiltRules(prebuiltRules); createRule( getNewRule({ rule_id: 'enabled_custom_rule', enabled: true, name: 'Enabled custom rule' }) ); From 0058dc61e72807487afc8dac89cdd26b5f87033c Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 6 Nov 2023 10:04:48 -0500 Subject: [PATCH 03/34] adds remaining cypress tests --- .../coverage_overview/coverage_overview.cy.ts | 51 +++++++++++++++++++ .../cypress/screens/alerts.ts | 2 + .../cypress/tasks/alerts.ts | 14 +++++ 3 files changed, 67 insertions(+) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 41fcccddbadfb..4379be8e2ee7c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -27,6 +27,7 @@ import { } from '../../../../tasks/common'; import { login } from '../../../../tasks/login'; import { + enableAllDisabledRules, openTechniquePanel, selectCoverageOverviewActivityFilterOption, selectCoverageOverviewSourceFilterOption, @@ -105,5 +106,55 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { .contains('Disabled custom rule') .should('not.exist'); cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + + // only filtering for prebuilt rules + selectCoverageOverviewActivityFilterOption('Enabled rules'); + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + + // only filtering for custom rules + selectCoverageOverviewSourceFilterOption('Custom rules'); + selectCoverageOverviewSourceFilterOption('Elastic rules'); // Turn off filter + + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + }); + + it('enables all disabled rules', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); + openTechniquePanel(getMockThreatData().technique.name); + enableAllDisabledRules(); + + // Should now render all rules in "enabled" section + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Disabled custom rule'); + + // Shouldn't render the rules in "disabled" section + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index 830054f2d05a1..978a1392b2c29 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -202,3 +202,5 @@ export const COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON = '[data-test-subj="coverageOverviewRuleSourceFilterButton"]'; export const COVERAGE_OVERVIEW_FILTER_LIST = '[data-test-subj="coverageOverviewFilterList"]'; + +export const COVERAGE_OVERVIEW_SEARCH_BAR = '[data-test-subj="coverageOverviewFilterSearchBar"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index 017232387a91a..393cc6c1bd22e 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -55,6 +55,8 @@ import { COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON, COVERAGE_OVERVIEW_FILTER_LIST, COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON, + COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, + COVERAGE_OVERVIEW_SEARCH_BAR, } from '../screens/alerts'; import { LOADING_INDICATOR, REFRESH_BUTTON } from '../screens/security_header'; import { TIMELINE_COLUMN_SPINNER } from '../screens/timeline'; @@ -513,3 +515,15 @@ export const selectCoverageOverviewSourceFilterOption = (option: string) => { cy.get(LOADING_INDICATOR).should('not.exist'); cy.get(COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON).click(); // close filter popover }; + +export const filterCoverageOverviewBySearchBar = (searchTerm: string) => { + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).type(searchTerm); + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).type('{enter}'); + cy.get(LOADING_INDICATOR).should('not.exist'); +}; + +export const enableAllDisabledRules = () => { + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).click(); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.exist'); + cy.get(LOADING_INDICATOR).should('not.exist'); +}; From d456e1030f7613a773ff8b9811a5bc9a20f58635 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 6 Nov 2023 23:30:13 -0500 Subject: [PATCH 04/34] removes duplicated ftr tests --- .../handle_coverage_overview_request.test.ts | 20 -- .../basic/tests/coverage_overview.ts | 96 ++++++ .../group1/coverage_overview.ts | 281 ------------------ .../security_and_spaces/group1/index.ts | 1 - .../coverage_overview/coverage_overview.cy.ts | 18 ++ .../cypress/tasks/alerts.ts | 6 +- 6 files changed, 117 insertions(+), 305 deletions(-) delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.test.ts index 8238b0f0f26d0..371ab0dce2c45 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.test.ts @@ -49,26 +49,6 @@ describe('handleCoverageOverviewRequest', () => { }) ); }); - - describe('filters', () => { - beforeEach(() => { - (findRules as jest.Mock).mockResolvedValue({ - total: 25555, - page: 3, - perPage: 10000, - data: generateRules(1), - }); - }); - it('no filters present', async () => { - await handleCoverageOverviewRequest({ - params: {}, - deps: { - rulesClient: rulesClientMock.create(), - }, - }); - expect(findRules).toHaveBeenCalledWith({}); - }); - }); }); function generateRules(count: number): Rule[] { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts index e894cea05325c..81c0ec97a0800 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts @@ -19,6 +19,7 @@ import { deleteAllRules, getSimpleRule, installPrebuiltRulesAndTimelines, + installPrebuiltRules, } from '../../utils'; import { createNonSecurityRule } from '../../utils/create_non_security_rule'; @@ -471,6 +472,101 @@ export default ({ getService }: FtrProviderContext): void => { }, }); }); + + it('returns response filtered by prebuilt rules', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedRule = created[0]; + + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + source: ['prebuilt'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule.id], + TA001: [expectedRule.id], + 'T001.001': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + }, + }); + }); + + it('returns all rules if both custom and prebuilt filters are specified in the request', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedPrebuiltRule = created[0]; + + const expectedCustomRule = await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + source: ['prebuilt', 'custom'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [expectedPrebuiltRule.id], + TA001: [expectedPrebuiltRule.id], + 'T001.001': [expectedPrebuiltRule.id], + T002: [expectedCustomRule.id], + TA002: [expectedCustomRule.id], + 'T002.002': [expectedCustomRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedPrebuiltRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + [expectedCustomRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts deleted file mode 100644 index 7464e04c2b6a8..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/coverage_overview.ts +++ /dev/null @@ -1,281 +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 { - CoverageOverviewFilter, - CoverageOverviewRuleActivity, - CoverageOverviewRuleSource, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; -import expect from 'expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createPrebuiltRuleAssetSavedObjects, - createRule, - deleteAllRules, - getCoverageOverviewData, - getRuleForSignalTesting, - installPrebuiltRules, -} from '../../utils'; -import { createRuleAssetSavedObject } from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const es = getService('es'); - const log = getService('log'); - - const getMockThreatData = (id: string) => [ - { - framework: 'MITRE ATT&CK', - tactic: { id, name: 'test-name', reference: 'test.com' }, - }, - ]; - - let enabledCustomRuleId: string; - let disabledCustomRuleId: string; - let enabledPrebuiltRuleId: string; - let disabledPrebuiltRuleId: string; - - describe('coverage_overview', () => { - beforeEach(async () => { - // create enabled rule - const enabledRule = { - ...getRuleForSignalTesting(['auditbeat-*'], 'enabled-custom-rule'), - threat: getMockThreatData('tactic-1'), - }; - const enabledRuleBody = await createRule(supertest, log, enabledRule); - enabledCustomRuleId = enabledRuleBody.id; - - // create disabled rule - const disabledRule = { - ...getRuleForSignalTesting(['auditbeat-*'], 'disabled-custom-rule', false), - threat: getMockThreatData('tactic-2'), - }; - const disabledRuleBody = await createRule(supertest, log, disabledRule); - disabledCustomRuleId = disabledRuleBody.id; - - // create/install enabled and disabled prebuilt rules - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'enabled-prebuilt-rule', - enabled: true, - threat: getMockThreatData('tactic-3'), - }), - createRuleAssetSavedObject({ - rule_id: 'disabled-prebuilt-rule', - threat: getMockThreatData('tactic-4'), - }), - ]); - const { - results: { created }, - } = await installPrebuiltRules(es, supertest); - if (created[0].enabled) { - enabledPrebuiltRuleId = created[0].id; - disabledPrebuiltRuleId = created[1].id; - } else { - enabledPrebuiltRuleId = created[1].id; - disabledPrebuiltRuleId = created[0].id; - } - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('works with no rule data present', async () => { - await deleteAllRules(supertest, log); - const response = await getCoverageOverviewData(supertest); - expect(response).toEqual({ - coverage: {}, - unmapped_rule_ids: [], - rules_data: {}, - }); - }); - - it('returns the correct rule data', async () => { - const response = await getCoverageOverviewData(supertest); - expect(response).toEqual({ - coverage: { - 'tactic-1': [enabledCustomRuleId], - 'tactic-2': [disabledCustomRuleId], - 'tactic-3': [enabledPrebuiltRuleId], - 'tactic-4': [disabledPrebuiltRuleId], - }, - unmapped_rule_ids: [], - rules_data: { - [enabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'enabled', - }, - [disabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'disabled', - }, - [enabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'enabled', - }, - [disabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'disabled', - }, - }, - }); - }); - - describe('filters', () => { - it('correctly filters on enabled rules', async () => { - const filter: CoverageOverviewFilter = { activity: [CoverageOverviewRuleActivity.Enabled] }; - const response = await getCoverageOverviewData(supertest, filter); - expect(response).toEqual({ - coverage: { - 'tactic-1': [enabledCustomRuleId], - 'tactic-3': [enabledPrebuiltRuleId], - }, - unmapped_rule_ids: [], - rules_data: { - [enabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'enabled', - }, - [enabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'enabled', - }, - }, - }); - }); - - it('correctly filters on disabled rules', async () => { - const filter: CoverageOverviewFilter = { - activity: [CoverageOverviewRuleActivity.Disabled], - }; - const response = await getCoverageOverviewData(supertest, filter); - expect(response).toEqual({ - coverage: { - 'tactic-2': [disabledCustomRuleId], - 'tactic-4': [disabledPrebuiltRuleId], - }, - unmapped_rule_ids: [], - rules_data: { - [disabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'disabled', - }, - - [disabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'disabled', - }, - }, - }); - }); - - it('correctly filters on custom rules', async () => { - const filter: CoverageOverviewFilter = { - source: [CoverageOverviewRuleSource.Custom], - }; - const response = await getCoverageOverviewData(supertest, filter); - expect(response).toEqual({ - coverage: { - 'tactic-1': [enabledCustomRuleId], - 'tactic-2': [disabledCustomRuleId], - }, - unmapped_rule_ids: [], - rules_data: { - [enabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'enabled', - }, - [disabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'disabled', - }, - }, - }); - }); - - it('correctly filters on prebuilt rules', async () => { - const filter: CoverageOverviewFilter = { - source: [CoverageOverviewRuleSource.Prebuilt], - }; - const response = await getCoverageOverviewData(supertest, filter); - expect(response).toEqual({ - coverage: { - 'tactic-3': [enabledPrebuiltRuleId], - 'tactic-4': [disabledPrebuiltRuleId], - }, - unmapped_rule_ids: [], - rules_data: { - [enabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'enabled', - }, - [disabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'disabled', - }, - }, - }); - }); - - it('correctly filters on multiple fields', async () => { - const filter: CoverageOverviewFilter = { - source: [CoverageOverviewRuleSource.Prebuilt, CoverageOverviewRuleSource.Custom], - activity: [CoverageOverviewRuleActivity.Enabled, CoverageOverviewRuleActivity.Disabled], - }; - const response = await getCoverageOverviewData(supertest, filter); - expect(response).toEqual({ - coverage: { - 'tactic-1': [enabledCustomRuleId], - 'tactic-2': [disabledCustomRuleId], - 'tactic-3': [enabledPrebuiltRuleId], - 'tactic-4': [disabledPrebuiltRuleId], - }, - unmapped_rule_ids: [], - rules_data: { - [enabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'enabled', - }, - [disabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'disabled', - }, - [enabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'enabled', - }, - [disabledPrebuiltRuleId]: { - name: 'Query with a rule id', - activity: 'disabled', - }, - }, - }); - }); - - it('correctly filters on search_term field', async () => { - const filter: CoverageOverviewFilter = { - search_term: 'tactic-1', - }; - const response = await getCoverageOverviewData(supertest, filter); - expect(response).toEqual({ - coverage: { - 'tactic-1': [enabledCustomRuleId], - }, - unmapped_rule_ids: [], - rules_data: { - [enabledCustomRuleId]: { - name: 'Signal Testing Query', - activity: 'enabled', - }, - }, - }); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts index 8bd5bed5f453f..94ba07b31bddf 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts @@ -24,6 +24,5 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_rule_exception_references')); loadTestFile(require.resolve('./get_rule_management_filters')); - loadTestFile(require.resolve('./coverage_overview')); }); }; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 4379be8e2ee7c..0e049272a93cf 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -28,6 +28,7 @@ import { import { login } from '../../../../tasks/login'; import { enableAllDisabledRules, + filterCoverageOverviewBySearchBar, openTechniquePanel, selectCoverageOverviewActivityFilterOption, selectCoverageOverviewSourceFilterOption, @@ -134,6 +135,23 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { .should('not.exist'); cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + + // filtering for search term + selectCoverageOverviewSourceFilterOption('Elastic rules'); + filterCoverageOverviewBySearchBar('Enabled custom rule'); + + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); }); it('enables all disabled rules', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index 393cc6c1bd22e..73b9b8e5fd78d 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -517,9 +517,9 @@ export const selectCoverageOverviewSourceFilterOption = (option: string) => { }; export const filterCoverageOverviewBySearchBar = (searchTerm: string) => { - cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).type(searchTerm); - cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).type('{enter}'); - cy.get(LOADING_INDICATOR).should('not.exist'); + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).type(`${searchTerm}`); + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).focus(); + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).realType('{enter}'); }; export const enableAllDisabledRules = () => { From 7f102aad379652058be0aa2e19fe3155db828fa7 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 6 Nov 2023 23:36:46 -0500 Subject: [PATCH 05/34] removes unnecessary util --- .../utils/coverage_overview.ts | 26 ------------------- .../utils/index.ts | 1 - 2 files changed, 27 deletions(-) delete mode 100644 x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts deleted file mode 100644 index 2d57fdd4f8296..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/coverage_overview.ts +++ /dev/null @@ -1,26 +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 type SuperTest from 'supertest'; -import { - CoverageOverviewFilter, - CoverageOverviewResponse, - RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; - -export const getCoverageOverviewData = async ( - supertest: SuperTest.SuperTest, - filter: CoverageOverviewFilter = {} -): Promise => { - const response = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ filter }) - .expect(200); - - return response.body; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 952342932609e..5aca507b7134f 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -8,7 +8,6 @@ export * from './binary_to_string'; export * from './count_down_es'; export * from './count_down_test'; -export * from './coverage_overview'; export * from './create_container_with_endpoint_entries'; export * from './create_container_with_entries'; export * from './create_exception_list'; From d845cbec0e80dad24521548d55d02ce515c058e9 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 7 Nov 2023 00:15:08 -0500 Subject: [PATCH 06/34] adds error cases --- .../hooks/use_fetch_coverage_overview_query.ts | 9 +++++++++ .../pages/coverage_overview/translations.ts | 7 +++++++ .../basic/tests/coverage_overview.ts | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_coverage_overview_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_coverage_overview_query.ts index 2866245bb4e87..7f27e08164fe3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_coverage_overview_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_coverage_overview_query.ts @@ -8,12 +8,14 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; +import * as i18n from '../../../rule_management_ui/pages/coverage_overview/translations'; import type { CoverageOverviewFilter } from '../../../../../common/api/detection_engine'; import { RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL } from '../../../../../common/api/detection_engine'; import { fetchCoverageOverview } from '../api'; import { buildCoverageOverviewDashboardModel } from '../../logic/coverage_overview/build_coverage_overview_dashboard_model'; import type { CoverageOverviewDashboard } from '../../model/coverage_overview/dashboard'; import { DEFAULT_QUERY_OPTIONS } from './constants'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; const COVERAGE_OVERVIEW_QUERY_KEY = ['POST', RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL]; @@ -29,6 +31,8 @@ export const useFetchCoverageOverviewQuery = ( filter: CoverageOverviewFilter = {}, options?: UseQueryOptions ) => { + const { addError } = useAppToasts(); + return useQuery( [...COVERAGE_OVERVIEW_QUERY_KEY, filter], async ({ signal }) => { @@ -39,6 +43,11 @@ export const useFetchCoverageOverviewQuery = ( { ...DEFAULT_QUERY_OPTIONS, ...options, + onError: (error) => { + addError(error, { + title: i18n.COVERAGE_OVERVIEW_FETCH_ERROR_TITLE, + }); + }, } ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts index eb9f06c350421..f61fa72e7f322 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts @@ -177,3 +177,10 @@ export const CoverageOverviewDashboardInformation = i18n.translate( "Your current coverage of MITRE ATT&CK\u00AE tactics and techniques, based on installed rules. Click a cell to view and enable a technique's rules. Rules must be mapped to the MITRE ATT&CK\u00AE framework to be displayed.", } ); + +export const COVERAGE_OVERVIEW_FETCH_ERROR_TITLE = i18n.translate( + 'xpack.securitySolution.coverageOverviewDashboard.fetchErrorTitle', + { + defaultMessage: 'Failed to fetch coverage overview data', + } +); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts index 81c0ec97a0800..87e2ebe66a5a3 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts @@ -569,6 +569,24 @@ export default ({ getService }: FtrProviderContext): void => { }); }); }); + + describe('error cases', async () => { + it('throws error when request body is not valid', async () => { + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ filter: { source: ['give me all the rules'] } }) + .expect(400); + + expect(body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + '[request body]: Invalid value "give me all the rules" supplied to "filter,source"', + }); + }); + }); }); }; From 85008c515c9bb5fb04f1af60f25079b2c5e40888 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 7 Nov 2023 14:02:55 -0500 Subject: [PATCH 07/34] moves ftr tests into new folder structure --- .../config/ess/config.base.basic.ts | 13 + .../rule_management/configs/ess.config.ts | 22 + .../configs/serverless.config.ts | 15 + .../rule_management/coverage_overview.ts | 623 ++++++++++++++++++ .../basic_license/rule_management/index.ts | 13 + .../utils/rules/create_non_security_rule.ts | 41 ++ .../detections_response/utils/rules/index.ts | 1 + .../utils/rules/prebuilt_rules/index.ts | 1 + .../prebuilt_rules/install_prebuilt_rules.ts | 62 ++ 9 files changed, 791 insertions(+) create mode 100644 x-pack/test/security_solution_api_integration/config/ess/config.base.basic.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules.ts diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.basic.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.basic.ts new file mode 100644 index 0000000000000..15104fe97292a --- /dev/null +++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.basic.ts @@ -0,0 +1,13 @@ +/* + * 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 { createTestConfig } from './config.base'; + +export default createTestConfig({ + license: 'basic', + ssl: true, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts new file mode 100644 index 0000000000000..eaae21719c720 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../config/ess/config.base.basic') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine ESS/ Rule management API Integration Tests', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts new file mode 100644 index 0000000000000..a95c9dc2bd434 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts @@ -0,0 +1,15 @@ +/* + * 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 { createTestConfig } from '../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine Serverless/ Rule management API Integration Tests', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts new file mode 100644 index 0000000000000..1cdb66c8c84c8 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -0,0 +1,623 @@ +/* + * 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 expect from '@kbn/expect'; + +import { + RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, + ThreatArray, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, + createRule, + deleteAllRules, + getSimpleRule, + installPrebuiltRulesAndTimelines, + installPrebuiltRules, + createNonSecurityRule, +} from '../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const log = getService('log'); + const es = getService('es'); + + describe('@serverless @ess coverage_overview', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('does NOT error when there are no security rules', async () => { + await createNonSecurityRule(supertest); + const rule1 = await createRule(supertest, log, { + ...getSimpleRule(), + threat: generateThreatArray(1), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({}) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [rule1.id], + TA001: [rule1.id], + 'T001.001': [rule1.id], + }, + unmapped_rule_ids: [], + rules_data: { + [rule1.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + describe('without filters', () => { + it('returns an empty response if there are no rules', async () => { + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({}) + .expect(200); + + expect(body).to.eql({ + coverage: {}, + unmapped_rule_ids: [], + rules_data: {}, + }); + }); + + it('returns response with a single rule mapped to MITRE categories', async () => { + const rule1 = await createRule(supertest, log, { + ...getSimpleRule(), + threat: generateThreatArray(1), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({}) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [rule1.id], + TA001: [rule1.id], + 'T001.001': [rule1.id], + }, + unmapped_rule_ids: [], + rules_data: { + [rule1.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + it('returns response with an unmapped rule', async () => { + const rule1 = await createRule(supertest, log, { ...getSimpleRule(), threat: undefined }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({}) + .expect(200); + + expect(body).to.eql({ + coverage: {}, + unmapped_rule_ids: [rule1.id], + rules_data: { + [rule1.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + }); + + describe('with filters', () => { + describe('search_term', () => { + it('returns response filtered by tactic', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(1), + }); + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-2'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + search_term: 'TA002', + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + it('returns response filtered by technique', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(1), + }); + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-2'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + search_term: 'T002', + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + it('returns response filtered by subtechnique', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(1), + }); + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-2'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + search_term: 'T002.002', + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + it('returns response filtered by rule name', async () => { + await createRule(supertest, log, getSimpleRule('rule-1')); + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-2'), + name: 'rule-2', + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + search_term: 'rule-2', + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: {}, + unmapped_rule_ids: [expectedRule.id], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'rule-2', + }, + }, + }); + }); + + it('returns response filtered by index pattern', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + index: ['index-pattern-1'], + }); + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-2'), + index: ['index-pattern-2'], + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + search_term: 'index-pattern-2', + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: {}, + unmapped_rule_ids: [expectedRule.id], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + }); + + describe('activity', () => { + it('returns response filtered by disabled rules', async () => { + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(1), + }); + await createRule(supertest, log, { + ...getSimpleRule('rule-2', true), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + activity: ['disabled'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule.id], + TA001: [expectedRule.id], + 'T001.001': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + it('returns response filtered by enabled rules', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(1), + }); + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-2', true), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + activity: ['enabled'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'enabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + it('returns all rules if both enabled and disabled filters are specified in the request', async () => { + const expectedRule1 = await createRule(supertest, log, { + ...getSimpleRule('rule-1', false), + name: 'Disabled rule', + threat: generateThreatArray(1), + }); + const expectedRule2 = await createRule(supertest, log, { + ...getSimpleRule('rule-2', true), + name: 'Enabled rule', + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + activity: ['enabled', 'disabled'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule1.id], + TA001: [expectedRule1.id], + 'T001.001': [expectedRule1.id], + T002: [expectedRule2.id], + TA002: [expectedRule2.id], + 'T002.002': [expectedRule2.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule1.id]: { + activity: 'disabled', + name: 'Disabled rule', + }, + [expectedRule2.id]: { + activity: 'enabled', + name: 'Enabled rule', + }, + }, + }); + }); + }); + + describe('source', () => { + it('returns response filtered by custom rules', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + await installPrebuiltRulesAndTimelines(es, supertest); + + const expectedRule = await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + source: ['custom'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + + it('returns response filtered by prebuilt rules', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedRule = created[0]; + + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + source: ['prebuilt'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule.id], + TA001: [expectedRule.id], + 'T001.001': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + }, + }); + }); + + it('returns all rules if both custom and prebuilt filters are specified in the request', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedPrebuiltRule = created[0]; + + const expectedCustomRule = await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + threat: generateThreatArray(2), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ + filter: { + source: ['prebuilt', 'custom'], + }, + }) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [expectedPrebuiltRule.id], + TA001: [expectedPrebuiltRule.id], + 'T001.001': [expectedPrebuiltRule.id], + T002: [expectedCustomRule.id], + TA002: [expectedCustomRule.id], + 'T002.002': [expectedCustomRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedPrebuiltRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + [expectedCustomRule.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); + }); + }); + + describe('error cases', async () => { + it('throws error when request body is not valid', async () => { + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({ filter: { source: ['give me all the rules'] } }) + .expect(400); + + expect(body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + '[request body]: Invalid value "give me all the rules" supplied to "filter,source"', + }); + }); + }); + }); +}; + +function generateThreatArray(startIndex: number, count = 1): ThreatArray { + const result: ThreatArray = []; + + for (let i = 0; i < count; ++i) { + const indexName = (i + startIndex).toString().padStart(3, '0'); + + result.push({ + framework: 'MITRE ATT&CK', + tactic: { + id: `TA${indexName}`, + name: `Tactic ${indexName}`, + reference: `http://some-link-${indexName}`, + }, + technique: [ + { + id: `T${indexName}`, + name: `Technique ${indexName}`, + reference: `http://some-technique-link-${indexName}`, + subtechnique: [ + { + id: `T${indexName}.${indexName}`, + name: `Subtechnique ${indexName}`, + reference: `http://some-sub-technique-link-${indexName}`, + }, + ], + }, + ], + }); + } + + return result; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/index.ts new file mode 100644 index 0000000000000..84d79ee1dd675 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Rule management API', function () { + loadTestFile(require.resolve('./coverage_overview')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts new file mode 100644 index 0000000000000..89bb2bbea5725 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts @@ -0,0 +1,41 @@ +/* + * 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 type SuperTest from 'supertest'; + +const SIMPLE_APM_RULE_DATA = { + name: 'Test rule', + rule_type_id: 'apm.anomaly', + enabled: false, + consumer: 'alerts', + tags: [], + actions: [], + params: { + windowSize: 30, + windowUnit: 'm', + anomalySeverityType: 'critical', + environment: 'ENVIRONMENT_ALL', + }, + schedule: { + interval: '10m', + }, +}; + +/** + * Created a non security rule. Helpful in tests to verify functionality works with presence of non security rules. + * @param supertest The supertest deps + */ +export async function createNonSecurityRule( + supertest: SuperTest.SuperTest +): Promise { + await supertest + .post('/api/alerting/rule') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(SIMPLE_APM_RULE_DATA) + .expect(200); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts index 26862dc62d038..2ca8e4587139f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts @@ -24,6 +24,7 @@ export * from './get_threshold_rule_for_alert_testing'; export * from './get_rule_actions'; export * from './find_immutable_rule_by_id'; export * from './create_rule_with_exception_entries'; +export * from './create_non_security_rule'; export * from './downgrade_immutable_rule'; export * from './get_eql_rule_for_alert_testing'; export * from './get_simple_preview_rule'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts index 9970b6b13eeec..8dc64bf7fefaa 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts @@ -8,3 +8,4 @@ export * from './create_prebuilt_rule_saved_objects'; export * from './get_prebuilt_rules_and_timelines_status'; export * from './install_mock_prebuilt_rules'; export * from './install_prebuilt_rules_and_timelines'; +export * from './install_prebuilt_rules'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules.ts new file mode 100644 index 0000000000000..52e34d67a1936 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules.ts @@ -0,0 +1,62 @@ +/* + * 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 { + PERFORM_RULE_INSTALLATION_URL, + RuleVersionSpecifier, + PerformRuleInstallationResponseBody, +} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; +import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; + +/** + * Installs available prebuilt rules in Kibana. Rules are + * installed from the security-rule saved objects. + * + * - No rules will be installed if there are no security-rule assets (e.g., the + * package is not installed or mocks are not created). + * + * - Pass in an array of rule version specifiers to install specific rules. Otherwise + * all available rules will be installed. + * + * @param supertest SuperTest instance + * @param rules Array of rule version specifiers to install (optional) + * @returns Install prebuilt rules response + */ +export const installPrebuiltRules = async ( + es: Client, + supertest: SuperTest.SuperTest, + rules?: RuleVersionSpecifier[] +): Promise => { + let payload = {}; + if (rules) { + payload = { mode: 'SPECIFIC_RULES', rules }; + } else { + payload = { mode: 'ALL_RULES' }; + } + const response = await supertest + .post(PERFORM_RULE_INSTALLATION_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send(payload) + .expect(200); + + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race conditions between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + + return response.body; +}; From 1bcf6c00c0da413acdcc317529a30763248c3dab Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 7 Nov 2023 14:35:24 -0500 Subject: [PATCH 08/34] add config file --- .buildkite/ftr_configs.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 26e8d3855bf7b..595e6e0b26ca2 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -461,8 +461,4 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts - - - - - + - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts From e9f8e15fc029aea91ccd8de4cb9c46f21e126099 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 7 Nov 2023 15:04:03 -0500 Subject: [PATCH 09/34] adds serverless config file --- .buildkite/ftr_configs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index fc83be5942f29..2227b64c3755f 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -460,6 +460,7 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/ess.config.ts From e585ede6c2c24b28224d9d9b53c885341dd9c336 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 7 Nov 2023 16:20:48 -0500 Subject: [PATCH 10/34] fixes types --- .../rule_management/coverage_overview.ts | 59 +++++++++---------- .../coverage_overview/coverage_overview.cy.ts | 10 +--- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts index 1cdb66c8c84c8..185c17a93da26 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -20,7 +20,6 @@ import { getSimpleRule, installPrebuiltRulesAndTimelines, installPrebuiltRules, - createNonSecurityRule, } from '../../utils'; export default ({ getService }: FtrProviderContext): void => { @@ -33,35 +32,35 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllRules(supertest, log); }); - it('does NOT error when there are no security rules', async () => { - await createNonSecurityRule(supertest); - const rule1 = await createRule(supertest, log, { - ...getSimpleRule(), - threat: generateThreatArray(1), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({}) - .expect(200); - - expect(body).to.eql({ - coverage: { - T001: [rule1.id], - TA001: [rule1.id], - 'T001.001': [rule1.id], - }, - unmapped_rule_ids: [], - rules_data: { - [rule1.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); + // it('does NOT error when there are no security rules', async () => { + // await createNonSecurityRule(supertest); + // const rule1 = await createRule(supertest, log, { + // ...getSimpleRule(), + // threat: generateThreatArray(1), + // }); + + // const { body } = await supertest + // .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + // .set('kbn-xsrf', 'true') + // .set('elastic-api-version', '1') + // .send({}) + // .expect(200); + + // expect(body).to.eql({ + // coverage: { + // T001: [rule1.id], + // TA001: [rule1.id], + // 'T001.001': [rule1.id], + // }, + // unmapped_rule_ids: [], + // rules_data: { + // [rule1.id]: { + // activity: 'disabled', + // name: 'Simple Rule Query', + // }, + // }, + // }); + // }); describe('without filters', () => { it('returns an empty response if there are no rules', async () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 0e049272a93cf..39175985a7ded 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -20,11 +20,7 @@ import { createAndInstallMockedPrebuiltRules, preventPrebuiltRulesPackageInstallation, } from '../../../../tasks/api_calls/prebuilt_rules'; -import { - cleanKibana, - deleteAlertsAndRules, - deletePrebuiltRulesAssets, -} from '../../../../tasks/common'; +import { deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../../../tasks/common'; import { login } from '../../../../tasks/login'; import { enableAllDisabledRules, @@ -49,10 +45,6 @@ const prebuiltRules = [ ]; describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - }); - beforeEach(() => { login(); deleteAlertsAndRules(); From eeb1d23fb6c5b30216f5f7df83ae05490e9000ca Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 8 Nov 2023 11:45:24 -0500 Subject: [PATCH 11/34] adds back rule test --- .../rule_management/coverage_overview.ts | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts index 185c17a93da26..1cdb66c8c84c8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -20,6 +20,7 @@ import { getSimpleRule, installPrebuiltRulesAndTimelines, installPrebuiltRules, + createNonSecurityRule, } from '../../utils'; export default ({ getService }: FtrProviderContext): void => { @@ -32,35 +33,35 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllRules(supertest, log); }); - // it('does NOT error when there are no security rules', async () => { - // await createNonSecurityRule(supertest); - // const rule1 = await createRule(supertest, log, { - // ...getSimpleRule(), - // threat: generateThreatArray(1), - // }); - - // const { body } = await supertest - // .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - // .set('kbn-xsrf', 'true') - // .set('elastic-api-version', '1') - // .send({}) - // .expect(200); - - // expect(body).to.eql({ - // coverage: { - // T001: [rule1.id], - // TA001: [rule1.id], - // 'T001.001': [rule1.id], - // }, - // unmapped_rule_ids: [], - // rules_data: { - // [rule1.id]: { - // activity: 'disabled', - // name: 'Simple Rule Query', - // }, - // }, - // }); - // }); + it('does NOT error when there are no security rules', async () => { + await createNonSecurityRule(supertest); + const rule1 = await createRule(supertest, log, { + ...getSimpleRule(), + threat: generateThreatArray(1), + }); + + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .send({}) + .expect(200); + + expect(body).to.eql({ + coverage: { + T001: [rule1.id], + TA001: [rule1.id], + 'T001.001': [rule1.id], + }, + unmapped_rule_ids: [], + rules_data: { + [rule1.id]: { + activity: 'disabled', + name: 'Simple Rule Query', + }, + }, + }); + }); describe('without filters', () => { it('returns an empty response if there are no rules', async () => { From 7539e30b620a3c1421fa97f247656808cb426024 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 9 Nov 2023 14:52:46 -0500 Subject: [PATCH 12/34] adds scripts --- .../package.json | 9 ++++++++- .../rule_management/coverage_overview.ts | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 24696f0c00bf0..53819b74601e0 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -6,7 +6,9 @@ "license": "Elastic License 2.0", "scripts": { "initialize-server:dr:default": "node ./scripts/index.js server detections_response default_license", + "initialize-server:dr:basic": "node ./scripts/index.js server detections_response basic_license", "run-tests:dr:default": "node ./scripts/index.js runner detections_response default_license", + "run-tests:dr:basic": "node ./scripts/index.js runner detections_response basic_license", "exception_workflows:server:serverless": "npm run initialize-server:dr:default exceptions/workflows serverless", "exception_workflows:runner:serverless": "npm run run-tests:dr:default exceptions/workflows serverless serverlessEnv", "exception_workflows:qa:serverless": "npm run run-tests:dr:default exceptions/workflows serverless qaEnv", @@ -41,6 +43,11 @@ "alerts:runner:serverless": "npm run run-tests:dr:default alerts serverless serverlessEnv", "alerts:qa:serverless": "npm run run-tests:dr:default alerts serverless qaEnv", "alerts:server:ess": "npm run initialize-server:dr:default alerts ess", - "alerts:runner:ess": "npm run run-tests:dr:default alerts ess essEnv" + "alerts:runner:ess": "npm run run-tests:dr:default alerts ess essEnv", + "rule_management:server:serverless": "npm run initialize-server:dr:basic rule_management serverless", + "rule_management:runner:serverless": "npm run run-tests:dr:basic rule_management serverless serverlessEnv", + "rule_management:qa:serverless": "npm run run-tests:dr:basic rule_management serverless qaEnv", + "rule_management:server:ess": "npm run initialize-server:dr:basic rule_management ess", + "rule_management:runner:ess": "npm run run-tests:dr:basic rule_management ess essEnv" } } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts index 1cdb66c8c84c8..c4f1b7ee0e28b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -44,6 +44,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({}) .expect(200); @@ -69,6 +70,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({}) .expect(200); @@ -89,6 +91,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({}) .expect(200); @@ -115,6 +118,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({}) .expect(200); @@ -147,6 +151,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { search_term: 'TA002', @@ -184,6 +189,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { search_term: 'T002', @@ -221,6 +227,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { search_term: 'T002.002', @@ -255,6 +262,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { search_term: 'rule-2', @@ -288,6 +296,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { search_term: 'index-pattern-2', @@ -323,6 +332,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { activity: ['disabled'], @@ -360,6 +370,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { activity: ['enabled'], @@ -399,6 +410,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { activity: ['enabled', 'disabled'], @@ -449,6 +461,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { source: ['custom'], @@ -493,6 +506,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { source: ['prebuilt'], @@ -537,6 +551,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { source: ['prebuilt', 'custom'], @@ -575,6 +590,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send({ filter: { source: ['give me all the rules'] } }) .expect(400); From a0f1cc44d487daa1bf86ca35ad18686f4df4fbdc Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 15 Nov 2023 14:41:50 -0500 Subject: [PATCH 13/34] refactors some utils --- .github/CODEOWNERS | 1 + .../basic/tests/coverage_overview.ts | 624 ------------------ .../basic/tests/index.ts | 1 - .../utils/create_non_security_rule.ts | 41 -- .../rule_management/coverage_overview.ts | 391 +++++------ .../utils/rules/create_non_security_rule.ts | 2 +- .../utils/rules/get_coverage_overview.ts | 28 + 7 files changed, 187 insertions(+), 901 deletions(-) delete mode 100644 x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts delete mode 100644 x-pack/test/detection_engine_api_integration/utils/create_non_security_rule.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_coverage_overview.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4edca2f43e6a4..06232a60845ad 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1302,6 +1302,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules @elastic/security-detection-rule-management /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management @elastic/security-detection-rule-management /x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/health_truncate_text @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/links_to_docs @elastic/security-detection-rule-management diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts deleted file mode 100644 index 87e2ebe66a5a3..0000000000000 --- a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts +++ /dev/null @@ -1,624 +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 expect from '@kbn/expect'; - -import { - RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, - ThreatArray, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createPrebuiltRuleAssetSavedObjects, - createRuleAssetSavedObject, - createRule, - deleteAllRules, - getSimpleRule, - installPrebuiltRulesAndTimelines, - installPrebuiltRules, -} from '../../utils'; -import { createNonSecurityRule } from '../../utils/create_non_security_rule'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const log = getService('log'); - const es = getService('es'); - - describe('coverage_overview', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('does NOT error when there are no security rules', async () => { - await createNonSecurityRule(supertest); - const rule1 = await createRule(supertest, log, { - ...getSimpleRule(), - threat: generateThreatArray(1), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({}) - .expect(200); - - expect(body).to.eql({ - coverage: { - T001: [rule1.id], - TA001: [rule1.id], - 'T001.001': [rule1.id], - }, - unmapped_rule_ids: [], - rules_data: { - [rule1.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - describe('without filters', () => { - it('returns an empty response if there are no rules', async () => { - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({}) - .expect(200); - - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [], - rules_data: {}, - }); - }); - - it('returns response with a single rule mapped to MITRE categories', async () => { - const rule1 = await createRule(supertest, log, { - ...getSimpleRule(), - threat: generateThreatArray(1), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({}) - .expect(200); - - expect(body).to.eql({ - coverage: { - T001: [rule1.id], - TA001: [rule1.id], - 'T001.001': [rule1.id], - }, - unmapped_rule_ids: [], - rules_data: { - [rule1.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - it('returns response with an unmapped rule', async () => { - const rule1 = await createRule(supertest, log, { ...getSimpleRule(), threat: undefined }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({}) - .expect(200); - - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [rule1.id], - rules_data: { - [rule1.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - }); - - describe('with filters', () => { - describe('search_term', () => { - it('returns response filtered by tactic', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - search_term: 'TA002', - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - it('returns response filtered by technique', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - search_term: 'T002', - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - it('returns response filtered by subtechnique', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - search_term: 'T002.002', - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - it('returns response filtered by rule name', async () => { - await createRule(supertest, log, getSimpleRule('rule-1')); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - name: 'rule-2', - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - search_term: 'rule-2', - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [expectedRule.id], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'rule-2', - }, - }, - }); - }); - - it('returns response filtered by index pattern', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - index: ['index-pattern-1'], - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - index: ['index-pattern-2'], - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - search_term: 'index-pattern-2', - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [expectedRule.id], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - }); - - describe('activity', () => { - it('returns response filtered by disabled rules', async () => { - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - await createRule(supertest, log, { - ...getSimpleRule('rule-2', true), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - activity: ['disabled'], - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T001: [expectedRule.id], - TA001: [expectedRule.id], - 'T001.001': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - it('returns response filtered by enabled rules', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2', true), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - activity: ['enabled'], - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'enabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - it('returns all rules if both enabled and disabled filters are specified in the request', async () => { - const expectedRule1 = await createRule(supertest, log, { - ...getSimpleRule('rule-1', false), - name: 'Disabled rule', - threat: generateThreatArray(1), - }); - const expectedRule2 = await createRule(supertest, log, { - ...getSimpleRule('rule-2', true), - name: 'Enabled rule', - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - activity: ['enabled', 'disabled'], - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T001: [expectedRule1.id], - TA001: [expectedRule1.id], - 'T001.001': [expectedRule1.id], - T002: [expectedRule2.id], - TA002: [expectedRule2.id], - 'T002.002': [expectedRule2.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule1.id]: { - activity: 'disabled', - name: 'Disabled rule', - }, - [expectedRule2.id]: { - activity: 'enabled', - name: 'Enabled rule', - }, - }, - }); - }); - }); - - describe('source', () => { - it('returns response filtered by custom rules', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'prebuilt-rule-1', - threat: generateThreatArray(1), - }), - ]); - await installPrebuiltRulesAndTimelines(es, supertest); - - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - source: ['custom'], - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - - it('returns response filtered by prebuilt rules', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'prebuilt-rule-1', - threat: generateThreatArray(1), - }), - ]); - const { - results: { created }, - } = await installPrebuiltRules(es, supertest); - const expectedRule = created[0]; - - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - source: ['prebuilt'], - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T001: [expectedRule.id], - TA001: [expectedRule.id], - 'T001.001': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Query with a rule id', - }, - }, - }); - }); - - it('returns all rules if both custom and prebuilt filters are specified in the request', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'prebuilt-rule-1', - threat: generateThreatArray(1), - }), - ]); - const { - results: { created }, - } = await installPrebuiltRules(es, supertest); - const expectedPrebuiltRule = created[0]; - - const expectedCustomRule = await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ - filter: { - source: ['prebuilt', 'custom'], - }, - }) - .expect(200); - - expect(body).to.eql({ - coverage: { - T001: [expectedPrebuiltRule.id], - TA001: [expectedPrebuiltRule.id], - 'T001.001': [expectedPrebuiltRule.id], - T002: [expectedCustomRule.id], - TA002: [expectedCustomRule.id], - 'T002.002': [expectedCustomRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedPrebuiltRule.id]: { - activity: 'disabled', - name: 'Query with a rule id', - }, - [expectedCustomRule.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); - }); - }); - - describe('error cases', async () => { - it('throws error when request body is not valid', async () => { - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .send({ filter: { source: ['give me all the rules'] } }) - .expect(400); - - expect(body).to.eql({ - statusCode: 400, - error: 'Bad Request', - message: - '[request body]: Invalid value "give me all the rules" supplied to "filter,source"', - }); - }); - }); - }); -}; - -function generateThreatArray(startIndex: number, count = 1): ThreatArray { - const result: ThreatArray = []; - - for (let i = 0; i < count; ++i) { - const indexName = (i + startIndex).toString().padStart(3, '0'); - - result.push({ - framework: 'MITRE ATT&CK', - tactic: { - id: `TA${indexName}`, - name: `Tactic ${indexName}`, - reference: `http://some-link-${indexName}`, - }, - technique: [ - { - id: `T${indexName}`, - name: `Technique ${indexName}`, - reference: `http://some-technique-link-${indexName}`, - subtechnique: [ - { - id: `T${indexName}.${indexName}`, - name: `Subtechnique ${indexName}`, - reference: `http://some-sub-technique-link-${indexName}`, - }, - ], - }, - ], - }); - } - - return result; -} diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts index 315a06043684f..ffcffb706bcec 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts @@ -25,6 +25,5 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./query_signals')); loadTestFile(require.resolve('./open_close_signals')); loadTestFile(require.resolve('./import_timelines')); - loadTestFile(require.resolve('./coverage_overview')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_non_security_rule.ts b/x-pack/test/detection_engine_api_integration/utils/create_non_security_rule.ts deleted file mode 100644 index 89bb2bbea5725..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_non_security_rule.ts +++ /dev/null @@ -1,41 +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 type SuperTest from 'supertest'; - -const SIMPLE_APM_RULE_DATA = { - name: 'Test rule', - rule_type_id: 'apm.anomaly', - enabled: false, - consumer: 'alerts', - tags: [], - actions: [], - params: { - windowSize: 30, - windowUnit: 'm', - anomalySeverityType: 'critical', - environment: 'ENVIRONMENT_ALL', - }, - schedule: { - interval: '10m', - }, -}; - -/** - * Created a non security rule. Helpful in tests to verify functionality works with presence of non security rules. - * @param supertest The supertest deps - */ -export async function createNonSecurityRule( - supertest: SuperTest.SuperTest -): Promise { - await supertest - .post('/api/alerting/rule') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(SIMPLE_APM_RULE_DATA) - .expect(200); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts index c4f1b7ee0e28b..4b6efaf5ad487 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -8,6 +8,8 @@ import expect from '@kbn/expect'; import { + CoverageOverviewRuleActivity, + CoverageOverviewRuleSource, RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, ThreatArray, } from '@kbn/security-solution-plugin/common/api/detection_engine'; @@ -17,11 +19,12 @@ import { createRuleAssetSavedObject, createRule, deleteAllRules, - getSimpleRule, installPrebuiltRulesAndTimelines, installPrebuiltRules, createNonSecurityRule, + getCustomQueryRuleParams, } from '../../utils'; +import { getCoverageOverview } from '../../utils/rules/get_coverage_overview'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); @@ -35,18 +38,13 @@ export default ({ getService }: FtrProviderContext): void => { it('does NOT error when there are no security rules', async () => { await createNonSecurityRule(supertest); - const rule1 = await createRule(supertest, log, { - ...getSimpleRule(), - threat: generateThreatArray(1), - }); + const rule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ threat: generateThreatArray(1) }) + ); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({}) - .expect(200); + const body = getCoverageOverview(supertest); expect(body).to.eql({ coverage: { @@ -66,13 +64,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('without filters', () => { it('returns an empty response if there are no rules', async () => { - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({}) - .expect(200); + const body = getCoverageOverview(supertest); expect(body).to.eql({ coverage: {}, @@ -82,18 +74,13 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns response with a single rule mapped to MITRE categories', async () => { - const rule1 = await createRule(supertest, log, { - ...getSimpleRule(), - threat: generateThreatArray(1), - }); + const rule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ threat: generateThreatArray(1) }) + ); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({}) - .expect(200); + const body = getCoverageOverview(supertest); expect(body).to.eql({ coverage: { @@ -112,15 +99,13 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns response with an unmapped rule', async () => { - const rule1 = await createRule(supertest, log, { ...getSimpleRule(), threat: undefined }); + const rule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ threat: undefined }) + ); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({}) - .expect(200); + const body = getCoverageOverview(supertest); expect(body).to.eql({ coverage: {}, @@ -138,27 +123,21 @@ export default ({ getService }: FtrProviderContext): void => { describe('with filters', () => { describe('search_term', () => { it('returns response filtered by tactic', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - threat: generateThreatArray(2), + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = getCoverageOverview(supertest, { + search_term: 'TA002', }); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - search_term: 'TA002', - }, - }) - .expect(200); - expect(body).to.eql({ coverage: { T002: [expectedRule.id], @@ -176,27 +155,21 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns response filtered by technique', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - threat: generateThreatArray(2), + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = getCoverageOverview(supertest, { + search_term: 'T002', }); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - search_term: 'T002', - }, - }) - .expect(200); - expect(body).to.eql({ coverage: { T002: [expectedRule.id], @@ -214,27 +187,21 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns response filtered by subtechnique', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), - }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - threat: generateThreatArray(2), + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = getCoverageOverview(supertest, { + search_term: 'T002.002', }); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - search_term: 'T002.002', - }, - }) - .expect(200); - expect(body).to.eql({ coverage: { T002: [expectedRule.id], @@ -252,24 +219,17 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns response filtered by rule name', async () => { - await createRule(supertest, log, getSimpleRule('rule-1')); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - name: 'rule-2', + await createRule(supertest, log, getCustomQueryRuleParams({ rule_id: 'rule-1' })); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', name: 'rule-2' }) + ); + + const body = getCoverageOverview(supertest, { + search_term: 'rule-2', }); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - search_term: 'rule-2', - }, - }) - .expect(200); - expect(body).to.eql({ coverage: {}, unmapped_rule_ids: [expectedRule.id], @@ -283,26 +243,20 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns response filtered by index pattern', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - index: ['index-pattern-1'], + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', index: ['index-pattern-1'] }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', index: ['index-pattern-2'] }) + ); + + const body = getCoverageOverview(supertest, { + search_term: 'index-pattern-2', }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2'), - index: ['index-pattern-2'], - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - search_term: 'index-pattern-2', - }, - }) - .expect(200); expect(body).to.eql({ coverage: {}, @@ -319,26 +273,20 @@ export default ({ getService }: FtrProviderContext): void => { describe('activity', () => { it('returns response filtered by disabled rules', async () => { - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = getCoverageOverview(supertest, { + activity: [CoverageOverviewRuleActivity.Disabled], }); - await createRule(supertest, log, { - ...getSimpleRule('rule-2', true), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - activity: ['disabled'], - }, - }) - .expect(200); expect(body).to.eql({ coverage: { @@ -357,26 +305,20 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns response filtered by enabled rules', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(1), + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = getCoverageOverview(supertest, { + activity: [CoverageOverviewRuleActivity.Enabled], }); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-2', true), - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - activity: ['enabled'], - }, - }) - .expect(200); expect(body).to.eql({ coverage: { @@ -395,28 +337,30 @@ export default ({ getService }: FtrProviderContext): void => { }); it('returns all rules if both enabled and disabled filters are specified in the request', async () => { - const expectedRule1 = await createRule(supertest, log, { - ...getSimpleRule('rule-1', false), - name: 'Disabled rule', - threat: generateThreatArray(1), - }); - const expectedRule2 = await createRule(supertest, log, { - ...getSimpleRule('rule-2', true), - name: 'Enabled rule', - threat: generateThreatArray(2), - }); - - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - activity: ['enabled', 'disabled'], - }, + const expectedRule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-1', + enabled: false, + name: 'Disabled rule', + threat: generateThreatArray(1), + }) + ); + const expectedRule2 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, + name: 'Enabled rule', + threat: generateThreatArray(2), }) - .expect(200); + ); + + const body = getCoverageOverview(supertest, { + activity: [CoverageOverviewRuleActivity.Enabled, CoverageOverviewRuleActivity.Disabled], + }); expect(body).to.eql({ coverage: { @@ -452,22 +396,15 @@ export default ({ getService }: FtrProviderContext): void => { ]); await installPrebuiltRulesAndTimelines(es, supertest); - const expectedRule = await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(2), - }); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - source: ['custom'], - }, - }) - .expect(200); + const body = getCoverageOverview(supertest, { + source: [CoverageOverviewRuleSource.Custom], + }); expect(body).to.eql({ coverage: { @@ -497,22 +434,15 @@ export default ({ getService }: FtrProviderContext): void => { } = await installPrebuiltRules(es, supertest); const expectedRule = created[0]; - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(2), - }); + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - source: ['prebuilt'], - }, - }) - .expect(200); + const body = getCoverageOverview(supertest, { + source: [CoverageOverviewRuleSource.Prebuilt], + }); expect(body).to.eql({ coverage: { @@ -542,22 +472,15 @@ export default ({ getService }: FtrProviderContext): void => { } = await installPrebuiltRules(es, supertest); const expectedPrebuiltRule = created[0]; - const expectedCustomRule = await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - threat: generateThreatArray(2), - }); + const expectedCustomRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ - filter: { - source: ['prebuilt', 'custom'], - }, - }) - .expect(200); + const body = getCoverageOverview(supertest, { + source: [CoverageOverviewRuleSource.Prebuilt, CoverageOverviewRuleSource.Custom], + }); expect(body).to.eql({ coverage: { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts index 89bb2bbea5725..9b5056848adca 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts @@ -26,7 +26,7 @@ const SIMPLE_APM_RULE_DATA = { }; /** - * Created a non security rule. Helpful in tests to verify functionality works with presence of non security rules. + * Creates an APM rule, which is not a security rule. Helpful in tests to verify functionality works with presence of non-security rules. * @param supertest The supertest deps */ export async function createNonSecurityRule( diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_coverage_overview.ts new file mode 100644 index 0000000000000..f93a29b0ec149 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_coverage_overview.ts @@ -0,0 +1,28 @@ +/* + * 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 type SuperTest from 'supertest'; + +import { + CoverageOverviewFilter, + CoverageOverviewResponse, + RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; + +export const getCoverageOverview = async ( + supertest: SuperTest.SuperTest, + filter?: CoverageOverviewFilter +): Promise => { + const response = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') + .send({ filter }) + .expect(200); + + return response.body; +}; From 51f4bbdc0ed391561b6e647363b956161372a074 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 20 Nov 2023 11:59:01 -0600 Subject: [PATCH 14/34] addresses cypress comments --- .../coverage_overview/coverage_overview.cy.ts | 142 +++++++++--------- 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 39175985a7ded..4eb08e90236cd 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -50,7 +50,6 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { deleteAlertsAndRules(); deletePrebuiltRulesAssets(); preventPrebuiltRulesPackageInstallation(); - visit(COVERAGE_OVERVIEW_URL); createAndInstallMockedPrebuiltRules(prebuiltRules); createRule( getNewRule({ rule_id: 'enabled_custom_rule', enabled: true, name: 'Enabled custom rule' }) @@ -58,10 +57,10 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { createRule( getNewRule({ rule_id: 'disabled_custom_rule', name: 'Disabled custom rule', enabled: false }) ); - cy.reload(); + visit(COVERAGE_OVERVIEW_URL); }); - it('technique panel renders correct data on page load', () => { + it('technique panel renders custom and prebuilt rule data on page load', () => { openTechniquePanel(getMockThreatData().technique.name); cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); @@ -74,76 +73,85 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); }); - it('filtering displays correct data', () => { - // all data filtered in - selectCoverageOverviewActivityFilterOption('Disabled rules'); - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + describe('filtering tests', () => { + it('filters for all data', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); - // only filtering for disabled and prebuilt rules - selectCoverageOverviewActivityFilterOption('Enabled rules'); - selectCoverageOverviewSourceFilterOption('Custom rules'); - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + }); - // only filtering for prebuilt rules - selectCoverageOverviewActivityFilterOption('Enabled rules'); - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + it('filters for disabled and prebuilt rules', () => { + selectCoverageOverviewActivityFilterOption('Enabled rules'); // Disables default filter + selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter - // only filtering for custom rules - selectCoverageOverviewSourceFilterOption('Custom rules'); - selectCoverageOverviewSourceFilterOption('Elastic rules'); // Turn off filter + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + }); - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + it('filters for only prebuilt rules', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter - // filtering for search term - selectCoverageOverviewSourceFilterOption('Elastic rules'); - filterCoverageOverviewBySearchBar('Enabled custom rule'); + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + }); - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); + it('filters for only custom rules', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewSourceFilterOption('Elastic rules'); // Disables default filter + + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + }); + + it('filters for search term', () => { + filterCoverageOverviewBySearchBar('Enabled custom rule'); // Disables default filter + + openTechniquePanel(getMockThreatData().technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); + }); }); it('enables all disabled rules', () => { From d8b2620dd962609aeca1f35342fdb706dc4e4b75 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 21 Nov 2023 10:11:08 -0600 Subject: [PATCH 15/34] switches package.json for flaky test runner --- x-pack/test/security_solution_cypress/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json index e43f32a447575..c86e945968b73 100644 --- a/x-pack/test/security_solution_cypress/package.json +++ b/x-pack/test/security_solution_cypress/package.json @@ -7,7 +7,7 @@ "scripts": { "cypress": "NODE_OPTIONS=--openssl-legacy-provider ../../../node_modules/.bin/cypress", "cypress:open:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel open --spec './cypress/e2e/**/*.cy.ts' --config-file ../../test/security_solution_cypress/cypress/cypress.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", - "cypress:run:ess": "yarn cypress:ess --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", + "cypress:run:ess": "yarn cypress:ess --spec './cypress/e2e/detection_response/rule_management/**/*.cy.ts'", "cypress:run:cases:ess": "yarn cypress:ess --spec './cypress/e2e/explore/cases/*.cy.ts'", "cypress:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel run --config-file ../../test/security_solution_cypress/cypress/cypress_ci.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", "cypress:run:respops:ess": "yarn cypress:ess --spec './cypress/e2e/(detection_response|exceptions)/**/*.cy.ts'", @@ -21,7 +21,7 @@ "cypress:cloud:serverless": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider NODE_TLS_REJECT_UNAUTHORIZED=0 ../../../node_modules/.bin/cypress", "cypress:open:cloud:serverless": "yarn cypress:cloud:serverless open --config-file ./cypress/cypress_serverless.config.ts --env CLOUD_SERVERLESS=true", "cypress:open:serverless": "yarn cypress:serverless open --config-file ../../test/security_solution_cypress/cypress/cypress_serverless.config.ts --spec './cypress/e2e/**/*.cy.ts'", - "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", + "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/detection_response/rule_management/**/*.cy.ts'", "cypress:run:cloud:serverless": "yarn cypress:cloud:serverless run --config-file ./cypress/cypress_ci_serverless.config.ts --env CLOUD_SERVERLESS=true", "cypress:investigations:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/explore/**/*.cy.ts'", From b48dc430ca7222c22700cec72b8f72570550e961 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 21 Nov 2023 10:56:11 -0600 Subject: [PATCH 16/34] resets package.json --- .../rule_management/coverage_overview.ts | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts index 4b6efaf5ad487..491e0273f2b90 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -21,7 +21,7 @@ import { deleteAllRules, installPrebuiltRulesAndTimelines, installPrebuiltRules, - createNonSecurityRule, + // createNonSecurityRule, getCustomQueryRuleParams, } from '../../utils'; import { getCoverageOverview } from '../../utils/rules/get_coverage_overview'; @@ -36,31 +36,31 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllRules(supertest, log); }); - it('does NOT error when there are no security rules', async () => { - await createNonSecurityRule(supertest); - const rule1 = await createRule( - supertest, - log, - getCustomQueryRuleParams({ threat: generateThreatArray(1) }) - ); - - const body = getCoverageOverview(supertest); - - expect(body).to.eql({ - coverage: { - T001: [rule1.id], - TA001: [rule1.id], - 'T001.001': [rule1.id], - }, - unmapped_rule_ids: [], - rules_data: { - [rule1.id]: { - activity: 'disabled', - name: 'Simple Rule Query', - }, - }, - }); - }); + // it('does NOT error when there are no security rules', async () => { + // await createNonSecurityRule(supertest); + // const rule1 = await createRule( + // supertest, + // log, + // getCustomQueryRuleParams({ threat: generateThreatArray(1) }) + // ); + + // const body = getCoverageOverview(supertest); + + // expect(body).to.eql({ + // coverage: { + // T001: [rule1.id], + // TA001: [rule1.id], + // 'T001.001': [rule1.id], + // }, + // unmapped_rule_ids: [], + // rules_data: { + // [rule1.id]: { + // activity: 'disabled', + // name: 'Simple Rule Query', + // }, + // }, + // }); + // }); describe('without filters', () => { it('returns an empty response if there are no rules', async () => { From 72aca4dfad55b4f8b1f6862b453d22dba8388f35 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 21 Nov 2023 10:56:20 -0600 Subject: [PATCH 17/34] resets package.json --- x-pack/test/security_solution_cypress/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json index c86e945968b73..e43f32a447575 100644 --- a/x-pack/test/security_solution_cypress/package.json +++ b/x-pack/test/security_solution_cypress/package.json @@ -7,7 +7,7 @@ "scripts": { "cypress": "NODE_OPTIONS=--openssl-legacy-provider ../../../node_modules/.bin/cypress", "cypress:open:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel open --spec './cypress/e2e/**/*.cy.ts' --config-file ../../test/security_solution_cypress/cypress/cypress.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", - "cypress:run:ess": "yarn cypress:ess --spec './cypress/e2e/detection_response/rule_management/**/*.cy.ts'", + "cypress:run:ess": "yarn cypress:ess --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", "cypress:run:cases:ess": "yarn cypress:ess --spec './cypress/e2e/explore/cases/*.cy.ts'", "cypress:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel run --config-file ../../test/security_solution_cypress/cypress/cypress_ci.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", "cypress:run:respops:ess": "yarn cypress:ess --spec './cypress/e2e/(detection_response|exceptions)/**/*.cy.ts'", @@ -21,7 +21,7 @@ "cypress:cloud:serverless": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider NODE_TLS_REJECT_UNAUTHORIZED=0 ../../../node_modules/.bin/cypress", "cypress:open:cloud:serverless": "yarn cypress:cloud:serverless open --config-file ./cypress/cypress_serverless.config.ts --env CLOUD_SERVERLESS=true", "cypress:open:serverless": "yarn cypress:serverless open --config-file ../../test/security_solution_cypress/cypress/cypress_serverless.config.ts --spec './cypress/e2e/**/*.cy.ts'", - "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/detection_response/rule_management/**/*.cy.ts'", + "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", "cypress:run:cloud:serverless": "yarn cypress:cloud:serverless run --config-file ./cypress/cypress_ci_serverless.config.ts --env CLOUD_SERVERLESS=true", "cypress:investigations:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/explore/**/*.cy.ts'", From a0343bd53b34ffa1ee40fa7ed6eeaa1d0af8ad97 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 21 Nov 2023 13:50:04 -0600 Subject: [PATCH 18/34] removes non-security test --- .../basic_license/rule_management/coverage_overview.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts index 491e0273f2b90..7126e8e24f276 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -21,7 +21,6 @@ import { deleteAllRules, installPrebuiltRulesAndTimelines, installPrebuiltRules, - // createNonSecurityRule, getCustomQueryRuleParams, } from '../../utils'; import { getCoverageOverview } from '../../utils/rules/get_coverage_overview'; From aea805b24913821f9d0bd98928511d94ee72550b Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 21 Nov 2023 14:59:02 -0600 Subject: [PATCH 19/34] fixes ftr tests --- .../rule_management/coverage_overview.ts | 65 +++++++++++-------- .../utils/rules/create_non_security_rule.ts | 4 +- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts index 7126e8e24f276..1f57eb8690247 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts @@ -22,6 +22,7 @@ import { installPrebuiltRulesAndTimelines, installPrebuiltRules, getCustomQueryRuleParams, + // createNonSecurityRule, } from '../../utils'; import { getCoverageOverview } from '../../utils/rules/get_coverage_overview'; @@ -43,7 +44,7 @@ export default ({ getService }: FtrProviderContext): void => { // getCustomQueryRuleParams({ threat: generateThreatArray(1) }) // ); - // const body = getCoverageOverview(supertest); + // const body = await getCoverageOverview(supertest); // expect(body).to.eql({ // coverage: { @@ -55,7 +56,7 @@ export default ({ getService }: FtrProviderContext): void => { // rules_data: { // [rule1.id]: { // activity: 'disabled', - // name: 'Simple Rule Query', + // name: 'Custom query rule', // }, // }, // }); @@ -63,7 +64,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('without filters', () => { it('returns an empty response if there are no rules', async () => { - const body = getCoverageOverview(supertest); + const body = await getCoverageOverview(supertest); expect(body).to.eql({ coverage: {}, @@ -79,7 +80,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ threat: generateThreatArray(1) }) ); - const body = getCoverageOverview(supertest); + const body = await getCoverageOverview(supertest); expect(body).to.eql({ coverage: { @@ -91,7 +92,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [rule1.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -104,7 +105,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ threat: undefined }) ); - const body = getCoverageOverview(supertest); + const body = await getCoverageOverview(supertest); expect(body).to.eql({ coverage: {}, @@ -112,7 +113,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [rule1.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -133,7 +134,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { search_term: 'TA002', }); @@ -147,7 +148,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [expectedRule.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -165,7 +166,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { search_term: 'T002', }); @@ -179,7 +180,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [expectedRule.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -197,7 +198,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { search_term: 'T002.002', }); @@ -211,7 +212,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [expectedRule.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -225,7 +226,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-2', name: 'rule-2' }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { search_term: 'rule-2', }); @@ -253,7 +254,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-2', index: ['index-pattern-2'] }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { search_term: 'index-pattern-2', }); @@ -263,7 +264,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [expectedRule.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -280,10 +281,14 @@ export default ({ getService }: FtrProviderContext): void => { await createRule( supertest, log, - getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, + threat: generateThreatArray(2), + }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { activity: [CoverageOverviewRuleActivity.Disabled], }); @@ -297,7 +302,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [expectedRule.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -312,10 +317,14 @@ export default ({ getService }: FtrProviderContext): void => { const expectedRule = await createRule( supertest, log, - getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, + threat: generateThreatArray(2), + }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { activity: [CoverageOverviewRuleActivity.Enabled], }); @@ -329,7 +338,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [expectedRule.id]: { activity: 'enabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -357,7 +366,7 @@ export default ({ getService }: FtrProviderContext): void => { }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { activity: [CoverageOverviewRuleActivity.Enabled, CoverageOverviewRuleActivity.Disabled], }); @@ -401,7 +410,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { source: [CoverageOverviewRuleSource.Custom], }); @@ -415,7 +424,7 @@ export default ({ getService }: FtrProviderContext): void => { rules_data: { [expectedRule.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); @@ -439,7 +448,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { source: [CoverageOverviewRuleSource.Prebuilt], }); @@ -477,7 +486,7 @@ export default ({ getService }: FtrProviderContext): void => { getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) ); - const body = getCoverageOverview(supertest, { + const body = await getCoverageOverview(supertest, { source: [CoverageOverviewRuleSource.Prebuilt, CoverageOverviewRuleSource.Custom], }); @@ -498,7 +507,7 @@ export default ({ getService }: FtrProviderContext): void => { }, [expectedCustomRule.id]: { activity: 'disabled', - name: 'Simple Rule Query', + name: 'Custom query rule', }, }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts index 9b5056848adca..4470bd7744f32 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts @@ -34,8 +34,8 @@ export async function createNonSecurityRule( ): Promise { await supertest .post('/api/alerting/rule') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') .send(SIMPLE_APM_RULE_DATA) .expect(200); } From e2dc4b378c8184dc5bfac79d5a4cb1541d0a4400 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 29 Nov 2023 00:30:36 -0700 Subject: [PATCH 20/34] fixes rest of merge conflicts --- .buildkite/ftr_configs.yml | 6 ++---- .github/CODEOWNERS | 2 +- x-pack/test/security_solution_api_integration/package.json | 7 +++---- .../rule_management/configs/ess.config.ts | 0 .../rule_management/configs/serverless.config.ts | 0 .../rule_management/coverage_overview.ts | 0 .../rule_management/index.ts | 0 7 files changed, 6 insertions(+), 9 deletions(-) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{basic_license => basic_essentials_license}/rule_management/configs/ess.config.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{basic_license => basic_essentials_license}/rule_management/configs/serverless.config.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{basic_license => basic_essentials_license}/rule_management/coverage_overview.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/{basic_license => basic_essentials_license}/rule_management/index.ts (100%) diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 20f47191687bd..5d8f3010519ab 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -471,8 +471,6 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts - - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts - - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts @@ -493,9 +491,9 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/ess.config.ts - - diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ae5e86f7eb254..40cbee4df9772 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1326,7 +1326,7 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules @elastic/security-detection-rule-management /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management @elastic/security-detection-rule-management /x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules @elastic/security-detection-rule-management -/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/health_truncate_text @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/links_to_docs @elastic/security-detection-rule-management diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 4fabc9c956714..30e361d68f495 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -10,7 +10,6 @@ "initialize-server:dr:basicEssentials": "node ./scripts/index.js server detections_response basic_essentials_license", "run-tests:dr:basicEssentials": "node ./scripts/index.js runner detections_response basic_essentials_license", "initialize-server:ea:default": "node ./scripts/index.js server entity_analytics default_license", - "run-tests:dr:basic": "node ./scripts/index.js runner detections_response basic_license", "run-tests:ea:default": "node ./scripts/index.js runner entity_analytics default_license", "initialize-server:lists:default": "node ./scripts/index.js server lists_and_exception_lists default_license", "run-tests:lists:default": "node ./scripts/index.js runner lists_and_exception_lists default_license", @@ -64,9 +63,9 @@ "alerts:qa:serverless": "npm run run-tests:dr:default alerts serverless qaEnv", "alerts:server:ess": "npm run initialize-server:dr:default alerts ess", "alerts:runner:ess": "npm run run-tests:dr:default alerts ess essEnv", - "rule_management:server:serverless": "npm run initialize-server:dr:basic rule_management serverless", - "rule_management:runner:serverless": "npm run run-tests:dr:basic rule_management serverless serverlessEnv", - "rule_management:qa:serverless": "npm run run-tests:dr:basic rule_management serverless qaEnv", + "rule_management:server:serverless": "npm run initialize-server:dr:basicEssentials rule_management serverless", + "rule_management:runner:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless serverlessEnv", + "rule_management:qa:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless qaEnv", "rule_management:server:ess": "npm run initialize-server:dr:basic rule_management ess", "rule_management:runner:ess": "npm run run-tests:dr:basic rule_management ess essEnv", "entity_analytics:server:serverless": "npm run initialize-server:ea:default risk_engine serverless", diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/ess.config.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/ess.config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/ess.config.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/serverless.config.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/configs/serverless.config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/serverless.config.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/coverage_overview.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/index.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_license/rule_management/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/index.ts From e66c6472f8e61320df76b98cb8cb1288f77f442c Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 29 Nov 2023 08:51:08 -0700 Subject: [PATCH 21/34] fix import path updates --- .../coverage_overview/coverage_overview.cy.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 4eb08e90236cd..923b08a1756fb 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -20,7 +20,10 @@ import { createAndInstallMockedPrebuiltRules, preventPrebuiltRulesPackageInstallation, } from '../../../../tasks/api_calls/prebuilt_rules'; -import { deleteAlertsAndRules, deletePrebuiltRulesAssets } from '../../../../tasks/common'; +import { + deleteAlertsAndRules, + deletePrebuiltRulesAssets, +} from '../../../../tasks/api_calls/common'; import { login } from '../../../../tasks/login'; import { enableAllDisabledRules, From a37bbc381df110f42005d2d0ad8abc23086cf148 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 1 Dec 2023 13:18:06 -0700 Subject: [PATCH 22/34] fixes non-security rule test --- .../rule_management/coverage_overview.ts | 67 ++++++++++++------- .../utils/rules/create_non_security_rule.ts | 41 ------------ 2 files changed, 41 insertions(+), 67 deletions(-) delete mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts index 1f57eb8690247..6c15f2bc42465 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts @@ -22,7 +22,6 @@ import { installPrebuiltRulesAndTimelines, installPrebuiltRules, getCustomQueryRuleParams, - // createNonSecurityRule, } from '../../utils'; import { getCoverageOverview } from '../../utils/rules/get_coverage_overview'; @@ -30,37 +29,53 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const alertingApi = getService('alertingApi'); describe('@serverless @ess coverage_overview', () => { beforeEach(async () => { await deleteAllRules(supertest, log); }); - // it('does NOT error when there are no security rules', async () => { - // await createNonSecurityRule(supertest); - // const rule1 = await createRule( - // supertest, - // log, - // getCustomQueryRuleParams({ threat: generateThreatArray(1) }) - // ); - - // const body = await getCoverageOverview(supertest); - - // expect(body).to.eql({ - // coverage: { - // T001: [rule1.id], - // TA001: [rule1.id], - // 'T001.001': [rule1.id], - // }, - // unmapped_rule_ids: [], - // rules_data: { - // [rule1.id]: { - // activity: 'disabled', - // name: 'Custom query rule', - // }, - // }, - // }); - // }); + it('does NOT error when there are no security rules', async () => { + // Creates a non-security type rule + await alertingApi.createRule({ + consumer: 'security', + name: 'Threshold rule', + ruleTypeId: 'observability.rules.custom_threshold', + params: { + criteria: [], + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: 'data-view-id', + }, + }, + }); + const rule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ threat: generateThreatArray(1) }) + ); + + const body = await getCoverageOverview(supertest); + + expect(body).to.eql({ + coverage: { + T001: [rule1.id], + TA001: [rule1.id], + 'T001.001': [rule1.id], + }, + unmapped_rule_ids: [], + rules_data: { + [rule1.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, + }, + }); + }); describe('without filters', () => { it('returns an empty response if there are no rules', async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts deleted file mode 100644 index 4470bd7744f32..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts +++ /dev/null @@ -1,41 +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 type SuperTest from 'supertest'; - -const SIMPLE_APM_RULE_DATA = { - name: 'Test rule', - rule_type_id: 'apm.anomaly', - enabled: false, - consumer: 'alerts', - tags: [], - actions: [], - params: { - windowSize: 30, - windowUnit: 'm', - anomalySeverityType: 'critical', - environment: 'ENVIRONMENT_ALL', - }, - schedule: { - interval: '10m', - }, -}; - -/** - * Creates an APM rule, which is not a security rule. Helpful in tests to verify functionality works with presence of non-security rules. - * @param supertest The supertest deps - */ -export async function createNonSecurityRule( - supertest: SuperTest.SuperTest -): Promise { - await supertest - .post('/api/alerting/rule') - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send(SIMPLE_APM_RULE_DATA) - .expect(200); -} From a06fb5e87fe4b3bba92e47b54815d2ef74c08489 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 1 Dec 2023 13:35:09 -0700 Subject: [PATCH 23/34] fixes import bug --- .../test_suites/detections_response/utils/rules/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts index f096c4ff44235..04c4cb778ef4a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts @@ -18,7 +18,6 @@ export * from './get_threshold_rule_for_alert_testing'; export * from './get_rule_actions'; export * from './find_immutable_rule_by_id'; export * from './create_rule_with_exception_entries'; -export * from './create_non_security_rule'; export * from './downgrade_immutable_rule'; export * from './get_eql_rule_for_alert_testing'; export * from './get_simple_preview_rule'; From 217217032e1c9c67f93102d9dc1aa5290aad3dfc Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 1 Dec 2023 14:52:21 -0700 Subject: [PATCH 24/34] add alerting api service to ess config --- .../config/ess/config.base.ts | 6 +++++- x-pack/test/security_solution_api_integration/package.json | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts index b4fbdba6de4c4..9431213395a2b 100644 --- a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts @@ -7,6 +7,7 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrConfigProviderContext, kbnTestConfig, kibanaTestUser } from '@kbn/test'; +import { AlertingApiProvider } from '../../../../test_serverless/api_integration/services/alerting_api'; import { services } from '../../../api_integration/services'; interface CreateTestConfigOptions { @@ -49,7 +50,10 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s return { testFiles, servers, - services, + services: { + ...services, + alertingApi: AlertingApiProvider, + }, junit: { reportName: 'X-Pack Detection Engine API Integration Tests', }, diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 30e361d68f495..8f4f7db22f0b0 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -66,8 +66,8 @@ "rule_management:server:serverless": "npm run initialize-server:dr:basicEssentials rule_management serverless", "rule_management:runner:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless serverlessEnv", "rule_management:qa:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless qaEnv", - "rule_management:server:ess": "npm run initialize-server:dr:basic rule_management ess", - "rule_management:runner:ess": "npm run run-tests:dr:basic rule_management ess essEnv", + "rule_management:server:ess": "npm run initialize-server:dr:basicEssentials rule_management ess", + "rule_management:runner:ess": "npm run run-tests:dr:basicEssentials rule_management ess essEnv", "entity_analytics:server:serverless": "npm run initialize-server:ea:default risk_engine serverless", "entity_analytics:runner:serverless": "npm run run-tests:ea:default risk_engine serverless serverlessEnv", "entity_analytics:qa:serverless": "npm run run-tests:ea:default risk_engine serverless qaEnv", From 6d471f691907b0b88fbf70bf8cfe62cdcc39db3d Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 6 Dec 2023 00:05:44 -0500 Subject: [PATCH 25/34] addresses comments --- .../coverage_overview_route.test.ts | 121 ++++++++++++++++++ .../configs/serverless.config.ts | 2 +- .../rule_management/coverage_overview.ts | 94 +++++++++++++- .../coverage_overview/coverage_overview.cy.ts | 9 +- .../cypress/screens/alerts.ts | 22 ---- .../screens/rules_coverage_overview.ts | 28 ++++ .../cypress/tasks/alerts.ts | 36 ------ .../cypress/tasks/rules_coverage_overview.ts | 46 +++++++ .../cypress/urls/rules_management.ts | 2 +- 9 files changed, 294 insertions(+), 66 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.test.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.test.ts new file mode 100644 index 0000000000000..dd8b2501b90a7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.test.ts @@ -0,0 +1,121 @@ +/* + * 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 { left } from 'fp-ts/lib/Either'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { + CoverageOverviewRequestBody, + CoverageOverviewRuleActivity, + CoverageOverviewRuleSource, +} from './coverage_overview_route'; + +describe('Coverage overview request schema', () => { + test('empty object validates', () => { + const payload: CoverageOverviewRequestBody = {}; + + const decoded = CoverageOverviewRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('validates with all fields populated', () => { + const payload: CoverageOverviewRequestBody = { + filter: { + activity: [CoverageOverviewRuleActivity.Enabled, CoverageOverviewRuleActivity.Disabled], + source: [CoverageOverviewRuleSource.Custom, CoverageOverviewRuleSource.Prebuilt], + search_term: 'search term', + }, + }; + + const decoded = CoverageOverviewRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('does NOT validate with extra fields', () => { + const payload: CoverageOverviewRequestBody & { invalid_field: string } = { + filter: { + activity: [CoverageOverviewRuleActivity.Enabled, CoverageOverviewRuleActivity.Disabled], + source: [CoverageOverviewRuleSource.Custom, CoverageOverviewRuleSource.Prebuilt], + search_term: 'search term', + }, + invalid_field: 'invalid field', + }; + + const decoded = CoverageOverviewRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_field"']); + expect(message.schema).toEqual({}); + }); + + test('does NOT validate with invalid filter values', () => { + const payload: CoverageOverviewRequestBody = { + filter: { + // @ts-expect-error + activity: ['Wrong activity field'], + // @ts-expect-error + source: ['Wrong source field'], + search_term: 'search term', + }, + }; + + const decoded = CoverageOverviewRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "Wrong activity field" supplied to "filter,activity"', + 'Invalid value "Wrong source field" supplied to "filter,source"', + ]); + expect(message.schema).toEqual({}); + }); + + test('does NOT validate with empty filter arrays', () => { + const payload: CoverageOverviewRequestBody = { + filter: { + activity: [], + source: [], + search_term: 'search term', + }, + }; + + const decoded = CoverageOverviewRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "[]" supplied to "filter,activity"', + 'Invalid value "[]" supplied to "filter,source"', + ]); + expect(message.schema).toEqual({}); + }); + + test('does NOT validate with empty search_term', () => { + const payload: CoverageOverviewRequestBody = { + filter: { + search_term: '', + }, + }; + + const decoded = CoverageOverviewRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "" supplied to "filter,search_term"', + ]); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/serverless.config.ts index a95c9dc2bd434..2e5b2f2b6ac69 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/configs/serverless.config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createTestConfig } from '../../../../../config/serverless/config.base'; +import { createTestConfig } from '../../../../../config/serverless/config.base.essentials'; export default createTestConfig({ testFiles: [require.resolve('..')], diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts index 6c15f2bc42465..052afbda4620a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts @@ -36,7 +36,7 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllRules(supertest, log); }); - it('does NOT error when there are no security rules', async () => { + it('does NOT error when there exist some stack rules in addition to security detection rules', async () => { // Creates a non-security type rule await alertingApi.createRule({ consumer: 'security', @@ -77,7 +77,7 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - describe('without filters', () => { + describe('base cases', () => { it('returns an empty response if there are no rules', async () => { const body = await getCoverageOverview(supertest); @@ -407,6 +407,53 @@ export default ({ getService }: FtrProviderContext): void => { }, }); }); + + it('returns all rules if neither enabled and disabled filters are specified in the request', async () => { + const expectedRule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-1', + enabled: false, + name: 'Disabled rule', + threat: generateThreatArray(1), + }) + ); + const expectedRule2 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, + name: 'Enabled rule', + threat: generateThreatArray(2), + }) + ); + + const body = await getCoverageOverview(supertest); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule1.id], + TA001: [expectedRule1.id], + 'T001.001': [expectedRule1.id], + T002: [expectedRule2.id], + TA002: [expectedRule2.id], + 'T002.002': [expectedRule2.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule1.id]: { + activity: 'disabled', + name: 'Disabled rule', + }, + [expectedRule2.id]: { + activity: 'enabled', + name: 'Enabled rule', + }, + }, + }); + }); }); describe('source', () => { @@ -527,6 +574,49 @@ export default ({ getService }: FtrProviderContext): void => { }, }); }); + + it('returns all rules if neither custom and prebuilt filters are specified in the request', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedPrebuiltRule = created[0]; + + const expectedCustomRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest); + + expect(body).to.eql({ + coverage: { + T001: [expectedPrebuiltRule.id], + TA001: [expectedPrebuiltRule.id], + 'T001.001': [expectedPrebuiltRule.id], + T002: [expectedCustomRule.id], + TA002: [expectedCustomRule.id], + 'T002.002': [expectedCustomRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedPrebuiltRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + [expectedCustomRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, + }, + }); + }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 923b08a1756fb..f47a494ca58c9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -10,10 +10,10 @@ import { COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES, COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES, -} from '../../../../screens/alerts'; +} from '../../../../screens/rules_coverage_overview'; import { createRule } from '../../../../tasks/api_calls/rules'; import { visit } from '../../../../tasks/navigation'; -import { COVERAGE_OVERVIEW_URL } from '../../../../urls/rules_management'; +import { RULES_COVERAGE_OVERVIEW_URL } from '../../../../urls/rules_management'; import { createRuleAssetSavedObject } from '../../../../helpers/rules'; import { getMitre1, getNewRule } from '../../../../objects/rule'; import { @@ -31,7 +31,7 @@ import { openTechniquePanel, selectCoverageOverviewActivityFilterOption, selectCoverageOverviewSourceFilterOption, -} from '../../../../tasks/alerts'; +} from '../../../../tasks/rules_coverage_overview'; const prebuiltRules = [ createRuleAssetSavedObject({ @@ -43,6 +43,7 @@ const prebuiltRules = [ createRuleAssetSavedObject({ name: `Disabled prebuilt rule`, rule_id: `disabled_prebuilt_rule`, + enabled: false, threat: [getMitre1()], }), ]; @@ -60,7 +61,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { createRule( getNewRule({ rule_id: 'disabled_custom_rule', name: 'Disabled custom rule', enabled: false }) ); - visit(COVERAGE_OVERVIEW_URL); + visit(RULES_COVERAGE_OVERVIEW_URL); }); it('technique panel renders custom and prebuilt rule data on page load', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index 622e48c5e1472..0f1a513fc5d86 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -186,28 +186,6 @@ export const ALERT_RENDERER_HOST_NAME = export const HOVER_ACTIONS_CONTAINER = getDataTestSubjectSelector('hover-actions-container'); -export const COVERAGE_OVERVIEW_TECHNIQUE_PANEL = - '[data-test-subj="coverageOverviewTechniquePanel"]'; - -export const COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES = - '[data-test-subj="coverageOverviewEnabledRulesList"]'; - -export const COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES = - '[data-test-subj="coverageOverviewDisabledRulesList"]'; - -export const COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON = - '[data-test-subj="enableAllDisabledButton"]'; - -export const COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON = - '[data-test-subj="coverageOverviewRuleActivityFilterButton"]'; - -export const COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON = - '[data-test-subj="coverageOverviewRuleSourceFilterButton"]'; - -export const COVERAGE_OVERVIEW_FILTER_LIST = '[data-test-subj="coverageOverviewFilterList"]'; - -export const COVERAGE_OVERVIEW_SEARCH_BAR = '[data-test-subj="coverageOverviewFilterSearchBar"]'; - export const SECURITY_SOLUTION_USERS_AVATAR = (user: string) => `[data-test-subj="securitySolutionUsersAvatar-${user}"]`; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts new file mode 100644 index 0000000000000..662d177c3f512 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts @@ -0,0 +1,28 @@ +/* + * 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 COVERAGE_OVERVIEW_TECHNIQUE_PANEL = + '[data-test-subj="coverageOverviewTechniquePanel"]'; + +export const COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES = + '[data-test-subj="coverageOverviewEnabledRulesList"]'; + +export const COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES = + '[data-test-subj="coverageOverviewDisabledRulesList"]'; + +export const COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON = + '[data-test-subj="enableAllDisabledButton"]'; + +export const COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON = + '[data-test-subj="coverageOverviewRuleActivityFilterButton"]'; + +export const COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON = + '[data-test-subj="coverageOverviewRuleSourceFilterButton"]'; + +export const COVERAGE_OVERVIEW_FILTER_LIST = '[data-test-subj="coverageOverviewFilterList"]'; + +export const COVERAGE_OVERVIEW_SEARCH_BAR = '[data-test-subj="coverageOverviewFilterSearchBar"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index ca9bfdb311800..cc1a06d3545de 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -51,12 +51,6 @@ import { ALERT_TABLE_EVENT_RENDERED_VIEW_OPTION, HOVER_ACTIONS_CONTAINER, ALERT_TABLE_GRID_VIEW_OPTION, - COVERAGE_OVERVIEW_TECHNIQUE_PANEL, - COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON, - COVERAGE_OVERVIEW_FILTER_LIST, - COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON, - COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, - COVERAGE_OVERVIEW_SEARCH_BAR, } from '../screens/alerts'; import { LOADING_INDICATOR, REFRESH_BUTTON } from '../screens/security_header'; import { TIMELINE_COLUMN_SPINNER } from '../screens/timeline'; @@ -498,33 +492,3 @@ export const switchAlertTableToGridView = () => { cy.get(ALERT_TABLE_SUMMARY_VIEW_SELECTABLE).should('be.visible').trigger('click'); cy.get(ALERT_TABLE_GRID_VIEW_OPTION).should('be.visible').trigger('click'); }; - -export const openTechniquePanel = (label: string) => { - cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL).contains(label).click(); -}; - -export const selectCoverageOverviewActivityFilterOption = (option: string) => { - cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // open filter popover - cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click(); - cy.get(LOADING_INDICATOR).should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // close filter popover -}; - -export const selectCoverageOverviewSourceFilterOption = (option: string) => { - cy.get(COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON).click(); // open filter popover - cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click(); - cy.get(LOADING_INDICATOR).should('not.exist'); - cy.get(COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON).click(); // close filter popover -}; - -export const filterCoverageOverviewBySearchBar = (searchTerm: string) => { - cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).type(`${searchTerm}`); - cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).focus(); - cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).realType('{enter}'); -}; - -export const enableAllDisabledRules = () => { - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).click(); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.exist'); - cy.get(LOADING_INDICATOR).should('not.exist'); -}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts new file mode 100644 index 0000000000000..c97484f526ff6 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts @@ -0,0 +1,46 @@ +/* + * 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 { + COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON, + COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, + COVERAGE_OVERVIEW_FILTER_LIST, + COVERAGE_OVERVIEW_SEARCH_BAR, + COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON, + COVERAGE_OVERVIEW_TECHNIQUE_PANEL, +} from '../screens/rules_coverage_overview'; +import { LOADING_INDICATOR } from '../screens/security_header'; + +export const openTechniquePanel = (label: string) => { + cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL).contains(label).click(); +}; + +export const selectCoverageOverviewActivityFilterOption = (option: string) => { + cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // open filter popover + cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click(); + cy.get(LOADING_INDICATOR).should('not.exist'); + cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // close filter popover +}; + +export const selectCoverageOverviewSourceFilterOption = (option: string) => { + cy.get(COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON).click(); // open filter popover + cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click(); + cy.get(LOADING_INDICATOR).should('not.exist'); + cy.get(COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON).click(); // close filter popover +}; + +export const filterCoverageOverviewBySearchBar = (searchTerm: string) => { + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).type(`${searchTerm}`); + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).focus(); + cy.get(COVERAGE_OVERVIEW_SEARCH_BAR).realType('{enter}'); +}; + +export const enableAllDisabledRules = () => { + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).click(); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.exist'); + cy.get(LOADING_INDICATOR).should('not.exist'); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts b/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts index be3b3fcce83c6..03af67cfe79db 100644 --- a/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts +++ b/x-pack/test/security_solution_cypress/cypress/urls/rules_management.ts @@ -7,4 +7,4 @@ export const RULES_MANAGEMENT_URL = '/app/security/rules/management'; export const RULES_MONITORING_URL = '/app/security/rules/monitoring'; -export const COVERAGE_OVERVIEW_URL = '/app/security/rules_coverage_overview'; +export const RULES_COVERAGE_OVERVIEW_URL = '/app/security/rules_coverage_overview'; From 089ee4b0151903e0fc7f1fe0ea9b108538628cab Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 6 Dec 2023 18:05:47 -0500 Subject: [PATCH 26/34] adds back ess non security rule util --- .../config/ess/config.base.ts | 6 +-- .../rule_management/coverage_overview.ts | 43 ++++++++++++------- .../utils/rules/create_non_security_rule.ts | 41 ++++++++++++++++++ .../coverage_overview/coverage_overview.cy.ts | 32 +++++++++++++- .../cypress/objects/rule.ts | 2 +- 5 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts index 05e646e77e029..b7f08d5180bbe 100644 --- a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts @@ -7,7 +7,6 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrConfigProviderContext, kbnTestConfig, kibanaTestUser } from '@kbn/test'; -import { AlertingApiProvider } from '../../../../test_serverless/api_integration/services/alerting_api'; import { services } from '../../../api_integration/services'; interface CreateTestConfigOptions { @@ -50,10 +49,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s return { testFiles, servers, - services: { - ...services, - alertingApi: AlertingApiProvider, - }, + services, junit: { reportName: 'X-Pack Detection Engine API Integration Tests', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts index 052afbda4620a..dc52fe73d3277 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts @@ -23,13 +23,13 @@ import { installPrebuiltRules, getCustomQueryRuleParams, } from '../../utils'; +import { createNonSecurityRule } from '../../utils/rules/create_non_security_rule'; import { getCoverageOverview } from '../../utils/rules/get_coverage_overview'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - const alertingApi = getService('alertingApi'); describe('@serverless @ess coverage_overview', () => { beforeEach(async () => { @@ -38,21 +38,32 @@ export default ({ getService }: FtrProviderContext): void => { it('does NOT error when there exist some stack rules in addition to security detection rules', async () => { // Creates a non-security type rule - await alertingApi.createRule({ - consumer: 'security', - name: 'Threshold rule', - ruleTypeId: 'observability.rules.custom_threshold', - params: { - criteria: [], - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: 'data-view-id', - }, - }, - }); + // await supertest + // .post(`/api/alerting/rule`) + // .set('kbn-xsrf', 'foo') + // .set('x-elastic-internal-origin', 'foo') + // .send({ + // params: { + // criteria: [], + // searchConfiguration: { + // query: { + // query: '', + // language: 'kuery', + // }, + // index: 'data-view-id', + // }, + // }, + // consumer: 'security', + // schedule: { + // interval: '5m', + // }, + // name: 'Threshold rule', + // rule_type_id: 'observability.rules.custom_threshold', + // }) + // .expect(200); + + await createNonSecurityRule(supertest); + const rule1 = await createRule( supertest, log, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts new file mode 100644 index 0000000000000..89bb2bbea5725 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts @@ -0,0 +1,41 @@ +/* + * 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 type SuperTest from 'supertest'; + +const SIMPLE_APM_RULE_DATA = { + name: 'Test rule', + rule_type_id: 'apm.anomaly', + enabled: false, + consumer: 'alerts', + tags: [], + actions: [], + params: { + windowSize: 30, + windowUnit: 'm', + anomalySeverityType: 'critical', + environment: 'ENVIRONMENT_ALL', + }, + schedule: { + interval: '10m', + }, +}; + +/** + * Created a non security rule. Helpful in tests to verify functionality works with presence of non security rules. + * @param supertest The supertest deps + */ +export async function createNonSecurityRule( + supertest: SuperTest.SuperTest +): Promise { + await supertest + .post('/api/alerting/rule') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(SIMPLE_APM_RULE_DATA) + .expect(200); +} diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index f47a494ca58c9..418dc44601f12 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -6,6 +6,7 @@ */ import { getMockThreatData } from '@kbn/security-solution-plugin/public/detections/mitre/mitre_tactics_techniques'; +import { Threat } from '@kbn/securitysolution-io-ts-alerting-types'; import { COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES, @@ -15,7 +16,7 @@ import { createRule } from '../../../../tasks/api_calls/rules'; import { visit } from '../../../../tasks/navigation'; import { RULES_COVERAGE_OVERVIEW_URL } from '../../../../urls/rules_management'; import { createRuleAssetSavedObject } from '../../../../helpers/rules'; -import { getMitre1, getNewRule } from '../../../../objects/rule'; +import { getNewRule } from '../../../../objects/rule'; import { createAndInstallMockedPrebuiltRules, preventPrebuiltRulesPackageInstallation, @@ -33,6 +34,35 @@ import { selectCoverageOverviewSourceFilterOption, } from '../../../../tasks/rules_coverage_overview'; +export const getMitre1 = (): Threat => ({ + framework: 'MITRE ATT&CK', + tactic: { + name: getMockThreatData().tactic.name, + id: getMockThreatData().tactic.id, + reference: getMockThreatData().tactic.reference, + }, + technique: [ + { + id: getMockThreatData().technique.id, + reference: getMockThreatData().technique.reference, + name: getMockThreatData().technique.name, + subtechnique: [ + { + id: getMockThreatData().subtechnique.id, + name: getMockThreatData().subtechnique.name, + reference: getMockThreatData().subtechnique.reference, + }, + ], + }, + { + name: getMockThreatData().technique.name, + id: getMockThreatData().technique.id, + reference: getMockThreatData().technique.reference, + subtechnique: [], + }, + ], +}); + const prebuiltRules = [ createRuleAssetSavedObject({ name: `Enabled prebuilt rule`, diff --git a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts index a08c20d204dc2..681eff67d071e 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts @@ -36,7 +36,7 @@ export const getIndexPatterns = (): string[] => [ export const getThreatIndexPatterns = (): string[] => ['logs-ti_*']; -export const getMitre1 = (): Threat => ({ +const getMitre1 = (): Threat => ({ framework: 'MITRE ATT&CK', tactic: { name: getMockThreatData().tactic.name, From 879594d01eaa156df4581b47baac0de4bc8d6071 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 12 Dec 2023 21:39:20 -0500 Subject: [PATCH 27/34] merge fixes --- .../rule_management/coverage_overview.ts | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts index dc52fe73d3277..25aabf129335b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts @@ -22,8 +22,8 @@ import { installPrebuiltRulesAndTimelines, installPrebuiltRules, getCustomQueryRuleParams, + createNonSecurityRule, } from '../../utils'; -import { createNonSecurityRule } from '../../utils/rules/create_non_security_rule'; import { getCoverageOverview } from '../../utils/rules/get_coverage_overview'; export default ({ getService }: FtrProviderContext): void => { @@ -36,32 +36,7 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllRules(supertest, log); }); - it('does NOT error when there exist some stack rules in addition to security detection rules', async () => { - // Creates a non-security type rule - // await supertest - // .post(`/api/alerting/rule`) - // .set('kbn-xsrf', 'foo') - // .set('x-elastic-internal-origin', 'foo') - // .send({ - // params: { - // criteria: [], - // searchConfiguration: { - // query: { - // query: '', - // language: 'kuery', - // }, - // index: 'data-view-id', - // }, - // }, - // consumer: 'security', - // schedule: { - // interval: '5m', - // }, - // name: 'Threshold rule', - // rule_type_id: 'observability.rules.custom_threshold', - // }) - // .expect(200); - + it('@ess does NOT error when there exist some stack rules in addition to security detection rules', async () => { await createNonSecurityRule(supertest); const rule1 = await createRule( From 72d41660d8aa7b26b8133438ec271412afdc3b0c Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 20 Dec 2023 00:57:37 -0700 Subject: [PATCH 28/34] changes script --- .../extract_tactics_techniques_mitre.js | 56 ++++++++++++++++--- .../coverage_overview/coverage_overview.cy.ts | 2 +- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js index ee037cae293b2..44fa3907a4939 100644 --- a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js +++ b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js @@ -22,6 +22,15 @@ const OUTPUT_DIRECTORY = resolve('public', 'detections', 'mitre'); const MITRE_CONTENT_VERSION = 'ATT&CK-v13.1'; // last updated when preparing for 8.10.3 release const MITRE_CONTENT_URL = `https://raw.githubusercontent.com/mitre/cti/${MITRE_CONTENT_VERSION}/enterprise-attack/enterprise-attack.json`; +/** + * An ID for a technique that exists in multiple tactics. This may change in further updates and on MITRE + * version upgrade, this ID should be double-checked to make sure it still represents these parameters. + * + * We have this in order to cover edge cases with our mock data that can't be achieved by simply generating + * data from the MITRE api. + */ +const MOCK_DUPLICATE_TECHNIQUE_ID = 'T1546'; + const getTacticsOptions = (tactics) => tactics.map((t) => `{ @@ -172,15 +181,36 @@ const extractSubtechniques = (mitreData) => { }; const buildMockThreatData = (tacticsData, techniques, subtechniques) => { - const subtechnique = subtechniques[0]; - const technique = techniques.find((technique) => technique.id === subtechnique.techniqueId); - const tactic = tacticsData.find((tactic) => tactic.shortName === technique.tactics[0]); - - return { - tactic: normalizeTacticsData([tactic])[0], - technique, - subtechnique, - }; + const mockThreatData = []; + for (let i = 0; i < 2; i++) { + const subtechnique = subtechniques[i]; + const technique = techniques.find((technique) => technique.id === subtechnique.techniqueId); + const tactic = tacticsData.find((tactic) => tactic.shortName === technique.tactics[0]); + + mockThreatData.push({ + tactic: normalizeTacticsData([tactic])[0], + technique, + subtechnique, + }); + } + return mockThreatData; +}; + +const buildDuplicateTechniqueMockThreatData = (tacticsData, techniques) => { + const technique = techniques.find((technique) => technique.id === MOCK_DUPLICATE_TECHNIQUE_ID); + const tacticOne = tacticsData.find((tactic) => tactic.shortName === technique.tactics[0]); + const tacticTwo = tacticsData.find((tactic) => tactic.shortName === technique.tactics[1]); + + return [ + { + tactic: normalizeTacticsData([tacticOne])[0], + technique, + }, + { + tactic: normalizeTacticsData([tacticTwo])[0], + technique, + }, + ]; }; async function main() { @@ -235,6 +265,14 @@ async function main() { ) .replace(/}"/g, '}') .replace(/"{/g, '{')}); + + export const getDuplicateTechniqueThreatData = () => (${JSON.stringify( + buildDuplicateTechniqueMockThreatData(tacticsData, techniques), + null, + 2 + ) + .replace(/}"/g, '}') + .replace(/"{/g, '{')}); `; fs.writeFileSync(`${OUTPUT_DIRECTORY}/mitre_tactics_techniques.ts`, body, 'utf-8'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 418dc44601f12..894e8daf2b8de 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -34,7 +34,7 @@ import { selectCoverageOverviewSourceFilterOption, } from '../../../../tasks/rules_coverage_overview'; -export const getMitre1 = (): Threat => ({ +const getMitre1 = (): Threat => ({ framework: 'MITRE ATT&CK', tactic: { name: getMockThreatData().tactic.name, From 2cfc2750eb9dd68cfef49f4bb1901e0be0496b00 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 4 Jan 2024 00:27:48 -0700 Subject: [PATCH 29/34] modifies mitre mock data and generation script --- .../coverage_overview/technique_panel.tsx | 2 +- .../mitre/mitre_tactics_techniques.ts | 134 +++++- .../detections/mitre/valid_threat_mock.ts | 2 +- .../extract_tactics_techniques_mitre.js | 12 +- .../coverage_overview/coverage_overview.cy.ts | 431 +++++++++++++----- .../cypress/objects/rule.ts | 42 +- .../screens/rules_coverage_overview.ts | 3 + .../cypress/tasks/rules_coverage_overview.ts | 2 +- 8 files changed, 460 insertions(+), 168 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx index 821f0f18dcd72..6b2d63818a05a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx @@ -79,7 +79,7 @@ const CoverageOverviewMitreTechniquePanelComponent = ({ > - +

{technique.name}

{SubtechniqueInfo} diff --git a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts index 6846462b94c63..abfd92ca074ac 100644 --- a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts +++ b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts @@ -7251,27 +7251,123 @@ export const subtechniques: MitreSubTechnique[] = [ ]; /** - * A full object of Mitre Attack Threat data that is taken directly from the `mitre_tactics_techniques.ts` file + * An array of full Mitre Attack Threat objects that are taken directly from the `mitre_tactics_techniques.ts` file * * Is built alongside and sampled from the data in the file so to always be valid with the most up to date MITRE ATT&CK data */ -export const getMockThreatData = () => ({ - tactic: { - name: 'Credential Access', - id: 'TA0006', - reference: 'https://attack.mitre.org/tactics/TA0006', - }, - technique: { - name: 'OS Credential Dumping', - id: 'T1003', - reference: 'https://attack.mitre.org/techniques/T1003', - tactics: ['credential-access'], +export const getMockThreatData = () => [ + { + tactic: { + name: 'Credential Access', + id: 'TA0006', + reference: 'https://attack.mitre.org/tactics/TA0006', + }, + technique: { + name: 'OS Credential Dumping', + id: 'T1003', + reference: 'https://attack.mitre.org/techniques/T1003', + tactics: ['credential-access'], + }, + subtechnique: { + name: '/etc/passwd and /etc/shadow', + id: 'T1003.008', + reference: 'https://attack.mitre.org/techniques/T1003/008', + tactics: ['credential-access'], + techniqueId: 'T1003', + }, + }, + { + tactic: { + name: 'Credential Access', + id: 'TA0006', + reference: 'https://attack.mitre.org/tactics/TA0006', + }, + technique: { + name: 'Steal or Forge Kerberos Tickets', + id: 'T1558', + reference: 'https://attack.mitre.org/techniques/T1558', + tactics: ['credential-access'], + }, + subtechnique: { + name: 'AS-REP Roasting', + id: 'T1558.004', + reference: 'https://attack.mitre.org/techniques/T1558/004', + tactics: ['credential-access'], + techniqueId: 'T1558', + }, + }, + { + tactic: { + name: 'Persistence', + id: 'TA0003', + reference: 'https://attack.mitre.org/tactics/TA0003', + }, + technique: { + name: 'Boot or Logon Autostart Execution', + id: 'T1547', + reference: 'https://attack.mitre.org/techniques/T1547', + tactics: ['persistence', 'privilege-escalation'], + }, + subtechnique: { + name: 'Active Setup', + id: 'T1547.014', + reference: 'https://attack.mitre.org/techniques/T1547/014', + tactics: ['persistence', 'privilege-escalation'], + techniqueId: 'T1547', + }, + }, + { + tactic: { + name: 'Persistence', + id: 'TA0003', + reference: 'https://attack.mitre.org/tactics/TA0003', + }, + technique: { + name: 'Account Manipulation', + id: 'T1098', + reference: 'https://attack.mitre.org/techniques/T1098', + tactics: ['persistence'], + }, + subtechnique: { + name: 'Additional Cloud Credentials', + id: 'T1098.001', + reference: 'https://attack.mitre.org/techniques/T1098/001', + tactics: ['persistence'], + techniqueId: 'T1098', + }, }, - subtechnique: { - name: '/etc/passwd and /etc/shadow', - id: 'T1003.008', - reference: 'https://attack.mitre.org/techniques/T1003/008', - tactics: ['credential-access'], - techniqueId: 'T1003', +]; + +/** + * An array of specifically chosen Mitre Attack Threat objects that is taken directly from the `mitre_tactics_techniques.ts` file + * + * These objects have identical technique fields but are assigned to different tactics + */ +export const getDuplicateTechniqueThreatData = () => [ + { + tactic: { + name: 'Privilege Escalation', + id: 'TA0004', + reference: 'https://attack.mitre.org/tactics/TA0004', + }, + technique: { + name: 'Event Triggered Execution', + id: 'T1546', + reference: 'https://attack.mitre.org/techniques/T1546', + tactics: ['privilege-escalation', 'persistence'], + }, + }, + { + tactic: { + name: 'Persistence', + id: 'TA0003', + reference: 'https://attack.mitre.org/tactics/TA0003', + }, + technique: { + name: 'Event Triggered Execution', + id: 'T1546', + reference: 'https://attack.mitre.org/techniques/T1546', + tactics: ['privilege-escalation', 'persistence'], + }, }, -}); +]; diff --git a/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts b/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts index c084a8a01d58e..f7885a26d119f 100644 --- a/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts +++ b/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts @@ -8,7 +8,7 @@ import type { Threats } from '@kbn/securitysolution-io-ts-alerting-types'; import { getMockThreatData } from './mitre_tactics_techniques'; -const { tactic, technique, subtechnique } = getMockThreatData(); +const { tactic, technique, subtechnique } = getMockThreatData()[0]; const { tactics, ...mockTechnique } = technique; const { tactics: subtechniqueTactics, ...mockSubtechnique } = subtechnique; diff --git a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js index 44fa3907a4939..1f8526538e8c9 100644 --- a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js +++ b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js @@ -181,9 +181,10 @@ const extractSubtechniques = (mitreData) => { }; const buildMockThreatData = (tacticsData, techniques, subtechniques) => { + const numberOfThreatsToGenerate = 4; const mockThreatData = []; - for (let i = 0; i < 2; i++) { - const subtechnique = subtechniques[i]; + for (let i = 0; i < numberOfThreatsToGenerate; i++) { + const subtechnique = subtechniques[i * 2]; // Double our interval to broaden the subtechnique types we're pulling data from a bit const technique = techniques.find((technique) => technique.id === subtechnique.techniqueId); const tactic = tacticsData.find((tactic) => tactic.shortName === technique.tactics[0]); @@ -254,7 +255,7 @@ async function main() { .replace(/"{/g, '{')}; /** - * A full object of Mitre Attack Threat data that is taken directly from the \`mitre_tactics_techniques.ts\` file + * An array of full Mitre Attack Threat objects that are taken directly from the \`mitre_tactics_techniques.ts\` file * * Is built alongside and sampled from the data in the file so to always be valid with the most up to date MITRE ATT&CK data */ @@ -266,6 +267,11 @@ async function main() { .replace(/}"/g, '}') .replace(/"{/g, '{')}); + /** + * An array of specifically chosen Mitre Attack Threat objects that is taken directly from the \`mitre_tactics_techniques.ts\` file + * + * These objects have identical technique fields but are assigned to different tactics + */ export const getDuplicateTechniqueThreatData = () => (${JSON.stringify( buildDuplicateTechniqueMockThreatData(tacticsData, techniques), null, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 894e8daf2b8de..d7932968e753b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -5,12 +5,16 @@ * 2.0. */ -import { getMockThreatData } from '@kbn/security-solution-plugin/public/detections/mitre/mitre_tactics_techniques'; +import { + getDuplicateTechniqueThreatData, + getMockThreatData, +} from '@kbn/security-solution-plugin/public/detections/mitre/mitre_tactics_techniques'; import { Threat } from '@kbn/securitysolution-io-ts-alerting-types'; import { COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES, COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES, + COVERAGE_OVERVIEW_TECHNIQUE_TITLE, } from '../../../../screens/rules_coverage_overview'; import { createRule } from '../../../../tasks/api_calls/rules'; import { visit } from '../../../../tasks/navigation'; @@ -29,184 +33,367 @@ import { login } from '../../../../tasks/login'; import { enableAllDisabledRules, filterCoverageOverviewBySearchBar, - openTechniquePanel, + openTechniquePanelByName, selectCoverageOverviewActivityFilterOption, selectCoverageOverviewSourceFilterOption, } from '../../../../tasks/rules_coverage_overview'; -const getMitre1 = (): Threat => ({ +// Mitre data used in base case tests +const EnabledPrebuiltRuleMitreData = getMockThreatData()[0]; +const DisabledPrebuiltRuleMitreData = getMockThreatData()[1]; +const EnabledCustomRuleMitreData = getMockThreatData()[2]; +const DisabledCustomRuleMitreData = getMockThreatData()[3]; + +// Mitre data used for duplicate technique tests +const DuplicateTechniqueMitreData1 = getDuplicateTechniqueThreatData()[0]; +const DuplicateTechniqueMitreData2 = getDuplicateTechniqueThreatData()[1]; + +const MockEnabledPrebuiltRuleThreat: Threat = { framework: 'MITRE ATT&CK', tactic: { - name: getMockThreatData().tactic.name, - id: getMockThreatData().tactic.id, - reference: getMockThreatData().tactic.reference, + name: EnabledPrebuiltRuleMitreData.tactic.name, + id: EnabledPrebuiltRuleMitreData.tactic.id, + reference: EnabledPrebuiltRuleMitreData.tactic.reference, }, technique: [ { - id: getMockThreatData().technique.id, - reference: getMockThreatData().technique.reference, - name: getMockThreatData().technique.name, + id: EnabledPrebuiltRuleMitreData.technique.id, + reference: EnabledPrebuiltRuleMitreData.technique.reference, + name: EnabledPrebuiltRuleMitreData.technique.name, subtechnique: [ { - id: getMockThreatData().subtechnique.id, - name: getMockThreatData().subtechnique.name, - reference: getMockThreatData().subtechnique.reference, + id: EnabledPrebuiltRuleMitreData.subtechnique.id, + name: EnabledPrebuiltRuleMitreData.subtechnique.name, + reference: EnabledPrebuiltRuleMitreData.subtechnique.reference, }, ], }, { - name: getMockThreatData().technique.name, - id: getMockThreatData().technique.id, - reference: getMockThreatData().technique.reference, + name: EnabledPrebuiltRuleMitreData.technique.name, + id: EnabledPrebuiltRuleMitreData.technique.id, + reference: EnabledPrebuiltRuleMitreData.technique.reference, subtechnique: [], }, ], -}); +}; + +const MockDisabledPrebuiltRuleThreat: Threat = { + framework: 'MITRE ATT&CK', + tactic: { + name: DisabledPrebuiltRuleMitreData.tactic.name, + id: DisabledPrebuiltRuleMitreData.tactic.id, + reference: DisabledPrebuiltRuleMitreData.tactic.reference, + }, + technique: [ + { + id: DisabledPrebuiltRuleMitreData.technique.id, + reference: DisabledPrebuiltRuleMitreData.technique.reference, + name: DisabledPrebuiltRuleMitreData.technique.name, + subtechnique: [ + { + id: DisabledPrebuiltRuleMitreData.subtechnique.id, + name: DisabledPrebuiltRuleMitreData.subtechnique.name, + reference: DisabledPrebuiltRuleMitreData.subtechnique.reference, + }, + ], + }, + ], +}; + +const MockEnabledCustomRuleThreat: Threat = { + framework: 'MITRE ATT&CK', + tactic: { + name: EnabledCustomRuleMitreData.tactic.name, + id: EnabledCustomRuleMitreData.tactic.id, + reference: EnabledCustomRuleMitreData.tactic.reference, + }, + technique: [ + { + id: EnabledCustomRuleMitreData.technique.id, + reference: EnabledCustomRuleMitreData.technique.reference, + name: EnabledCustomRuleMitreData.technique.name, + subtechnique: [ + { + id: EnabledCustomRuleMitreData.subtechnique.id, + name: EnabledCustomRuleMitreData.subtechnique.name, + reference: EnabledCustomRuleMitreData.subtechnique.reference, + }, + ], + }, + ], +}; + +const MockDisabledCustomRuleThreat: Threat = { + framework: 'MITRE ATT&CK', + tactic: { + name: DisabledCustomRuleMitreData.tactic.name, + id: DisabledCustomRuleMitreData.tactic.id, + reference: DisabledCustomRuleMitreData.tactic.reference, + }, + technique: [ + { + id: DisabledCustomRuleMitreData.technique.id, + reference: DisabledCustomRuleMitreData.technique.reference, + name: DisabledCustomRuleMitreData.technique.name, + }, + ], +}; + +const MockCustomRuleDuplicateTechniqueThreat1: Threat = { + framework: 'MITRE ATT&CK', + tactic: { + name: DuplicateTechniqueMitreData1.tactic.name, + id: DuplicateTechniqueMitreData1.tactic.id, + reference: DuplicateTechniqueMitreData1.tactic.reference, + }, + technique: [ + { + id: DuplicateTechniqueMitreData1.technique.id, + reference: DuplicateTechniqueMitreData1.technique.reference, + name: DuplicateTechniqueMitreData1.technique.name, + }, + ], +}; + +const MockCustomRuleDuplicateTechniqueThreat2: Threat = { + framework: 'MITRE ATT&CK', + tactic: { + name: DuplicateTechniqueMitreData2.tactic.name, + id: DuplicateTechniqueMitreData2.tactic.id, + reference: DuplicateTechniqueMitreData2.tactic.reference, + }, + technique: [ + { + id: DuplicateTechniqueMitreData2.technique.id, + reference: DuplicateTechniqueMitreData2.technique.reference, + name: DuplicateTechniqueMitreData2.technique.name, + }, + ], +}; const prebuiltRules = [ createRuleAssetSavedObject({ name: `Enabled prebuilt rule`, rule_id: `enabled_prebuilt_rule`, enabled: true, - threat: [getMitre1()], + threat: [MockEnabledPrebuiltRuleThreat], }), createRuleAssetSavedObject({ name: `Disabled prebuilt rule`, rule_id: `disabled_prebuilt_rule`, enabled: false, - threat: [getMitre1()], + threat: [MockDisabledPrebuiltRuleThreat], }), ]; describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { - beforeEach(() => { - login(); - deleteAlertsAndRules(); - deletePrebuiltRulesAssets(); - preventPrebuiltRulesPackageInstallation(); - createAndInstallMockedPrebuiltRules(prebuiltRules); - createRule( - getNewRule({ rule_id: 'enabled_custom_rule', enabled: true, name: 'Enabled custom rule' }) - ); - createRule( - getNewRule({ rule_id: 'disabled_custom_rule', name: 'Disabled custom rule', enabled: false }) - ); - visit(RULES_COVERAGE_OVERVIEW_URL); - }); - - it('technique panel renders custom and prebuilt rule data on page load', () => { - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); - }); - - describe('filtering tests', () => { - it('filters for all data', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); - - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + describe('base cases', () => { + beforeEach(() => { + login(); + deleteAlertsAndRules(); + deletePrebuiltRulesAssets(); + preventPrebuiltRulesPackageInstallation(); + createAndInstallMockedPrebuiltRules(prebuiltRules); + createRule( + getNewRule({ + rule_id: 'enabled_custom_rule', + enabled: true, + name: 'Enabled custom rule', + threat: [MockEnabledCustomRuleThreat], + }) + ); + createRule( + getNewRule({ + rule_id: 'disabled_custom_rule', + name: 'Disabled custom rule', + enabled: false, + threat: [MockDisabledCustomRuleThreat], + }) + ); + visit(RULES_COVERAGE_OVERVIEW_URL); }); - it('filters for disabled and prebuilt rules', () => { - selectCoverageOverviewActivityFilterOption('Enabled rules'); // Disables default filter - selectCoverageOverviewActivityFilterOption('Disabled rules'); - selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter - - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled prebuilt rule') - .should('not.exist'); + it('technique panel renders custom and prebuilt rule data on page load', () => { + openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) .contains('Enabled custom rule') .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) .contains('Disabled custom rule') .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); }); - it('filters for only prebuilt rules', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); - selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter + describe('filtering tests', () => { + it('filters for all data', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + + openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + + openTechniquePanelByName(EnabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + + openTechniquePanelByName(DisabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); + }); + + it('filters for disabled and prebuilt rules', () => { + selectCoverageOverviewActivityFilterOption('Enabled rules'); // Disables default filter + selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter + + openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + + openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + + openTechniquePanelByName(EnabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled custom rule') + .should('not.exist'); + + openTechniquePanelByName(DisabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + }); + + it('filters for only prebuilt rules', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter + + openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); + + openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled prebuilt rule'); + + openTechniquePanelByName(EnabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled custom rule') + .should('not.exist'); + + openTechniquePanelByName(DisabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + }); + + it('filters for only custom rules', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewSourceFilterOption('Elastic rules'); // Disables default filter + + openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + + openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + + openTechniquePanelByName(EnabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + + openTechniquePanelByName(DisabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); + }); + + it('filters for search term', () => { + filterCoverageOverviewBySearchBar('Enabled custom rule'); // Disables default filter + + openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Enabled prebuilt rule') + .should('not.exist'); + + openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled prebuilt rule') + .should('not.exist'); + + openTechniquePanelByName(EnabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + + openTechniquePanelByName(DisabledCustomRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) + .contains('Disabled custom rule') + .should('not.exist'); + }); }); - it('filters for only custom rules', () => { + it('enables all disabled rules', () => { selectCoverageOverviewActivityFilterOption('Disabled rules'); - selectCoverageOverviewSourceFilterOption('Elastic rules'); // Disables default filter + openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); + enableAllDisabledRules(); - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); + // Should now render all rules in "enabled" section + openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Disabled prebuilt rule'); + + // Shouldn't render the rules in "disabled" section cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) .contains('Disabled prebuilt rule') .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Disabled custom rule'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('not.be.disabled'); + cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); }); + }); - it('filters for search term', () => { - filterCoverageOverviewBySearchBar('Enabled custom rule'); // Disables default filter + describe('with rules that have identical mitre techniques that belong to multiple tactics', () => { + beforeEach(() => { + login(); + deleteAlertsAndRules(); + deletePrebuiltRulesAssets(); + createRule( + getNewRule({ + rule_id: 'duplicate_technique_rule_1', + enabled: true, + name: 'Rule with tactic 1', + threat: [MockCustomRuleDuplicateTechniqueThreat1], + }) + ); + createRule( + getNewRule({ + rule_id: 'duplicate_technique_rule_2', + name: 'Rule with tactic 2', + enabled: true, + threat: [MockCustomRuleDuplicateTechniqueThreat2], + }) + ); + visit(RULES_COVERAGE_OVERVIEW_URL); + }); - openTechniquePanel(getMockThreatData().technique.name); + it('technique panel renders rule data when the same technique exists in multiple tactics', () => { + // Open duplicated technique panel under first tactic + cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData1.technique.id)) + .first() + .click(); cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Enabled prebuilt rule') + .contains('Rule with tactic 1') .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled custom rule') + + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule with tactic 2'); + + // Open duplicated technique panel under second tactic + cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData2.technique.id)) + .last() + .click(); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule with tactic 1'); + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Rule with tactic 2') .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); }); }); - - it('enables all disabled rules', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); - openTechniquePanel(getMockThreatData().technique.name); - enableAllDisabledRules(); - - // Should now render all rules in "enabled" section - openTechniquePanel(getMockThreatData().technique.name); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled custom rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Disabled prebuilt rule'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Disabled custom rule'); - - // Shouldn't render the rules in "disabled" section - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled prebuilt rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Disabled custom rule') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON).should('be.disabled'); - }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts index 681eff67d071e..5b20fe24b6c7a 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts @@ -39,27 +39,27 @@ export const getThreatIndexPatterns = (): string[] => ['logs-ti_*']; const getMitre1 = (): Threat => ({ framework: 'MITRE ATT&CK', tactic: { - name: getMockThreatData().tactic.name, - id: getMockThreatData().tactic.id, - reference: getMockThreatData().tactic.reference, + name: getMockThreatData()[0].tactic.name, + id: getMockThreatData()[0].tactic.id, + reference: getMockThreatData()[0].tactic.reference, }, technique: [ { - id: getMockThreatData().technique.id, - reference: getMockThreatData().technique.reference, - name: getMockThreatData().technique.name, + id: getMockThreatData()[0].technique.id, + reference: getMockThreatData()[0].technique.reference, + name: getMockThreatData()[0].technique.name, subtechnique: [ { - id: getMockThreatData().subtechnique.id, - name: getMockThreatData().subtechnique.name, - reference: getMockThreatData().subtechnique.reference, + id: getMockThreatData()[0].subtechnique.id, + name: getMockThreatData()[0].subtechnique.name, + reference: getMockThreatData()[0].subtechnique.reference, }, ], }, { - name: getMockThreatData().technique.name, - id: getMockThreatData().technique.id, - reference: getMockThreatData().technique.reference, + name: getMockThreatData()[0].technique.name, + id: getMockThreatData()[0].technique.id, + reference: getMockThreatData()[0].technique.reference, subtechnique: [], }, ], @@ -68,20 +68,20 @@ const getMitre1 = (): Threat => ({ const getMitre2 = (): Threat => ({ framework: 'MITRE ATT&CK', tactic: { - name: getMockThreatData().tactic.name, - id: getMockThreatData().tactic.id, - reference: getMockThreatData().tactic.reference, + name: getMockThreatData()[1].tactic.name, + id: getMockThreatData()[1].tactic.id, + reference: getMockThreatData()[1].tactic.reference, }, technique: [ { - id: getMockThreatData().technique.id, - reference: getMockThreatData().technique.reference, - name: getMockThreatData().technique.name, + id: getMockThreatData()[1].technique.id, + reference: getMockThreatData()[1].technique.reference, + name: getMockThreatData()[1].technique.name, subtechnique: [ { - id: getMockThreatData().subtechnique.id, - name: getMockThreatData().subtechnique.name, - reference: getMockThreatData().subtechnique.reference, + id: getMockThreatData()[1].subtechnique.id, + name: getMockThreatData()[1].subtechnique.name, + reference: getMockThreatData()[1].subtechnique.reference, }, ], }, diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts index 662d177c3f512..10dc41b3a4594 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts @@ -8,6 +8,9 @@ export const COVERAGE_OVERVIEW_TECHNIQUE_PANEL = '[data-test-subj="coverageOverviewTechniquePanel"]'; +export const COVERAGE_OVERVIEW_TECHNIQUE_TITLE = (id: string) => + `[data-test-subj="coverageOverviewTechniqueTitle-${id}"]`; + export const COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES = '[data-test-subj="coverageOverviewEnabledRulesList"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts index c97484f526ff6..3f9b9f472940f 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts @@ -15,7 +15,7 @@ import { } from '../screens/rules_coverage_overview'; import { LOADING_INDICATOR } from '../screens/security_header'; -export const openTechniquePanel = (label: string) => { +export const openTechniquePanelByName = (label: string) => { cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL).contains(label).click(); }; From 4b137266c5993e2150c8b68c12d50faefda7db19 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 4 Jan 2024 00:57:58 -0700 Subject: [PATCH 30/34] fix unused code --- .../utils/index.ts | 1 - .../coverage_overview/coverage_overview.cy.ts | 40 ++++++++++++++++--- .../screens/rules_coverage_overview.ts | 8 ++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 1938a069a2f53..baa4be0491625 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -31,7 +31,6 @@ export * from './prebuilt_rules/create_prebuilt_rule_saved_objects'; export * from './prebuilt_rules/install_prebuilt_rules_and_timelines'; export * from './get_simple_rule_update'; export * from './get_simple_ml_rule_update'; -export * from './create_non_security_rule'; export * from './get_simple_rule_as_ndjson'; export * from './rule_to_ndjson'; export * from './delete_rule'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index d7932968e753b..8a935e203fe83 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -14,6 +14,9 @@ import { COVERAGE_OVERVIEW_ENABLE_ALL_DISABLED_BUTTON, COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES, COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES, + COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS, + COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS, + COVERAGE_OVERVIEW_TACTIC_PANEL, COVERAGE_OVERVIEW_TECHNIQUE_TITLE, } from '../../../../screens/rules_coverage_overview'; import { createRule } from '../../../../tasks/api_calls/rules'; @@ -368,14 +371,16 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { getNewRule({ rule_id: 'duplicate_technique_rule_2', name: 'Rule with tactic 2', - enabled: true, + enabled: false, threat: [MockCustomRuleDuplicateTechniqueThreat2], }) ); visit(RULES_COVERAGE_OVERVIEW_URL); }); - it('technique panel renders rule data when the same technique exists in multiple tactics', () => { + it('technique panels render unique rule data', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); + // Open duplicated technique panel under first tactic cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData1.technique.id)) .first() @@ -383,17 +388,42 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) .contains('Rule with tactic 1') .should('not.exist'); - - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule with tactic 2'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Rule with tactic 2'); // Open duplicated technique panel under second tactic cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData2.technique.id)) .last() .click(); cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule with tactic 1'); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) .contains('Rule with tactic 2') .should('not.exist'); }); + + it('tactic panels render correct rule stats', () => { + selectCoverageOverviewActivityFilterOption('Disabled rules'); + + // Validate rule count stats for first tactic + cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) + .contains(DuplicateTechniqueMitreData1.tactic.name) + .get(COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS) + .contains('0'); + + cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) + .contains(DuplicateTechniqueMitreData1.tactic.name) + .get(COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS) + .contains('1'); + + // Validate rule count stats for second tactic + cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) + .contains(DuplicateTechniqueMitreData2.tactic.name) + .get(COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS) + .contains('1'); + + cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) + .contains(DuplicateTechniqueMitreData2.tactic.name) + .get(COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS) + .contains('0'); + }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts index 10dc41b3a4594..2bcfa86cf0f9f 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts @@ -29,3 +29,11 @@ export const COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON = export const COVERAGE_OVERVIEW_FILTER_LIST = '[data-test-subj="coverageOverviewFilterList"]'; export const COVERAGE_OVERVIEW_SEARCH_BAR = '[data-test-subj="coverageOverviewFilterSearchBar"]'; + +export const COVERAGE_OVERVIEW_TACTIC_PANEL = '[data-test-subj="coverageOverviewTacticPanel"]'; + +export const COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS = + '[data-test-subj="ruleStatsEnabledRulesCount"]'; + +export const COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS = + '[data-test-subj="ruleStatsDisabledRulesCount"]'; From a8b6d959ca3ae3f043654681f2e8fc91951e55dd Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 4 Jan 2024 09:07:24 -0700 Subject: [PATCH 31/34] splits ftr test tags --- .../rule_management/coverage_overview.ts | 961 +++++++++--------- 1 file changed, 485 insertions(+), 476 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts index 25aabf129335b..b22b76e4b8670 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/rule_management/coverage_overview.ts @@ -31,50 +31,16 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); const es = getService('es'); - describe('@serverless @ess coverage_overview', () => { + describe('coverage_overview', () => { beforeEach(async () => { await deleteAllRules(supertest, log); }); - it('@ess does NOT error when there exist some stack rules in addition to security detection rules', async () => { - await createNonSecurityRule(supertest); + // ESS only + describe('@ess specific tests', () => { + it('does NOT error when there exist some stack rules in addition to security detection rules', async () => { + await createNonSecurityRule(supertest); - const rule1 = await createRule( - supertest, - log, - getCustomQueryRuleParams({ threat: generateThreatArray(1) }) - ); - - const body = await getCoverageOverview(supertest); - - expect(body).to.eql({ - coverage: { - T001: [rule1.id], - TA001: [rule1.id], - 'T001.001': [rule1.id], - }, - unmapped_rule_ids: [], - rules_data: { - [rule1.id]: { - activity: 'disabled', - name: 'Custom query rule', - }, - }, - }); - }); - - describe('base cases', () => { - it('returns an empty response if there are no rules', async () => { - const body = await getCoverageOverview(supertest); - - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [], - rules_data: {}, - }); - }); - - it('returns response with a single rule mapped to MITRE categories', async () => { const rule1 = await createRule( supertest, log, @@ -98,88 +64,39 @@ export default ({ getService }: FtrProviderContext): void => { }, }); }); - - it('returns response with an unmapped rule', async () => { - const rule1 = await createRule( - supertest, - log, - getCustomQueryRuleParams({ threat: undefined }) - ); - - const body = await getCoverageOverview(supertest); - - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [rule1.id], - rules_data: { - [rule1.id]: { - activity: 'disabled', - name: 'Custom query rule', - }, - }, - }); - }); }); - describe('with filters', () => { - describe('search_term', () => { - it('returns response filtered by tactic', async () => { - await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) - ); - const expectedRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) - ); - - const body = await getCoverageOverview(supertest, { - search_term: 'TA002', - }); + // Both serverless and ESS + describe('@serverless @ess tests', () => { + describe('base cases', () => { + it('returns an empty response if there are no rules', async () => { + const body = await getCoverageOverview(supertest); expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, + coverage: {}, unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Custom query rule', - }, - }, + rules_data: {}, }); }); - it('returns response filtered by technique', async () => { - await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) - ); - const expectedRule = await createRule( + it('returns response with a single rule mapped to MITRE categories', async () => { + const rule1 = await createRule( supertest, log, - getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + getCustomQueryRuleParams({ threat: generateThreatArray(1) }) ); - const body = await getCoverageOverview(supertest, { - search_term: 'T002', - }); + const body = await getCoverageOverview(supertest); expect(body).to.eql({ coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], + T001: [rule1.id], + TA001: [rule1.id], + 'T001.001': [rule1.id], }, unmapped_rule_ids: [], rules_data: { - [expectedRule.id]: { + [rule1.id]: { activity: 'disabled', name: 'Custom query rule', }, @@ -187,440 +104,532 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('returns response filtered by subtechnique', async () => { - await createRule( + it('returns response with an unmapped rule', async () => { + const rule1 = await createRule( supertest, log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) - ); - const expectedRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + getCustomQueryRuleParams({ threat: undefined }) ); - const body = await getCoverageOverview(supertest, { - search_term: 'T002.002', - }); + const body = await getCoverageOverview(supertest); expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], + coverage: {}, + unmapped_rule_ids: [rule1.id], rules_data: { - [expectedRule.id]: { + [rule1.id]: { activity: 'disabled', name: 'Custom query rule', }, }, }); }); + }); - it('returns response filtered by rule name', async () => { - await createRule(supertest, log, getCustomQueryRuleParams({ rule_id: 'rule-1' })); - const expectedRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-2', name: 'rule-2' }) - ); - - const body = await getCoverageOverview(supertest, { - search_term: 'rule-2', - }); - - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [expectedRule.id], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'rule-2', + describe('with filters', () => { + describe('search_term', () => { + it('returns response filtered by tactic', async () => { + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest, { + search_term: 'TA002', + }); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], }, - }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, + }, + }); }); - }); - - it('returns response filtered by index pattern', async () => { - await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', index: ['index-pattern-1'] }) - ); - const expectedRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-2', index: ['index-pattern-2'] }) - ); - const body = await getCoverageOverview(supertest, { - search_term: 'index-pattern-2', + it('returns response filtered by technique', async () => { + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest, { + search_term: 'T002', + }); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, + }, + }); }); - expect(body).to.eql({ - coverage: {}, - unmapped_rule_ids: [expectedRule.id], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Custom query rule', + it('returns response filtered by subtechnique', async () => { + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest, { + search_term: 'T002.002', + }); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], }, - }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, + }, + }); }); - }); - }); - describe('activity', () => { - it('returns response filtered by disabled rules', async () => { - const expectedRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) - ); - await createRule( - supertest, - log, - getCustomQueryRuleParams({ - rule_id: 'rule-2', - enabled: true, - threat: generateThreatArray(2), - }) - ); - - const body = await getCoverageOverview(supertest, { - activity: [CoverageOverviewRuleActivity.Disabled], + it('returns response filtered by rule name', async () => { + await createRule(supertest, log, getCustomQueryRuleParams({ rule_id: 'rule-1' })); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', name: 'rule-2' }) + ); + + const body = await getCoverageOverview(supertest, { + search_term: 'rule-2', + }); + + expect(body).to.eql({ + coverage: {}, + unmapped_rule_ids: [expectedRule.id], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'rule-2', + }, + }, + }); }); - expect(body).to.eql({ - coverage: { - T001: [expectedRule.id], - TA001: [expectedRule.id], - 'T001.001': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Custom query rule', + it('returns response filtered by index pattern', async () => { + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', index: ['index-pattern-1'] }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-2', index: ['index-pattern-2'] }) + ); + + const body = await getCoverageOverview(supertest, { + search_term: 'index-pattern-2', + }); + + expect(body).to.eql({ + coverage: {}, + unmapped_rule_ids: [expectedRule.id], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, }, - }, + }); }); }); - it('returns response filtered by enabled rules', async () => { - await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) - ); - const expectedRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ - rule_id: 'rule-2', - enabled: true, - threat: generateThreatArray(2), - }) - ); - - const body = await getCoverageOverview(supertest, { - activity: [CoverageOverviewRuleActivity.Enabled], - }); - - expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'enabled', - name: 'Custom query rule', + describe('activity', () => { + it('returns response filtered by disabled rules', async () => { + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, + threat: generateThreatArray(2), + }) + ); + + const body = await getCoverageOverview(supertest, { + activity: [CoverageOverviewRuleActivity.Disabled], + }); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule.id], + TA001: [expectedRule.id], + 'T001.001': [expectedRule.id], }, - }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, + }, + }); }); - }); - it('returns all rules if both enabled and disabled filters are specified in the request', async () => { - const expectedRule1 = await createRule( - supertest, - log, - getCustomQueryRuleParams({ - rule_id: 'rule-1', - enabled: false, - name: 'Disabled rule', - threat: generateThreatArray(1), - }) - ); - const expectedRule2 = await createRule( - supertest, - log, - getCustomQueryRuleParams({ - rule_id: 'rule-2', - enabled: true, - name: 'Enabled rule', - threat: generateThreatArray(2), - }) - ); - - const body = await getCoverageOverview(supertest, { - activity: [CoverageOverviewRuleActivity.Enabled, CoverageOverviewRuleActivity.Disabled], + it('returns response filtered by enabled rules', async () => { + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(1) }) + ); + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, + threat: generateThreatArray(2), + }) + ); + + const body = await getCoverageOverview(supertest, { + activity: [CoverageOverviewRuleActivity.Enabled], + }); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'enabled', + name: 'Custom query rule', + }, + }, + }); }); - expect(body).to.eql({ - coverage: { - T001: [expectedRule1.id], - TA001: [expectedRule1.id], - 'T001.001': [expectedRule1.id], - T002: [expectedRule2.id], - TA002: [expectedRule2.id], - 'T002.002': [expectedRule2.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule1.id]: { - activity: 'disabled', + it('returns all rules if both enabled and disabled filters are specified in the request', async () => { + const expectedRule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-1', + enabled: false, name: 'Disabled rule', - }, - [expectedRule2.id]: { - activity: 'enabled', + threat: generateThreatArray(1), + }) + ); + const expectedRule2 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, name: 'Enabled rule', + threat: generateThreatArray(2), + }) + ); + + const body = await getCoverageOverview(supertest, { + activity: [ + CoverageOverviewRuleActivity.Enabled, + CoverageOverviewRuleActivity.Disabled, + ], + }); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule1.id], + TA001: [expectedRule1.id], + 'T001.001': [expectedRule1.id], + T002: [expectedRule2.id], + TA002: [expectedRule2.id], + 'T002.002': [expectedRule2.id], }, - }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule1.id]: { + activity: 'disabled', + name: 'Disabled rule', + }, + [expectedRule2.id]: { + activity: 'enabled', + name: 'Enabled rule', + }, + }, + }); }); - }); - it('returns all rules if neither enabled and disabled filters are specified in the request', async () => { - const expectedRule1 = await createRule( - supertest, - log, - getCustomQueryRuleParams({ - rule_id: 'rule-1', - enabled: false, - name: 'Disabled rule', - threat: generateThreatArray(1), - }) - ); - const expectedRule2 = await createRule( - supertest, - log, - getCustomQueryRuleParams({ - rule_id: 'rule-2', - enabled: true, - name: 'Enabled rule', - threat: generateThreatArray(2), - }) - ); - - const body = await getCoverageOverview(supertest); - - expect(body).to.eql({ - coverage: { - T001: [expectedRule1.id], - TA001: [expectedRule1.id], - 'T001.001': [expectedRule1.id], - T002: [expectedRule2.id], - TA002: [expectedRule2.id], - 'T002.002': [expectedRule2.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule1.id]: { - activity: 'disabled', + it('returns all rules if neither enabled and disabled filters are specified in the request', async () => { + const expectedRule1 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-1', + enabled: false, name: 'Disabled rule', - }, - [expectedRule2.id]: { - activity: 'enabled', + threat: generateThreatArray(1), + }) + ); + const expectedRule2 = await createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: 'rule-2', + enabled: true, name: 'Enabled rule', + threat: generateThreatArray(2), + }) + ); + + const body = await getCoverageOverview(supertest); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule1.id], + TA001: [expectedRule1.id], + 'T001.001': [expectedRule1.id], + T002: [expectedRule2.id], + TA002: [expectedRule2.id], + 'T002.002': [expectedRule2.id], }, - }, - }); - }); - }); - - describe('source', () => { - it('returns response filtered by custom rules', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'prebuilt-rule-1', - threat: generateThreatArray(1), - }), - ]); - await installPrebuiltRulesAndTimelines(es, supertest); - - const expectedRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) - ); - - const body = await getCoverageOverview(supertest, { - source: [CoverageOverviewRuleSource.Custom], - }); - - expect(body).to.eql({ - coverage: { - T002: [expectedRule.id], - TA002: [expectedRule.id], - 'T002.002': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Custom query rule', + unmapped_rule_ids: [], + rules_data: { + [expectedRule1.id]: { + activity: 'disabled', + name: 'Disabled rule', + }, + [expectedRule2.id]: { + activity: 'enabled', + name: 'Enabled rule', + }, }, - }, + }); }); }); - it('returns response filtered by prebuilt rules', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'prebuilt-rule-1', - threat: generateThreatArray(1), - }), - ]); - const { - results: { created }, - } = await installPrebuiltRules(es, supertest); - const expectedRule = created[0]; - - await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) - ); - - const body = await getCoverageOverview(supertest, { - source: [CoverageOverviewRuleSource.Prebuilt], - }); - - expect(body).to.eql({ - coverage: { - T001: [expectedRule.id], - TA001: [expectedRule.id], - 'T001.001': [expectedRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedRule.id]: { - activity: 'disabled', - name: 'Query with a rule id', + describe('source', () => { + it('returns response filtered by custom rules', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + await installPrebuiltRulesAndTimelines(es, supertest); + + const expectedRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest, { + source: [CoverageOverviewRuleSource.Custom], + }); + + expect(body).to.eql({ + coverage: { + T002: [expectedRule.id], + TA002: [expectedRule.id], + 'T002.002': [expectedRule.id], }, - }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, + }, + }); }); - }); - it('returns all rules if both custom and prebuilt filters are specified in the request', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'prebuilt-rule-1', - threat: generateThreatArray(1), - }), - ]); - const { - results: { created }, - } = await installPrebuiltRules(es, supertest); - const expectedPrebuiltRule = created[0]; - - const expectedCustomRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) - ); - - const body = await getCoverageOverview(supertest, { - source: [CoverageOverviewRuleSource.Prebuilt, CoverageOverviewRuleSource.Custom], + it('returns response filtered by prebuilt rules', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedRule = created[0]; + + await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest, { + source: [CoverageOverviewRuleSource.Prebuilt], + }); + + expect(body).to.eql({ + coverage: { + T001: [expectedRule.id], + TA001: [expectedRule.id], + 'T001.001': [expectedRule.id], + }, + unmapped_rule_ids: [], + rules_data: { + [expectedRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + }, + }); }); - expect(body).to.eql({ - coverage: { - T001: [expectedPrebuiltRule.id], - TA001: [expectedPrebuiltRule.id], - 'T001.001': [expectedPrebuiltRule.id], - T002: [expectedCustomRule.id], - TA002: [expectedCustomRule.id], - 'T002.002': [expectedCustomRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedPrebuiltRule.id]: { - activity: 'disabled', - name: 'Query with a rule id', + it('returns all rules if both custom and prebuilt filters are specified in the request', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedPrebuiltRule = created[0]; + + const expectedCustomRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest, { + source: [CoverageOverviewRuleSource.Prebuilt, CoverageOverviewRuleSource.Custom], + }); + + expect(body).to.eql({ + coverage: { + T001: [expectedPrebuiltRule.id], + TA001: [expectedPrebuiltRule.id], + 'T001.001': [expectedPrebuiltRule.id], + T002: [expectedCustomRule.id], + TA002: [expectedCustomRule.id], + 'T002.002': [expectedCustomRule.id], }, - [expectedCustomRule.id]: { - activity: 'disabled', - name: 'Custom query rule', + unmapped_rule_ids: [], + rules_data: { + [expectedPrebuiltRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + [expectedCustomRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, }, - }, + }); }); - }); - it('returns all rules if neither custom and prebuilt filters are specified in the request', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'prebuilt-rule-1', - threat: generateThreatArray(1), - }), - ]); - const { - results: { created }, - } = await installPrebuiltRules(es, supertest); - const expectedPrebuiltRule = created[0]; - - const expectedCustomRule = await createRule( - supertest, - log, - getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) - ); - - const body = await getCoverageOverview(supertest); - - expect(body).to.eql({ - coverage: { - T001: [expectedPrebuiltRule.id], - TA001: [expectedPrebuiltRule.id], - 'T001.001': [expectedPrebuiltRule.id], - T002: [expectedCustomRule.id], - TA002: [expectedCustomRule.id], - 'T002.002': [expectedCustomRule.id], - }, - unmapped_rule_ids: [], - rules_data: { - [expectedPrebuiltRule.id]: { - activity: 'disabled', - name: 'Query with a rule id', + it('returns all rules if neither custom and prebuilt filters are specified in the request', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ + rule_id: 'prebuilt-rule-1', + threat: generateThreatArray(1), + }), + ]); + const { + results: { created }, + } = await installPrebuiltRules(es, supertest); + const expectedPrebuiltRule = created[0]; + + const expectedCustomRule = await createRule( + supertest, + log, + getCustomQueryRuleParams({ rule_id: 'rule-1', threat: generateThreatArray(2) }) + ); + + const body = await getCoverageOverview(supertest); + + expect(body).to.eql({ + coverage: { + T001: [expectedPrebuiltRule.id], + TA001: [expectedPrebuiltRule.id], + 'T001.001': [expectedPrebuiltRule.id], + T002: [expectedCustomRule.id], + TA002: [expectedCustomRule.id], + 'T002.002': [expectedCustomRule.id], }, - [expectedCustomRule.id]: { - activity: 'disabled', - name: 'Custom query rule', + unmapped_rule_ids: [], + rules_data: { + [expectedPrebuiltRule.id]: { + activity: 'disabled', + name: 'Query with a rule id', + }, + [expectedCustomRule.id]: { + activity: 'disabled', + name: 'Custom query rule', + }, }, - }, + }); }); }); }); - }); - describe('error cases', async () => { - it('throws error when request body is not valid', async () => { - const { body } = await supertest - .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .set('x-elastic-internal-origin', 'foo') - .send({ filter: { source: ['give me all the rules'] } }) - .expect(400); + describe('error cases', async () => { + it('throws error when request body is not valid', async () => { + const { body } = await supertest + .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') + .send({ filter: { source: ['give me all the rules'] } }) + .expect(400); - expect(body).to.eql({ - statusCode: 400, - error: 'Bad Request', - message: - '[request body]: Invalid value "give me all the rules" supplied to "filter,source"', + expect(body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + '[request body]: Invalid value "give me all the rules" supplied to "filter,source"', + }); }); }); }); From e18f5732c3c5c552453fdece998de0df6753e83b Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 4 Jan 2024 13:45:24 -0700 Subject: [PATCH 32/34] fixes script conflict --- .../security_solution_api_integration/package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index f30c2862c907a..53acd95fe2515 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -59,11 +59,6 @@ "alerts:qa:serverless": "npm run run-tests:dr:default alerts serverless qaEnv", "alerts:server:ess": "npm run initialize-server:dr:default alerts ess", "alerts:runner:ess": "npm run run-tests:dr:default alerts ess essEnv", - "rule_management:server:serverless": "npm run initialize-server:dr:basicEssentials rule_management serverless", - "rule_management:runner:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless serverlessEnv", - "rule_management:qa:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless qaEnv", - "rule_management:server:ess": "npm run initialize-server:dr:basicEssentials rule_management ess", - "rule_management:runner:ess": "npm run run-tests:dr:basicEssentials rule_management ess essEnv", "entity_analytics:server:serverless": "npm run initialize-server:ea:default risk_engine serverless", "entity_analytics:runner:serverless": "npm run run-tests:ea:default risk_engine serverless serverlessEnv", "entity_analytics:qa:serverless": "npm run run-tests:ea:default risk_engine serverless qaEnv", @@ -161,6 +156,12 @@ "detection_engine_basicessentionals:qa:serverless": "npm run run-tests:dr:basicEssentials detection_engine serverless qaEnv", "detection_engine_basicessentionals:server:ess": "npm run initialize-server:dr:basicEssentials detection_engine ess", "detection_engine_basicessentionals:runner:ess": "npm run run-tests:dr:basicEssentials detection_engine ess essEnv", + + "rule_management_basicessentionals:server:serverless": "npm run initialize-server:dr:basicEssentials rule_management serverless", + "rule_management_basicessentionals:runner:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless serverlessEnv", + "rule_management_basicessentionals:qa:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless qaEnv", + "rule_management_basicessentionals:server:ess": "npm run initialize-server:dr:basicEssentials rule_management ess", + "rule_management_basicessentionals:runner:ess": "npm run run-tests:dr:basicEssentials rule_management ess essEnv", "exception_lists_items:server:serverless": "npm run initialize-server:lists:default exception_lists_items serverless", "exception_lists_items:runner:serverless": "npm run run-tests:lists:default exception_lists_items serverless serverlessEnv", From 6cd40c311fa75ed5a7d40be8cb9b6bd09fb5abc8 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 5 Jan 2024 15:48:19 -0700 Subject: [PATCH 33/34] addresses comments --- .../coverage_overview_dashboard.tsx | 7 +- .../coverage_overview/coverage_overview.cy.ts | 78 +++++++++++-------- .../screens/rules_coverage_overview.ts | 4 +- .../cypress/tasks/rules_coverage_overview.ts | 5 ++ 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx index 11ee8f0d70bbc..fb14abd42ee61 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx @@ -40,7 +40,12 @@ const CoverageOverviewDashboardComponent = () => { {data?.mitreTactics.map((tactic) => ( - + diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts index 8a935e203fe83..6a52134d4d738 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts @@ -17,7 +17,6 @@ import { COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS, COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS, COVERAGE_OVERVIEW_TACTIC_PANEL, - COVERAGE_OVERVIEW_TECHNIQUE_TITLE, } from '../../../../screens/rules_coverage_overview'; import { createRule } from '../../../../tasks/api_calls/rules'; import { visit } from '../../../../tasks/navigation'; @@ -37,6 +36,7 @@ import { enableAllDisabledRules, filterCoverageOverviewBySearchBar, openTechniquePanelByName, + openTechniquePanelByNameAndTacticId, selectCoverageOverviewActivityFilterOption, selectCoverageOverviewSourceFilterOption, } from '../../../../tasks/rules_coverage_overview'; @@ -48,8 +48,8 @@ const EnabledCustomRuleMitreData = getMockThreatData()[2]; const DisabledCustomRuleMitreData = getMockThreatData()[3]; // Mitre data used for duplicate technique tests -const DuplicateTechniqueMitreData1 = getDuplicateTechniqueThreatData()[0]; -const DuplicateTechniqueMitreData2 = getDuplicateTechniqueThreatData()[1]; +const DuplicateTechniqueMitreData1 = getDuplicateTechniqueThreatData()[1]; +const DuplicateTechniqueMitreData2 = getDuplicateTechniqueThreatData()[0]; const MockEnabledPrebuiltRuleThreat: Threat = { framework: 'MITRE ATT&CK', @@ -233,7 +233,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { describe('filtering tests', () => { it('filters for all data', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule'); @@ -250,7 +250,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { it('filters for disabled and prebuilt rules', () => { selectCoverageOverviewActivityFilterOption('Enabled rules'); // Disables default filter - selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); @@ -273,7 +273,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { }); it('filters for only prebuilt rules', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); @@ -294,7 +294,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { }); it('filters for only custom rules', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load selectCoverageOverviewSourceFilterOption('Elastic rules'); // Disables default filter openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name); @@ -338,7 +338,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { }); it('enables all disabled rules', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name); enableAllDisabledRules(); @@ -355,6 +355,10 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { }); describe('with rules that have identical mitre techniques that belong to multiple tactics', () => { + const SharedTechniqueName = DuplicateTechniqueMitreData1.technique.name; + const TacticOfRule1 = DuplicateTechniqueMitreData1.tactic; + const TacticOfRule2 = DuplicateTechniqueMitreData2.tactic; + beforeEach(() => { login(); deleteAlertsAndRules(); @@ -363,14 +367,14 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { getNewRule({ rule_id: 'duplicate_technique_rule_1', enabled: true, - name: 'Rule with tactic 1', + name: 'Rule under Persistence tactic', threat: [MockCustomRuleDuplicateTechniqueThreat1], }) ); createRule( getNewRule({ rule_id: 'duplicate_technique_rule_2', - name: 'Rule with tactic 2', + name: 'Rule under Privilege Escalation tactic', enabled: false, threat: [MockCustomRuleDuplicateTechniqueThreat2], }) @@ -379,49 +383,55 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => { }); it('technique panels render unique rule data', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); + // Tests to make sure each rule only exists in the specific technique and tactic that's assigned to it - // Open duplicated technique panel under first tactic - cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData1.technique.id)) - .first() - .click(); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) - .contains('Rule with tactic 1') - .should('not.exist'); - cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Rule with tactic 2'); + selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load - // Open duplicated technique panel under second tactic - cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData2.technique.id)) - .last() - .click(); - cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule with tactic 1'); + // Open duplicated technique panel under Persistence tactic + openTechniquePanelByNameAndTacticId(SharedTechniqueName, TacticOfRule1.id); + + // Only rule 1 data is present + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule under Persistence tactic'); cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES) - .contains('Rule with tactic 2') + .contains('Rule under Privilege Escalation tactic') .should('not.exist'); + + // Open duplicated technique panel under Privilege Escalation tactic + openTechniquePanelByNameAndTacticId(SharedTechniqueName, TacticOfRule2.id); + + // Only rule 2 data is present + cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES) + .contains('Rule under Persistence tactic') + .should('not.exist'); + cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains( + 'Rule under Privilege Escalation tactic' + ); }); it('tactic panels render correct rule stats', () => { - selectCoverageOverviewActivityFilterOption('Disabled rules'); + selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load - // Validate rule count stats for first tactic + // Validate rule count stats for the Persistence tactic only show stats based on its own technique + // Enabled rule count cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) - .contains(DuplicateTechniqueMitreData1.tactic.name) + .contains(TacticOfRule1.name) .get(COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS) .contains('0'); - + // Disabled rule count cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) - .contains(DuplicateTechniqueMitreData1.tactic.name) + .contains(TacticOfRule1.name) .get(COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS) .contains('1'); - // Validate rule count stats for second tactic + // Validate rule count stats for the Privilege Escalation tactic only show stats based on its own technique + // Enabled rule count cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) - .contains(DuplicateTechniqueMitreData2.tactic.name) + .contains(TacticOfRule2.name) .get(COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS) .contains('1'); - + // Disabled rule count cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL) - .contains(DuplicateTechniqueMitreData2.tactic.name) + .contains(TacticOfRule2.name) .get(COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS) .contains('0'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts index 2bcfa86cf0f9f..bf696867e61cf 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts @@ -8,8 +8,8 @@ export const COVERAGE_OVERVIEW_TECHNIQUE_PANEL = '[data-test-subj="coverageOverviewTechniquePanel"]'; -export const COVERAGE_OVERVIEW_TECHNIQUE_TITLE = (id: string) => - `[data-test-subj="coverageOverviewTechniqueTitle-${id}"]`; +export const COVERAGE_OVERVIEW_TECHNIQUE_PANEL_IN_TACTIC_GROUP = (id: string) => + `[data-test-subj="coverageOverviewTacticGroup-${id}"] [data-test-subj="coverageOverviewTechniquePanel"]`; export const COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES = '[data-test-subj="coverageOverviewEnabledRulesList"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts index 3f9b9f472940f..5f8f18bb8a36b 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts @@ -11,6 +11,7 @@ import { COVERAGE_OVERVIEW_FILTER_LIST, COVERAGE_OVERVIEW_SEARCH_BAR, COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON, + COVERAGE_OVERVIEW_TECHNIQUE_PANEL_IN_TACTIC_GROUP, COVERAGE_OVERVIEW_TECHNIQUE_PANEL, } from '../screens/rules_coverage_overview'; import { LOADING_INDICATOR } from '../screens/security_header'; @@ -19,6 +20,10 @@ export const openTechniquePanelByName = (label: string) => { cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL).contains(label).click(); }; +export const openTechniquePanelByNameAndTacticId = (label: string, tacticId: string) => { + cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL_IN_TACTIC_GROUP(tacticId)).contains(label).click(); +}; + export const selectCoverageOverviewActivityFilterOption = (option: string) => { cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // open filter popover cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click(); From 636311cc773cc77422e47b8f774091f4e5c8c230 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 8 Jan 2024 11:37:01 -0700 Subject: [PATCH 34/34] merge conflicts --- .../detections_response/utils/rules/create_non_security_rule.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts index 89bb2bbea5725..09bc0f9b81a6d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts @@ -18,6 +18,7 @@ const SIMPLE_APM_RULE_DATA = { windowSize: 30, windowUnit: 'm', anomalySeverityType: 'critical', + anomalyDetectorTypes: ['txLatency'], environment: 'ENVIRONMENT_ALL', }, schedule: {