mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.5] [Security Solution] [Exceptions] Bug Fix: Import rule + exceptions route removes all references to all imported exception lists (#143882) (#144040)
* [Security Solution] [Exceptions] Bug Fix: Import rule + exceptions route removes all references to all imported exception lists (#143882)
* If the first exceptions_list array was empty, the code to get all of the referenced exceptions lists would only get the references for the first exception list (none) and remove any references to every other exception list. I've updated the code to resovle this bug and another incidental bug related to the way the reduce function would yield an array of arrays which would not have worked
* update jest test
* adds tests and splits out parsing logic from logic for fetching data for referenced lists
(cherry picked from commit bcb2d942f4
)
# Conflicts:
# x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts
# x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts
* removes files that should not have been added via backport
* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
4982b338fe
commit
72a5b750c5
2 changed files with 278 additions and 61 deletions
|
@ -6,77 +6,278 @@
|
|||
*/
|
||||
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import { findExceptionList } from '@kbn/lists-plugin/server/services/exception_lists/find_exception_list';
|
||||
import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock';
|
||||
import { getReferencedExceptionLists } from './gather_referenced_exceptions';
|
||||
import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock';
|
||||
import {
|
||||
getReferencedExceptionLists,
|
||||
parseReferencedExceptionsLists,
|
||||
} from './gather_referenced_exceptions';
|
||||
|
||||
jest.mock('@kbn/lists-plugin/server/services/exception_lists/find_exception_list');
|
||||
|
||||
describe('getReferencedExceptionLists', () => {
|
||||
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
describe('get referenced exceptions', () => {
|
||||
describe('getReferencedExceptions', () => {
|
||||
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
|
||||
beforeEach(() => {
|
||||
savedObjectsClient = savedObjectsClientMock.create();
|
||||
beforeEach(() => {
|
||||
savedObjectsClient = savedObjectsClientMock.create();
|
||||
|
||||
(findExceptionList as jest.Mock).mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
(findExceptionList as jest.Mock).mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '123',
|
||||
list_id: 'my-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
total: 1,
|
||||
});
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns empty object if no rules to search', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [],
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('returns found referenced exception lists', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
],
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
'my-list': {
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '123',
|
||||
list_id: 'my-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
total: 1,
|
||||
});
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns empty object if no rules to search', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [],
|
||||
savedObjectsClient,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
it('returns found referenced exception lists when first exceptions list is empty array and second list has a value', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [],
|
||||
},
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
],
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
it('returns found referenced exception lists', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [
|
||||
expect(result).toEqual({
|
||||
'my-list': {
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '123',
|
||||
list_id: 'my-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns found referenced exception lists when two rules reference same list', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
],
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
'my-list': {
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '123',
|
||||
list_id: 'my-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns two found referenced exception lists when two rules reference different lists', async () => {
|
||||
(findExceptionList as jest.Mock).mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '123',
|
||||
list_id: 'my-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
{
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '456',
|
||||
list_id: 'other-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
total: 2,
|
||||
});
|
||||
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
],
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
// the problem with these tests is that they are entirely dependent on
|
||||
// the result from the saved objects client matching what we put here
|
||||
// so it essentially just bypasses the code that is not interacting with
|
||||
// the saved objects client.
|
||||
expect(result).toEqual({
|
||||
'my-list': {
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '123',
|
||||
list_id: 'my-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
'other-list': {
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '456',
|
||||
list_id: 'other-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty object if no referenced exception lists found', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [],
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
describe('parseReferencdedExceptionsLists', () => {
|
||||
it('should return parsed lists when exception lists are not empty', () => {
|
||||
const res = parseReferencedExceptionsLists([
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
],
|
||||
savedObjectsClient,
|
||||
]);
|
||||
expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]);
|
||||
});
|
||||
it('should return parsed lists when one empty exception list and one non-empty list', () => {
|
||||
const res = parseReferencedExceptionsLists([
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [],
|
||||
},
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]);
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
'my-list': {
|
||||
...getExceptionListSchemaMock(),
|
||||
id: '123',
|
||||
list_id: 'my-list',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty object if no referenced exception lists found', async () => {
|
||||
const result = await getReferencedExceptionLists({
|
||||
rules: [],
|
||||
savedObjectsClient,
|
||||
it('should return parsed lists when two non-empty exception lists reference same list', () => {
|
||||
const res = parseReferencedExceptionsLists([
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(res).toEqual([
|
||||
[],
|
||||
[
|
||||
{ listId: 'my-list', namespaceType: 'single' },
|
||||
{ listId: 'my-list', namespaceType: 'single' },
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
it('should return parsed lists when two non-empty exception lists reference differet lists', () => {
|
||||
const res = parseReferencedExceptionsLists([
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
...getImportRulesSchemaMock(),
|
||||
exceptions_list: [
|
||||
{ id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(res).toEqual([
|
||||
[],
|
||||
[
|
||||
{ listId: 'my-list', namespaceType: 'single' },
|
||||
{ listId: 'other-list', namespaceType: 'single' },
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,29 +11,28 @@ import { getAllListTypes } from '@kbn/lists-plugin/server/services/exception_lis
|
|||
import type { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema';
|
||||
|
||||
/**
|
||||
* Helper that takes rules, goes through their referenced exception lists and
|
||||
* searches for them, returning an object with all those found, using list_id as keys
|
||||
* @param rules {array}
|
||||
* @param savedObjectsClient {object}
|
||||
* @returns {Promise} an object with all referenced lists found, using list_id as keys
|
||||
* splitting out the parsing of the lists from the fetching
|
||||
* for easier and more compartmentalized testing
|
||||
* @param rules Array<RuleToImport | Error>
|
||||
* @returns [ExceptionListQueryInfo[], ExceptionListQueryInfo[]]
|
||||
*/
|
||||
export const getReferencedExceptionLists = async ({
|
||||
rules,
|
||||
savedObjectsClient,
|
||||
}: {
|
||||
rules: Array<ImportRulesSchema | Error>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}): Promise<Record<string, ExceptionListSchema>> => {
|
||||
const [lists] = rules.reduce<ListArray[]>((acc, rule) => {
|
||||
if (!(rule instanceof Error) && rule.exceptions_list != null) {
|
||||
return [...acc, rule.exceptions_list];
|
||||
export const parseReferencedExceptionsLists = (
|
||||
rules: Array<ImportRulesSchema | Error>
|
||||
): [ExceptionListQueryInfo[], ExceptionListQueryInfo[]] => {
|
||||
const lists = rules.reduce<ListArray>((acc, rule) => {
|
||||
if (
|
||||
!(rule instanceof Error) &&
|
||||
rule.exceptions_list != null &&
|
||||
rule.exceptions_list.length > 0
|
||||
) {
|
||||
return [...acc, ...rule.exceptions_list];
|
||||
} else {
|
||||
return acc;
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (lists == null) {
|
||||
return {};
|
||||
if (lists == null || lists.length === 0) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
const [agnosticLists, nonAgnosticLists] = lists.reduce<
|
||||
|
@ -49,6 +48,23 @@ export const getReferencedExceptionLists = async ({
|
|||
},
|
||||
[[], []]
|
||||
);
|
||||
return [agnosticLists, nonAgnosticLists];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper that takes rules, goes through their referenced exception lists and
|
||||
* searches for them, returning an object with all those found, using list_id as keys
|
||||
* @param rules {array}
|
||||
* @param savedObjectsClient {object}
|
||||
* @returns {Promise} an object with all referenced lists found, using list_id as keys
|
||||
*/
|
||||
export const getReferencedExceptionLists = async ({
|
||||
rules,
|
||||
savedObjectsClient,
|
||||
}: {
|
||||
rules: Array<ImportRulesSchema | Error>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}): Promise<Record<string, ExceptionListSchema>> => {
|
||||
const [agnosticLists, nonAgnosticLists] = parseReferencedExceptionsLists(rules);
|
||||
return getAllListTypes(agnosticLists, nonAgnosticLists, savedObjectsClient);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue