mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Detection Rules] Update prebuilt rule threats to match schema (#92281)
This commit is contained in:
parent
ebb0435401
commit
3471eaa481
10 changed files with 137 additions and 98 deletions
|
@ -444,13 +444,19 @@ 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.exact(
|
||||
t.type({
|
||||
framework: threat_framework,
|
||||
tactic: threat_tactic,
|
||||
technique: threat_techniques,
|
||||
})
|
||||
);
|
||||
export const threat = t.intersection([
|
||||
t.exact(
|
||||
t.type({
|
||||
framework: threat_framework,
|
||||
tactic: threat_tactic,
|
||||
})
|
||||
),
|
||||
t.exact(
|
||||
t.partial({
|
||||
technique: threat_techniques,
|
||||
})
|
||||
),
|
||||
]);
|
||||
export type Threat = t.TypeOf<typeof threat>;
|
||||
|
||||
export const threats = t.array(threat);
|
||||
|
|
|
@ -924,7 +924,7 @@ describe('add prepackaged rules schema', () => {
|
|||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threat that are missing "technique"', () => {
|
||||
test('You can send in an array of threat that are missing "technique"', () => {
|
||||
const payload: Omit<AddPrepackagedRulesSchema, 'threat'> & {
|
||||
threat: Array<Partial<Omit<AddPrepackagedRulesSchema['threat'], 'technique'>>>;
|
||||
} = {
|
||||
|
@ -944,10 +944,21 @@ describe('add prepackaged rules schema', () => {
|
|||
const decoded = addPrepackagedRulesSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "threat,technique"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
const expected: AddPrepackagedRulesSchemaDecoded = {
|
||||
...getAddPrepackagedRulesSchemaDecodedMock(),
|
||||
threat: [
|
||||
{
|
||||
framework: 'fake',
|
||||
tactic: {
|
||||
id: 'fakeId',
|
||||
name: 'fakeName',
|
||||
reference: 'fakeRef',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(message.schema).toEqual(expected);
|
||||
});
|
||||
|
||||
test('You can optionally send in an array of false positives', () => {
|
||||
|
|
|
@ -926,7 +926,7 @@ describe('import rules schema', () => {
|
|||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threat that are missing "technique"', () => {
|
||||
test('You can send in an array of threat that are missing "technique"', () => {
|
||||
const payload: Omit<ImportRulesSchema, 'threat'> & {
|
||||
threat: Array<Partial<Omit<ImportRulesSchema['threat'], 'technique'>>>;
|
||||
} = {
|
||||
|
@ -946,10 +946,21 @@ describe('import rules schema', () => {
|
|||
const decoded = importRulesSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "threat,technique"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
const expected: ImportRulesSchemaDecoded = {
|
||||
...getImportRulesSchemaDecodedMock(),
|
||||
threat: [
|
||||
{
|
||||
framework: 'fake',
|
||||
tactic: {
|
||||
id: 'fakeId',
|
||||
name: 'fakeName',
|
||||
reference: 'fakeRef',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(message.schema).toEqual(expected);
|
||||
});
|
||||
|
||||
test('You can optionally send in an array of false positives', () => {
|
||||
|
|
|
@ -973,7 +973,7 @@ describe('patch_rules_schema', () => {
|
|||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('threat is invalid when updated with missing technique', () => {
|
||||
test('threat is valid when updated with missing technique', () => {
|
||||
const threat: Omit<PatchRulesSchema['threat'], 'technique'> = [
|
||||
{
|
||||
framework: 'fake',
|
||||
|
@ -993,10 +993,8 @@ describe('patch_rules_schema', () => {
|
|||
const decoded = patchRulesSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "threat,technique"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('validates with timeline_id and timeline_title', () => {
|
||||
|
|
|
@ -618,7 +618,7 @@ describe('create rules schema', () => {
|
|||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threat that are missing "technique"', () => {
|
||||
test('You can send in an array of threat that are missing "technique"', () => {
|
||||
const payload = {
|
||||
...getCreateRulesSchemaMock(),
|
||||
threat: [
|
||||
|
@ -636,10 +636,8 @@ describe('create rules schema', () => {
|
|||
const decoded = createRulesSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "threat,technique"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('You can optionally send in an array of false positives', () => {
|
||||
|
|
|
@ -157,49 +157,54 @@ export const buildThreatDescription = ({ label, threat }: BuildThreatDescription
|
|||
: `${singleThreat.tactic.name} (${singleThreat.tactic.id})`}
|
||||
</EuiLink>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="flexStart" direction="column">
|
||||
{singleThreat.technique.map((technique, techniqueIndex) => {
|
||||
const myTechnique = techniquesOptions.find((t) => t.id === technique.id);
|
||||
return (
|
||||
<EuiFlexItem key={myTechnique?.id ?? techniqueIndex}>
|
||||
<TechniqueLinkItem
|
||||
data-test-subj="threatTechniqueLink"
|
||||
href={technique.reference}
|
||||
target="_blank"
|
||||
iconType={ListTreeIcon}
|
||||
size="xs"
|
||||
>
|
||||
{myTechnique != null
|
||||
? myTechnique.label
|
||||
: `${technique.name} (${technique.id})`}
|
||||
</TechniqueLinkItem>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="flexStart" direction="column">
|
||||
{technique.subtechnique != null &&
|
||||
technique.subtechnique.map((subtechnique, subtechniqueIndex) => {
|
||||
const mySubtechnique = subtechniquesOptions.find(
|
||||
(t) => t.id === subtechnique.id
|
||||
);
|
||||
return (
|
||||
<SubtechniqueFlexItem
|
||||
key={mySubtechnique?.id ?? subtechniqueIndex}
|
||||
>
|
||||
<TechniqueLinkItem
|
||||
data-test-subj="threatSubtechniqueLink"
|
||||
href={subtechnique.reference}
|
||||
target="_blank"
|
||||
iconType={ListTreeIcon}
|
||||
size="xs"
|
||||
{singleThreat.technique &&
|
||||
singleThreat.technique.map((technique, techniqueIndex) => {
|
||||
const myTechnique = techniquesOptions.find((t) => t.id === technique.id);
|
||||
return (
|
||||
<EuiFlexItem key={myTechnique?.id ?? techniqueIndex}>
|
||||
<TechniqueLinkItem
|
||||
data-test-subj="threatTechniqueLink"
|
||||
href={technique.reference}
|
||||
target="_blank"
|
||||
iconType={ListTreeIcon}
|
||||
size="xs"
|
||||
>
|
||||
{myTechnique != null
|
||||
? myTechnique.label
|
||||
: `${technique.name} (${technique.id})`}
|
||||
</TechniqueLinkItem>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
alignItems="flexStart"
|
||||
direction="column"
|
||||
>
|
||||
{technique.subtechnique != null &&
|
||||
technique.subtechnique.map((subtechnique, subtechniqueIndex) => {
|
||||
const mySubtechnique = subtechniquesOptions.find(
|
||||
(t) => t.id === subtechnique.id
|
||||
);
|
||||
return (
|
||||
<SubtechniqueFlexItem
|
||||
key={mySubtechnique?.id ?? subtechniqueIndex}
|
||||
>
|
||||
{mySubtechnique != null
|
||||
? mySubtechnique.label
|
||||
: `${subtechnique.name} (${subtechnique.id})`}
|
||||
</TechniqueLinkItem>
|
||||
</SubtechniqueFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
<TechniqueLinkItem
|
||||
data-test-subj="threatSubtechniqueLink"
|
||||
href={subtechnique.reference}
|
||||
target="_blank"
|
||||
iconType={ListTreeIcon}
|
||||
size="xs"
|
||||
>
|
||||
{mySubtechnique != null
|
||||
? mySubtechnique.label
|
||||
: `${subtechnique.name} (${subtechnique.id})`}
|
||||
</TechniqueLinkItem>
|
||||
</SubtechniqueFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { getValidThreat } from '../../../mitre/valid_threat_mock';
|
||||
import { hasSubtechniqueOptions } from './helpers';
|
||||
|
||||
const mockTechniques = getValidThreat()[0].technique;
|
||||
const mockTechniques = getValidThreat()[0].technique ?? [];
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('hasSubtechniqueOptions', () => {
|
||||
|
|
|
@ -51,45 +51,46 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
|||
const values = field.value as Threats;
|
||||
|
||||
const technique = useMemo(() => {
|
||||
return values[threatIndex].technique[techniqueIndex];
|
||||
}, [values, threatIndex, techniqueIndex]);
|
||||
return [...(values[threatIndex].technique ?? [])];
|
||||
}, [values, threatIndex]);
|
||||
|
||||
const removeSubtechnique = useCallback(
|
||||
(index: number) => {
|
||||
const threats = [...(field.value as Threats)];
|
||||
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
|
||||
const subtechniques = technique[techniqueIndex].subtechnique ?? [];
|
||||
if (subtechniques != null) {
|
||||
subtechniques.splice(index, 1);
|
||||
|
||||
threats[threatIndex].technique[techniqueIndex] = {
|
||||
...threats[threatIndex].technique[techniqueIndex],
|
||||
technique[techniqueIndex] = {
|
||||
...technique[techniqueIndex],
|
||||
subtechnique: subtechniques,
|
||||
};
|
||||
threats[threatIndex].technique = technique;
|
||||
onFieldChange(threats);
|
||||
}
|
||||
},
|
||||
[field, threatIndex, onFieldChange, techniqueIndex]
|
||||
[field, onFieldChange, techniqueIndex, technique, threatIndex]
|
||||
);
|
||||
|
||||
const addMitreAttackSubtechnique = useCallback(() => {
|
||||
const threats = [...(field.value as Threats)];
|
||||
|
||||
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
|
||||
const subtechniques = technique[techniqueIndex].subtechnique;
|
||||
|
||||
if (subtechniques != null) {
|
||||
threats[threatIndex].technique[techniqueIndex] = {
|
||||
...threats[threatIndex].technique[techniqueIndex],
|
||||
technique[techniqueIndex] = {
|
||||
...technique[techniqueIndex],
|
||||
subtechnique: [...subtechniques, { id: 'none', name: 'none', reference: 'none' }],
|
||||
};
|
||||
} else {
|
||||
threats[threatIndex].technique[techniqueIndex] = {
|
||||
...threats[threatIndex].technique[techniqueIndex],
|
||||
technique[techniqueIndex] = {
|
||||
...technique[techniqueIndex],
|
||||
subtechnique: [{ id: 'none', name: 'none', reference: 'none' }],
|
||||
};
|
||||
}
|
||||
|
||||
threats[threatIndex].technique = technique;
|
||||
onFieldChange(threats);
|
||||
}, [field, threatIndex, onFieldChange, techniqueIndex]);
|
||||
}, [field, onFieldChange, techniqueIndex, technique, threatIndex]);
|
||||
|
||||
const updateSubtechnique = useCallback(
|
||||
(index: number, value: string) => {
|
||||
|
@ -99,7 +100,7 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
|||
name: '',
|
||||
reference: '',
|
||||
};
|
||||
const subtechniques = threats[threatIndex].technique[techniqueIndex].subtechnique;
|
||||
const subtechniques = technique[techniqueIndex].subtechnique;
|
||||
|
||||
if (subtechniques != null) {
|
||||
onFieldChange([
|
||||
|
@ -107,9 +108,9 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
|||
{
|
||||
...threats[threatIndex],
|
||||
technique: [
|
||||
...threats[threatIndex].technique.slice(0, techniqueIndex),
|
||||
...technique.slice(0, techniqueIndex),
|
||||
{
|
||||
...threats[threatIndex].technique[techniqueIndex],
|
||||
...technique[techniqueIndex],
|
||||
subtechnique: [
|
||||
...subtechniques.slice(0, index),
|
||||
{
|
||||
|
@ -120,19 +121,21 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
|||
...subtechniques.slice(index + 1),
|
||||
],
|
||||
},
|
||||
...threats[threatIndex].technique.slice(techniqueIndex + 1),
|
||||
...technique.slice(techniqueIndex + 1),
|
||||
],
|
||||
},
|
||||
...threats.slice(threatIndex + 1),
|
||||
]);
|
||||
}
|
||||
},
|
||||
[threatIndex, techniqueIndex, onFieldChange, field]
|
||||
[threatIndex, techniqueIndex, onFieldChange, field, technique]
|
||||
);
|
||||
|
||||
const getSelectSubtechnique = useCallback(
|
||||
(index: number, disabled: boolean, subtechnique: ThreatSubtechnique) => {
|
||||
const options = subtechniquesOptions.filter((t) => t.techniqueId === technique.id);
|
||||
const options = subtechniquesOptions.filter(
|
||||
(t) => t.techniqueId === technique[techniqueIndex].id
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -166,13 +169,17 @@ export const MitreAttackSubtechniqueFields: React.FC<AddSubtechniqueProps> = ({
|
|||
</>
|
||||
);
|
||||
},
|
||||
[field, updateSubtechnique, technique]
|
||||
[field, updateSubtechnique, technique, techniqueIndex]
|
||||
);
|
||||
|
||||
const subtechniques = useMemo(() => {
|
||||
return technique[techniqueIndex].subtechnique;
|
||||
}, [technique, techniqueIndex]);
|
||||
|
||||
return (
|
||||
<SubtechniqueContainer>
|
||||
{technique.subtechnique != null &&
|
||||
technique.subtechnique.map((subtechnique, index) => (
|
||||
{subtechniques != null &&
|
||||
subtechniques.map((subtechnique, index) => (
|
||||
<div key={index}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
|
|
|
@ -57,7 +57,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
|||
const removeTechnique = useCallback(
|
||||
(index: number) => {
|
||||
const threats = [...(field.value as Threats)];
|
||||
const techniques = threats[threatIndex].technique;
|
||||
const techniques = threats[threatIndex].technique ?? [];
|
||||
techniques.splice(index, 1);
|
||||
threats[threatIndex] = {
|
||||
...threats[threatIndex],
|
||||
|
@ -73,7 +73,7 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
|||
threats[threatIndex] = {
|
||||
...threats[threatIndex],
|
||||
technique: [
|
||||
...threats[threatIndex].technique,
|
||||
...(threats[threatIndex].technique ?? []),
|
||||
{ id: 'none', name: 'none', reference: 'none', subtechnique: [] },
|
||||
],
|
||||
};
|
||||
|
@ -88,19 +88,20 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
|||
name: '',
|
||||
reference: '',
|
||||
};
|
||||
const technique = threats[threatIndex].technique ?? [];
|
||||
onFieldChange([
|
||||
...threats.slice(0, threatIndex),
|
||||
{
|
||||
...threats[threatIndex],
|
||||
technique: [
|
||||
...threats[threatIndex].technique.slice(0, index),
|
||||
...technique.slice(0, index),
|
||||
{
|
||||
id,
|
||||
reference,
|
||||
name,
|
||||
subtechnique: [],
|
||||
},
|
||||
...threats[threatIndex].technique.slice(index + 1),
|
||||
...technique.slice(index + 1),
|
||||
],
|
||||
},
|
||||
...threats.slice(threatIndex + 1),
|
||||
|
@ -147,9 +148,11 @@ export const MitreAttackTechniqueFields: React.FC<AddTechniqueProps> = ({
|
|||
[field, updateTechnique]
|
||||
);
|
||||
|
||||
const techniques = values[threatIndex].technique ?? [];
|
||||
|
||||
return (
|
||||
<TechniqueContainer>
|
||||
{values[threatIndex].technique.map((technique, index) => (
|
||||
{techniques.map((technique, index) => (
|
||||
<div key={index}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
|
|
|
@ -182,7 +182,7 @@ export const filterEmptyThreats = (threats: Threats): Threats => {
|
|||
.map((threat) => {
|
||||
return {
|
||||
...threat,
|
||||
technique: trimThreatsWithNoName(threat.technique).map((technique) => {
|
||||
technique: trimThreatsWithNoName(threat.technique ?? []).map((technique) => {
|
||||
return {
|
||||
...technique,
|
||||
subtechnique:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue