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();