[Security Solution][Detection Rules] Update prebuilt rule threats to match schema (#92281)

This commit is contained in:
Davis Plumlee 2021-02-24 17:16:50 -05:00 committed by GitHub
parent ebb0435401
commit 3471eaa481
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 137 additions and 98 deletions

View file

@ -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);

View file

@ -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', () => {

View file

@ -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', () => {

View file

@ -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', () => {

View file

@ -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', () => {

View file

@ -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>
);

View file

@ -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', () => {

View file

@ -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

View file

@ -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

View file

@ -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: