[Security Solutions][Detection Engine] Fixes pre-packaged rules which contain exception lists to not overwrite user defined lists (#80592)
## Summary Fixes a bug where when you update your pre-packaged rules you could end up removing any existing exception lists the user might have already added. See: https://github.com/elastic/kibana/issues/80417 * Fixes the merge logic so that any exception lists from pre-packaged rules will be additive if they do not already exist on the rule. User based exception lists will not be lost. * Added new backend integration tests for exception lists that did not exist before including ones that test the functionality of exception lists * Refactored some of the code in the `get_rules_to_update.ts` * Refactored some of the integration tests to use helper utils of `countDownES`, and `countDownTest` which simplify the retry logic within the integration tests * Added unit tests to exercise the bug and then the fix. * Added integration tests that fail this logic and then fixed the logic ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
20edc75276
commit
e1aec17910
|
@ -4,102 +4,470 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getRulesToUpdate } from './get_rules_to_update';
|
||||
import { filterInstalledRules, getRulesToUpdate, mergeExceptionLists } from './get_rules_to_update';
|
||||
import { getResult } from '../routes/__mocks__/request_responses';
|
||||
import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock';
|
||||
|
||||
describe('get_rules_to_update', () => {
|
||||
test('should return empty array if both rule sets are empty', () => {
|
||||
const update = getRulesToUpdate([], []);
|
||||
expect(update).toEqual([]);
|
||||
describe('get_rules_to_update', () => {
|
||||
test('should return empty array if both rule sets are empty', () => {
|
||||
const update = getRulesToUpdate([], []);
|
||||
expect(update).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return empty array if the rule_id of the two rules do not match', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-2';
|
||||
installedRule.params.version = 1;
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return empty array if the version of file system rule is less than the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 1;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 2;
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return empty array if the version of file system rule is the same as the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 1;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 1;
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return the rule to update if the version of file system rule is greater than the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 1;
|
||||
installedRule.params.exceptionsList = [];
|
||||
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([ruleFromFileSystem]);
|
||||
});
|
||||
|
||||
test('should return 1 rule out of 2 to update if the version of file system rule is greater than the installed version of just one', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [];
|
||||
|
||||
const installedRule2 = getResult();
|
||||
installedRule2.params.ruleId = 'rule-2';
|
||||
installedRule2.params.version = 1;
|
||||
installedRule2.params.exceptionsList = [];
|
||||
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule1, installedRule2]);
|
||||
expect(update).toEqual([ruleFromFileSystem]);
|
||||
});
|
||||
|
||||
test('should return 2 rules out of 2 to update if the version of file system rule is greater than the installed version of both', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem2.rule_id = 'rule-2';
|
||||
ruleFromFileSystem2.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [];
|
||||
|
||||
const installedRule2 = getResult();
|
||||
installedRule2.params.ruleId = 'rule-2';
|
||||
installedRule2.params.version = 1;
|
||||
installedRule2.params.exceptionsList = [];
|
||||
|
||||
const update = getRulesToUpdate(
|
||||
[ruleFromFileSystem1, ruleFromFileSystem2],
|
||||
[installedRule1, installedRule2]
|
||||
);
|
||||
expect(update).toEqual([ruleFromFileSystem1, ruleFromFileSystem2]);
|
||||
});
|
||||
|
||||
test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [];
|
||||
|
||||
const [update] = getRulesToUpdate([ruleFromFileSystem1], [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual(ruleFromFileSystem1.exceptions_list);
|
||||
});
|
||||
|
||||
test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'second_exception_list',
|
||||
list_id: 'some-other-id',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
];
|
||||
|
||||
const [update] = getRulesToUpdate([ruleFromFileSystem1], [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual([
|
||||
...ruleFromFileSystem1.exceptions_list,
|
||||
...installedRule1.params.exceptionsList,
|
||||
]);
|
||||
});
|
||||
|
||||
test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
|
||||
const [update] = getRulesToUpdate([ruleFromFileSystem1], [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual(ruleFromFileSystem1.exceptions_list);
|
||||
});
|
||||
|
||||
test('should not remove an existing exception_list if the rule has an empty exceptions list', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
|
||||
const [update] = getRulesToUpdate([ruleFromFileSystem1], [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual(installedRule1.params.exceptionsList);
|
||||
});
|
||||
|
||||
test('should not remove an existing exception_list if the rule has an empty exceptions list for multiple rules', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem2.exceptions_list = [];
|
||||
ruleFromFileSystem2.rule_id = 'rule-2';
|
||||
ruleFromFileSystem2.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
const installedRule2 = getResult();
|
||||
installedRule2.params.ruleId = 'rule-2';
|
||||
installedRule2.params.version = 1;
|
||||
installedRule2.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
|
||||
const [update1, update2] = getRulesToUpdate(
|
||||
[ruleFromFileSystem1, ruleFromFileSystem2],
|
||||
[installedRule1, installedRule2]
|
||||
);
|
||||
expect(update1.exceptions_list).toEqual(installedRule1.params.exceptionsList);
|
||||
expect(update2.exceptions_list).toEqual(installedRule2.params.exceptionsList);
|
||||
});
|
||||
|
||||
test('should not remove an existing exception_list if the rule has an empty exceptions list for mixed rules', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem2.exceptions_list = [];
|
||||
ruleFromFileSystem2.rule_id = 'rule-2';
|
||||
ruleFromFileSystem2.version = 2;
|
||||
ruleFromFileSystem2.exceptions_list = [
|
||||
{
|
||||
id: 'second_list',
|
||||
list_id: 'second_list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
];
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
|
||||
const installedRule2 = getResult();
|
||||
installedRule2.params.ruleId = 'rule-2';
|
||||
installedRule2.params.version = 1;
|
||||
installedRule2.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
|
||||
const [update1, update2] = getRulesToUpdate(
|
||||
[ruleFromFileSystem1, ruleFromFileSystem2],
|
||||
[installedRule1, installedRule2]
|
||||
);
|
||||
expect(update1.exceptions_list).toEqual(installedRule1.params.exceptionsList);
|
||||
expect(update2.exceptions_list).toEqual([
|
||||
...ruleFromFileSystem2.exceptions_list,
|
||||
...installedRule2.params.exceptionsList,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('should return empty array if the id of the two rules do not match', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
describe('filterInstalledRules', () => {
|
||||
test('should return "false" if the id of the two rules do not match', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-2';
|
||||
installedRule.params.version = 1;
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([]);
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-2';
|
||||
installedRule.params.version = 1;
|
||||
const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]);
|
||||
expect(shouldUpdate).toEqual(false);
|
||||
});
|
||||
|
||||
test('should return "false" if the version of file system rule is less than the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 1;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 2;
|
||||
const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]);
|
||||
expect(shouldUpdate).toEqual(false);
|
||||
});
|
||||
|
||||
test('should return "false" if the version of file system rule is the same as the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 1;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 1;
|
||||
const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]);
|
||||
expect(shouldUpdate).toEqual(false);
|
||||
});
|
||||
|
||||
test('should return "true" to update if the version of file system rule is greater than the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 1;
|
||||
installedRule.params.exceptionsList = [];
|
||||
|
||||
const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]);
|
||||
expect(shouldUpdate).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('should return empty array if the id of file system rule is less than the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 1;
|
||||
describe('mergeExceptionLists', () => {
|
||||
test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 2;
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([]);
|
||||
});
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [];
|
||||
|
||||
test('should return empty array if the id of file system rule is the same as the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 1;
|
||||
const update = mergeExceptionLists(ruleFromFileSystem1, [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual(ruleFromFileSystem1.exceptions_list);
|
||||
});
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 1;
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([]);
|
||||
});
|
||||
test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
test('should return the rule to update if the id of file system rule is greater than the installed version', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'second_exception_list',
|
||||
list_id: 'some-other-id',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
];
|
||||
|
||||
const installedRule = getResult();
|
||||
installedRule.params.ruleId = 'rule-1';
|
||||
installedRule.params.version = 1;
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
|
||||
expect(update).toEqual([ruleFromFileSystem]);
|
||||
});
|
||||
const update = mergeExceptionLists(ruleFromFileSystem1, [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual([
|
||||
...ruleFromFileSystem1.exceptions_list,
|
||||
...installedRule1.params.exceptionsList,
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return 1 rule out of 2 to update if the id of file system rule is greater than the installed version of just one', () => {
|
||||
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem.rule_id = 'rule-1';
|
||||
ruleFromFileSystem.version = 2;
|
||||
test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
|
||||
const installedRule2 = getResult();
|
||||
installedRule2.params.ruleId = 'rule-2';
|
||||
installedRule2.params.version = 1;
|
||||
const update = mergeExceptionLists(ruleFromFileSystem1, [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual(ruleFromFileSystem1.exceptions_list);
|
||||
});
|
||||
|
||||
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule1, installedRule2]);
|
||||
expect(update).toEqual([ruleFromFileSystem]);
|
||||
});
|
||||
test('should not remove an existing exception_list if the rule has an empty exceptions list', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.exceptions_list = [];
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
|
||||
test('should return 2 rules out of 2 to update if the id of file system rule is greater than the installed version of both', () => {
|
||||
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem1.rule_id = 'rule-1';
|
||||
ruleFromFileSystem1.version = 2;
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
installedRule1.params.exceptionsList = [
|
||||
{
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
];
|
||||
|
||||
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock();
|
||||
ruleFromFileSystem2.rule_id = 'rule-2';
|
||||
ruleFromFileSystem2.version = 2;
|
||||
|
||||
const installedRule1 = getResult();
|
||||
installedRule1.params.ruleId = 'rule-1';
|
||||
installedRule1.params.version = 1;
|
||||
|
||||
const installedRule2 = getResult();
|
||||
installedRule2.params.ruleId = 'rule-2';
|
||||
installedRule2.params.version = 1;
|
||||
|
||||
const update = getRulesToUpdate(
|
||||
[ruleFromFileSystem1, ruleFromFileSystem2],
|
||||
[installedRule1, installedRule2]
|
||||
);
|
||||
expect(update).toEqual([ruleFromFileSystem1, ruleFromFileSystem2]);
|
||||
const update = mergeExceptionLists(ruleFromFileSystem1, [installedRule1]);
|
||||
expect(update.exceptions_list).toEqual(installedRule1.params.exceptionsList);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,15 +7,67 @@
|
|||
import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema';
|
||||
import { RuleAlertType } from './types';
|
||||
|
||||
/**
|
||||
* Returns the rules to update by doing a compare to the rules from the file system against
|
||||
* the installed rules already. This also merges exception list items between the two since
|
||||
* exception list items can exist on both rules to update and already installed rules.
|
||||
* @param rulesFromFileSystem The rules on the file system to check against installed
|
||||
* @param installedRules The installed rules
|
||||
*/
|
||||
export const getRulesToUpdate = (
|
||||
rulesFromFileSystem: AddPrepackagedRulesSchemaDecoded[],
|
||||
installedRules: RuleAlertType[]
|
||||
): AddPrepackagedRulesSchemaDecoded[] => {
|
||||
return rulesFromFileSystem.filter((rule) =>
|
||||
installedRules.some((installedRule) => {
|
||||
return (
|
||||
rule.rule_id === installedRule.params.ruleId && rule.version > installedRule.params.version
|
||||
);
|
||||
})
|
||||
);
|
||||
return rulesFromFileSystem
|
||||
.filter((ruleFromFileSystem) => filterInstalledRules(ruleFromFileSystem, installedRules))
|
||||
.map((ruleFromFileSystem) => mergeExceptionLists(ruleFromFileSystem, installedRules));
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters rules from the file system that do not match the installed rules so you only
|
||||
* get back rules that are going to be updated
|
||||
* @param ruleFromFileSystem The rules from the file system to check if any are updates
|
||||
* @param installedRules The installed rules to compare against for updates
|
||||
*/
|
||||
export const filterInstalledRules = (
|
||||
ruleFromFileSystem: AddPrepackagedRulesSchemaDecoded,
|
||||
installedRules: RuleAlertType[]
|
||||
): boolean => {
|
||||
return installedRules.some((installedRule) => {
|
||||
return (
|
||||
ruleFromFileSystem.rule_id === installedRule.params.ruleId &&
|
||||
ruleFromFileSystem.version > installedRule.params.version
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a rule from the file system and the set of installed rules this will merge the exception lists
|
||||
* from the installed rules onto the rules from the file system.
|
||||
* @param ruleFromFileSystem The rules from the file system that might have exceptions_lists
|
||||
* @param installedRules The installed rules which might have user driven exceptions_lists
|
||||
*/
|
||||
export const mergeExceptionLists = (
|
||||
ruleFromFileSystem: AddPrepackagedRulesSchemaDecoded,
|
||||
installedRules: RuleAlertType[]
|
||||
): AddPrepackagedRulesSchemaDecoded => {
|
||||
if (ruleFromFileSystem.exceptions_list != null) {
|
||||
const installedRule = installedRules.find(
|
||||
(ruleToFind) => ruleToFind.params.ruleId === ruleFromFileSystem.rule_id
|
||||
);
|
||||
if (installedRule != null && installedRule.params.exceptionsList != null) {
|
||||
const installedExceptionList = installedRule.params.exceptionsList;
|
||||
const fileSystemExceptions = ruleFromFileSystem.exceptions_list.filter((potentialDuplicate) =>
|
||||
installedExceptionList.every((item) => item.list_id !== potentialDuplicate.list_id)
|
||||
);
|
||||
return {
|
||||
...ruleFromFileSystem,
|
||||
exceptions_list: [...fileSystemExceptions, ...installedRule.params.exceptionsList],
|
||||
};
|
||||
} else {
|
||||
return ruleFromFileSystem;
|
||||
}
|
||||
} else {
|
||||
return ruleFromFileSystem;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,704 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { Signal } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types';
|
||||
import { getCreateExceptionListItemMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_item_schema.mock';
|
||||
import { deleteAllExceptions } from '../../../lists_api_integration/utils';
|
||||
import { RulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/response';
|
||||
import { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request';
|
||||
import { getCreateExceptionListMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_schema.mock';
|
||||
import { CreateExceptionListItemSchema } from '../../../../plugins/lists/common';
|
||||
import {
|
||||
EXCEPTION_LIST_ITEM_URL,
|
||||
EXCEPTION_LIST_URL,
|
||||
} from '../../../../plugins/lists/common/constants';
|
||||
|
||||
import {
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
DETECTION_ENGINE_RULES_STATUS_URL,
|
||||
DETECTION_ENGINE_QUERY_SIGNALS_URL,
|
||||
DETECTION_ENGINE_PREPACKAGED_URL,
|
||||
} from '../../../../plugins/security_solution/common/constants';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import {
|
||||
createSignalsIndex,
|
||||
deleteAllAlerts,
|
||||
deleteSignalsIndex,
|
||||
getSimpleRule,
|
||||
getSimpleRuleOutput,
|
||||
removeServerGeneratedProperties,
|
||||
waitFor,
|
||||
getQueryAllSignals,
|
||||
downgradeImmutableRule,
|
||||
} from '../../utils';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const es = getService('es');
|
||||
|
||||
describe('create_rules_with_exceptions', () => {
|
||||
describe('creating rules with exceptions', () => {
|
||||
beforeEach(async () => {
|
||||
await createSignalsIndex(supertest);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await deleteSignalsIndex(supertest);
|
||||
await deleteAllAlerts(es);
|
||||
await deleteAllExceptions(es);
|
||||
});
|
||||
|
||||
it('should create a single rule with a rule_id and add an exception list to the rule', async () => {
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
const ruleWithException: CreateRulesSchema = {
|
||||
...getSimpleRule(),
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { body } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(ruleWithException)
|
||||
.expect(200);
|
||||
|
||||
const expected: Partial<RulesSchema> = {
|
||||
...getSimpleRuleOutput(),
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
};
|
||||
const bodyToCompare = removeServerGeneratedProperties(body);
|
||||
expect(bodyToCompare).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should create a single rule with an exception list and validate it ran successfully', async () => {
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
const ruleWithException: CreateRulesSchema = {
|
||||
...getSimpleRule(),
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { body } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(ruleWithException)
|
||||
.expect(200);
|
||||
|
||||
// wait for Task Manager to execute the rule and update status
|
||||
await waitFor(async () => {
|
||||
const { body: statusBody } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_STATUS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ids: [body.id] })
|
||||
.expect(200);
|
||||
|
||||
return statusBody[body.id]?.current_status?.status === 'succeeded';
|
||||
});
|
||||
|
||||
const { body: statusBody } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_STATUS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ids: [body.id] })
|
||||
.expect(200);
|
||||
|
||||
const bodyToCompare = removeServerGeneratedProperties(body);
|
||||
const expected: Partial<RulesSchema> = {
|
||||
...getSimpleRuleOutput(),
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(bodyToCompare).to.eql(expected);
|
||||
expect(statusBody[body.id].current_status.status).to.eql('succeeded');
|
||||
});
|
||||
|
||||
it('should allow removing an exception list from an immutable rule through patch', async () => {
|
||||
// add all the immutable rules from the pre-packaged url
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json
|
||||
// This rule has an existing exceptions_list that we are going to use
|
||||
const { body: immutableRule } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one
|
||||
|
||||
// remove the exceptions list as a user is allowed to remove it from an immutable rule
|
||||
await supertest
|
||||
.patch(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ rule_id: '9a1a2dae-0b5f-4c3d-8305-a268d404c306', exceptions_list: [] })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
expect(body.exceptions_list.length).to.eql(0);
|
||||
});
|
||||
|
||||
it('should allow adding a second exception list to an immutable rule through patch', async () => {
|
||||
// add all the immutable rules from the pre-packaged url
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// Create a new exception list
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json
|
||||
// This rule has an existing exceptions_list that we are going to use
|
||||
const { body: immutableRule } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one
|
||||
|
||||
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
|
||||
await supertest
|
||||
.patch(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
rule_id: '9a1a2dae-0b5f-4c3d-8305-a268d404c306',
|
||||
exceptions_list: [
|
||||
...immutableRule.exceptions_list,
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
expect(body.exceptions_list.length).to.eql(2);
|
||||
});
|
||||
|
||||
it('should override any updates to pre-packaged rules if the user removes the exception list through the API but the new version of a rule has an exception list again', async () => {
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json
|
||||
// This rule has an existing exceptions_list that we are going to use
|
||||
const { body: immutableRule } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
expect(immutableRule.exceptions_list.length).greaterThan(0); // make sure we have at least one exception list
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json
|
||||
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
|
||||
// remove the exceptions list as a user is allowed to remove it
|
||||
await supertest
|
||||
.patch(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ rule_id: '9a1a2dae-0b5f-4c3d-8305-a268d404c306', exceptions_list: [] })
|
||||
.expect(200);
|
||||
|
||||
// downgrade the version number of the rule
|
||||
await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306');
|
||||
|
||||
// re-add the pre-packaged rule to get the single upgrade to happen
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// get the pre-packaged rule after we upgraded it
|
||||
const { body } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
// We should have a length of 1 and it should be the same as our original before we tried to remove it using patch
|
||||
expect(body.exceptions_list.length).to.eql(1);
|
||||
expect(body.exceptions_list).to.eql(immutableRule.exceptions_list);
|
||||
});
|
||||
|
||||
it('should merge back an exceptions_list if it was removed from the immutable rule through PATCH', async () => {
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// Create a new exception list
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json
|
||||
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
|
||||
const { body: immutableRule } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
// remove the exception list and only have a single list that is not an endpoint_list
|
||||
await supertest
|
||||
.patch(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
rule_id: '9a1a2dae-0b5f-4c3d-8305-a268d404c306',
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// downgrade the version number of the rule
|
||||
await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306');
|
||||
|
||||
// re-add the pre-packaged rule to get the single upgrade to happen
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// get the immutable rule after we installed it a second time
|
||||
const { body } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
// The installed rule should have both the original immutable exceptions list back and the
|
||||
// new list the user added.
|
||||
expect(body.exceptions_list).to.eql([
|
||||
...immutableRule.exceptions_list,
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should NOT add an extra exceptions_list that already exists on a rule during an upgrade', async () => {
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json
|
||||
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
|
||||
const { body: immutableRule } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
// downgrade the version number of the rule
|
||||
await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306');
|
||||
|
||||
// re-add the pre-packaged rule to get the single upgrade to happen
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// get the immutable rule after we installed it a second time
|
||||
const { body } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
// The installed rule should have both the original immutable exceptions list back and the
|
||||
// new list the user added.
|
||||
expect(body.exceptions_list).to.eql([...immutableRule.exceptions_list]);
|
||||
});
|
||||
|
||||
it('should NOT allow updates to pre-packaged rules to overwrite existing exception based rules when the user adds an additional exception list', async () => {
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// Create a new exception list
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json
|
||||
// This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule
|
||||
const { body: immutableRule } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
|
||||
await supertest
|
||||
.patch(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
rule_id: '9a1a2dae-0b5f-4c3d-8305-a268d404c306',
|
||||
exceptions_list: [
|
||||
...immutableRule.exceptions_list,
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// downgrade the version number of the rule
|
||||
await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306');
|
||||
|
||||
// re-add the pre-packaged rule to get the single upgrade to happen
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=9a1a2dae-0b5f-4c3d-8305-a268d404c306`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
// It should be the same as what the user added originally
|
||||
expect(body.exceptions_list).to.eql([
|
||||
...immutableRule.exceptions_list,
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not remove any exceptions added to a pre-packaged/immutable rule during an update if that rule has no existing exception lists', async () => {
|
||||
// add all the immutable rules from the pre-packaged url
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// Create a new exception list
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
// Rule id of "6d3456a5-4a42-49d1-aaf2-7b1fd475b2c6" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/c2_reg_beacon.json
|
||||
// since this rule does not have existing exceptions_list that we are going to use for tests
|
||||
const { body: immutableRule } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=6d3456a5-4a42-49d1-aaf2-7b1fd475b2c6`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
expect(immutableRule.exceptions_list.length).eql(0); // make sure we have no exceptions_list
|
||||
|
||||
// add a second exceptions list as a user is allowed to add a second list to an immutable rule
|
||||
await supertest
|
||||
.patch(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
rule_id: '6d3456a5-4a42-49d1-aaf2-7b1fd475b2c6',
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// downgrade the version number of the rule
|
||||
await downgradeImmutableRule(es, '9a1a2dae-0b5f-4c3d-8305-a268d404c306');
|
||||
|
||||
// re-add the pre-packaged rule to get the single upgrade of the rule to happen
|
||||
await supertest
|
||||
.put(DETECTION_ENGINE_PREPACKAGED_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// ensure that the same exception is still on the rule
|
||||
const { body } = await supertest
|
||||
.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=6d3456a5-4a42-49d1-aaf2-7b1fd475b2c6`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getSimpleRule())
|
||||
.expect(200);
|
||||
|
||||
expect(body.exceptions_list).to.eql([
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('tests with auditbeat data', () => {
|
||||
beforeEach(async () => {
|
||||
await createSignalsIndex(supertest);
|
||||
await esArchiver.load('auditbeat/hosts');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await deleteSignalsIndex(supertest);
|
||||
await deleteAllAlerts(es);
|
||||
await deleteAllExceptions(es);
|
||||
await esArchiver.unload('auditbeat/hosts');
|
||||
});
|
||||
|
||||
it('should be able to execute against an exception list that does not include valid entries and get back 10 signals', async () => {
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
const exceptionListItem: CreateExceptionListItemSchema = {
|
||||
...getCreateExceptionListItemMinimalSchemaMock(),
|
||||
entries: [
|
||||
{
|
||||
field: 'some.none.existent.field', // non-existent field where we should not exclude anything
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'some value',
|
||||
},
|
||||
],
|
||||
};
|
||||
await supertest
|
||||
.post(EXCEPTION_LIST_ITEM_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(exceptionListItem)
|
||||
.expect(200);
|
||||
|
||||
const ruleWithException: CreateRulesSchema = {
|
||||
...getSimpleRule(),
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: 'host.name: "suricata-sensor-amsterdam"',
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await supertest
|
||||
.post(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(ruleWithException)
|
||||
.expect(200);
|
||||
|
||||
// wait until rules show up and are present
|
||||
await waitFor(async () => {
|
||||
const {
|
||||
body: signalsOpen,
|
||||
}: { body: SearchResponse<{ signal: Signal }> } = await supertest
|
||||
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getQueryAllSignals())
|
||||
.expect(200);
|
||||
return signalsOpen.hits.hits.length > 0;
|
||||
});
|
||||
|
||||
const {
|
||||
body: signalsOpen,
|
||||
}: { body: SearchResponse<{ signal: Signal }> } = await supertest
|
||||
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getQueryAllSignals())
|
||||
.expect(200);
|
||||
|
||||
// expect there to be 10
|
||||
expect(signalsOpen.hits.hits.length).equal(10);
|
||||
});
|
||||
|
||||
it('should be able to execute against an exception list that does include valid entries and get back 0 signals', async () => {
|
||||
const {
|
||||
body: { id, list_id, namespace_type, type },
|
||||
} = await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
const exceptionListItem: CreateExceptionListItemSchema = {
|
||||
...getCreateExceptionListItemMinimalSchemaMock(),
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name', // This matches the query below which will exclude everything
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'suricata-sensor-amsterdam',
|
||||
},
|
||||
],
|
||||
};
|
||||
await supertest
|
||||
.post(EXCEPTION_LIST_ITEM_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(exceptionListItem)
|
||||
.expect(200);
|
||||
|
||||
const ruleWithException: CreateRulesSchema = {
|
||||
...getSimpleRule(),
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: 'host.name: "suricata-sensor-amsterdam"', // this matches all the exceptions we should exclude
|
||||
exceptions_list: [
|
||||
{
|
||||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
type,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { body: resBody } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(ruleWithException)
|
||||
.expect(200);
|
||||
|
||||
// wait for Task Manager to finish executing the rule
|
||||
await waitFor(async () => {
|
||||
const { body } = await supertest
|
||||
.post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ids: [resBody.id] })
|
||||
.expect(200);
|
||||
return body[resBody.id]?.current_status?.status === 'succeeded';
|
||||
});
|
||||
|
||||
// Get the signals now that we are done running and expect the result to always be zero
|
||||
const {
|
||||
body: signalsOpen,
|
||||
}: { body: SearchResponse<{ signal: Signal }> } = await supertest
|
||||
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getQueryAllSignals())
|
||||
.expect(200);
|
||||
|
||||
// expect there to be 10
|
||||
expect(signalsOpen.hits.hits.length).equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -170,7 +170,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(signalsOpen.hits.hits.length).equal(10);
|
||||
});
|
||||
|
||||
it('should be return zero matches if the mapping does not match against anything in the mapping', async () => {
|
||||
it('should return zero matches if the mapping does not match against anything in the mapping', async () => {
|
||||
const rule: CreateRulesSchema = {
|
||||
...getCreateThreatMatchRulesSchemaMock(),
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
|
|
|
@ -16,6 +16,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
|
|||
loadTestFile(require.resolve('./create_rules'));
|
||||
loadTestFile(require.resolve('./create_rules_bulk'));
|
||||
loadTestFile(require.resolve('./create_threat_matching'));
|
||||
loadTestFile(require.resolve('./create_exceptions'));
|
||||
loadTestFile(require.resolve('./delete_rules'));
|
||||
loadTestFile(require.resolve('./delete_rules_bulk'));
|
||||
loadTestFile(require.resolve('./export_rules'));
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
import { ApiResponse, Client } from '@elastic/elasticsearch';
|
||||
import { SuperTest } from 'supertest';
|
||||
import supertestAsPromised from 'supertest-as-promised';
|
||||
import { Context } from '@elastic/elasticsearch/lib/Transport';
|
||||
import {
|
||||
Status,
|
||||
SignalIds,
|
||||
|
@ -14,7 +15,10 @@ import {
|
|||
import { CreateRulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema';
|
||||
import { UpdateRulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema';
|
||||
import { RulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema';
|
||||
import { DETECTION_ENGINE_INDEX_URL } from '../../plugins/security_solution/common/constants';
|
||||
import {
|
||||
DETECTION_ENGINE_INDEX_URL,
|
||||
INTERNAL_RULE_ID_KEY,
|
||||
} from '../../plugins/security_solution/common/constants';
|
||||
|
||||
/**
|
||||
* This will remove server generated properties such as date times, etc...
|
||||
|
@ -245,34 +249,38 @@ export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial<RulesSchema> =
|
|||
* This will retry 20 times before giving up and hopefully still not interfere with other tests
|
||||
* @param es The ElasticSearch handle
|
||||
*/
|
||||
export const deleteAllAlerts = async (es: Client, retryCount = 20): Promise<void> => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
const result = await es.deleteByQuery({
|
||||
index: '.kibana',
|
||||
q: 'type:alert',
|
||||
wait_for_completion: true,
|
||||
refresh: true,
|
||||
conflicts: 'proceed',
|
||||
body: {},
|
||||
});
|
||||
// deleteByQuery will cause version conflicts as alerts are being updated
|
||||
// by background processes; the code below accounts for that
|
||||
if (result.body.version_conflicts !== 0) {
|
||||
throw new Error(`Version conflicts for ${result.body.version_conflicts} alerts`);
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Error in deleteAllAlerts(), retries left: ${retryCount - 1}`, err);
|
||||
export const deleteAllAlerts = async (es: Client): Promise<void> => {
|
||||
return countDownES(async () => {
|
||||
return es.deleteByQuery({
|
||||
index: '.kibana',
|
||||
q: 'type:alert',
|
||||
wait_for_completion: true,
|
||||
refresh: true,
|
||||
conflicts: 'proceed',
|
||||
body: {},
|
||||
});
|
||||
}, 'deleteAllAlerts');
|
||||
};
|
||||
|
||||
// retry, counting down, and delay a bit before
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
await deleteAllAlerts(es, retryCount - 1);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not deleteAllAlerts, no retries are left');
|
||||
}
|
||||
export const downgradeImmutableRule = async (es: Client, ruleId: string): Promise<void> => {
|
||||
return countDownES(async () => {
|
||||
return es.updateByQuery({
|
||||
index: '.kibana',
|
||||
refresh: true,
|
||||
wait_for_completion: true,
|
||||
body: {
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source: 'ctx._source.alert.params.version--',
|
||||
},
|
||||
query: {
|
||||
term: {
|
||||
'alert.tags': `${INTERNAL_RULE_ID_KEY}:${ruleId}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 'downgradeImmutableRule');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -295,27 +303,15 @@ export const deleteAllTimelines = async (es: Client): Promise<void> => {
|
|||
* @param es The ElasticSearch handle
|
||||
*/
|
||||
export const deleteAllRulesStatuses = async (es: Client, retryCount = 20): Promise<void> => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
await es.deleteByQuery({
|
||||
index: '.kibana',
|
||||
q: 'type:siem-detection-engine-rule-status',
|
||||
wait_for_completion: true,
|
||||
refresh: true,
|
||||
body: {},
|
||||
});
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Failure trying to deleteAllRulesStatuses, retries left are: ${retryCount - 1}`,
|
||||
err
|
||||
);
|
||||
await deleteAllRulesStatuses(es, retryCount - 1);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not deleteAllRulesStatuses, no retries are left');
|
||||
}
|
||||
return countDownES(async () => {
|
||||
return es.deleteByQuery({
|
||||
index: '.kibana',
|
||||
q: 'type:siem-detection-engine-rule-status',
|
||||
wait_for_completion: true,
|
||||
refresh: true,
|
||||
body: {},
|
||||
});
|
||||
}, 'deleteAllRulesStatuses');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -324,24 +320,12 @@ export const deleteAllRulesStatuses = async (es: Client, retryCount = 20): Promi
|
|||
* @param supertest The supertest client library
|
||||
*/
|
||||
export const createSignalsIndex = async (
|
||||
supertest: SuperTest<supertestAsPromised.Test>,
|
||||
retryCount = 20
|
||||
supertest: SuperTest<supertestAsPromised.Test>
|
||||
): Promise<void> => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send();
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Failure trying to create the signals index, retries left are: ${retryCount - 1}`,
|
||||
err
|
||||
);
|
||||
await createSignalsIndex(supertest, retryCount - 1);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not createSignalsIndex, no retries are left');
|
||||
}
|
||||
await countDownTest(async () => {
|
||||
await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send();
|
||||
return true;
|
||||
}, 'createSignalsIndex');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -349,21 +333,12 @@ export const createSignalsIndex = async (
|
|||
* @param supertest The supertest client library
|
||||
*/
|
||||
export const deleteSignalsIndex = async (
|
||||
supertest: SuperTest<supertestAsPromised.Test>,
|
||||
retryCount = 20
|
||||
supertest: SuperTest<supertestAsPromised.Test>
|
||||
): Promise<void> => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
await supertest.delete(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send();
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Failure trying to deleteSignalsIndex, retries left are: ${retryCount - 1}`, err);
|
||||
await deleteSignalsIndex(supertest, retryCount - 1);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not deleteSignalsIndex, no retries are left');
|
||||
}
|
||||
await countDownTest(async () => {
|
||||
await supertest.delete(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send();
|
||||
return true;
|
||||
}, 'deleteSignalsIndex');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -616,7 +591,7 @@ export const waitFor = async (
|
|||
functionToTest: () => Promise<boolean>,
|
||||
maxTimeout: number = 5000,
|
||||
timeoutWait: number = 10
|
||||
) => {
|
||||
): Promise<void> => {
|
||||
await new Promise(async (resolve, reject) => {
|
||||
let found = false;
|
||||
let numberOfTries = 0;
|
||||
|
@ -636,3 +611,82 @@ export const waitFor = async (
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Does a plain countdown and checks against es queries for either conflicts in the error
|
||||
* or for any over the wire issues such as timeouts or temp 404's to make the tests more
|
||||
* reliant.
|
||||
* @param esFunction The function to test against
|
||||
* @param esFunctionName The name of the function to print if we encounter errors
|
||||
* @param retryCount The number of times to retry before giving up (has default)
|
||||
* @param timeoutWait Time to wait before trying again (has default)
|
||||
*/
|
||||
export const countDownES = async (
|
||||
esFunction: () => Promise<ApiResponse<Record<string, any>, Context>>,
|
||||
esFunctionName: string,
|
||||
retryCount: number = 20,
|
||||
timeoutWait = 250
|
||||
): Promise<void> => {
|
||||
await countDownTest(
|
||||
async () => {
|
||||
const result = await esFunction();
|
||||
if (result.body.version_conflicts !== 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Version conflicts for ${result.body.version_conflicts}`);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
esFunctionName,
|
||||
retryCount,
|
||||
timeoutWait
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Does a plain countdown and checks against a boolean to determine if to wait and try again.
|
||||
* This is useful for over the wire things that can cause issues such as conflict or timeouts
|
||||
* for testing resiliency.
|
||||
* @param functionToTest The function to test against
|
||||
* @param name The name of the function to print if we encounter errors
|
||||
* @param retryCount The number of times to retry before giving up (has default)
|
||||
* @param timeoutWait Time to wait before trying again (has default)
|
||||
*/
|
||||
export const countDownTest = async (
|
||||
functionToTest: () => Promise<boolean>,
|
||||
name: string,
|
||||
retryCount: number = 20,
|
||||
timeoutWait = 250,
|
||||
ignoreThrow: boolean = false
|
||||
) => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
const passed = await functionToTest();
|
||||
if (!passed) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Failure trying to ${name}, retries left are: ${retryCount - 1}`);
|
||||
// retry, counting down, and delay a bit before
|
||||
await new Promise((resolve) => setTimeout(resolve, timeoutWait));
|
||||
await countDownTest(functionToTest, name, retryCount - 1, timeoutWait, ignoreThrow);
|
||||
}
|
||||
} catch (err) {
|
||||
if (ignoreThrow) {
|
||||
throw err;
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Failure trying to ${name}, with exception message of:`,
|
||||
err.message,
|
||||
`retries left are: ${retryCount - 1}`
|
||||
);
|
||||
// retry, counting down, and delay a bit before
|
||||
await new Promise((resolve) => setTimeout(resolve, timeoutWait));
|
||||
await countDownTest(functionToTest, name, retryCount - 1, timeoutWait, ignoreThrow);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Could not ${name}, no retries are left`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '../../plugins/lists/common/schemas';
|
||||
import { ListSchema } from '../../plugins/lists/common';
|
||||
import { LIST_INDEX } from '../../plugins/lists/common/constants';
|
||||
import { countDownES, countDownTest } from '../detection_engine_api_integration/utils';
|
||||
|
||||
/**
|
||||
* Creates the lists and lists items index for use inside of beforeEach blocks of tests
|
||||
|
@ -22,24 +23,12 @@ import { LIST_INDEX } from '../../plugins/lists/common/constants';
|
|||
* @param supertest The supertest client library
|
||||
*/
|
||||
export const createListsIndex = async (
|
||||
supertest: SuperTest<supertestAsPromised.Test>,
|
||||
retryCount = 20
|
||||
supertest: SuperTest<supertestAsPromised.Test>
|
||||
): Promise<void> => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send();
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Failure trying to create the lists index, retries left are: ${retryCount - 1}`,
|
||||
err
|
||||
);
|
||||
await createListsIndex(supertest, retryCount - 1);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not createListsIndex, no retries are left');
|
||||
}
|
||||
return countDownTest(async () => {
|
||||
await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send();
|
||||
return true;
|
||||
}, 'createListsIndex');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -47,21 +36,26 @@ export const createListsIndex = async (
|
|||
* @param supertest The supertest client library
|
||||
*/
|
||||
export const deleteListsIndex = async (
|
||||
supertest: SuperTest<supertestAsPromised.Test>,
|
||||
retryCount = 20
|
||||
supertest: SuperTest<supertestAsPromised.Test>
|
||||
): Promise<void> => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
await supertest.delete(LIST_INDEX).set('kbn-xsrf', 'true').send();
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Failure trying to deleteListsIndex, retries left are: ${retryCount - 1}`, err);
|
||||
await deleteListsIndex(supertest, retryCount - 1);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not deleteListsIndex, no retries are left');
|
||||
}
|
||||
return countDownTest(async () => {
|
||||
await supertest.delete(LIST_INDEX).set('kbn-xsrf', 'true').send();
|
||||
return true;
|
||||
}, 'deleteListsIndex');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the exception lists and lists items index for use inside of beforeEach blocks of tests
|
||||
* This will retry 20 times before giving up and hopefully still not interfere with other tests
|
||||
* @param supertest The supertest client library
|
||||
*/
|
||||
export const createExceptionListsIndex = async (
|
||||
supertest: SuperTest<supertestAsPromised.Test>
|
||||
): Promise<void> => {
|
||||
return countDownTest(async () => {
|
||||
await supertest.post(LIST_INDEX).set('kbn-xsrf', 'true').send();
|
||||
return true;
|
||||
}, 'createListsIndex');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -159,26 +153,14 @@ export const binaryToString = (res: any, callback: any): void => {
|
|||
* This will retry 20 times before giving up and hopefully still not interfere with other tests
|
||||
* @param es The ElasticSearch handle
|
||||
*/
|
||||
export const deleteAllExceptions = async (es: Client, retryCount = 20): Promise<void> => {
|
||||
if (retryCount > 0) {
|
||||
try {
|
||||
await es.deleteByQuery({
|
||||
index: '.kibana',
|
||||
q: 'type:exception-list or type:exception-list-agnostic',
|
||||
wait_for_completion: true,
|
||||
refresh: true,
|
||||
body: {},
|
||||
});
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Failure trying to deleteAllExceptions, retries left are: ${retryCount - 1}`,
|
||||
err
|
||||
);
|
||||
await deleteAllExceptions(es, retryCount - 1);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not deleteAllExceptions, no retries are left');
|
||||
}
|
||||
export const deleteAllExceptions = async (es: Client): Promise<void> => {
|
||||
return countDownES(async () => {
|
||||
return es.deleteByQuery({
|
||||
index: '.kibana',
|
||||
q: 'type:exception-list or type:exception-list-agnostic',
|
||||
wait_for_completion: true,
|
||||
refresh: true,
|
||||
body: {},
|
||||
});
|
||||
}, 'deleteAllExceptions');
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue