mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[8.16] [Security Solution] Fixes data normalization in diff algorithms for `threat` and `rule_schedule` fields (#200105) (#200645)
# Backport This will backport the following commits from `main` to `8.16`: - [[Security Solution] Fixes data normalization in diff algorithms for `threat` and `rule_schedule` fields (#200105)](https://github.com/elastic/kibana/pull/200105) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Davis Plumlee","email":"56367316+dplumlee@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-11-18T19:48:14Z","message":"[Security Solution] Fixes data normalization in diff algorithms for `threat` and `rule_schedule` fields (#200105)\n\n**Fixes https://github.com/elastic/kibana/issues/199629**\r\n\r\n## Summary\r\n\r\nFixes the data normalization we do before comparison for the `threat`\r\nand `rule_schedule` fields so that they align with our prebuilt rule\r\nspecs. Specifically:\r\n\r\n- Trims any extra optional nested fields in the `threat` field that were\r\nleft as empty arrays\r\n- Removes the logic to use the `from` value in the `meta` field if it\r\nexisted, so that we can normalize the time strings for `rule_schedule`\r\n\r\nThese errors were occurring when a rule was saved via the Rule Editing\r\nform in the UI and extra fields were added in the update API call. This\r\nPR makes the diff algorithms more robust against different field values\r\nthat are represented differently but are logically the same.\r\n\r\nThis extra data added in the Rule Edit UI form was also causing rules to\r\nappear as modified when saved from the form, even if no fields had been\r\nmodified.\r\n\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed","sha":"a8fd0c95148ab42411e5ad8e6a65df0634f67dbe","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","impact:high","v9.0.0","Team:Detections and Resp","Team: SecuritySolution","Team:Detection Rule Management","Feature:Prebuilt Detection Rules","backport:version","v8.17.0","v8.16.1"],"title":"[Security Solution] Fixes data normalization in diff algorithms for `threat` and `rule_schedule` fields","number":200105,"url":"https://github.com/elastic/kibana/pull/200105","mergeCommit":{"message":"[Security Solution] Fixes data normalization in diff algorithms for `threat` and `rule_schedule` fields (#200105)\n\n**Fixes https://github.com/elastic/kibana/issues/199629**\r\n\r\n## Summary\r\n\r\nFixes the data normalization we do before comparison for the `threat`\r\nand `rule_schedule` fields so that they align with our prebuilt rule\r\nspecs. Specifically:\r\n\r\n- Trims any extra optional nested fields in the `threat` field that were\r\nleft as empty arrays\r\n- Removes the logic to use the `from` value in the `meta` field if it\r\nexisted, so that we can normalize the time strings for `rule_schedule`\r\n\r\nThese errors were occurring when a rule was saved via the Rule Editing\r\nform in the UI and extra fields were added in the update API call. This\r\nPR makes the diff algorithms more robust against different field values\r\nthat are represented differently but are logically the same.\r\n\r\nThis extra data added in the Rule Edit UI form was also causing rules to\r\nappear as modified when saved from the form, even if no fields had been\r\nmodified.\r\n\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed","sha":"a8fd0c95148ab42411e5ad8e6a65df0634f67dbe"}},"sourceBranch":"main","suggestedTargetBranches":["8.x","8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/200105","number":200105,"mergeCommit":{"message":"[Security Solution] Fixes data normalization in diff algorithms for `threat` and `rule_schedule` fields (#200105)\n\n**Fixes https://github.com/elastic/kibana/issues/199629**\r\n\r\n## Summary\r\n\r\nFixes the data normalization we do before comparison for the `threat`\r\nand `rule_schedule` fields so that they align with our prebuilt rule\r\nspecs. Specifically:\r\n\r\n- Trims any extra optional nested fields in the `threat` field that were\r\nleft as empty arrays\r\n- Removes the logic to use the `from` value in the `meta` field if it\r\nexisted, so that we can normalize the time strings for `rule_schedule`\r\n\r\nThese errors were occurring when a rule was saved via the Rule Editing\r\nform in the UI and extra fields were added in the update API call. This\r\nPR makes the diff algorithms more robust against different field values\r\nthat are represented differently but are logically the same.\r\n\r\nThis extra data added in the Rule Edit UI form was also causing rules to\r\nappear as modified when saved from the form, even if no fields had been\r\nmodified.\r\n\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed","sha":"a8fd0c95148ab42411e5ad8e6a65df0634f67dbe"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
This commit is contained in:
parent
b2c5123e03
commit
c8b46e87c4
6 changed files with 108 additions and 20 deletions
|
@ -53,6 +53,7 @@ import { extractRuleNameOverrideObject } from './extract_rule_name_override_obje
|
|||
import { extractRuleSchedule } from './extract_rule_schedule';
|
||||
import { extractTimelineTemplateReference } from './extract_timeline_template_reference';
|
||||
import { extractTimestampOverrideObject } from './extract_timestamp_override_object';
|
||||
import { extractThreatArray } from './extract_threat_array';
|
||||
|
||||
/**
|
||||
* Normalizes a given rule to the form which is suitable for passing to the diff algorithm.
|
||||
|
@ -128,7 +129,7 @@ const extractDiffableCommonFields = (
|
|||
// About -> Advanced settings
|
||||
references: rule.references ?? [],
|
||||
false_positives: rule.false_positives ?? [],
|
||||
threat: rule.threat ?? [],
|
||||
threat: extractThreatArray(rule),
|
||||
note: rule.note ?? '',
|
||||
setup: rule.setup ?? '',
|
||||
related_integrations: rule.related_integrations ?? [],
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getRulesSchemaMock } from '../../../api/detection_engine/model/rule_schema/mocks';
|
||||
import { extractRuleSchedule } from './extract_rule_schedule';
|
||||
|
||||
describe('extractRuleSchedule', () => {
|
||||
it('normalizes lookback strings to seconds', () => {
|
||||
const mockRule = { ...getRulesSchemaMock(), from: 'now-6m', interval: '5m', to: 'now' };
|
||||
const normalizedRuleSchedule = extractRuleSchedule(mockRule);
|
||||
|
||||
expect(normalizedRuleSchedule).toEqual({ interval: '5m', lookback: '60s' });
|
||||
});
|
||||
});
|
|
@ -9,7 +9,7 @@ import moment from 'moment';
|
|||
import dateMath from '@elastic/datemath';
|
||||
import { parseDuration } from '@kbn/alerting-plugin/common';
|
||||
|
||||
import type { RuleMetadata, RuleResponse } from '../../../api/detection_engine/model/rule_schema';
|
||||
import type { RuleResponse } from '../../../api/detection_engine/model/rule_schema';
|
||||
import type { RuleSchedule } from '../../../api/detection_engine/prebuilt_rules';
|
||||
|
||||
export const extractRuleSchedule = (rule: RuleResponse): RuleSchedule => {
|
||||
|
@ -17,26 +17,9 @@ export const extractRuleSchedule = (rule: RuleResponse): RuleSchedule => {
|
|||
const from = rule.from ?? 'now-6m';
|
||||
const to = rule.to ?? 'now';
|
||||
|
||||
const ruleMeta: RuleMetadata = ('meta' in rule ? rule.meta : undefined) ?? {};
|
||||
const lookbackFromMeta = String(ruleMeta.from ?? '');
|
||||
|
||||
const intervalDuration = parseInterval(interval);
|
||||
const lookbackFromMetaDuration = parseInterval(lookbackFromMeta);
|
||||
const driftToleranceDuration = parseDriftTolerance(from, to);
|
||||
|
||||
if (lookbackFromMetaDuration != null) {
|
||||
if (intervalDuration != null) {
|
||||
return {
|
||||
interval,
|
||||
lookback: lookbackFromMeta,
|
||||
};
|
||||
}
|
||||
return {
|
||||
interval: `Cannot parse: interval="${interval}"`,
|
||||
lookback: lookbackFromMeta,
|
||||
};
|
||||
}
|
||||
|
||||
if (intervalDuration == null) {
|
||||
return {
|
||||
interval: `Cannot parse: interval="${interval}"`,
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getRulesSchemaMock } from '../../../api/detection_engine/model/rule_schema/mocks';
|
||||
import { getThreatMock } from '../../schemas/types/threat.mock';
|
||||
import { extractThreatArray } from './extract_threat_array';
|
||||
|
||||
const mockThreat = getThreatMock()[0];
|
||||
|
||||
describe('extractThreatArray', () => {
|
||||
it('trims empty technique fields from threat object', () => {
|
||||
const mockRule = { ...getRulesSchemaMock(), threat: [{ ...mockThreat, technique: [] }] };
|
||||
const normalizedThreatArray = extractThreatArray(mockRule);
|
||||
|
||||
expect(normalizedThreatArray).toEqual([
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
id: 'TA0000',
|
||||
name: 'test tactic',
|
||||
reference: 'https://attack.mitre.org/tactics/TA0000/',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('trims empty subtechnique fields from threat object', () => {
|
||||
const mockRule = {
|
||||
...getRulesSchemaMock(),
|
||||
threat: [{ ...mockThreat, technique: [{ ...mockThreat.technique![0], subtechnique: [] }] }],
|
||||
};
|
||||
const normalizedThreatArray = extractThreatArray(mockRule);
|
||||
|
||||
expect(normalizedThreatArray).toEqual([
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
id: 'TA0000',
|
||||
name: 'test tactic',
|
||||
reference: 'https://attack.mitre.org/tactics/TA0000/',
|
||||
},
|
||||
technique: [
|
||||
{
|
||||
id: 'T0000',
|
||||
name: 'test technique',
|
||||
reference: 'https://attack.mitre.org/techniques/T0000/',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type {
|
||||
RuleResponse,
|
||||
ThreatArray,
|
||||
ThreatTechnique,
|
||||
} from '../../../api/detection_engine/model/rule_schema';
|
||||
|
||||
export const extractThreatArray = (rule: RuleResponse): ThreatArray =>
|
||||
rule.threat.map((threat) => {
|
||||
if (threat.technique && threat.technique.length) {
|
||||
return { ...threat, technique: trimTechniqueArray(threat.technique) };
|
||||
}
|
||||
return { ...threat, technique: undefined }; // If `technique` is an empty array, remove the field from the `threat` object
|
||||
});
|
||||
|
||||
const trimTechniqueArray = (techniqueArray: ThreatTechnique[]): ThreatTechnique[] => {
|
||||
return techniqueArray.map((technique) => ({
|
||||
...technique,
|
||||
subtechnique:
|
||||
technique.subtechnique && technique.subtechnique.length ? technique.subtechnique : undefined, // If `subtechnique` is an empty array, remove the field from the `technique` object
|
||||
}));
|
||||
};
|
|
@ -375,7 +375,9 @@ export const filterEmptyThreats = (threats: Threats): Threats => {
|
|||
return {
|
||||
...technique,
|
||||
subtechnique:
|
||||
technique.subtechnique != null ? trimThreatsWithNoName(technique.subtechnique) : [],
|
||||
technique.subtechnique != null
|
||||
? trimThreatsWithNoName(technique.subtechnique)
|
||||
: undefined,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue