diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 7314277c440f2..86bc1d4682854 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -89,15 +89,6 @@ export const allowedExperimentalValues = Object.freeze({ **/ newUserDetailsFlyout: false, - /** - * Enables Protections/Detections Coverage Overview page (Epic link https://github.com/elastic/security-team/issues/2905) - * - * This flag aims to facilitate the development process as the feature may not make it to 8.10 release. - * - * The flag doesn't have to be documented and has to be removed after the feature is ready to release. - */ - detectionsCoverageOverview: true, - /** * Enable risk engine client and initialisation of datastream, component templates and mappings */ diff --git a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx index 82d4c5b5a3e27..f5595ac4a826c 100644 --- a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx @@ -29,3 +29,7 @@ export const MlJobCompatibilityLink = () => ( linkText={i18n.ML_JOB_COMPATIBILITY_LINK_TEXT} /> ); + +export const CoverageOverviewLink = () => ( + +); diff --git a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts index a495d95b3cf37..fcb17b6db540f 100644 --- a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts @@ -41,3 +41,11 @@ export const ML_JOB_COMPATIBILITY_LINK_TEXT = i18n.translate( defaultMessage: 'ML job compatibility', } ); + +export const COVERAGE_OVERVIEW_LINK_PATH = 'rules-coverage.html'; +export const COVERAGE_OVERVIEW_LINK_TEXT = i18n.translate( + 'xpack.securitySolution.documentationLinks.coverageOverview.text', + { + defaultMessage: 'Learn more.', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts index 7a775b3292991..acae46d0f3701 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts @@ -15,6 +15,24 @@ import type { CoverageOverviewMitreSubTechnique } from '../../model/coverage_ove import type { CoverageOverviewMitreTactic } from '../../model/coverage_overview/mitre_tactic'; import type { CoverageOverviewMitreTechnique } from '../../model/coverage_overview/mitre_technique'; +// The order the tactic columns will appear in on the coverage overview page +const tacticOrder = [ + 'TA0043', + 'TA0042', + 'TA0001', + 'TA0002', + 'TA0003', + 'TA0004', + 'TA0005', + 'TA0006', + 'TA0007', + 'TA0008', + 'TA0009', + 'TA0011', + 'TA0010', + 'TA0040', +]; + export function buildCoverageOverviewMitreGraph( tactics: MitreTactic[], techniques: MitreTechnique[], @@ -67,9 +85,13 @@ export function buildCoverageOverviewMitreGraph( } } + const sortedTactics = tactics.sort( + (a, b) => tacticOrder.indexOf(a.id) - tacticOrder.indexOf(b.id) + ); + const result: CoverageOverviewMitreTactic[] = []; - for (const tactic of tactics) { + for (const tactic of sortedTactics) { result.push({ id: tactic.id, name: tactic.name, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts new file mode 100644 index 0000000000000..01794b34c8ee7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts @@ -0,0 +1,32 @@ +/* + * 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 { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; +import { getTotalRuleCount } from './mitre_technique'; +import { getMockCoverageOverviewMitreTechnique } from './__mocks__'; + +describe('getTotalRuleCount', () => { + it('returns count of all rules when no activity filter is present', () => { + const payload = getMockCoverageOverviewMitreTechnique(); + expect(getTotalRuleCount(payload)).toEqual(2); + }); + + it('returns count of one rule type when an activity filter is present', () => { + const payload = getMockCoverageOverviewMitreTechnique(); + expect(getTotalRuleCount(payload, [CoverageOverviewRuleActivity.Disabled])).toEqual(1); + }); + + it('returns count of multiple rule type when multiple activity filter is present', () => { + const payload = getMockCoverageOverviewMitreTechnique(); + expect( + getTotalRuleCount(payload, [ + CoverageOverviewRuleActivity.Enabled, + CoverageOverviewRuleActivity.Disabled, + ]) + ).toEqual(2); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts index 35587ca59fdda..589629d643810 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; import type { CoverageOverviewMitreSubTechnique } from './mitre_subtechnique'; import type { CoverageOverviewRule } from './rule'; @@ -20,3 +21,20 @@ export interface CoverageOverviewMitreTechnique { disabledRules: CoverageOverviewRule[]; availableRules: CoverageOverviewRule[]; } + +export const getTotalRuleCount = ( + technique: CoverageOverviewMitreTechnique, + activity?: CoverageOverviewRuleActivity[] +): number => { + if (!activity) { + return technique.enabledRules.length + technique.disabledRules.length; + } + let totalRuleCount = 0; + if (activity.includes(CoverageOverviewRuleActivity.Enabled)) { + totalRuleCount += technique.enabledRules.length; + } + if (activity.includes(CoverageOverviewRuleActivity.Disabled)) { + totalRuleCount += technique.disabledRules.length; + } + return totalRuleCount; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts index 7c170579a217a..3bbc2f5dfbfb9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts @@ -11,8 +11,6 @@ import { } from '../../../../../common/api/detection_engine'; import * as i18n from './translations'; -export const coverageOverviewPaletteColors = ['#00BFB326', '#00BFB34D', '#00BFB399', '#00BFB3']; - export const coverageOverviewPanelWidth = 160; export const coverageOverviewLegendWidth = 380; @@ -25,10 +23,10 @@ export const coverageOverviewFilterWidth = 300; * A corresponding color is applied if rules count >= a specific threshold */ export const coverageOverviewCardColorThresholds = [ - { threshold: 10, color: coverageOverviewPaletteColors[3] }, - { threshold: 7, color: coverageOverviewPaletteColors[2] }, - { threshold: 3, color: coverageOverviewPaletteColors[1] }, - { threshold: 1, color: coverageOverviewPaletteColors[0] }, + { threshold: 10, color: '#00BFB3' }, + { threshold: 7, color: '#00BFB399' }, + { threshold: 3, color: '#00BFB34D' }, + { threshold: 1, color: '#00BFB326' }, ]; export const ruleActivityFilterDefaultOptions = [ 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 6762ee6b1b0e2..11ee8f0d70bbc 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 @@ -5,7 +5,8 @@ * 2.0. */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import { CoverageOverviewLink } from '../../../../common/components/links_to_docs'; import { HeaderPage } from '../../../../common/components/header_page'; import * as i18n from './translations'; @@ -14,26 +15,27 @@ import { CoverageOverviewMitreTechniquePanelPopover } from './technique_panel_po import { CoverageOverviewFiltersPanel } from './filters_panel'; import { useCoverageOverviewDashboardContext } from './coverage_overview_dashboard_context'; +const CoverageOverviewHeaderComponent = () => ( + + {i18n.CoverageOverviewDashboardInformation} + + } + /> +); + +const CoverageOverviewHeader = React.memo(CoverageOverviewHeaderComponent); + const CoverageOverviewDashboardComponent = () => { const { state: { data }, } = useCoverageOverviewDashboardContext(); - const subtitle = ( - - {i18n.CoverageOverviewDashboardInformation}{' '} - - {i18n.CoverageOverviewDashboardInformationLink} - - - ); + return ( <> - + diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts index 738eb981f37bb..5a1aee424352a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; +import type { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; import { getCoverageOverviewFilterMock } from '../../../../../common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.mock'; import { getMockCoverageOverviewMitreSubTechnique, @@ -17,7 +17,6 @@ import { extractSelected, getNumOfCoveredSubtechniques, getNumOfCoveredTechniques, - getTotalRuleCount, populateSelected, } from './helpers'; @@ -89,26 +88,4 @@ describe('helpers', () => { ]); }); }); - - describe('getTotalRuleCount', () => { - it('returns count of all rules when no activity filter is present', () => { - const payload = getMockCoverageOverviewMitreTechnique(); - expect(getTotalRuleCount(payload)).toEqual(2); - }); - - it('returns count of one rule type when an activity filter is present', () => { - const payload = getMockCoverageOverviewMitreTechnique(); - expect(getTotalRuleCount(payload, [CoverageOverviewRuleActivity.Disabled])).toEqual(1); - }); - - it('returns count of multiple rule type when multiple activity filter is present', () => { - const payload = getMockCoverageOverviewMitreTechnique(); - expect( - getTotalRuleCount(payload, [ - CoverageOverviewRuleActivity.Enabled, - CoverageOverviewRuleActivity.Disabled, - ]) - ).toEqual(2); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts index 7e8f757561a78..82d50e7b9721b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts @@ -6,8 +6,10 @@ */ import type { EuiSelectableOption } from '@elastic/eui'; -import type { CoverageOverviewRuleSource } from '../../../../../common/api/detection_engine'; -import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; +import type { + CoverageOverviewRuleActivity, + CoverageOverviewRuleSource, +} from '../../../../../common/api/detection_engine'; import type { CoverageOverviewMitreTactic } from '../../../rule_management/model/coverage_overview/mitre_tactic'; import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique'; import { coverageOverviewCardColorThresholds } from './constants'; @@ -41,20 +43,3 @@ export const populateSelected = ( allOptions.map((option) => selected.includes(option.label) ? { ...option, checked: 'on' } : option ); - -export const getTotalRuleCount = ( - technique: CoverageOverviewMitreTechnique, - activity?: CoverageOverviewRuleActivity[] -): number => { - if (!activity) { - return technique.enabledRules.length + technique.disabledRules.length; - } - let totalRuleCount = 0; - if (activity.includes(CoverageOverviewRuleActivity.Enabled)) { - totalRuleCount += technique.enabledRules.length; - } - if (activity.includes(CoverageOverviewRuleActivity.Disabled)) { - totalRuleCount += technique.disabledRules.length; - } - return totalRuleCount; -}; 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 e182b6445513c..821f0f18dcd72 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 @@ -9,9 +9,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; import { css } from '@emotion/css'; import React, { memo, useCallback, useMemo } from 'react'; import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique'; +import { getTotalRuleCount } from '../../../rule_management/model/coverage_overview/mitre_technique'; import { coverageOverviewPanelWidth } from './constants'; import { useCoverageOverviewDashboardContext } from './coverage_overview_dashboard_context'; -import { getCardBackgroundColor, getTotalRuleCount } from './helpers'; +import { getCardBackgroundColor } from './helpers'; import { CoverageOverviewPanelRuleStats } from './shared_components/panel_rule_stats'; import * as i18n from './translations'; 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 c3e205531fdce..eb9f06c350421 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 @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export const COVERAGE_OVERVIEW_DASHBOARD_TITLE = i18n.translate( 'xpack.securitySolution.coverageOverviewDashboard.pageTitle', { - defaultMessage: 'MITRE ATT&CK\u00AE Coverage', + defaultMessage: 'MITRE ATT&CK\u00AE coverage', } ); @@ -174,13 +174,6 @@ export const CoverageOverviewDashboardInformation = i18n.translate( 'xpack.securitySolution.coverageOverviewDashboard.dashboardInformation', { defaultMessage: - 'The interactive MITRE ATT&CK coverage below shows the current state of your coverage from installed rules, click on a cell to view further details. Unmapped rules will not be displayed. View further information from our', - } -); - -export const CoverageOverviewDashboardInformationLink = i18n.translate( - 'xpack.securitySolution.coverageOverviewDashboard.dashboardInformationLink', - { - defaultMessage: 'docs.', + "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.", } ); diff --git a/x-pack/plugins/security_solution/public/rules/links.ts b/x-pack/plugins/security_solution/public/rules/links.ts index d5ae12b4b1ff0..14d4c955e4a3b 100644 --- a/x-pack/plugins/security_solution/public/rules/links.ts +++ b/x-pack/plugins/security_solution/public/rules/links.ts @@ -103,7 +103,6 @@ export const links: LinkItem = { defaultMessage: 'MITRE ATT&CK Coverage', }), ], - experimentalKey: 'detectionsCoverageOverview', }, ], categories: [ diff --git a/x-pack/plugins/security_solution/public/rules/routes.tsx b/x-pack/plugins/security_solution/public/rules/routes.tsx index aefdd505c95e0..182a69c39f19b 100644 --- a/x-pack/plugins/security_solution/public/rules/routes.tsx +++ b/x-pack/plugins/security_solution/public/rules/routes.tsx @@ -31,7 +31,6 @@ import { AllRulesTabs } from '../detection_engine/rule_management_ui/components/ import { AddRulesPage } from '../detection_engine/rule_management_ui/pages/add_rules'; import type { SecuritySubPluginRoutes } from '../app/types'; import { RulesLandingPage } from './landing'; -import { useIsExperimentalFeatureEnabled } from '../common/hooks/use_experimental_features'; import { CoverageOverviewPage } from '../detection_engine/rule_management_ui/pages/coverage_overview'; const RulesSubRoutes = [ @@ -109,21 +108,13 @@ const RulesContainerComponent: React.FC = () => { const Rules = React.memo(RulesContainerComponent); -const CoverageOverviewRoutes = () => { - const isDetectionsCoverageOverviewEnabled = useIsExperimentalFeatureEnabled( - 'detectionsCoverageOverview' - ); - - return isDetectionsCoverageOverviewEnabled ? ( - - - - - - ) : ( - - ); -}; +const CoverageOverviewRoutes = () => ( + + + + + +); export const routes: SecuritySubPluginRoutes = [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts index f7c865605dc3d..ed0f19c7015b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts @@ -62,8 +62,6 @@ export const registerRuleManagementRoutes = ( // Rules filters getRuleManagementFilters(router); - // Rules dashboard - if (config.experimentalFeatures.detectionsCoverageOverview) { - getCoverageOverviewRoute(router); - } + // Rules coverage overview + getCoverageOverviewRoute(router); }; diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts index 7f3598ee8d5e8..c4c3c44f1c418 100644 --- a/x-pack/test/detection_engine_api_integration/common/config.ts +++ b/x-pack/test/detection_engine_api_integration/common/config.ts @@ -78,7 +78,6 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s 'previewTelemetryUrlEnabled', 'riskScoringPersistence', 'riskScoringRoutesEnabled', - 'detectionsCoverageOverview', ])}`, '--xpack.task_manager.poll_interval=1000', `--xpack.actions.preconfigured=${JSON.stringify({