Skip to content

Commit

Permalink
[8.12] [Security Solution] Adds tests for coverage overview page (#16…
Browse files Browse the repository at this point in the history
…8058) (#174549)

# Backport

This will backport the following commits from `main` to `8.12`:
- [[Security Solution] Adds tests for coverage overview page
(#168058)](#168058)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Davis
Plumlee","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-01-09T19:09:40Z","message":"[Security
Solution] Adds tests for coverage overview page (#168058)\n\n**Resolves:
https://github.com/elastic/kibana/issues/162250**\r\n\r\n##
Summary\r\n\r\nAdds remaining unit, api integration, and e2e cypress
tests for the\r\ncoverage overview page in accordance to the [existing
test\r\nplan](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management/coverage_overview_dashboard.md)\r\n\r\n-
[Flaky test
runner\r\nbuild](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/4756)\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"315cf9d399c62a69dd02a1aed5ce7118ffa9885a","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["technical
debt","release_note:skip","Team:Detections and Resp","Team:
SecuritySolution","Feature:Rule Management","Team:Detection Rule
Management","v8.12.0","v8.13.0"],"number":168058,"url":"https://github.com/elastic/kibana/pull/168058","mergeCommit":{"message":"[Security
Solution] Adds tests for coverage overview page (#168058)\n\n**Resolves:
https://github.com/elastic/kibana/issues/162250**\r\n\r\n##
Summary\r\n\r\nAdds remaining unit, api integration, and e2e cypress
tests for the\r\ncoverage overview page in accordance to the [existing
test\r\nplan](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management/coverage_overview_dashboard.md)\r\n\r\n-
[Flaky test
runner\r\nbuild](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/4756)\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"315cf9d399c62a69dd02a1aed5ce7118ffa9885a"}},"sourceBranch":"main","suggestedTargetBranches":["8.12"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/168058","number":168058,"mergeCommit":{"message":"[Security
Solution] Adds tests for coverage overview page (#168058)\n\n**Resolves:
https://github.com/elastic/kibana/issues/162250**\r\n\r\n##
Summary\r\n\r\nAdds remaining unit, api integration, and e2e cypress
tests for the\r\ncoverage overview page in accordance to the [existing
test\r\nplan](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management/coverage_overview_dashboard.md)\r\n\r\n-
[Flaky test
runner\r\nbuild](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/4756)\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"315cf9d399c62a69dd02a1aed5ce7118ffa9885a"}}]}]
BACKPORT-->
  • Loading branch information
dplumlee authored Jan 10, 2024
1 parent cec6529 commit 8233fd0
Show file tree
Hide file tree
Showing 26 changed files with 1,624 additions and 567 deletions.
2 changes: 2 additions & 0 deletions .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/ess.config.ts
- 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/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/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/ess.config.ts

Expand Down
Original file line number Diff line number Diff line change
@@ -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({});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -29,6 +31,8 @@ export const useFetchCoverageOverviewQuery = (
filter: CoverageOverviewFilter = {},
options?: UseQueryOptions<CoverageOverviewDashboard>
) => {
const { addError } = useAppToasts();

return useQuery<CoverageOverviewDashboard>(
[...COVERAGE_OVERVIEW_QUERY_KEY, filter],
async ({ signal }) => {
Expand All @@ -39,6 +43,11 @@ export const useFetchCoverageOverviewQuery = (
{
...DEFAULT_QUERY_OPTIONS,
...options,
onError: (error) => {
addError(error, {
title: i18n.COVERAGE_OVERVIEW_FETCH_ERROR_TITLE,
});
},
}
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ const CoverageOverviewDashboardComponent = () => {
<EuiSpacer />
<EuiFlexGroup gutterSize="m" className="eui-xScroll">
{data?.mitreTactics.map((tactic) => (
<EuiFlexGroup direction="column" key={tactic.id} gutterSize="s">
<EuiFlexGroup
data-test-subj={`coverageOverviewTacticGroup-${tactic.id}`}
direction="column"
key={tactic.id}
gutterSize="s"
>
<EuiFlexItem grow={false}>
<CoverageOverviewTacticPanel tactic={tactic} />
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const RuleSourceFilterComponent = ({
`}
>
<EuiPopover
id="ruleActivityFilterPopover"
id="ruleSourceFilterPopover"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const CoverageOverviewMitreTechniquePanelComponent = ({
>
<EuiFlexGroup css={{ height: '100%' }} direction="column" justifyContent="spaceBetween">
<EuiFlexItem>
<EuiText size="xs">
<EuiText data-test-subj={`coverageOverviewTechniqueTitle-${technique.id}`} size="xs">
<h4>{technique.name}</h4>
</EuiText>
{SubtechniqueInfo}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
},
},
});
];
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading

0 comments on commit 8233fd0

Please sign in to comment.