mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution][Detection Rules] Makes threat techniques optional (#85481)
This commit is contained in:
parent
07f395f7dd
commit
13e5e55901
30 changed files with 104 additions and 223 deletions
|
@ -413,6 +413,7 @@ export const threat_tactic = t.type({
|
||||||
name: threat_tactic_name,
|
name: threat_tactic_name,
|
||||||
reference: threat_tactic_reference,
|
reference: threat_tactic_reference,
|
||||||
});
|
});
|
||||||
|
export type ThreatTactic = t.TypeOf<typeof threat_tactic>;
|
||||||
export const threat_subtechnique_id = t.string;
|
export const threat_subtechnique_id = t.string;
|
||||||
export const threat_subtechnique_name = t.string;
|
export const threat_subtechnique_name = t.string;
|
||||||
export const threat_subtechnique_reference = t.string;
|
export const threat_subtechnique_reference = t.string;
|
||||||
|
@ -421,6 +422,7 @@ export const threat_subtechnique = t.type({
|
||||||
name: threat_subtechnique_name,
|
name: threat_subtechnique_name,
|
||||||
reference: threat_subtechnique_reference,
|
reference: threat_subtechnique_reference,
|
||||||
});
|
});
|
||||||
|
export type ThreatSubtechnique = t.TypeOf<typeof threat_subtechnique>;
|
||||||
export const threat_subtechniques = t.array(threat_subtechnique);
|
export const threat_subtechniques = t.array(threat_subtechnique);
|
||||||
export const threat_technique_id = t.string;
|
export const threat_technique_id = t.string;
|
||||||
export const threat_technique_name = t.string;
|
export const threat_technique_name = t.string;
|
||||||
|
@ -439,21 +441,22 @@ export const threat_technique = t.intersection([
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
export type ThreatTechnique = t.TypeOf<typeof threat_technique>;
|
||||||
export const threat_techniques = t.array(threat_technique);
|
export const threat_techniques = t.array(threat_technique);
|
||||||
export const threat = t.array(
|
export const threat = t.exact(
|
||||||
t.exact(
|
t.type({
|
||||||
t.type({
|
framework: threat_framework,
|
||||||
framework: threat_framework,
|
tactic: threat_tactic,
|
||||||
tactic: threat_tactic,
|
technique: threat_techniques,
|
||||||
technique: threat_techniques,
|
})
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export type Threat = t.TypeOf<typeof threat>;
|
export type Threat = t.TypeOf<typeof threat>;
|
||||||
|
|
||||||
export const threatOrUndefined = t.union([threat, t.undefined]);
|
export const threats = t.array(threat);
|
||||||
export type ThreatOrUndefined = t.TypeOf<typeof threatOrUndefined>;
|
export type Threats = t.TypeOf<typeof threats>;
|
||||||
|
|
||||||
|
export const threatsOrUndefined = t.union([threats, t.undefined]);
|
||||||
|
export type ThreatsOrUndefined = t.TypeOf<typeof threatsOrUndefined>;
|
||||||
|
|
||||||
export const threshold = t.exact(
|
export const threshold = t.exact(
|
||||||
t.type({
|
t.type({
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
Tags,
|
Tags,
|
||||||
To,
|
To,
|
||||||
type,
|
type,
|
||||||
Threat,
|
Threats,
|
||||||
threshold,
|
threshold,
|
||||||
ThrottleOrNull,
|
ThrottleOrNull,
|
||||||
note,
|
note,
|
||||||
|
@ -171,7 +171,7 @@ export type AddPrepackagedRulesSchemaDecoded = Omit<
|
||||||
severity_mapping: SeverityMapping;
|
severity_mapping: SeverityMapping;
|
||||||
tags: Tags;
|
tags: Tags;
|
||||||
to: To;
|
to: To;
|
||||||
threat: Threat;
|
threat: Threats;
|
||||||
throttle: ThrottleOrNull;
|
throttle: ThrottleOrNull;
|
||||||
exceptions_list: ListArray;
|
exceptions_list: ListArray;
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
Tags,
|
Tags,
|
||||||
To,
|
To,
|
||||||
type,
|
type,
|
||||||
Threat,
|
Threats,
|
||||||
threshold,
|
threshold,
|
||||||
ThrottleOrNull,
|
ThrottleOrNull,
|
||||||
note,
|
note,
|
||||||
|
@ -193,7 +193,7 @@ export type ImportRulesSchemaDecoded = Omit<
|
||||||
severity_mapping: SeverityMapping;
|
severity_mapping: SeverityMapping;
|
||||||
tags: Tags;
|
tags: Tags;
|
||||||
to: To;
|
to: To;
|
||||||
threat: Threat;
|
threat: Threats;
|
||||||
throttle: ThrottleOrNull;
|
throttle: ThrottleOrNull;
|
||||||
version: Version;
|
version: Version;
|
||||||
exceptions_list: ListArray;
|
exceptions_list: ListArray;
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {
|
||||||
from,
|
from,
|
||||||
enabled,
|
enabled,
|
||||||
tags,
|
tags,
|
||||||
threat,
|
threats,
|
||||||
threshold,
|
threshold,
|
||||||
throttle,
|
throttle,
|
||||||
references,
|
references,
|
||||||
|
@ -98,7 +98,7 @@ export const patchRulesSchema = t.exact(
|
||||||
severity_mapping,
|
severity_mapping,
|
||||||
tags,
|
tags,
|
||||||
to,
|
to,
|
||||||
threat,
|
threat: threats,
|
||||||
threshold,
|
threshold,
|
||||||
throttle,
|
throttle,
|
||||||
timestamp_override,
|
timestamp_override,
|
||||||
|
|
|
@ -42,7 +42,7 @@ import {
|
||||||
max_signals,
|
max_signals,
|
||||||
risk_score,
|
risk_score,
|
||||||
severity,
|
severity,
|
||||||
threat,
|
threats,
|
||||||
to,
|
to,
|
||||||
references,
|
references,
|
||||||
version,
|
version,
|
||||||
|
@ -167,7 +167,7 @@ const commonParams = {
|
||||||
max_signals,
|
max_signals,
|
||||||
risk_score_mapping,
|
risk_score_mapping,
|
||||||
severity_mapping,
|
severity_mapping,
|
||||||
threat,
|
threat: threats,
|
||||||
to,
|
to,
|
||||||
references,
|
references,
|
||||||
version,
|
version,
|
||||||
|
|
|
@ -43,7 +43,7 @@ import {
|
||||||
timeline_id,
|
timeline_id,
|
||||||
timeline_title,
|
timeline_title,
|
||||||
type,
|
type,
|
||||||
threat,
|
threats,
|
||||||
threshold,
|
threshold,
|
||||||
throttle,
|
throttle,
|
||||||
job_status,
|
job_status,
|
||||||
|
@ -106,7 +106,7 @@ export const requiredRulesSchema = t.type({
|
||||||
tags,
|
tags,
|
||||||
to,
|
to,
|
||||||
type,
|
type,
|
||||||
threat,
|
threat: threats,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at,
|
||||||
created_by,
|
created_by,
|
||||||
|
|
|
@ -8,11 +8,11 @@ import { DefaultThreatArray } from './default_threat_array';
|
||||||
import { pipe } from 'fp-ts/lib/pipeable';
|
import { pipe } from 'fp-ts/lib/pipeable';
|
||||||
import { left } from 'fp-ts/lib/Either';
|
import { left } from 'fp-ts/lib/Either';
|
||||||
import { foldLeftRight, getPaths } from '../../../test_utils';
|
import { foldLeftRight, getPaths } from '../../../test_utils';
|
||||||
import { Threat } from '../common/schemas';
|
import { Threats } from '../common/schemas';
|
||||||
|
|
||||||
describe('default_threat_null', () => {
|
describe('default_threat_null', () => {
|
||||||
test('it should validate an empty array', () => {
|
test('it should validate an empty array', () => {
|
||||||
const payload: Threat = [];
|
const payload: Threats = [];
|
||||||
const decoded = DefaultThreatArray.decode(payload);
|
const decoded = DefaultThreatArray.decode(payload);
|
||||||
const message = pipe(decoded, foldLeftRight);
|
const message = pipe(decoded, foldLeftRight);
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ describe('default_threat_null', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should validate an array of threats', () => {
|
test('it should validate an array of threats', () => {
|
||||||
const payload: Threat = [
|
const payload: Threats = [
|
||||||
{
|
{
|
||||||
framework: 'MITRE ATTACK',
|
framework: 'MITRE ATTACK',
|
||||||
technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }],
|
technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }],
|
||||||
|
|
|
@ -6,16 +6,16 @@
|
||||||
|
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { Either } from 'fp-ts/lib/Either';
|
import { Either } from 'fp-ts/lib/Either';
|
||||||
import { Threat, threat } from '../common/schemas';
|
import { Threats, threats } from '../common/schemas';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types the DefaultThreatArray as:
|
* Types the DefaultThreatArray as:
|
||||||
* - If null or undefined, then an empty array will be set
|
* - If null or undefined, then an empty array will be set
|
||||||
*/
|
*/
|
||||||
export const DefaultThreatArray = new t.Type<Threat, Threat | undefined, unknown>(
|
export const DefaultThreatArray = new t.Type<Threats, Threats | undefined, unknown>(
|
||||||
'DefaultThreatArray',
|
'DefaultThreatArray',
|
||||||
threat.is,
|
threats.is,
|
||||||
(input, context): Either<t.Errors, Threat> =>
|
(input, context): Either<t.Errors, Threats> =>
|
||||||
input == null ? t.success([]) : threat.validate(input, context),
|
input == null ? t.success([]) : threats.validate(input, context),
|
||||||
t.identity
|
t.identity
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Threat } from '../common/schemas';
|
import { Threats } from '../common/schemas';
|
||||||
|
|
||||||
export const getThreatMock = (): Threat => [
|
export const getThreatMock = (): Threats => [
|
||||||
{
|
{
|
||||||
framework: 'MITRE ATT&CK',
|
framework: 'MITRE ATT&CK',
|
||||||
tactic: {
|
tactic: {
|
||||||
|
|
|
@ -18,11 +18,7 @@ import {
|
||||||
} from '../../../../../../../../src/plugins/data/public';
|
} from '../../../../../../../../src/plugins/data/public';
|
||||||
import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations';
|
import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations';
|
||||||
import { useKibana } from '../../../../common/lib/kibana';
|
import { useKibana } from '../../../../common/lib/kibana';
|
||||||
import {
|
import { AboutStepRiskScore, AboutStepSeverity } from '../../../pages/detection_engine/rules/types';
|
||||||
AboutStepRiskScore,
|
|
||||||
AboutStepSeverity,
|
|
||||||
IMitreEnterpriseAttack,
|
|
||||||
} from '../../../pages/detection_engine/rules/types';
|
|
||||||
import { FieldValueTimeline } from '../pick_timeline';
|
import { FieldValueTimeline } from '../pick_timeline';
|
||||||
import { FormSchema } from '../../../../shared_imports';
|
import { FormSchema } from '../../../../shared_imports';
|
||||||
import { ListItems } from './types';
|
import { ListItems } from './types';
|
||||||
|
@ -42,7 +38,7 @@ import {
|
||||||
import { buildMlJobDescription } from './ml_job_description';
|
import { buildMlJobDescription } from './ml_job_description';
|
||||||
import { buildActionsDescription } from './actions_description';
|
import { buildActionsDescription } from './actions_description';
|
||||||
import { buildThrottleDescription } from './throttle_description';
|
import { buildThrottleDescription } from './throttle_description';
|
||||||
import { Type } from '../../../../../common/detection_engine/schemas/common/schemas';
|
import { Threats, Type } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import { THREAT_QUERY_LABEL } from './translations';
|
import { THREAT_QUERY_LABEL } from './translations';
|
||||||
import { filterEmptyThreats } from '../../../pages/detection_engine/rules/create/helpers';
|
import { filterEmptyThreats } from '../../../pages/detection_engine/rules/create/helpers';
|
||||||
|
|
||||||
|
@ -179,7 +175,7 @@ export const getDescriptionItem = (
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
});
|
});
|
||||||
} else if (field === 'threat') {
|
} else if (field === 'threat') {
|
||||||
const threats: IMitreEnterpriseAttack[] = get(field, data);
|
const threats: Threats = get(field, data);
|
||||||
return buildThreatDescription({ label, threat: filterEmptyThreats(threats) });
|
return buildThreatDescription({ label, threat: filterEmptyThreats(threats) });
|
||||||
} else if (field === 'threshold') {
|
} else if (field === 'threshold') {
|
||||||
const threshold = get(field, data);
|
const threshold = get(field, data);
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
import { Threats } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IIndexPattern,
|
IIndexPattern,
|
||||||
Filter,
|
Filter,
|
||||||
FilterManager,
|
FilterManager,
|
||||||
} from '../../../../../../../../src/plugins/data/public';
|
} from '../../../../../../../../src/plugins/data/public';
|
||||||
import { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types';
|
|
||||||
|
|
||||||
export interface ListItems {
|
export interface ListItems {
|
||||||
title: NonNullable<ReactNode>;
|
title: NonNullable<ReactNode>;
|
||||||
|
@ -29,5 +29,5 @@ export interface BuildQueryBarDescription {
|
||||||
|
|
||||||
export interface BuildThreatDescription {
|
export interface BuildThreatDescription {
|
||||||
label: string;
|
label: string;
|
||||||
threat: IMitreEnterpriseAttack[];
|
threat: Threats;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,51 +5,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getValidThreat } from '../../../mitre/valid_threat_mock';
|
import { getValidThreat } from '../../../mitre/valid_threat_mock';
|
||||||
import { hasSubtechniqueOptions, isMitreAttackInvalid } from './helpers';
|
import { hasSubtechniqueOptions } from './helpers';
|
||||||
|
|
||||||
const mockTechniques = getValidThreat()[0].technique;
|
const mockTechniques = getValidThreat()[0].technique;
|
||||||
|
|
||||||
describe('helpers', () => {
|
describe('helpers', () => {
|
||||||
describe('isMitreAttackInvalid', () => {
|
|
||||||
describe('when technique param is undefined', () => {
|
|
||||||
it('returns false', () => {
|
|
||||||
expect(isMitreAttackInvalid('', undefined)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when technique param is empty', () => {
|
|
||||||
it('returns false if tacticName is `none`', () => {
|
|
||||||
expect(isMitreAttackInvalid('none', [])).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true if tacticName exists and is not `none`', () => {
|
|
||||||
expect(isMitreAttackInvalid('Test', [])).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when technique param exists', () => {
|
|
||||||
describe('and contains valid techniques', () => {
|
|
||||||
const validTechniques = mockTechniques;
|
|
||||||
it('returns false', () => {
|
|
||||||
expect(isMitreAttackInvalid('Test', validTechniques)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and contains empty techniques', () => {
|
|
||||||
const emptyTechniques = [
|
|
||||||
{
|
|
||||||
reference: 'https://test.com',
|
|
||||||
name: 'none',
|
|
||||||
id: '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
it('returns true', () => {
|
|
||||||
expect(isMitreAttackInvalid('Test', emptyTechniques)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('hasSubtechniqueOptions', () => {
|
describe('hasSubtechniqueOptions', () => {
|
||||||
describe('when technique has subtechnique options', () => {
|
describe('when technique has subtechnique options', () => {
|
||||||
const technique = mockTechniques[0];
|
const technique = mockTechniques[0];
|
||||||
|
|
|
@ -3,32 +3,12 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import { isEmpty } from 'lodash/fp';
|
import { ThreatTechnique } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import { subtechniquesOptions } from '../../../mitre/mitre_tactics_techniques';
|
import { subtechniquesOptions } from '../../../mitre/mitre_tactics_techniques';
|
||||||
|
|
||||||
import { IMitreAttackTechnique } from '../../../pages/detection_engine/rules/types';
|
|
||||||
|
|
||||||
export const isMitreAttackInvalid = (
|
|
||||||
tacticName: string | null | undefined,
|
|
||||||
technique: IMitreAttackTechnique[] | null | undefined
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
tacticName !== 'none' &&
|
|
||||||
technique != null &&
|
|
||||||
(isEmpty(technique) || !containsTechniques(technique))
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const containsTechniques = (techniques: IMitreAttackTechnique[]) => {
|
|
||||||
return techniques.some((technique) => technique.name !== 'none');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given mitre technique has any subtechniques
|
* Returns true if the given mitre technique has any subtechniques
|
||||||
*/
|
*/
|
||||||
export const hasSubtechniqueOptions = (technique: IMitreAttackTechnique) => {
|
export const hasSubtechniqueOptions = (technique: ThreatTechnique) => {
|
||||||
return subtechniquesOptions.some((subtechnique) => subtechnique.techniqueId === technique.id);
|
return subtechniquesOptions.some((subtechnique) => subtechnique.techniqueId === technique.id);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,19 +6,18 @@
|
||||||
|
|
||||||
import { EuiButtonIcon, EuiFormRow, EuiSuperSelect, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiButtonIcon, EuiFormRow, EuiSuperSelect, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { isEmpty, camelCase } from 'lodash/fp';
|
import { isEmpty, camelCase } from 'lodash/fp';
|
||||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
import { Threat, Threats } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import { tacticsOptions } from '../../../mitre/mitre_tactics_techniques';
|
import { tacticsOptions } from '../../../mitre/mitre_tactics_techniques';
|
||||||
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
|
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
|
||||||
import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
|
import { FieldHook } from '../../../../shared_imports';
|
||||||
import { threatDefault } from '../step_about_rule/default_value';
|
import { threatDefault } from '../step_about_rule/default_value';
|
||||||
import { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types';
|
|
||||||
import { MyAddItemButton } from '../add_item_form';
|
import { MyAddItemButton } from '../add_item_form';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
import { MitreAttackTechniqueFields } from './technique_fields';
|
import { MitreAttackTechniqueFields } from './technique_fields';
|
||||||
import { isMitreAttackInvalid } from './helpers';
|
|
||||||
|
|
||||||
const MitreAttackContainer = styled.div`
|
const MitreAttackContainer = styled.div`
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
@ -40,12 +39,9 @@ interface AddItemProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItemProps) => {
|
export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItemProps) => {
|
||||||
const [showValidation, setShowValidation] = useState(false);
|
|
||||||
const { errorMessage } = getFieldValidityAndErrorMessage(field);
|
|
||||||
|
|
||||||
const removeTactic = useCallback(
|
const removeTactic = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
const values = [...(field.value as IMitreEnterpriseAttack[])];
|
const values = [...(field.value as Threats)];
|
||||||
values.splice(index, 1);
|
values.splice(index, 1);
|
||||||
if (isEmpty(values)) {
|
if (isEmpty(values)) {
|
||||||
field.setValue(threatDefault);
|
field.setValue(threatDefault);
|
||||||
|
@ -57,7 +53,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
|
||||||
);
|
);
|
||||||
|
|
||||||
const addMitreAttackTactic = useCallback(() => {
|
const addMitreAttackTactic = useCallback(() => {
|
||||||
const values = [...(field.value as IMitreEnterpriseAttack[])];
|
const values = [...(field.value as Threats)];
|
||||||
if (!isEmpty(values[values.length - 1])) {
|
if (!isEmpty(values[values.length - 1])) {
|
||||||
field.setValue([
|
field.setValue([
|
||||||
...values,
|
...values,
|
||||||
|
@ -70,7 +66,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
|
||||||
|
|
||||||
const updateTactic = useCallback(
|
const updateTactic = useCallback(
|
||||||
(index: number, value: string) => {
|
(index: number, value: string) => {
|
||||||
const values = [...(field.value as IMitreEnterpriseAttack[])];
|
const values = [...(field.value as Threats)];
|
||||||
const { id, reference, name } = tacticsOptions.find((t) => t.value === value) || {
|
const { id, reference, name } = tacticsOptions.find((t) => t.value === value) || {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -87,15 +83,11 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
|
||||||
);
|
);
|
||||||
|
|
||||||
const values = useMemo(() => {
|
const values = useMemo(() => {
|
||||||
return [...(field.value as IMitreEnterpriseAttack[])];
|
return [...(field.value as Threats)];
|
||||||
}, [field]);
|
}, [field]);
|
||||||
|
|
||||||
const isTacticValid = useCallback((threat: IMitreEnterpriseAttack) => {
|
|
||||||
return isMitreAttackInvalid(threat.tactic.name, threat.technique);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getSelectTactic = useCallback(
|
const getSelectTactic = useCallback(
|
||||||
(threat: IMitreEnterpriseAttack, index: number, disabled: boolean) => {
|
(threat: Threat, index: number, disabled: boolean) => {
|
||||||
const tacticName = threat.tactic.name;
|
const tacticName = threat.tactic.name;
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||||
|
@ -125,8 +117,6 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
|
||||||
valueOfSelected={camelCase(tacticName)}
|
valueOfSelected={camelCase(tacticName)}
|
||||||
data-test-subj="mitreAttackTactic"
|
data-test-subj="mitreAttackTactic"
|
||||||
placeholder={i18n.TACTIC_PLACEHOLDER}
|
placeholder={i18n.TACTIC_PLACEHOLDER}
|
||||||
isInvalid={showValidation && isTacticValid(threat)}
|
|
||||||
onBlur={() => setShowValidation(true)}
|
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
|
@ -141,7 +131,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[field, isDisabled, removeTactic, showValidation, updateTactic, values, isTacticValid]
|
[field, isDisabled, removeTactic, updateTactic, values]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,7 +140,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
|
||||||
* Value is memoized on top level props, any deep changes will have to be new objects
|
* Value is memoized on top level props, any deep changes will have to be new objects
|
||||||
*/
|
*/
|
||||||
const onFieldChange = useCallback(
|
const onFieldChange = useCallback(
|
||||||
(threats: IMitreEnterpriseAttack[]) => {
|
(threats: Threats) => {
|
||||||
field.setValue(threats);
|
field.setValue(threats);
|
||||||
},
|
},
|
||||||
[field]
|
[field]
|
||||||
|
@ -166,16 +156,12 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
|
||||||
label={`${field.label} ${i18n.THREATS}`}
|
label={`${field.label} ${i18n.THREATS}`}
|
||||||
labelAppend={field.labelAppend}
|
labelAppend={field.labelAppend}
|
||||||
describedByIds={idAria ? [`${idAria} ${i18n.TACTIC}`] : undefined}
|
describedByIds={idAria ? [`${idAria} ${i18n.TACTIC}`] : undefined}
|
||||||
isInvalid={showValidation && isTacticValid(threat)}
|
|
||||||
error={errorMessage}
|
|
||||||
>
|
>
|
||||||
<>{getSelectTactic(threat, index, isDisabled)}</>
|
<>{getSelectTactic(threat, index, isDisabled)}</>
|
||||||
</InitialMitreAttackFormRow>
|
</InitialMitreAttackFormRow>
|
||||||
) : (
|
) : (
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
fullWidth
|
fullWidth
|
||||||
isInvalid={showValidation && isTacticValid(threat)}
|
|
||||||
error={errorMessage}
|
|
||||||
describedByIds={idAria ? [`${idAria} ${i18n.TACTIC}`] : undefined}
|
describedByIds={idAria ? [`${idAria} ${i18n.TACTIC}`] : undefined}
|
||||||
>
|
>
|
||||||
{getSelectTactic(threat, index, isDisabled)}
|
{getSelectTactic(threat, index, isDisabled)}
|
||||||
|
|
|
@ -16,10 +16,13 @@ import { camelCase } from 'lodash/fp';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Threats,
|
||||||
|
ThreatSubtechnique,
|
||||||
|
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import { subtechniquesOptions } from '../../../mitre/mitre_tactics_techniques';
|
import { subtechniquesOptions } from '../../../mitre/mitre_tactics_techniques';
|
||||||
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
|
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
|
||||||
import { FieldHook } from '../../../../shared_imports';
|
import { FieldHook } from '../../../../shared_imports';
|
||||||
import { IMitreAttack, IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types';
|
|
||||||
import { MyAddItemButton } from '../add_item_form';
|
import { MyAddItemButton } from '../add_item_form';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
|
|
||||||
|
@ -33,7 +36,7 @@ interface AddSubtechniqueProps {
|
||||||
techniqueIndex: number;
|
techniqueIndex: number;
|
||||||
idAria: string;
|
idAria: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
onFieldChange: (threats: IMitreEnterpriseAttack[]) => void;
|
onFieldChange: (threats: Threats) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
||||||
|
@ -44,7 +47,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
||||||
techniqueIndex,
|
techniqueIndex,
|
||||||
onFieldChange,
|
onFieldChange,
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const values = field.value as IMitreEnterpriseAttack[];
|
const values = field.value as Threats;
|
||||||
|
|
||||||
const technique = useMemo(() => {
|
const technique = useMemo(() => {
|
||||||
return values[threatIndex].technique[techniqueIndex];
|
return values[threatIndex].technique[techniqueIndex];
|
||||||
|
@ -52,7 +55,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
||||||
|
|
||||||
const removeSubtechnique = useCallback(
|
const removeSubtechnique = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
const threats = [...(field.value as IMitreEnterpriseAttack[])];
|
const threats = [...(field.value as Threats)];
|
||||||
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
|
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
|
||||||
if (subtechniques != null) {
|
if (subtechniques != null) {
|
||||||
subtechniques.splice(index, 1);
|
subtechniques.splice(index, 1);
|
||||||
|
@ -68,7 +71,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const addMitreAttackSubtechnique = useCallback(() => {
|
const addMitreAttackSubtechnique = useCallback(() => {
|
||||||
const threats = [...(field.value as IMitreEnterpriseAttack[])];
|
const threats = [...(field.value as Threats)];
|
||||||
|
|
||||||
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
|
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
|
||||||
|
|
||||||
|
@ -89,7 +92,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
||||||
|
|
||||||
const updateSubtechnique = useCallback(
|
const updateSubtechnique = useCallback(
|
||||||
(index: number, value: string) => {
|
(index: number, value: string) => {
|
||||||
const threats = [...(field.value as IMitreEnterpriseAttack[])];
|
const threats = [...(field.value as Threats)];
|
||||||
const { id, reference, name } = subtechniquesOptions.find((t) => t.value === value) || {
|
const { id, reference, name } = subtechniquesOptions.find((t) => t.value === value) || {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -127,7 +130,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const getSelectSubtechnique = useCallback(
|
const getSelectSubtechnique = useCallback(
|
||||||
(index: number, disabled: boolean, subtechnique: IMitreAttack) => {
|
(index: number, disabled: boolean, subtechnique: ThreatSubtechnique) => {
|
||||||
const options = subtechniquesOptions.filter((t) => t.techniqueId === technique.id);
|
const options = subtechniquesOptions.filter((t) => t.techniqueId === technique.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -16,13 +16,13 @@ import { kebabCase, camelCase } from 'lodash/fp';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Threats,
|
||||||
|
ThreatTechnique,
|
||||||
|
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import { techniquesOptions } from '../../../mitre/mitre_tactics_techniques';
|
import { techniquesOptions } from '../../../mitre/mitre_tactics_techniques';
|
||||||
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
|
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
|
||||||
import { FieldHook } from '../../../../shared_imports';
|
import { FieldHook } from '../../../../shared_imports';
|
||||||
import {
|
|
||||||
IMitreAttackTechnique,
|
|
||||||
IMitreEnterpriseAttack,
|
|
||||||
} from '../../../pages/detection_engine/rules/types';
|
|
||||||
import { MyAddItemButton } from '../add_item_form';
|
import { MyAddItemButton } from '../add_item_form';
|
||||||
import { hasSubtechniqueOptions } from './helpers';
|
import { hasSubtechniqueOptions } from './helpers';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
|
@ -41,7 +41,7 @@ interface AddTechniqueProps {
|
||||||
threatIndex: number;
|
threatIndex: number;
|
||||||
idAria: string;
|
idAria: string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
onFieldChange: (threats: IMitreEnterpriseAttack[]) => void;
|
onFieldChange: (threats: Threats) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
||||||
|
@ -51,11 +51,11 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
||||||
threatIndex,
|
threatIndex,
|
||||||
onFieldChange,
|
onFieldChange,
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const values = field.value as IMitreEnterpriseAttack[];
|
const values = field.value as Threats;
|
||||||
|
|
||||||
const removeTechnique = useCallback(
|
const removeTechnique = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
const threats = [...(field.value as IMitreEnterpriseAttack[])];
|
const threats = [...(field.value as Threats)];
|
||||||
const techniques = threats[threatIndex].technique;
|
const techniques = threats[threatIndex].technique;
|
||||||
techniques.splice(index, 1);
|
techniques.splice(index, 1);
|
||||||
threats[threatIndex] = {
|
threats[threatIndex] = {
|
||||||
|
@ -68,7 +68,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const addMitreAttackTechnique = useCallback(() => {
|
const addMitreAttackTechnique = useCallback(() => {
|
||||||
const threats = [...(field.value as IMitreEnterpriseAttack[])];
|
const threats = [...(field.value as Threats)];
|
||||||
threats[threatIndex] = {
|
threats[threatIndex] = {
|
||||||
...threats[threatIndex],
|
...threats[threatIndex],
|
||||||
technique: [
|
technique: [
|
||||||
|
@ -81,7 +81,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
||||||
|
|
||||||
const updateTechnique = useCallback(
|
const updateTechnique = useCallback(
|
||||||
(index: number, value: string) => {
|
(index: number, value: string) => {
|
||||||
const threats = [...(field.value as IMitreEnterpriseAttack[])];
|
const threats = [...(field.value as Threats)];
|
||||||
const { id, reference, name } = techniquesOptions.find((t) => t.value === value) || {
|
const { id, reference, name } = techniquesOptions.find((t) => t.value === value) || {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -109,7 +109,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const getSelectTechnique = useCallback(
|
const getSelectTechnique = useCallback(
|
||||||
(tacticName: string, index: number, disabled: boolean, technique: IMitreAttackTechnique) => {
|
(tacticName: string, index: number, disabled: boolean, technique: ThreatTechnique) => {
|
||||||
const options = techniquesOptions.filter((t) => t.tactics.includes(kebabCase(tacticName)));
|
const options = techniquesOptions.filter((t) => t.tactics.includes(kebabCase(tacticName)));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -13,8 +13,7 @@ import {
|
||||||
ValidationFunc,
|
ValidationFunc,
|
||||||
ERROR_CODE,
|
ERROR_CODE,
|
||||||
} from '../../../../shared_imports';
|
} from '../../../../shared_imports';
|
||||||
import { IMitreEnterpriseAttack, AboutStepRule } from '../../../pages/detection_engine/rules/types';
|
import { AboutStepRule } from '../../../pages/detection_engine/rules/types';
|
||||||
import { isMitreAttackInvalid } from '../mitre/helpers';
|
|
||||||
import { OptionalFieldLabel } from '../optional_field_label';
|
import { OptionalFieldLabel } from '../optional_field_label';
|
||||||
import { isUrlInvalid } from '../../../../common/utils/validators';
|
import { isUrlInvalid } from '../../../../common/utils/validators';
|
||||||
import * as I18n from './translations';
|
import * as I18n from './translations';
|
||||||
|
@ -192,29 +191,6 @@ export const schema: FormSchema<AboutStepRule> = {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
labelAppend: OptionalFieldLabel,
|
labelAppend: OptionalFieldLabel,
|
||||||
validations: [
|
|
||||||
{
|
|
||||||
validator: (
|
|
||||||
...args: Parameters<ValidationFunc>
|
|
||||||
): ReturnType<ValidationFunc<{}, ERROR_CODE>> | undefined => {
|
|
||||||
const [{ value, path }] = args;
|
|
||||||
let hasTechniqueError = false;
|
|
||||||
(value as IMitreEnterpriseAttack[]).forEach((v) => {
|
|
||||||
if (isMitreAttackInvalid(v.tactic.name, v.technique)) {
|
|
||||||
hasTechniqueError = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return hasTechniqueError
|
|
||||||
? {
|
|
||||||
code: 'ERR_FIELD_MISSING',
|
|
||||||
path: `${path}.tactic`,
|
|
||||||
message: I18n.CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
},
|
|
||||||
exitOnFail: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
timestampOverride: {
|
timestampOverride: {
|
||||||
type: FIELD_TYPES.TEXT,
|
type: FIELD_TYPES.TEXT,
|
||||||
|
|
|
@ -69,13 +69,6 @@ export const CRITICAL = i18n.translate(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED = i18n.translate(
|
|
||||||
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customMitreAttackTechniquesFieldRequiredError',
|
|
||||||
{
|
|
||||||
defaultMessage: 'At least one Technique is required with a Tactic.',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const URL_FORMAT_INVALID = i18n.translate(
|
export const URL_FORMAT_INVALID = i18n.translate(
|
||||||
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError',
|
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError',
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
timestamp_override,
|
timestamp_override,
|
||||||
threshold,
|
threshold,
|
||||||
type,
|
type,
|
||||||
|
threats,
|
||||||
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import {
|
import {
|
||||||
listArray,
|
listArray,
|
||||||
|
@ -77,6 +78,7 @@ const StatusTypes = t.union([
|
||||||
t.literal('partial failure'),
|
t.literal('partial failure'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// TODO: make a ticket
|
||||||
export const RuleSchema = t.intersection([
|
export const RuleSchema = t.intersection([
|
||||||
t.type({
|
t.type({
|
||||||
author,
|
author,
|
||||||
|
@ -100,7 +102,7 @@ export const RuleSchema = t.intersection([
|
||||||
tags: t.array(t.string),
|
tags: t.array(t.string),
|
||||||
type,
|
type,
|
||||||
to: t.string,
|
to: t.string,
|
||||||
threat: t.array(t.unknown),
|
threat: threats,
|
||||||
updated_at: t.string,
|
updated_at: t.string,
|
||||||
updated_by: t.string,
|
updated_by: t.string,
|
||||||
actions: t.array(action),
|
actions: t.array(action),
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Threat } from '../../../common/detection_engine/schemas/common/schemas';
|
import { Threats } from '../../../common/detection_engine/schemas/common/schemas';
|
||||||
import { mockThreatData } from './mitre_tactics_techniques';
|
import { mockThreatData } from './mitre_tactics_techniques';
|
||||||
|
|
||||||
const { tactic, technique, subtechnique } = mockThreatData;
|
const { tactic, technique, subtechnique } = mockThreatData;
|
||||||
const { tactics, ...mockTechnique } = technique;
|
const { tactics, ...mockTechnique } = technique;
|
||||||
const { tactics: subtechniqueTactics, ...mockSubtechnique } = subtechnique;
|
const { tactics: subtechniqueTactics, ...mockSubtechnique } = subtechnique;
|
||||||
|
|
||||||
export const getValidThreat = (): Threat => [
|
export const getValidThreat = (): Threats => [
|
||||||
{
|
{
|
||||||
framework: 'MITRE ATT&CK',
|
framework: 'MITRE ATT&CK',
|
||||||
tactic,
|
tactic,
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {
|
||||||
ActionsStepRule,
|
ActionsStepRule,
|
||||||
ScheduleStepRule,
|
ScheduleStepRule,
|
||||||
DefineStepRule,
|
DefineStepRule,
|
||||||
IMitreEnterpriseAttack,
|
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
getTimeTypeValue,
|
getTimeTypeValue,
|
||||||
|
@ -40,6 +39,7 @@ import {
|
||||||
mockActionsStepRule,
|
mockActionsStepRule,
|
||||||
} from '../all/__mocks__/mock';
|
} from '../all/__mocks__/mock';
|
||||||
import { getThreatMock } from '../../../../../../common/detection_engine/schemas/types/threat.mock';
|
import { getThreatMock } from '../../../../../../common/detection_engine/schemas/types/threat.mock';
|
||||||
|
import { Threat, Threats } from '../../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
|
|
||||||
describe('helpers', () => {
|
describe('helpers', () => {
|
||||||
describe('getTimeTypeValue', () => {
|
describe('getTimeTypeValue', () => {
|
||||||
|
@ -87,14 +87,14 @@ describe('helpers', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('filterEmptyThreats', () => {
|
describe('filterEmptyThreats', () => {
|
||||||
let mockThreat: IMitreEnterpriseAttack;
|
let mockThreat: Threat;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockThreat = mockAboutStepRule().threat[0];
|
mockThreat = mockAboutStepRule().threat[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
test('filters out fields with empty tactics', () => {
|
test('filters out fields with empty tactics', () => {
|
||||||
const threat: IMitreEnterpriseAttack[] = [
|
const threat: Threats = [
|
||||||
mockThreat,
|
mockThreat,
|
||||||
{ ...mockThreat, tactic: { ...mockThreat.tactic, name: 'none' } },
|
{ ...mockThreat, tactic: { ...mockThreat.tactic, name: 'none' } },
|
||||||
];
|
];
|
||||||
|
|
|
@ -14,7 +14,12 @@ import { transformAlertToRuleAction } from '../../../../../../common/detection_e
|
||||||
import { List } from '../../../../../../common/detection_engine/schemas/types';
|
import { List } from '../../../../../../common/detection_engine/schemas/types';
|
||||||
import { ENDPOINT_LIST_ID, ExceptionListType, NamespaceType } from '../../../../../shared_imports';
|
import { ENDPOINT_LIST_ID, ExceptionListType, NamespaceType } from '../../../../../shared_imports';
|
||||||
import { Rule } from '../../../../containers/detection_engine/rules';
|
import { Rule } from '../../../../containers/detection_engine/rules';
|
||||||
import { Type } from '../../../../../../common/detection_engine/schemas/common/schemas';
|
import {
|
||||||
|
Threats,
|
||||||
|
ThreatSubtechnique,
|
||||||
|
ThreatTechnique,
|
||||||
|
Type,
|
||||||
|
} from '../../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AboutStepRule,
|
AboutStepRule,
|
||||||
|
@ -27,9 +32,6 @@ import {
|
||||||
ActionsStepRuleJson,
|
ActionsStepRuleJson,
|
||||||
RuleStepsFormData,
|
RuleStepsFormData,
|
||||||
RuleStep,
|
RuleStep,
|
||||||
IMitreEnterpriseAttack,
|
|
||||||
IMitreAttack,
|
|
||||||
IMitreAttackTechnique,
|
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
export const getTimeTypeValue = (time: string): { unit: string; value: number } => {
|
export const getTimeTypeValue = (time: string): { unit: string; value: number } => {
|
||||||
|
@ -164,7 +166,7 @@ export const filterRuleFieldsForType = <T extends Partial<RuleFields>>(
|
||||||
assertUnreachable(type);
|
assertUnreachable(type);
|
||||||
};
|
};
|
||||||
|
|
||||||
function trimThreatsWithNoName<T extends IMitreAttack | IMitreAttackTechnique>(
|
function trimThreatsWithNoName<T extends ThreatSubtechnique | ThreatTechnique>(
|
||||||
filterable: T[]
|
filterable: T[]
|
||||||
): T[] {
|
): T[] {
|
||||||
return filterable.filter((item) => item.name !== 'none');
|
return filterable.filter((item) => item.name !== 'none');
|
||||||
|
@ -173,7 +175,7 @@ function trimThreatsWithNoName<T extends IMitreAttack | IMitreAttackTechnique>(
|
||||||
/**
|
/**
|
||||||
* Filter out unfilled/empty threat, technique, and subtechnique fields based on if their name is `none`
|
* Filter out unfilled/empty threat, technique, and subtechnique fields based on if their name is `none`
|
||||||
*/
|
*/
|
||||||
export const filterEmptyThreats = (threats: IMitreEnterpriseAttack[]): IMitreEnterpriseAttack[] => {
|
export const filterEmptyThreats = (threats: Threats): Threats => {
|
||||||
return threats
|
return threats
|
||||||
.filter((singleThreat) => singleThreat.tactic.name !== 'none')
|
.filter((singleThreat) => singleThreat.tactic.name !== 'none')
|
||||||
.map((threat) => {
|
.map((threat) => {
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {
|
||||||
AboutStepRule,
|
AboutStepRule,
|
||||||
AboutStepRuleDetails,
|
AboutStepRuleDetails,
|
||||||
DefineStepRule,
|
DefineStepRule,
|
||||||
IMitreEnterpriseAttack,
|
|
||||||
ScheduleStepRule,
|
ScheduleStepRule,
|
||||||
ActionsStepRule,
|
ActionsStepRule,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
@ -30,6 +29,7 @@ import {
|
||||||
SeverityMapping,
|
SeverityMapping,
|
||||||
Type,
|
Type,
|
||||||
Severity,
|
Severity,
|
||||||
|
Threats,
|
||||||
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import { severityOptions } from '../../../components/rules/step_about_rule/data';
|
import { severityOptions } from '../../../components/rules/step_about_rule/data';
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu
|
||||||
isMappingChecked: riskScoreMapping.length > 0,
|
isMappingChecked: riskScoreMapping.length > 0,
|
||||||
},
|
},
|
||||||
falsePositives,
|
falsePositives,
|
||||||
threat: threat as IMitreEnterpriseAttack[],
|
threat: threat as Threats,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
TimestampOverride,
|
TimestampOverride,
|
||||||
Type,
|
Type,
|
||||||
Severity,
|
Severity,
|
||||||
|
Threats,
|
||||||
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
} from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
|
@ -99,7 +100,7 @@ export interface AboutStepRule {
|
||||||
ruleNameOverride: string;
|
ruleNameOverride: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
timestampOverride: string;
|
timestampOverride: string;
|
||||||
threat: IMitreEnterpriseAttack[];
|
threat: Threats;
|
||||||
note: string;
|
note: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +179,7 @@ export interface AboutStepRuleJson {
|
||||||
false_positives: string[];
|
false_positives: string[];
|
||||||
rule_name_override?: RuleNameOverride;
|
rule_name_override?: RuleNameOverride;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
threat: IMitreEnterpriseAttack[];
|
threat: Threats;
|
||||||
timestamp_override?: TimestampOverride;
|
timestamp_override?: TimestampOverride;
|
||||||
note?: string;
|
note?: string;
|
||||||
}
|
}
|
||||||
|
@ -196,22 +197,3 @@ export interface ActionsStepRuleJson {
|
||||||
throttle?: string | null;
|
throttle?: string | null;
|
||||||
meta?: unknown;
|
meta?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMitreAttack {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
reference: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMitreAttackTechnique {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
reference: string;
|
|
||||||
subtechnique?: IMitreAttack[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMitreEnterpriseAttack {
|
|
||||||
framework: string;
|
|
||||||
tactic: IMitreAttack;
|
|
||||||
technique: IMitreAttackTechnique[];
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
Name,
|
Name,
|
||||||
Severity,
|
Severity,
|
||||||
Tags,
|
Tags,
|
||||||
Threat,
|
Threats,
|
||||||
To,
|
To,
|
||||||
Type,
|
Type,
|
||||||
References,
|
References,
|
||||||
|
@ -59,7 +59,7 @@ import {
|
||||||
SeverityOrUndefined,
|
SeverityOrUndefined,
|
||||||
TagsOrUndefined,
|
TagsOrUndefined,
|
||||||
ToOrUndefined,
|
ToOrUndefined,
|
||||||
ThreatOrUndefined,
|
ThreatsOrUndefined,
|
||||||
ThresholdOrUndefined,
|
ThresholdOrUndefined,
|
||||||
TypeOrUndefined,
|
TypeOrUndefined,
|
||||||
ReferencesOrUndefined,
|
ReferencesOrUndefined,
|
||||||
|
@ -231,7 +231,7 @@ export interface CreateRulesOptions {
|
||||||
severity: Severity;
|
severity: Severity;
|
||||||
severityMapping: SeverityMapping;
|
severityMapping: SeverityMapping;
|
||||||
tags: Tags;
|
tags: Tags;
|
||||||
threat: Threat;
|
threat: Threats;
|
||||||
threshold: ThresholdOrUndefined;
|
threshold: ThresholdOrUndefined;
|
||||||
threatFilters: ThreatFiltersOrUndefined;
|
threatFilters: ThreatFiltersOrUndefined;
|
||||||
threatIndex: ThreatIndexOrUndefined;
|
threatIndex: ThreatIndexOrUndefined;
|
||||||
|
@ -288,7 +288,7 @@ export interface PatchRulesOptions {
|
||||||
severity: SeverityOrUndefined;
|
severity: SeverityOrUndefined;
|
||||||
severityMapping: SeverityMappingOrUndefined;
|
severityMapping: SeverityMappingOrUndefined;
|
||||||
tags: TagsOrUndefined;
|
tags: TagsOrUndefined;
|
||||||
threat: ThreatOrUndefined;
|
threat: ThreatsOrUndefined;
|
||||||
itemsPerSearch: ItemsPerSearchOrUndefined;
|
itemsPerSearch: ItemsPerSearchOrUndefined;
|
||||||
concurrentSearches: ConcurrentSearchesOrUndefined;
|
concurrentSearches: ConcurrentSearchesOrUndefined;
|
||||||
threshold: ThresholdOrUndefined;
|
threshold: ThresholdOrUndefined;
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
SeverityOrUndefined,
|
SeverityOrUndefined,
|
||||||
TagsOrUndefined,
|
TagsOrUndefined,
|
||||||
ToOrUndefined,
|
ToOrUndefined,
|
||||||
ThreatOrUndefined,
|
ThreatsOrUndefined,
|
||||||
ThresholdOrUndefined,
|
ThresholdOrUndefined,
|
||||||
TypeOrUndefined,
|
TypeOrUndefined,
|
||||||
ReferencesOrUndefined,
|
ReferencesOrUndefined,
|
||||||
|
@ -93,7 +93,7 @@ export interface UpdateProperties {
|
||||||
severity: SeverityOrUndefined;
|
severity: SeverityOrUndefined;
|
||||||
severityMapping: SeverityMappingOrUndefined;
|
severityMapping: SeverityMappingOrUndefined;
|
||||||
tags: TagsOrUndefined;
|
tags: TagsOrUndefined;
|
||||||
threat: ThreatOrUndefined;
|
threat: ThreatsOrUndefined;
|
||||||
threshold: ThresholdOrUndefined;
|
threshold: ThresholdOrUndefined;
|
||||||
threatFilters: ThreatFiltersOrUndefined;
|
threatFilters: ThreatFiltersOrUndefined;
|
||||||
threatIndex: ThreatIndexOrUndefined;
|
threatIndex: ThreatIndexOrUndefined;
|
||||||
|
|
|
@ -43,7 +43,7 @@ import {
|
||||||
severityMappingOrUndefined,
|
severityMappingOrUndefined,
|
||||||
tags,
|
tags,
|
||||||
timestampOverrideOrUndefined,
|
timestampOverrideOrUndefined,
|
||||||
threat,
|
threats,
|
||||||
to,
|
to,
|
||||||
references,
|
references,
|
||||||
version,
|
version,
|
||||||
|
@ -85,7 +85,7 @@ export const baseRuleParams = t.exact(
|
||||||
severity,
|
severity,
|
||||||
severityMapping: severityMappingOrUndefined,
|
severityMapping: severityMappingOrUndefined,
|
||||||
timestampOverride: timestampOverrideOrUndefined,
|
timestampOverride: timestampOverrideOrUndefined,
|
||||||
threat,
|
threat: threats,
|
||||||
to,
|
to,
|
||||||
references,
|
references,
|
||||||
version,
|
version,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
AnomalyThresholdOrUndefined,
|
AnomalyThresholdOrUndefined,
|
||||||
Description,
|
Description,
|
||||||
NoteOrUndefined,
|
NoteOrUndefined,
|
||||||
ThreatOrUndefined,
|
ThreatsOrUndefined,
|
||||||
ThresholdOrUndefined,
|
ThresholdOrUndefined,
|
||||||
FalsePositives,
|
FalsePositives,
|
||||||
From,
|
From,
|
||||||
|
@ -82,7 +82,7 @@ export interface RuleTypeParams {
|
||||||
ruleNameOverride: RuleNameOverrideOrUndefined;
|
ruleNameOverride: RuleNameOverrideOrUndefined;
|
||||||
severity: Severity;
|
severity: Severity;
|
||||||
severityMapping: SeverityMappingOrUndefined;
|
severityMapping: SeverityMappingOrUndefined;
|
||||||
threat: ThreatOrUndefined;
|
threat: ThreatsOrUndefined;
|
||||||
threshold: ThresholdOrUndefined;
|
threshold: ThresholdOrUndefined;
|
||||||
threatFilters: PartialFilter[] | undefined;
|
threatFilters: PartialFilter[] | undefined;
|
||||||
threatIndex: ThreatIndexOrUndefined;
|
threatIndex: ThreatIndexOrUndefined;
|
||||||
|
|
|
@ -16668,7 +16668,6 @@
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionHighDescription": "高",
|
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionHighDescription": "高",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionLowDescription": "低",
|
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionLowDescription": "低",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionMediumDescription": "中",
|
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionMediumDescription": "中",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customMitreAttackTechniquesFieldRequiredError": "Tacticには1つ以上のTechniqueが必要です。",
|
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError": "KQLが無効です",
|
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError": "KQLが無効です",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "カスタムクエリが必要です。",
|
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "カスタムクエリが必要です。",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "すべての一致には、フィールドと脅威インデックスフィールドの両方が必要です。",
|
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "すべての一致には、フィールドと脅威インデックスフィールドの両方が必要です。",
|
||||||
|
|
|
@ -16685,7 +16685,6 @@
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionHighDescription": "高",
|
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionHighDescription": "高",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionLowDescription": "低",
|
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionLowDescription": "低",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionMediumDescription": "中",
|
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionMediumDescription": "中",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customMitreAttackTechniquesFieldRequiredError": "一个策略至少需要一个技术。",
|
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError": "KQL 无效",
|
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError": "KQL 无效",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "需要定制查询。",
|
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "需要定制查询。",
|
||||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "所有匹配项都需要字段和威胁索引字段。",
|
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "所有匹配项都需要字段和威胁索引字段。",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue