[8.x] [Security Solution] Integration tests for `query` diff algorithms (#192655) (#193108)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] Integration tests for `query` diff
algorithms (#192655)](https://github.com/elastic/kibana/pull/192655)

<!--- 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-09-16T23:58:55Z","message":"[Security
Solution] Integration tests for `query` diff algorithms (#192655)\n\n##
Summary\r\n\r\nCompletes
https://github.com/elastic/kibana/issues/187658\r\n\r\n\r\nSwitches
`kql_query`, `eql_query`, and `esql_query` fields to use
the\r\nimplemented diff algorithms assigned to them
in\r\nhttps://github.com/elastic/kibana/pull/190179\r\n\r\n\r\nAdds
integration tests in accordance
to\r\nhttps://github.com/elastic/kibana/pull/192529 for the
`upgrade/_review`\r\nAPI endpoint for the `query` field diff
algorithms.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\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- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"ceb1b1a4bf253ac94f9ba0ba649e9a4908a76c51","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["test","release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection Rule
Management","Feature:Prebuilt Detection
Rules","v8.16.0"],"title":"[Security Solution] Integration tests for
`query` diff
algorithms","number":192655,"url":"https://github.com/elastic/kibana/pull/192655","mergeCommit":{"message":"[Security
Solution] Integration tests for `query` diff algorithms (#192655)\n\n##
Summary\r\n\r\nCompletes
https://github.com/elastic/kibana/issues/187658\r\n\r\n\r\nSwitches
`kql_query`, `eql_query`, and `esql_query` fields to use
the\r\nimplemented diff algorithms assigned to them
in\r\nhttps://github.com/elastic/kibana/pull/190179\r\n\r\n\r\nAdds
integration tests in accordance
to\r\nhttps://github.com/elastic/kibana/pull/192529 for the
`upgrade/_review`\r\nAPI endpoint for the `query` field diff
algorithms.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\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- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"ceb1b1a4bf253ac94f9ba0ba649e9a4908a76c51"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/192655","number":192655,"mergeCommit":{"message":"[Security
Solution] Integration tests for `query` diff algorithms (#192655)\n\n##
Summary\r\n\r\nCompletes
https://github.com/elastic/kibana/issues/187658\r\n\r\n\r\nSwitches
`kql_query`, `eql_query`, and `esql_query` fields to use
the\r\nimplemented diff algorithms assigned to them
in\r\nhttps://github.com/elastic/kibana/pull/190179\r\n\r\n\r\nAdds
integration tests in accordance
to\r\nhttps://github.com/elastic/kibana/pull/192529 for the
`upgrade/_review`\r\nAPI endpoint for the `query` field diff
algorithms.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\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- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"ceb1b1a4bf253ac94f9ba0ba649e9a4908a76c51"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-09-17 11:26:54 +10:00 committed by GitHub
parent 87786b5783
commit 9d09dbfcd4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 2041 additions and 8 deletions

View file

@ -44,6 +44,9 @@ import {
scalarArrayDiffAlgorithm,
simpleDiffAlgorithm,
singleLineStringDiffAlgorithm,
kqlQueryDiffAlgorithm,
eqlQueryDiffAlgorithm,
esqlQueryDiffAlgorithm,
} from './algorithms';
const BASE_TYPE_ERROR = `Base version can't be of different rule type`;
@ -210,7 +213,7 @@ const calculateCustomQueryFieldsDiff = (
const customQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableCustomQueryFields> = {
type: simpleDiffAlgorithm,
kql_query: simpleDiffAlgorithm,
kql_query: kqlQueryDiffAlgorithm,
data_source: dataSourceDiffAlgorithm,
alert_suppression: simpleDiffAlgorithm,
};
@ -223,7 +226,7 @@ const calculateSavedQueryFieldsDiff = (
const savedQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableSavedQueryFields> = {
type: simpleDiffAlgorithm,
kql_query: simpleDiffAlgorithm,
kql_query: kqlQueryDiffAlgorithm,
data_source: dataSourceDiffAlgorithm,
alert_suppression: simpleDiffAlgorithm,
};
@ -236,7 +239,7 @@ const calculateEqlFieldsDiff = (
const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableEqlFields> = {
type: simpleDiffAlgorithm,
eql_query: simpleDiffAlgorithm,
eql_query: eqlQueryDiffAlgorithm,
data_source: dataSourceDiffAlgorithm,
event_category_override: singleLineStringDiffAlgorithm,
timestamp_field: singleLineStringDiffAlgorithm,
@ -252,7 +255,7 @@ const calculateEsqlFieldsDiff = (
const esqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableEsqlFields> = {
type: simpleDiffAlgorithm,
esql_query: simpleDiffAlgorithm,
esql_query: esqlQueryDiffAlgorithm,
alert_suppression: simpleDiffAlgorithm,
};
@ -264,9 +267,9 @@ const calculateThreatMatchFieldsDiff = (
const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableThreatMatchFields> = {
type: simpleDiffAlgorithm,
kql_query: simpleDiffAlgorithm,
kql_query: kqlQueryDiffAlgorithm,
data_source: dataSourceDiffAlgorithm,
threat_query: simpleDiffAlgorithm,
threat_query: kqlQueryDiffAlgorithm,
threat_index: scalarArrayDiffAlgorithm,
threat_mapping: simpleDiffAlgorithm,
threat_indicator_path: singleLineStringDiffAlgorithm,
@ -282,7 +285,7 @@ const calculateThresholdFieldsDiff = (
const thresholdFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableThresholdFields> = {
type: simpleDiffAlgorithm,
kql_query: simpleDiffAlgorithm,
kql_query: kqlQueryDiffAlgorithm,
data_source: dataSourceDiffAlgorithm,
threshold: simpleDiffAlgorithm,
alert_suppression: simpleDiffAlgorithm,
@ -310,7 +313,7 @@ const calculateNewTermsFieldsDiff = (
const newTermsFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableNewTermsFields> = {
type: simpleDiffAlgorithm,
kql_query: simpleDiffAlgorithm,
kql_query: kqlQueryDiffAlgorithm,
data_source: dataSourceDiffAlgorithm,
new_terms_fields: scalarArrayDiffAlgorithm,
history_window_start: singleLineStringDiffAlgorithm,

View file

@ -22,6 +22,9 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.scalar_array_fields'));
loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.multi_line_string_fields'));
loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.data_source_fields'));
loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.kql_query_fields'));
loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.eql_query_fields'));
loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.esql_query_fields'));
loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.stats'));
});
};

View file

@ -0,0 +1,465 @@
/*
* 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 expect from 'expect';
import {
AllFieldsDiff,
RuleUpdateProps,
ThreeWayDiffConflict,
ThreeWayDiffOutcome,
ThreeWayMergeOutcome,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { getPrebuiltRuleMock } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import {
deleteAllTimelines,
deleteAllPrebuiltRuleAssets,
createRuleAssetSavedObject,
installPrebuiltRules,
createPrebuiltRuleAssetSavedObjects,
reviewPrebuiltRulesToUpgrade,
createHistoricalPrebuiltRuleAssetSavedObjects,
updateRule,
} from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllTimelines(es, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
describe(`eql_query fields`, () => {
const getRuleAssetSavedObjects = () => [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 1,
type: 'eql',
query: 'query where true',
language: 'eql',
filters: [],
}),
];
describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => {
it('should not show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Add a v2 rule asset to make the upgrade possible, do NOT update the related eql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'eql',
query: 'query where true',
language: 'eql',
filters: [],
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update but eql_query field is NOT returned
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.eql_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", () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Customize an eql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'eql',
query: 'query where false',
language: 'eql',
filters: [],
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, do NOT update the related eql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'eql',
query: 'query where true',
language: 'eql',
filters: [],
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that eql_query diff field is returned but field does not have an update
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.eql_query).toEqual({
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
merged_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
has_update: false,
has_base_version: true,
});
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_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update but does not have a custom value - scenario AAB', () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Add a v2 rule asset to make the upgrade possible, update an eql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'eql',
query: 'query where false',
language: 'eql',
filters: [],
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.eql_query).toEqual({
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
merged_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Target,
conflict: ThreeWayDiffConflict.NONE,
has_update: true,
has_base_version: true,
});
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `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_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update and a custom value that are the same - scenario ABB', () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Customize an eql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'eql',
query: 'query where false',
language: 'eql',
filters: [],
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, update an eql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'eql',
query: 'query where false',
language: 'eql',
filters: [],
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains eql_query field
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.eql_query).toEqual({
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
target_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
merged_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
has_update: false,
has_base_version: true,
});
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_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update and a custom value that are different - scenario ABC', () => {
it('should show a non-solvable conflict in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Customize an eql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'eql',
query: 'query where true',
language: 'eql',
filters: [{ field: 'query' }],
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, update an eql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'eql',
query: 'query where false',
language: 'eql',
filters: [],
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
// and eql_query field update has conflict
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.eql_query).toEqual({
base_version: {
query: 'query where true',
language: 'eql',
filters: [],
},
current_version: {
query: 'query where true',
language: 'eql',
filters: [{ field: 'query' }],
},
target_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
merged_version: {
query: 'query where true',
language: 'eql',
filters: [{ field: 'query' }],
},
diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
has_update: true,
has_base_version: true,
});
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field
expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1);
expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1);
});
});
describe('when rule base version does not exist', () => {
describe('when rule field has an update and a custom value that are the same - scenario -AA', () => {
it('should not show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Clear previous rule assets
await deleteAllPrebuiltRuleAssets(es, log);
// Add a v2 rule asset to make the upgrade possible, but keep eql_query field unchanged
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'eql',
query: 'query where true',
language: 'eql',
filters: [],
}),
];
await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
// but does NOT contain eql_query field
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.eql_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(1); // `version` is considered conflict
expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update and a custom value that are different - scenario -AB', () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Clear previous rule assets
await deleteAllPrebuiltRuleAssets(es, log);
// Customize an eql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'eql',
query: 'query where false',
language: 'eql',
filters: [],
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, update an eql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'eql',
query: 'new query where true',
language: 'eql',
filters: [],
}),
];
await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
// and eql_query field update does not have a conflict
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.eql_query).toEqual({
current_version: {
query: 'query where false',
language: 'eql',
filters: [],
},
target_version: {
query: 'new query where true',
language: 'eql',
filters: [],
},
merged_version: {
query: 'new query where true',
language: 'eql',
filters: [],
},
diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Target,
conflict: ThreeWayDiffConflict.SOLVABLE,
has_update: true,
has_base_version: false,
});
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // version + query
expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + query
expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
});
});
});
};

View file

@ -0,0 +1,434 @@
/*
* 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 expect from 'expect';
import {
AllFieldsDiff,
RuleUpdateProps,
ThreeWayDiffConflict,
ThreeWayDiffOutcome,
ThreeWayMergeOutcome,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { getPrebuiltRuleMock } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import {
deleteAllTimelines,
deleteAllPrebuiltRuleAssets,
createRuleAssetSavedObject,
installPrebuiltRules,
createPrebuiltRuleAssetSavedObjects,
reviewPrebuiltRulesToUpgrade,
createHistoricalPrebuiltRuleAssetSavedObjects,
updateRule,
} from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllTimelines(es, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
describe(`esql_query fields`, () => {
const getRuleAssetSavedObjects = () => [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 1,
type: 'esql',
query: 'FROM query WHERE true',
language: 'esql',
}),
];
describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => {
it('should not show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Add a v2 rule asset to make the upgrade possible, do NOT update the related esql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'esql',
query: 'FROM query WHERE true',
language: 'esql',
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update but esql_query field is NOT returned
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.esql_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", () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Customize an esql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'esql',
query: 'FROM query WHERE false',
language: 'esql',
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, do NOT update the related esql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'esql',
query: 'FROM query WHERE true',
language: 'esql',
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that esql_query diff field is returned but field does not have an update
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.esql_query).toEqual({
base_version: {
query: 'FROM query WHERE true',
language: 'esql',
},
current_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
target_version: {
query: 'FROM query WHERE true',
language: 'esql',
},
merged_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
has_update: false,
has_base_version: true,
});
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_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update but does not have a custom value - scenario AAB', () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Add a v2 rule asset to make the upgrade possible, update an esql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'esql',
query: 'FROM query WHERE false',
language: 'esql',
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.esql_query).toEqual({
base_version: {
query: 'FROM query WHERE true',
language: 'esql',
},
current_version: {
query: 'FROM query WHERE true',
language: 'esql',
},
target_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
merged_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Target,
conflict: ThreeWayDiffConflict.NONE,
has_update: true,
has_base_version: true,
});
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `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_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update and a custom value that are the same - scenario ABB', () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Customize an esql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'esql',
query: 'FROM query WHERE false',
language: 'esql',
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, update an esql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'esql',
query: 'FROM query WHERE false',
language: 'esql',
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains esql_query field
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.esql_query).toEqual({
base_version: {
query: 'FROM query WHERE true',
language: 'esql',
},
current_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
target_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
merged_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NONE,
has_update: false,
has_base_version: true,
});
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_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update and a custom value that are different - scenario ABC', () => {
it('should show a non-solvable conflict in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Customize an esql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'esql',
query: 'FROM query WHERE false',
language: 'esql',
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, update an esql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'esql',
query: 'FROM new query WHERE true',
language: 'esql',
}),
];
await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
// and esql_query field update has conflict
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.esql_query).toEqual({
base_version: {
query: 'FROM query WHERE true',
language: 'esql',
},
current_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
target_version: {
query: 'FROM new query WHERE true',
language: 'esql',
},
merged_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Current,
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
has_update: true,
has_base_version: true,
});
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field
expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1);
expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1);
});
});
describe('when rule base version does not exist', () => {
describe('when rule field has an update and a custom value that are the same - scenario -AA', () => {
it('should not show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Clear previous rule assets
await deleteAllPrebuiltRuleAssets(es, log);
// Add a v2 rule asset to make the upgrade possible, but keep esql_query field unchanged
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'esql',
query: 'FROM query WHERE true',
language: 'esql',
}),
];
await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
// but does NOT contain esql_query field
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.esql_query).toBeUndefined();
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1);
expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered conflict
expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
describe('when rule field has an update and a custom value that are different - scenario -AB', () => {
it('should show in the upgrade/_review API response', async () => {
// Install base prebuilt detection rule
await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects());
await installPrebuiltRules(es, supertest);
// Clear previous rule assets
await deleteAllPrebuiltRuleAssets(es, log);
// Customize an esql_query field on the installed rule
await updateRule(supertest, {
...getPrebuiltRuleMock(),
rule_id: 'rule-1',
type: 'esql',
query: 'FROM query WHERE false',
language: 'esql',
} as RuleUpdateProps);
// Add a v2 rule asset to make the upgrade possible, update an esql_query field, and create the new rule assets
const updatedRuleAssetSavedObjects = [
createRuleAssetSavedObject({
rule_id: 'rule-1',
version: 2,
type: 'esql',
query: 'FROM new query WHERE true',
language: 'esql',
}),
];
await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects);
// Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update
// and esql_query field update does not have a conflict
const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest);
const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff;
expect(fieldDiffObject.esql_query).toEqual({
current_version: {
query: 'FROM query WHERE false',
language: 'esql',
},
target_version: {
query: 'FROM new query WHERE true',
language: 'esql',
},
merged_version: {
query: 'FROM new query WHERE true',
language: 'esql',
},
diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
merge_outcome: ThreeWayMergeOutcome.Target,
conflict: ThreeWayDiffConflict.SOLVABLE,
has_update: true,
has_base_version: false,
});
expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2);
expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + query
expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0);
expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1);
expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1);
expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0);
});
});
});
});
});
};