[Security Solution][Detection Rules] Makes threat techniques optional (#85481)

This commit is contained in:
Davis Plumlee 2020-12-15 12:08:15 -05:00 committed by GitHub
parent 07f395f7dd
commit 13e5e55901
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 104 additions and 223 deletions

View file

@ -413,6 +413,7 @@ export const threat_tactic = t.type({
name: threat_tactic_name,
reference: threat_tactic_reference,
});
export type ThreatTactic = t.TypeOf<typeof threat_tactic>;
export const threat_subtechnique_id = t.string;
export const threat_subtechnique_name = t.string;
export const threat_subtechnique_reference = t.string;
@ -421,6 +422,7 @@ export const threat_subtechnique = t.type({
name: threat_subtechnique_name,
reference: threat_subtechnique_reference,
});
export type ThreatSubtechnique = t.TypeOf<typeof threat_subtechnique>;
export const threat_subtechniques = t.array(threat_subtechnique);
export const threat_technique_id = 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 = t.array(
t.exact(
t.type({
framework: threat_framework,
tactic: threat_tactic,
technique: threat_techniques,
})
)
export const threat = t.exact(
t.type({
framework: threat_framework,
tactic: threat_tactic,
technique: threat_techniques,
})
);
export type Threat = t.TypeOf<typeof threat>;
export const threatOrUndefined = t.union([threat, t.undefined]);
export type ThreatOrUndefined = t.TypeOf<typeof threatOrUndefined>;
export const threats = t.array(threat);
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(
t.type({

View file

@ -23,7 +23,7 @@ import {
Tags,
To,
type,
Threat,
Threats,
threshold,
ThrottleOrNull,
note,
@ -171,7 +171,7 @@ export type AddPrepackagedRulesSchemaDecoded = Omit<
severity_mapping: SeverityMapping;
tags: Tags;
to: To;
threat: Threat;
threat: Threats;
throttle: ThrottleOrNull;
exceptions_list: ListArray;
};

View file

@ -25,7 +25,7 @@ import {
Tags,
To,
type,
Threat,
Threats,
threshold,
ThrottleOrNull,
note,
@ -193,7 +193,7 @@ export type ImportRulesSchemaDecoded = Omit<
severity_mapping: SeverityMapping;
tags: Tags;
to: To;
threat: Threat;
threat: Threats;
throttle: ThrottleOrNull;
version: Version;
exceptions_list: ListArray;

View file

@ -31,7 +31,7 @@ import {
from,
enabled,
tags,
threat,
threats,
threshold,
throttle,
references,
@ -98,7 +98,7 @@ export const patchRulesSchema = t.exact(
severity_mapping,
tags,
to,
threat,
threat: threats,
threshold,
throttle,
timestamp_override,

View file

@ -42,7 +42,7 @@ import {
max_signals,
risk_score,
severity,
threat,
threats,
to,
references,
version,
@ -167,7 +167,7 @@ const commonParams = {
max_signals,
risk_score_mapping,
severity_mapping,
threat,
threat: threats,
to,
references,
version,

View file

@ -43,7 +43,7 @@ import {
timeline_id,
timeline_title,
type,
threat,
threats,
threshold,
throttle,
job_status,
@ -106,7 +106,7 @@ export const requiredRulesSchema = t.type({
tags,
to,
type,
threat,
threat: threats,
created_at,
updated_at,
created_by,

View file

@ -8,11 +8,11 @@ import { DefaultThreatArray } from './default_threat_array';
import { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { foldLeftRight, getPaths } from '../../../test_utils';
import { Threat } from '../common/schemas';
import { Threats } from '../common/schemas';
describe('default_threat_null', () => {
test('it should validate an empty array', () => {
const payload: Threat = [];
const payload: Threats = [];
const decoded = DefaultThreatArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -21,7 +21,7 @@ describe('default_threat_null', () => {
});
test('it should validate an array of threats', () => {
const payload: Threat = [
const payload: Threats = [
{
framework: 'MITRE ATTACK',
technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }],

View file

@ -6,16 +6,16 @@
import * as t from 'io-ts';
import { Either } from 'fp-ts/lib/Either';
import { Threat, threat } from '../common/schemas';
import { Threats, threats } from '../common/schemas';
/**
* Types the DefaultThreatArray as:
* - 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',
threat.is,
(input, context): Either<t.Errors, Threat> =>
input == null ? t.success([]) : threat.validate(input, context),
threats.is,
(input, context): Either<t.Errors, Threats> =>
input == null ? t.success([]) : threats.validate(input, context),
t.identity
);

View file

@ -4,9 +4,9 @@
* 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',
tactic: {

View file

@ -18,11 +18,7 @@ import {
} from '../../../../../../../../src/plugins/data/public';
import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations';
import { useKibana } from '../../../../common/lib/kibana';
import {
AboutStepRiskScore,
AboutStepSeverity,
IMitreEnterpriseAttack,
} from '../../../pages/detection_engine/rules/types';
import { AboutStepRiskScore, AboutStepSeverity } from '../../../pages/detection_engine/rules/types';
import { FieldValueTimeline } from '../pick_timeline';
import { FormSchema } from '../../../../shared_imports';
import { ListItems } from './types';
@ -42,7 +38,7 @@ import {
import { buildMlJobDescription } from './ml_job_description';
import { buildActionsDescription } from './actions_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 { filterEmptyThreats } from '../../../pages/detection_engine/rules/create/helpers';
@ -179,7 +175,7 @@ export const getDescriptionItem = (
indexPatterns,
});
} else if (field === 'threat') {
const threats: IMitreEnterpriseAttack[] = get(field, data);
const threats: Threats = get(field, data);
return buildThreatDescription({ label, threat: filterEmptyThreats(threats) });
} else if (field === 'threshold') {
const threshold = get(field, data);

View file

@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ReactNode } from 'react';
import { Threats } from '../../../../../common/detection_engine/schemas/common/schemas';
import {
IIndexPattern,
Filter,
FilterManager,
} from '../../../../../../../../src/plugins/data/public';
import { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types';
export interface ListItems {
title: NonNullable<ReactNode>;
@ -29,5 +29,5 @@ export interface BuildQueryBarDescription {
export interface BuildThreatDescription {
label: string;
threat: IMitreEnterpriseAttack[];
threat: Threats;
}

View file

@ -5,51 +5,11 @@
*/
import { getValidThreat } from '../../../mitre/valid_threat_mock';
import { hasSubtechniqueOptions, isMitreAttackInvalid } from './helpers';
import { hasSubtechniqueOptions } from './helpers';
const mockTechniques = getValidThreat()[0].technique;
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('when technique has subtechnique options', () => {
const technique = mockTechniques[0];

View file

@ -3,32 +3,12 @@
* or more contributor license agreements. Licensed under 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 { 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
*/
export const hasSubtechniqueOptions = (technique: IMitreAttackTechnique) => {
export const hasSubtechniqueOptions = (technique: ThreatTechnique) => {
return subtechniquesOptions.some((subtechnique) => subtechnique.techniqueId === technique.id);
};

View file

@ -6,19 +6,18 @@
import { EuiButtonIcon, EuiFormRow, EuiSuperSelect, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
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 { isEqual } from 'lodash';
import { Threat, Threats } from '../../../../../common/detection_engine/schemas/common/schemas';
import { tacticsOptions } from '../../../mitre/mitre_tactics_techniques';
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 { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types';
import { MyAddItemButton } from '../add_item_form';
import * as i18n from './translations';
import { MitreAttackTechniqueFields } from './technique_fields';
import { isMitreAttackInvalid } from './helpers';
const MitreAttackContainer = styled.div`
margin-top: 16px;
@ -40,12 +39,9 @@ interface AddItemProps {
}
export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItemProps) => {
const [showValidation, setShowValidation] = useState(false);
const { errorMessage } = getFieldValidityAndErrorMessage(field);
const removeTactic = useCallback(
(index: number) => {
const values = [...(field.value as IMitreEnterpriseAttack[])];
const values = [...(field.value as Threats)];
values.splice(index, 1);
if (isEmpty(values)) {
field.setValue(threatDefault);
@ -57,7 +53,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
);
const addMitreAttackTactic = useCallback(() => {
const values = [...(field.value as IMitreEnterpriseAttack[])];
const values = [...(field.value as Threats)];
if (!isEmpty(values[values.length - 1])) {
field.setValue([
...values,
@ -70,7 +66,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
const updateTactic = useCallback(
(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) || {
id: '',
name: '',
@ -87,15 +83,11 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
);
const values = useMemo(() => {
return [...(field.value as IMitreEnterpriseAttack[])];
return [...(field.value as Threats)];
}, [field]);
const isTacticValid = useCallback((threat: IMitreEnterpriseAttack) => {
return isMitreAttackInvalid(threat.tactic.name, threat.technique);
}, []);
const getSelectTactic = useCallback(
(threat: IMitreEnterpriseAttack, index: number, disabled: boolean) => {
(threat: Threat, index: number, disabled: boolean) => {
const tacticName = threat.tactic.name;
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
@ -125,8 +117,6 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
valueOfSelected={camelCase(tacticName)}
data-test-subj="mitreAttackTactic"
placeholder={i18n.TACTIC_PLACEHOLDER}
isInvalid={showValidation && isTacticValid(threat)}
onBlur={() => setShowValidation(true)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -141,7 +131,7 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
</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
*/
const onFieldChange = useCallback(
(threats: IMitreEnterpriseAttack[]) => {
(threats: Threats) => {
field.setValue(threats);
},
[field]
@ -166,16 +156,12 @@ export const AddMitreAttackThreat = memo(({ field, idAria, isDisabled }: AddItem
label={`${field.label} ${i18n.THREATS}`}
labelAppend={field.labelAppend}
describedByIds={idAria ? [`${idAria} ${i18n.TACTIC}`] : undefined}
isInvalid={showValidation && isTacticValid(threat)}
error={errorMessage}
>
<>{getSelectTactic(threat, index, isDisabled)}</>
</InitialMitreAttackFormRow>
) : (
<EuiFormRow
fullWidth
isInvalid={showValidation && isTacticValid(threat)}
error={errorMessage}
describedByIds={idAria ? [`${idAria} ${i18n.TACTIC}`] : undefined}
>
{getSelectTactic(threat, index, isDisabled)}

View file

@ -16,10 +16,13 @@ import { camelCase } from 'lodash/fp';
import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import {
Threats,
ThreatSubtechnique,
} from '../../../../../common/detection_engine/schemas/common/schemas';
import { subtechniquesOptions } from '../../../mitre/mitre_tactics_techniques';
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
import { FieldHook } from '../../../../shared_imports';
import { IMitreAttack, IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types';
import { MyAddItemButton } from '../add_item_form';
import * as i18n from './translations';
@ -33,7 +36,7 @@ interface AddSubtechniqueProps {
techniqueIndex: number;
idAria: string;
isDisabled: boolean;
onFieldChange: (threats: IMitreEnterpriseAttack[]) => void;
onFieldChange: (threats: Threats) => void;
}
export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
@ -44,7 +47,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
techniqueIndex,
onFieldChange,
}): JSX.Element => {
const values = field.value as IMitreEnterpriseAttack[];
const values = field.value as Threats;
const technique = useMemo(() => {
return values[threatIndex].technique[techniqueIndex];
@ -52,7 +55,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
const removeSubtechnique = useCallback(
(index: number) => {
const threats = [...(field.value as IMitreEnterpriseAttack[])];
const threats = [...(field.value as Threats)];
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
if (subtechniques != null) {
subtechniques.splice(index, 1);
@ -68,7 +71,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
);
const addMitreAttackSubtechnique = useCallback(() => {
const threats = [...(field.value as IMitreEnterpriseAttack[])];
const threats = [...(field.value as Threats)];
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
@ -89,7 +92,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
const updateSubtechnique = useCallback(
(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) || {
id: '',
name: '',
@ -127,7 +130,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
);
const getSelectSubtechnique = useCallback(
(index: number, disabled: boolean, subtechnique: IMitreAttack) => {
(index: number, disabled: boolean, subtechnique: ThreatSubtechnique) => {
const options = subtechniquesOptions.filter((t) => t.techniqueId === technique.id);
return (

View file

@ -16,13 +16,13 @@ import { kebabCase, camelCase } from 'lodash/fp';
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components';
import {
Threats,
ThreatTechnique,
} from '../../../../../common/detection_engine/schemas/common/schemas';
import { techniquesOptions } from '../../../mitre/mitre_tactics_techniques';
import * as Rulei18n from '../../../pages/detection_engine/rules/translations';
import { FieldHook } from '../../../../shared_imports';
import {
IMitreAttackTechnique,
IMitreEnterpriseAttack,
} from '../../../pages/detection_engine/rules/types';
import { MyAddItemButton } from '../add_item_form';
import { hasSubtechniqueOptions } from './helpers';
import * as i18n from './translations';
@ -41,7 +41,7 @@ interface AddTechniqueProps {
threatIndex: number;
idAria: string;
isDisabled: boolean;
onFieldChange: (threats: IMitreEnterpriseAttack[]) => void;
onFieldChange: (threats: Threats) => void;
}
export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
@ -51,11 +51,11 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
threatIndex,
onFieldChange,
}): JSX.Element => {
const values = field.value as IMitreEnterpriseAttack[];
const values = field.value as Threats;
const removeTechnique = useCallback(
(index: number) => {
const threats = [...(field.value as IMitreEnterpriseAttack[])];
const threats = [...(field.value as Threats)];
const techniques = threats[threatIndex].technique;
techniques.splice(index, 1);
threats[threatIndex] = {
@ -68,7 +68,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
);
const addMitreAttackTechnique = useCallback(() => {
const threats = [...(field.value as IMitreEnterpriseAttack[])];
const threats = [...(field.value as Threats)];
threats[threatIndex] = {
...threats[threatIndex],
technique: [
@ -81,7 +81,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
const updateTechnique = useCallback(
(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) || {
id: '',
name: '',
@ -109,7 +109,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
);
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)));
return (
<>

View file

@ -13,8 +13,7 @@ import {
ValidationFunc,
ERROR_CODE,
} from '../../../../shared_imports';
import { IMitreEnterpriseAttack, AboutStepRule } from '../../../pages/detection_engine/rules/types';
import { isMitreAttackInvalid } from '../mitre/helpers';
import { AboutStepRule } from '../../../pages/detection_engine/rules/types';
import { OptionalFieldLabel } from '../optional_field_label';
import { isUrlInvalid } from '../../../../common/utils/validators';
import * as I18n from './translations';
@ -192,29 +191,6 @@ export const schema: FormSchema<AboutStepRule> = {
}
),
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: {
type: FIELD_TYPES.TEXT,

View file

@ -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(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError',
{

View file

@ -17,6 +17,7 @@ import {
timestamp_override,
threshold,
type,
threats,
} from '../../../../../common/detection_engine/schemas/common/schemas';
import {
listArray,
@ -77,6 +78,7 @@ const StatusTypes = t.union([
t.literal('partial failure'),
]);
// TODO: make a ticket
export const RuleSchema = t.intersection([
t.type({
author,
@ -100,7 +102,7 @@ export const RuleSchema = t.intersection([
tags: t.array(t.string),
type,
to: t.string,
threat: t.array(t.unknown),
threat: threats,
updated_at: t.string,
updated_by: t.string,
actions: t.array(action),

View file

@ -4,14 +4,14 @@
* 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';
const { tactic, technique, subtechnique } = mockThreatData;
const { tactics, ...mockTechnique } = technique;
const { tactics: subtechniqueTactics, ...mockSubtechnique } = subtechnique;
export const getValidThreat = (): Threat => [
export const getValidThreat = (): Threats => [
{
framework: 'MITRE ATT&CK',
tactic,

View file

@ -20,7 +20,6 @@ import {
ActionsStepRule,
ScheduleStepRule,
DefineStepRule,
IMitreEnterpriseAttack,
} from '../types';
import {
getTimeTypeValue,
@ -40,6 +39,7 @@ import {
mockActionsStepRule,
} from '../all/__mocks__/mock';
import { getThreatMock } from '../../../../../../common/detection_engine/schemas/types/threat.mock';
import { Threat, Threats } from '../../../../../../common/detection_engine/schemas/common/schemas';
describe('helpers', () => {
describe('getTimeTypeValue', () => {
@ -87,14 +87,14 @@ describe('helpers', () => {
});
describe('filterEmptyThreats', () => {
let mockThreat: IMitreEnterpriseAttack;
let mockThreat: Threat;
beforeEach(() => {
mockThreat = mockAboutStepRule().threat[0];
});
test('filters out fields with empty tactics', () => {
const threat: IMitreEnterpriseAttack[] = [
const threat: Threats = [
mockThreat,
{ ...mockThreat, tactic: { ...mockThreat.tactic, name: 'none' } },
];

View file

@ -14,7 +14,12 @@ import { transformAlertToRuleAction } from '../../../../../../common/detection_e
import { List } from '../../../../../../common/detection_engine/schemas/types';
import { ENDPOINT_LIST_ID, ExceptionListType, NamespaceType } from '../../../../../shared_imports';
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 {
AboutStepRule,
@ -27,9 +32,6 @@ import {
ActionsStepRuleJson,
RuleStepsFormData,
RuleStep,
IMitreEnterpriseAttack,
IMitreAttack,
IMitreAttackTechnique,
} from '../types';
export const getTimeTypeValue = (time: string): { unit: string; value: number } => {
@ -164,7 +166,7 @@ export const filterRuleFieldsForType = <T extends Partial<RuleFields>>(
assertUnreachable(type);
};
function trimThreatsWithNoName<T extends IMitreAttack | IMitreAttackTechnique>(
function trimThreatsWithNoName<T extends ThreatSubtechnique | ThreatTechnique>(
filterable: T[]
): T[] {
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`
*/
export const filterEmptyThreats = (threats: IMitreEnterpriseAttack[]): IMitreEnterpriseAttack[] => {
export const filterEmptyThreats = (threats: Threats): Threats => {
return threats
.filter((singleThreat) => singleThreat.tactic.name !== 'none')
.map((threat) => {

View file

@ -22,7 +22,6 @@ import {
AboutStepRule,
AboutStepRuleDetails,
DefineStepRule,
IMitreEnterpriseAttack,
ScheduleStepRule,
ActionsStepRule,
} from './types';
@ -30,6 +29,7 @@ import {
SeverityMapping,
Type,
Severity,
Threats,
} from '../../../../../common/detection_engine/schemas/common/schemas';
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,
},
falsePositives,
threat: threat as IMitreEnterpriseAttack[],
threat: threat as Threats,
};
};

View file

@ -21,6 +21,7 @@ import {
TimestampOverride,
Type,
Severity,
Threats,
} from '../../../../../common/detection_engine/schemas/common/schemas';
import {
List,
@ -99,7 +100,7 @@ export interface AboutStepRule {
ruleNameOverride: string;
tags: string[];
timestampOverride: string;
threat: IMitreEnterpriseAttack[];
threat: Threats;
note: string;
}
@ -178,7 +179,7 @@ export interface AboutStepRuleJson {
false_positives: string[];
rule_name_override?: RuleNameOverride;
tags: string[];
threat: IMitreEnterpriseAttack[];
threat: Threats;
timestamp_override?: TimestampOverride;
note?: string;
}
@ -196,22 +197,3 @@ export interface ActionsStepRuleJson {
throttle?: string | null;
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[];
}

View file

@ -28,7 +28,7 @@ import {
Name,
Severity,
Tags,
Threat,
Threats,
To,
Type,
References,
@ -59,7 +59,7 @@ import {
SeverityOrUndefined,
TagsOrUndefined,
ToOrUndefined,
ThreatOrUndefined,
ThreatsOrUndefined,
ThresholdOrUndefined,
TypeOrUndefined,
ReferencesOrUndefined,
@ -231,7 +231,7 @@ export interface CreateRulesOptions {
severity: Severity;
severityMapping: SeverityMapping;
tags: Tags;
threat: Threat;
threat: Threats;
threshold: ThresholdOrUndefined;
threatFilters: ThreatFiltersOrUndefined;
threatIndex: ThreatIndexOrUndefined;
@ -288,7 +288,7 @@ export interface PatchRulesOptions {
severity: SeverityOrUndefined;
severityMapping: SeverityMappingOrUndefined;
tags: TagsOrUndefined;
threat: ThreatOrUndefined;
threat: ThreatsOrUndefined;
itemsPerSearch: ItemsPerSearchOrUndefined;
concurrentSearches: ConcurrentSearchesOrUndefined;
threshold: ThresholdOrUndefined;

View file

@ -28,7 +28,7 @@ import {
SeverityOrUndefined,
TagsOrUndefined,
ToOrUndefined,
ThreatOrUndefined,
ThreatsOrUndefined,
ThresholdOrUndefined,
TypeOrUndefined,
ReferencesOrUndefined,
@ -93,7 +93,7 @@ export interface UpdateProperties {
severity: SeverityOrUndefined;
severityMapping: SeverityMappingOrUndefined;
tags: TagsOrUndefined;
threat: ThreatOrUndefined;
threat: ThreatsOrUndefined;
threshold: ThresholdOrUndefined;
threatFilters: ThreatFiltersOrUndefined;
threatIndex: ThreatIndexOrUndefined;

View file

@ -43,7 +43,7 @@ import {
severityMappingOrUndefined,
tags,
timestampOverrideOrUndefined,
threat,
threats,
to,
references,
version,
@ -85,7 +85,7 @@ export const baseRuleParams = t.exact(
severity,
severityMapping: severityMappingOrUndefined,
timestampOverride: timestampOverrideOrUndefined,
threat,
threat: threats,
to,
references,
version,

View file

@ -8,7 +8,7 @@ import {
AnomalyThresholdOrUndefined,
Description,
NoteOrUndefined,
ThreatOrUndefined,
ThreatsOrUndefined,
ThresholdOrUndefined,
FalsePositives,
From,
@ -82,7 +82,7 @@ export interface RuleTypeParams {
ruleNameOverride: RuleNameOverrideOrUndefined;
severity: Severity;
severityMapping: SeverityMappingOrUndefined;
threat: ThreatOrUndefined;
threat: ThreatsOrUndefined;
threshold: ThresholdOrUndefined;
threatFilters: PartialFilter[] | undefined;
threatIndex: ThreatIndexOrUndefined;

View file

@ -16668,7 +16668,6 @@
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionHighDescription": "高",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionLowDescription": "低",
"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.customQueryFieldRequiredError": "カスタムクエリが必要です。",
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "すべての一致には、フィールドと脅威インデックスフィールドの両方が必要です。",

View file

@ -16685,7 +16685,6 @@
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionHighDescription": "高",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionLowDescription": "低",
"xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.severityOptionMediumDescription": "中",
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customMitreAttackTechniquesFieldRequiredError": "一个策略至少需要一个技术。",
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError": "KQL 无效",
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "需要定制查询。",
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "所有匹配项都需要字段和威胁索引字段。",