mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[8.18] [Security Solution] Normalizes `filters` field before rule diff comparison (#206344) (#209531)
# Backport This will backport the following commits from `main` to `8.18`: - [[Security Solution] Normalizes `filters` field before rule diff comparison (#206344)](https://github.com/elastic/kibana/pull/206344) <!--- 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":"2025-02-04T12:40:52Z","message":"[Security Solution] Normalizes `filters` field before rule diff comparison (#206344)\n\n**Fixes: https://github.com/elastic/kibana/issues/202966**\r\n**Fixes: https://github.com/elastic/kibana/issues/206527**\r\n\r\n## Summary\r\n\r\nThe issue that causes the overarching problem mentioned in the ticket is\r\nthat we add an extra `alias: null` property to the filter via the kibana\r\nfilter utils instead of keeping the `alias` field unset. This is\r\nfunctionally the same rule but since the prebuilt rule objects are\r\ntechnically different (`alias` is set to `undefined` instead of `null`),\r\nwe mark these rules as customized and causes the query fields to show as\r\na modified field on update.\r\n\r\nTo address this, since changing the kibana util filter would be very\r\ninvasive and touching a lot of code, we instead normalize the field on\r\nour side before version comparison. This fixes the bug reported and\r\nimproves resiliency of rule upgrades in the future.\r\n\r\n### Testing (copied from ticket) \r\n\r\n- Ensure the `prebuiltRulesCustomizationEnabled` feature flag is enabled\r\n- Allow internal APIs via adding `server.restrictInternalApis: false` to\r\n`kibana.dev.yaml`\r\n- Clear Elasticsearch data\r\n- Run Elasticsearch and Kibana locally (do not open Kibana in a web\r\nbrowser)\r\n- Install an outdated version of the `security_detection_engine` Fleet\r\npackage\r\n```bash\r\ncurl -X POST --user elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123' -H \"elastic-api-version: 2023-10-31\" -d '{\"force\":true}' http://localhost:5601/kbn/api/fleet/epm/packages/security_detection_engine/8.14.1\r\n```\r\n\r\n- Install prebuilt rules\r\n```bash\r\ncurl -X POST --user elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123' -H \"elastic-api-version: 1\" -d '{\"mode\":\"ALL_RULES\"}' http://localhost:5601/kbn/internal/detection_engine/prebuilt_rules/installation/_perform\r\n```\r\n\r\n- Open a `threat_match` rule for editing. For example `Threat Intel Hash\r\nIndicator Match` with rule_id `aab184d3-72b3-4639-b242-6597c99d8bca`.\r\n\r\nWith this fix, users should **NOT** see any extra fields in the rule\r\nupgrade flyout, nor should the rule be marked as \"Modified\" if opened\r\nand saved with no other modifications\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\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"a5b4570cd86b8c23d1687f4b2a6b1fec47876dcc","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","v9.0.0","Team:Detections and Resp","Team: SecuritySolution","Team:Detection Rule Management","Feature:Prebuilt Detection Rules","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[Security Solution] Normalizes `filters` field before rule diff comparison ","number":206344,"url":"https://github.com/elastic/kibana/pull/206344","mergeCommit":{"message":"[Security Solution] Normalizes `filters` field before rule diff comparison (#206344)\n\n**Fixes: https://github.com/elastic/kibana/issues/202966**\r\n**Fixes: https://github.com/elastic/kibana/issues/206527**\r\n\r\n## Summary\r\n\r\nThe issue that causes the overarching problem mentioned in the ticket is\r\nthat we add an extra `alias: null` property to the filter via the kibana\r\nfilter utils instead of keeping the `alias` field unset. This is\r\nfunctionally the same rule but since the prebuilt rule objects are\r\ntechnically different (`alias` is set to `undefined` instead of `null`),\r\nwe mark these rules as customized and causes the query fields to show as\r\na modified field on update.\r\n\r\nTo address this, since changing the kibana util filter would be very\r\ninvasive and touching a lot of code, we instead normalize the field on\r\nour side before version comparison. This fixes the bug reported and\r\nimproves resiliency of rule upgrades in the future.\r\n\r\n### Testing (copied from ticket) \r\n\r\n- Ensure the `prebuiltRulesCustomizationEnabled` feature flag is enabled\r\n- Allow internal APIs via adding `server.restrictInternalApis: false` to\r\n`kibana.dev.yaml`\r\n- Clear Elasticsearch data\r\n- Run Elasticsearch and Kibana locally (do not open Kibana in a web\r\nbrowser)\r\n- Install an outdated version of the `security_detection_engine` Fleet\r\npackage\r\n```bash\r\ncurl -X POST --user elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123' -H \"elastic-api-version: 2023-10-31\" -d '{\"force\":true}' http://localhost:5601/kbn/api/fleet/epm/packages/security_detection_engine/8.14.1\r\n```\r\n\r\n- Install prebuilt rules\r\n```bash\r\ncurl -X POST --user elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123' -H \"elastic-api-version: 1\" -d '{\"mode\":\"ALL_RULES\"}' http://localhost:5601/kbn/internal/detection_engine/prebuilt_rules/installation/_perform\r\n```\r\n\r\n- Open a `threat_match` rule for editing. For example `Threat Intel Hash\r\nIndicator Match` with rule_id `aab184d3-72b3-4639-b242-6597c99d8bca`.\r\n\r\nWith this fix, users should **NOT** see any extra fields in the rule\r\nupgrade flyout, nor should the rule be marked as \"Modified\" if opened\r\nand saved with no other modifications\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\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"a5b4570cd86b8c23d1687f4b2a6b1fec47876dcc"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/206344","number":206344,"mergeCommit":{"message":"[Security Solution] Normalizes `filters` field before rule diff comparison (#206344)\n\n**Fixes: https://github.com/elastic/kibana/issues/202966**\r\n**Fixes: https://github.com/elastic/kibana/issues/206527**\r\n\r\n## Summary\r\n\r\nThe issue that causes the overarching problem mentioned in the ticket is\r\nthat we add an extra `alias: null` property to the filter via the kibana\r\nfilter utils instead of keeping the `alias` field unset. This is\r\nfunctionally the same rule but since the prebuilt rule objects are\r\ntechnically different (`alias` is set to `undefined` instead of `null`),\r\nwe mark these rules as customized and causes the query fields to show as\r\na modified field on update.\r\n\r\nTo address this, since changing the kibana util filter would be very\r\ninvasive and touching a lot of code, we instead normalize the field on\r\nour side before version comparison. This fixes the bug reported and\r\nimproves resiliency of rule upgrades in the future.\r\n\r\n### Testing (copied from ticket) \r\n\r\n- Ensure the `prebuiltRulesCustomizationEnabled` feature flag is enabled\r\n- Allow internal APIs via adding `server.restrictInternalApis: false` to\r\n`kibana.dev.yaml`\r\n- Clear Elasticsearch data\r\n- Run Elasticsearch and Kibana locally (do not open Kibana in a web\r\nbrowser)\r\n- Install an outdated version of the `security_detection_engine` Fleet\r\npackage\r\n```bash\r\ncurl -X POST --user elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123' -H \"elastic-api-version: 2023-10-31\" -d '{\"force\":true}' http://localhost:5601/kbn/api/fleet/epm/packages/security_detection_engine/8.14.1\r\n```\r\n\r\n- Install prebuilt rules\r\n```bash\r\ncurl -X POST --user elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123' -H \"elastic-api-version: 1\" -d '{\"mode\":\"ALL_RULES\"}' http://localhost:5601/kbn/internal/detection_engine/prebuilt_rules/installation/_perform\r\n```\r\n\r\n- Open a `threat_match` rule for editing. For example `Threat Intel Hash\r\nIndicator Match` with rule_id `aab184d3-72b3-4639-b242-6597c99d8bca`.\r\n\r\nWith this fix, users should **NOT** see any extra fields in the rule\r\nupgrade flyout, nor should the rule be marked as \"Modified\" if opened\r\nand saved with no other modifications\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\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"a5b4570cd86b8c23d1687f4b2a6b1fec47876dcc"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
This commit is contained in:
parent
f6e739b19e
commit
f25de199b7
6 changed files with 182 additions and 6 deletions
|
@ -75,7 +75,7 @@ export function mapFilter(filter: Filter) {
|
|||
mappedFilter.meta.params = mapped.params;
|
||||
mappedFilter.meta.disabled = Boolean(mappedFilter.meta.disabled);
|
||||
mappedFilter.meta.negate = Boolean(mappedFilter.meta.negate);
|
||||
mappedFilter.meta.alias = mappedFilter.meta.alias || null;
|
||||
mappedFilter.meta.alias = mappedFilter.meta.alias;
|
||||
|
||||
return mappedFilter;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('filter manager utilities', () => {
|
|||
"params": Array [
|
||||
Object {
|
||||
"meta": Object {
|
||||
"alias": null,
|
||||
"alias": undefined,
|
||||
"disabled": false,
|
||||
"index": "logstash-*",
|
||||
"key": "bytes",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { KqlQueryType } from '../../../api/detection_engine';
|
||||
import {
|
||||
extractRuleEqlQuery,
|
||||
|
@ -12,6 +13,24 @@ import {
|
|||
extractRuleKqlQuery,
|
||||
} from './extract_rule_data_query';
|
||||
|
||||
const mockFilter: Filter = {
|
||||
meta: {
|
||||
alias: null,
|
||||
negate: false,
|
||||
disabled: false,
|
||||
type: 'phrase',
|
||||
key: 'test',
|
||||
params: {
|
||||
query: 'value',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
term: {
|
||||
field: 'value',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('extract rule data queries', () => {
|
||||
describe('extractRuleKqlQuery', () => {
|
||||
it('extracts a trimmed version of the query field for inline query types', () => {
|
||||
|
@ -24,6 +43,39 @@ describe('extract rule data queries', () => {
|
|||
filters: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('normalizes filters', () => {
|
||||
const extractedKqlQuery = extractRuleKqlQuery(
|
||||
'event.kind:alert',
|
||||
'kuery',
|
||||
[mockFilter],
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(extractedKqlQuery).toEqual({
|
||||
type: KqlQueryType.inline_query,
|
||||
query: 'event.kind:alert',
|
||||
language: 'kuery',
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
negate: false,
|
||||
disabled: false,
|
||||
type: 'phrase',
|
||||
key: 'test',
|
||||
params: {
|
||||
query: 'value',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
term: {
|
||||
field: 'value',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractRuleEqlQuery', () => {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import type {
|
||||
EqlQueryLanguage,
|
||||
EsqlQueryLanguage,
|
||||
|
@ -48,7 +49,7 @@ export const extractInlineKqlQuery = (
|
|||
type: KqlQueryType.inline_query,
|
||||
query: query?.trim() ?? '',
|
||||
language: language ?? 'kuery',
|
||||
filters: filters ?? [],
|
||||
filters: normalizeFilterArray(filters),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -65,7 +66,7 @@ export const extractRuleEqlQuery = (params: ExtractRuleEqlQueryParams): RuleEqlQ
|
|||
return {
|
||||
query: params.query.trim(),
|
||||
language: params.language,
|
||||
filters: params.filters ?? [],
|
||||
filters: normalizeFilterArray(params.filters),
|
||||
event_category_override: params.eventCategoryOverride,
|
||||
timestamp_field: params.timestampField,
|
||||
tiebreaker_field: params.tiebreakerField,
|
||||
|
@ -81,3 +82,22 @@ export const extractRuleEsqlQuery = (
|
|||
language,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the null `alias` field that gets appended from the internal kibana filter util for comparison
|
||||
* Relevant issue: https://github.com/elastic/kibana/issues/202966
|
||||
*/
|
||||
const normalizeFilterArray = (filters: RuleFilterArray | undefined): RuleFilterArray => {
|
||||
if (!filters?.length) {
|
||||
return [];
|
||||
}
|
||||
return (filters as Filter[]).map((filter) => ({
|
||||
...filter,
|
||||
meta: filter.meta
|
||||
? {
|
||||
...filter.meta,
|
||||
alias: filter.meta.alias ?? undefined,
|
||||
}
|
||||
: undefined,
|
||||
}));
|
||||
};
|
||||
|
|
|
@ -175,8 +175,8 @@ export const QueryBarField = ({
|
|||
// if saved query fetched, reset values in queryBar input and filters to saved query's values
|
||||
useEffect(() => {
|
||||
if (resetToSavedQuery && savedQuery) {
|
||||
const newFiledValue = savedQueryToFieldValue(savedQuery);
|
||||
setFieldValue(newFiledValue);
|
||||
const newFieldValue = savedQueryToFieldValue(savedQuery);
|
||||
setFieldValue(newFieldValue);
|
||||
}
|
||||
}, [resetToSavedQuery, savedQuery, setFieldValue]);
|
||||
|
||||
|
|
|
@ -179,6 +179,110 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when all query versions have filters with alias fields set to null', () => {
|
||||
it('should not show in the upgrade/_review API response', async () => {
|
||||
// Install base prebuilt detection rule
|
||||
await createHistoricalPrebuiltRuleAssetSavedObjects(es, [
|
||||
createRuleAssetSavedObject({
|
||||
rule_id: 'rule-1',
|
||||
version: 1,
|
||||
type: 'query',
|
||||
query: 'query string = true',
|
||||
language: 'kuery',
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
negate: false,
|
||||
disabled: false,
|
||||
type: 'phrase',
|
||||
key: 'test',
|
||||
params: {
|
||||
query: 'value',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
term: {
|
||||
field: 'value',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
]);
|
||||
await installPrebuiltRules(es, supertest);
|
||||
|
||||
// Customize a kql_query field on the installed rule
|
||||
await updateRule(supertest, {
|
||||
...getPrebuiltRuleMock(),
|
||||
rule_id: 'rule-1',
|
||||
type: 'query',
|
||||
query: 'query string = true',
|
||||
language: 'kuery',
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
alias: null,
|
||||
negate: false,
|
||||
disabled: false,
|
||||
type: 'phrase',
|
||||
key: 'test',
|
||||
params: {
|
||||
query: 'value',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
term: {
|
||||
field: 'value',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
saved_id: undefined,
|
||||
} as RuleUpdateProps);
|
||||
|
||||
// Add a v2 rule asset to make the upgrade possible, do NOT update the related kql_query field, and create the new rule assets
|
||||
const updatedRuleAssetSavedObjects = [
|
||||
createRuleAssetSavedObject({
|
||||
rule_id: 'rule-1',
|
||||
version: 2,
|
||||
type: 'query',
|
||||
query: 'query string = true',
|
||||
language: 'kuery',
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
negate: false,
|
||||
disabled: false,
|
||||
type: 'phrase',
|
||||
key: 'test',
|
||||
params: {
|
||||
query: 'value',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
term: {
|
||||
field: 'value',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
|
||||
|
||||
// Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update but kql_query field is NOT returned
|
||||
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
|
||||
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
|
||||
expect(fieldDiffObject.kql_query).toBeUndefined();
|
||||
|
||||
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field
|
||||
expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0);
|
||||
expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0);
|
||||
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0);
|
||||
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue