mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Exceptions] - Update rule.exceptions_list to include exception list list_id (#73349)
## Summary This PR addresses the following: - Adds `list_id` to `rule.exceptions_list` - this is needed in a number of features - Updated `getExceptions` in `x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts` to use the latest exception item find endpoint that accepts an array of lists (previously was looping through lists and conducting a `find` for each) - Updated prepackaged rule that makes reference to global endpoint list to include `list_id` - Updates `formatAboutStepData` in `x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts` to include exception list `list_id`
This commit is contained in:
parent
7059270ce9
commit
e645732319
21 changed files with 122 additions and 137 deletions
|
@ -41,7 +41,9 @@ describe('useExceptionList', () => {
|
|||
useExceptionList({
|
||||
filterOptions: { filter: '', tags: [] },
|
||||
http: mockKibanaHttpService,
|
||||
lists: [{ id: 'myListId', namespaceType: 'single', type: 'detection' }],
|
||||
lists: [
|
||||
{ id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' },
|
||||
],
|
||||
onError: onErrorMock,
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
@ -76,7 +78,9 @@ describe('useExceptionList', () => {
|
|||
useExceptionList({
|
||||
filterOptions: { filter: '', tags: [] },
|
||||
http: mockKibanaHttpService,
|
||||
lists: [{ id: 'myListId', namespaceType: 'single', type: 'detection' }],
|
||||
lists: [
|
||||
{ id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' },
|
||||
],
|
||||
onError: onErrorMock,
|
||||
onSuccess: onSuccessMock,
|
||||
pagination: {
|
||||
|
@ -131,7 +135,9 @@ describe('useExceptionList', () => {
|
|||
initialProps: {
|
||||
filterOptions: { filter: '', tags: [] },
|
||||
http: mockKibanaHttpService,
|
||||
lists: [{ id: 'myListId', namespaceType: 'single', type: 'detection' }],
|
||||
lists: [
|
||||
{ id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' },
|
||||
],
|
||||
onError: onErrorMock,
|
||||
onSuccess: onSuccessMock,
|
||||
pagination: {
|
||||
|
@ -146,7 +152,9 @@ describe('useExceptionList', () => {
|
|||
rerender({
|
||||
filterOptions: { filter: '', tags: [] },
|
||||
http: mockKibanaHttpService,
|
||||
lists: [{ id: 'newListId', namespaceType: 'single', type: 'detection' }],
|
||||
lists: [
|
||||
{ id: 'newListId', listId: 'new_list_id', namespaceType: 'single', type: 'detection' },
|
||||
],
|
||||
onError: onErrorMock,
|
||||
onSuccess: onSuccessMock,
|
||||
pagination: {
|
||||
|
@ -173,7 +181,9 @@ describe('useExceptionList', () => {
|
|||
useExceptionList({
|
||||
filterOptions: { filter: '', tags: [] },
|
||||
http: mockKibanaHttpService,
|
||||
lists: [{ id: 'myListId', namespaceType: 'single', type: 'detection' }],
|
||||
lists: [
|
||||
{ id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' },
|
||||
],
|
||||
onError: onErrorMock,
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
@ -210,7 +220,9 @@ describe('useExceptionList', () => {
|
|||
useExceptionList({
|
||||
filterOptions: { filter: '', tags: [] },
|
||||
http: mockKibanaHttpService,
|
||||
lists: [{ id: 'myListId', namespaceType: 'single', type: 'detection' }],
|
||||
lists: [
|
||||
{ id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' },
|
||||
],
|
||||
onError: onErrorMock,
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
@ -238,7 +250,9 @@ describe('useExceptionList', () => {
|
|||
useExceptionList({
|
||||
filterOptions: { filter: '', tags: [] },
|
||||
http: mockKibanaHttpService,
|
||||
lists: [{ id: 'myListId', namespaceType: 'single', type: 'detection' }],
|
||||
lists: [
|
||||
{ id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' },
|
||||
],
|
||||
onError: onErrorMock,
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
|
|
@ -60,6 +60,7 @@ export interface UseExceptionListProps {
|
|||
|
||||
export interface ExceptionIdentifiers {
|
||||
id: string;
|
||||
listId: string;
|
||||
namespaceType: NamespaceType;
|
||||
type: ExceptionListType;
|
||||
}
|
||||
|
|
|
@ -1446,11 +1446,13 @@ describe('add prepackaged rules schema', () => {
|
|||
exceptions_list: [
|
||||
{
|
||||
id: 'some_uuid',
|
||||
list_id: 'list_id_single',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
{
|
||||
id: 'some_uuid',
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
|
@ -1535,6 +1537,7 @@ describe('add prepackaged rules schema', () => {
|
|||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "exceptions_list,list_id"',
|
||||
'Invalid value "undefined" supplied to "exceptions_list,type"',
|
||||
'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"',
|
||||
]);
|
||||
|
|
|
@ -1513,11 +1513,13 @@ describe('create rules schema', () => {
|
|||
exceptions_list: [
|
||||
{
|
||||
id: 'some_uuid',
|
||||
list_id: 'list_id_single',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
{
|
||||
id: 'some_uuid',
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
|
@ -1600,6 +1602,7 @@ describe('create rules schema', () => {
|
|||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "exceptions_list,list_id"',
|
||||
'Invalid value "undefined" supplied to "exceptions_list,type"',
|
||||
'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"',
|
||||
]);
|
||||
|
|
|
@ -1642,11 +1642,13 @@ describe('import rules schema', () => {
|
|||
exceptions_list: [
|
||||
{
|
||||
id: 'some_uuid',
|
||||
list_id: 'list_id_single',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
{
|
||||
id: 'some_uuid',
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
|
@ -1730,6 +1732,7 @@ describe('import rules schema', () => {
|
|||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "exceptions_list,list_id"',
|
||||
'Invalid value "undefined" supplied to "exceptions_list,type"',
|
||||
'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"',
|
||||
]);
|
||||
|
|
|
@ -1176,11 +1176,13 @@ describe('patch_rules_schema', () => {
|
|||
exceptions_list: [
|
||||
{
|
||||
id: 'some_uuid',
|
||||
list_id: 'list_id_single',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
{
|
||||
id: 'some_uuid',
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
|
@ -1251,6 +1253,7 @@ describe('patch_rules_schema', () => {
|
|||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "exceptions_list,list_id"',
|
||||
'Invalid value "undefined" supplied to "exceptions_list,type"',
|
||||
'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"',
|
||||
'Invalid value "[{"id":"uuid_here","namespace_type":"not a namespace type"}]" supplied to "exceptions_list"',
|
||||
|
|
|
@ -1448,11 +1448,13 @@ describe('update rules schema', () => {
|
|||
exceptions_list: [
|
||||
{
|
||||
id: 'some_uuid',
|
||||
list_id: 'list_id_single',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
{
|
||||
id: 'some_uuid',
|
||||
id: 'endpoint_list',
|
||||
list_id: 'endpoint_list',
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
|
@ -1534,6 +1536,7 @@ describe('update rules schema', () => {
|
|||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "exceptions_list,list_id"',
|
||||
'Invalid value "undefined" supplied to "exceptions_list,type"',
|
||||
'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"',
|
||||
]);
|
||||
|
|
|
@ -4,17 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { List, ListArray } from './lists';
|
||||
import { ENDPOINT_LIST_ID } from '../../../shared_imports';
|
||||
|
||||
export const getListMock = (): List => ({
|
||||
id: 'some_uuid',
|
||||
list_id: 'list_id_single',
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
});
|
||||
|
||||
export const getListAgnosticMock = (): List => ({
|
||||
id: 'some_uuid',
|
||||
export const getEndpointListMock = (): List => ({
|
||||
id: ENDPOINT_LIST_ID,
|
||||
list_id: ENDPOINT_LIST_ID,
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
});
|
||||
|
||||
export const getListArrayMock = (): ListArray => [getListMock(), getListAgnosticMock()];
|
||||
export const getListArrayMock = (): ListArray => [getListMock(), getEndpointListMock()];
|
||||
|
|
|
@ -9,7 +9,7 @@ import { left } from 'fp-ts/lib/Either';
|
|||
|
||||
import { foldLeftRight, getPaths } from '../../../test_utils';
|
||||
|
||||
import { getListAgnosticMock, getListMock, getListArrayMock } from './lists.mock';
|
||||
import { getEndpointListMock, getListMock, getListArrayMock } from './lists.mock';
|
||||
import {
|
||||
List,
|
||||
ListArray,
|
||||
|
@ -31,7 +31,7 @@ describe('Lists', () => {
|
|||
});
|
||||
|
||||
test('it should validate a list with "namespace_type" of "agnostic"', () => {
|
||||
const payload = getListAgnosticMock();
|
||||
const payload = getEndpointListMock();
|
||||
const decoded = list.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
|
@ -91,7 +91,7 @@ describe('Lists', () => {
|
|||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<{| id: string, type: "detection" | "endpoint", namespace_type: "agnostic" | "single" |}>"',
|
||||
'Invalid value "1" supplied to "Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint", namespace_type: "agnostic" | "single" |}>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
@ -122,8 +122,8 @@ describe('Lists', () => {
|
|||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "(Array<{| id: string, type: "detection" | "endpoint", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
'Invalid value "[1]" supplied to "(Array<{| id: string, type: "detection" | "endpoint", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
'Invalid value "1" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
'Invalid value "[1]" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
|
|
@ -8,9 +8,12 @@ import * as t from 'io-ts';
|
|||
|
||||
import { exceptionListType, namespaceType } from '../../../shared_imports';
|
||||
|
||||
import { NonEmptyString } from './non_empty_string';
|
||||
|
||||
export const list = t.exact(
|
||||
t.type({
|
||||
id: t.string,
|
||||
id: NonEmptyString,
|
||||
list_id: NonEmptyString,
|
||||
type: exceptionListType,
|
||||
namespace_type: namespaceType,
|
||||
})
|
||||
|
|
|
@ -102,6 +102,7 @@ export const useFetchOrCreateRuleExceptionList = ({
|
|||
|
||||
const newExceptionListReference = {
|
||||
id: newExceptionList.id,
|
||||
list_id: newExceptionList.list_id,
|
||||
type: newExceptionList.type,
|
||||
namespace_type: newExceptionList.namespace_type,
|
||||
};
|
||||
|
|
|
@ -72,6 +72,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionListsMeta={[
|
||||
{
|
||||
id: '5b543420',
|
||||
listId: 'list_id',
|
||||
type: 'endpoint',
|
||||
namespaceType: 'single',
|
||||
},
|
||||
|
@ -124,6 +125,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionListsMeta={[
|
||||
{
|
||||
id: '5b543420',
|
||||
listId: 'list_id',
|
||||
type: 'endpoint',
|
||||
namespaceType: 'single',
|
||||
},
|
||||
|
|
|
@ -176,8 +176,6 @@ const ExceptionsViewerComponent = ({
|
|||
|
||||
const handleEditException = useCallback(
|
||||
(exception: ExceptionListItemSchema): void => {
|
||||
// TODO: Added this just for testing. Update
|
||||
// modal state logic as needed once ready
|
||||
dispatch({
|
||||
type: 'updateExceptionToEdit',
|
||||
exception,
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { esFilters } from '../../../../../../../../../../src/plugins/data/public';
|
||||
import { Rule, RuleError } from '../../../../../containers/detection_engine/rules';
|
||||
import { List } from '../../../../../../../common/detection_engine/schemas/types';
|
||||
import { AboutStepRule, ActionsStepRule, DefineStepRule, ScheduleStepRule } from '../../types';
|
||||
import { FieldValueQueryBar } from '../../../../../components/rules/query_bar';
|
||||
import { fillEmptySeverityMappings } from '../../helpers';
|
||||
|
@ -242,9 +241,3 @@ export const mockRules: Rule[] = [
|
|||
mockRule('abe6c564-050d-45a5-aaf0-386c37dd1f61'),
|
||||
mockRule('63f06f34-c181-4b2d-af35-f2ace572a1ee'),
|
||||
];
|
||||
|
||||
export const mockExceptionsList: List = {
|
||||
namespace_type: 'single',
|
||||
id: '75cd4380-cc5e-11ea-9101-5b34f44aeb44',
|
||||
type: 'detection',
|
||||
};
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
*/
|
||||
|
||||
import { List } from '../../../../../../common/detection_engine/schemas/types';
|
||||
import { ENDPOINT_LIST_ID } from '../../../../../shared_imports';
|
||||
import { NewRule } from '../../../../containers/detection_engine/rules';
|
||||
|
||||
import {
|
||||
getListMock,
|
||||
getEndpointListMock,
|
||||
} from '../../../../../../common/detection_engine/schemas/types/lists.mock';
|
||||
import {
|
||||
DefineStepRuleJson,
|
||||
ScheduleStepRuleJson,
|
||||
|
@ -29,19 +31,12 @@ import {
|
|||
} from './helpers';
|
||||
import {
|
||||
mockDefineStepRule,
|
||||
mockExceptionsList,
|
||||
mockQueryBar,
|
||||
mockScheduleStepRule,
|
||||
mockAboutStepRule,
|
||||
mockActionsStepRule,
|
||||
} from '../all/__mocks__/mock';
|
||||
|
||||
const ENDPOINT_LIST = {
|
||||
id: ENDPOINT_LIST_ID,
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
} as List;
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('getTimeTypeValue', () => {
|
||||
test('returns timeObj with value 0 if no time value found', () => {
|
||||
|
@ -391,14 +386,12 @@ describe('helpers', () => {
|
|||
},
|
||||
[]
|
||||
);
|
||||
expect(result.exceptions_list).toEqual([
|
||||
{ id: ENDPOINT_LIST_ID, namespace_type: 'agnostic', type: 'endpoint' },
|
||||
]);
|
||||
expect(result.exceptions_list).toEqual([getEndpointListMock()]);
|
||||
});
|
||||
|
||||
test('returns formatted object with detections exceptions_list', () => {
|
||||
const result: AboutStepRuleJson = formatAboutStepData(mockData, [mockExceptionsList]);
|
||||
expect(result.exceptions_list).toEqual([mockExceptionsList]);
|
||||
const result: AboutStepRuleJson = formatAboutStepData(mockData, [getListMock()]);
|
||||
expect(result.exceptions_list).toEqual([getListMock()]);
|
||||
});
|
||||
|
||||
test('returns formatted object with both exceptions_lists', () => {
|
||||
|
@ -407,13 +400,13 @@ describe('helpers', () => {
|
|||
...mockData,
|
||||
isAssociatedToEndpointList: true,
|
||||
},
|
||||
[mockExceptionsList]
|
||||
[getListMock()]
|
||||
);
|
||||
expect(result.exceptions_list).toEqual([ENDPOINT_LIST, mockExceptionsList]);
|
||||
expect(result.exceptions_list).toEqual([getEndpointListMock(), getListMock()]);
|
||||
});
|
||||
|
||||
test('returns formatted object with pre-existing exceptions lists', () => {
|
||||
const exceptionsLists: List[] = [ENDPOINT_LIST, mockExceptionsList];
|
||||
const exceptionsLists: List[] = [getEndpointListMock(), getListMock()];
|
||||
const result: AboutStepRuleJson = formatAboutStepData(
|
||||
{
|
||||
...mockData,
|
||||
|
@ -425,9 +418,9 @@ describe('helpers', () => {
|
|||
});
|
||||
|
||||
test('returns formatted object with pre-existing endpoint exceptions list disabled', () => {
|
||||
const exceptionsLists: List[] = [ENDPOINT_LIST, mockExceptionsList];
|
||||
const exceptionsLists: List[] = [getEndpointListMock(), getListMock()];
|
||||
const result: AboutStepRuleJson = formatAboutStepData(mockData, exceptionsLists);
|
||||
expect(result.exceptions_list).toEqual([mockExceptionsList]);
|
||||
expect(result.exceptions_list).toEqual([getListMock()]);
|
||||
});
|
||||
|
||||
test('returns formatted object with empty falsePositive and references filtered out', () => {
|
||||
|
|
|
@ -177,7 +177,12 @@ export const formatAboutStepData = (
|
|||
...(isAssociatedToEndpointList
|
||||
? {
|
||||
exceptions_list: [
|
||||
{ id: ENDPOINT_LIST_ID, namespace_type: 'agnostic', type: 'endpoint' },
|
||||
{
|
||||
id: ENDPOINT_LIST_ID,
|
||||
list_id: ENDPOINT_LIST_ID,
|
||||
namespace_type: 'agnostic',
|
||||
type: 'endpoint',
|
||||
},
|
||||
...detectionExceptionLists,
|
||||
] as AboutStepRuleJson['exceptions_list'],
|
||||
}
|
||||
|
|
|
@ -328,13 +328,13 @@ export const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
|
|||
lists: ExceptionIdentifiers[];
|
||||
allowedExceptionListTypes: ExceptionListTypeEnum[];
|
||||
}>(
|
||||
(acc, { id, namespace_type, type }) => {
|
||||
(acc, { id, list_id, namespace_type, type }) => {
|
||||
const { allowedExceptionListTypes, lists } = acc;
|
||||
const shouldAddEndpoint =
|
||||
type === ExceptionListTypeEnum.ENDPOINT &&
|
||||
!allowedExceptionListTypes.includes(ExceptionListTypeEnum.ENDPOINT);
|
||||
return {
|
||||
lists: [...lists, { id, namespaceType: namespace_type, type }],
|
||||
lists: [...lists, { id, listId: list_id, namespaceType: namespace_type, type }],
|
||||
allowedExceptionListTypes: shouldAddEndpoint
|
||||
? [...allowedExceptionListTypes, ExceptionListTypeEnum.ENDPOINT]
|
||||
: allowedExceptionListTypes,
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
{
|
||||
"author": [
|
||||
"Elastic"
|
||||
],
|
||||
"author": ["Elastic"],
|
||||
"description": "Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Elastic Endpoint alerts.",
|
||||
"enabled": true,
|
||||
"exceptions_list": [
|
||||
{
|
||||
"id": "endpoint_list",
|
||||
"list_id": "endpoint_list",
|
||||
"namespace_type": "agnostic",
|
||||
"type": "endpoint"
|
||||
}
|
||||
],
|
||||
"from": "now-10m",
|
||||
"index": [
|
||||
"logs-endpoint.alerts-*"
|
||||
],
|
||||
"index": ["logs-endpoint.alerts-*"],
|
||||
"language": "kuery",
|
||||
"license": "Elastic License",
|
||||
"max_signals": 10000,
|
||||
|
@ -57,10 +54,7 @@
|
|||
"value": "99"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Elastic",
|
||||
"Endpoint"
|
||||
],
|
||||
"tags": ["Elastic", "Endpoint"],
|
||||
"timestamp_override": "event.ingested",
|
||||
"type": "query",
|
||||
"version": 1
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
{
|
||||
"author": [
|
||||
"Elastic"
|
||||
],
|
||||
"author": ["Elastic"],
|
||||
"description": "Generates a detection alert for each external alert written to the configured indices. Enabling this rule allows you to immediately begin investigating external alerts in the app.",
|
||||
"index": [
|
||||
"apm-*-transaction*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"logs-*",
|
||||
"packetbeat-*",
|
||||
|
@ -54,9 +51,7 @@
|
|||
"value": "99"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Elastic"
|
||||
],
|
||||
"tags": ["Elastic"],
|
||||
"timestamp_override": "event.ingested",
|
||||
"type": "query",
|
||||
"version": 1
|
||||
|
|
|
@ -716,26 +716,31 @@ describe('utils', () => {
|
|||
|
||||
describe('#getExceptions', () => {
|
||||
test('it successfully returns array of exception list items', async () => {
|
||||
listMock.getExceptionListClient = () =>
|
||||
(({
|
||||
findExceptionListsItem: jest.fn().mockResolvedValue({
|
||||
data: [getExceptionListItemSchemaMock()],
|
||||
page: 1,
|
||||
per_page: 10000,
|
||||
total: 1,
|
||||
}),
|
||||
} as unknown) as ExceptionListClient);
|
||||
const client = listMock.getExceptionListClient();
|
||||
const exceptions = await getExceptions({
|
||||
client,
|
||||
lists: getListArrayMock(),
|
||||
});
|
||||
|
||||
expect(client.getExceptionList).toHaveBeenNthCalledWith(1, {
|
||||
id: 'some_uuid',
|
||||
listId: undefined,
|
||||
namespaceType: 'single',
|
||||
expect(client.findExceptionListsItem).toHaveBeenCalledWith({
|
||||
listId: ['list_id_single', 'endpoint_list'],
|
||||
namespaceType: ['single', 'agnostic'],
|
||||
page: 1,
|
||||
perPage: 10000,
|
||||
filter: [],
|
||||
sortOrder: undefined,
|
||||
sortField: undefined,
|
||||
});
|
||||
expect(client.getExceptionList).toHaveBeenNthCalledWith(2, {
|
||||
id: 'some_uuid',
|
||||
listId: undefined,
|
||||
namespaceType: 'agnostic',
|
||||
});
|
||||
expect(exceptions).toEqual([
|
||||
getExceptionListItemSchemaMock(),
|
||||
getExceptionListItemSchemaMock(),
|
||||
]);
|
||||
expect(exceptions).toEqual([getExceptionListItemSchemaMock()]);
|
||||
});
|
||||
|
||||
test('it throws if "client" is undefined', async () => {
|
||||
|
@ -747,7 +752,7 @@ describe('utils', () => {
|
|||
).rejects.toThrowError('lists plugin unavailable during rule execution');
|
||||
});
|
||||
|
||||
test('it returns empty array if no "lists" is undefined', async () => {
|
||||
test('it returns empty array if "lists" is undefined', async () => {
|
||||
const exceptions = await getExceptions({
|
||||
client: listMock.getExceptionListClient(),
|
||||
lists: undefined,
|
||||
|
@ -771,11 +776,11 @@ describe('utils', () => {
|
|||
).rejects.toThrowError('unable to fetch exception list items');
|
||||
});
|
||||
|
||||
test('it throws if "findExceptionListItem" fails', async () => {
|
||||
test('it throws if "findExceptionListsItem" fails', async () => {
|
||||
const err = new Error('error fetching list');
|
||||
listMock.getExceptionListClient = () =>
|
||||
(({
|
||||
findExceptionListItem: jest.fn().mockRejectedValue(err),
|
||||
findExceptionListsItem: jest.fn().mockRejectedValue(err),
|
||||
} as unknown) as ExceptionListClient);
|
||||
|
||||
await expect(() =>
|
||||
|
@ -786,24 +791,10 @@ describe('utils', () => {
|
|||
).rejects.toThrowError('unable to fetch exception list items');
|
||||
});
|
||||
|
||||
test('it returns empty array if "getExceptionList" returns null', async () => {
|
||||
test('it returns empty array if "findExceptionListsItem" returns null', async () => {
|
||||
listMock.getExceptionListClient = () =>
|
||||
(({
|
||||
getExceptionList: jest.fn().mockResolvedValue(null),
|
||||
} as unknown) as ExceptionListClient);
|
||||
|
||||
const exceptions = await getExceptions({
|
||||
client: listMock.getExceptionListClient(),
|
||||
lists: undefined,
|
||||
});
|
||||
|
||||
expect(exceptions).toEqual([]);
|
||||
});
|
||||
|
||||
test('it returns empty array if "findExceptionListItem" returns null', async () => {
|
||||
listMock.getExceptionListClient = () =>
|
||||
(({
|
||||
findExceptionListItem: jest.fn().mockResolvedValue(null),
|
||||
findExceptionListsItem: jest.fn().mockResolvedValue(null),
|
||||
} as unknown) as ExceptionListClient);
|
||||
|
||||
const exceptions = await getExceptions({
|
||||
|
|
|
@ -161,43 +161,20 @@ export const getExceptions = async ({
|
|||
throw new Error('lists plugin unavailable during rule execution');
|
||||
}
|
||||
|
||||
if (lists != null) {
|
||||
if (lists != null && lists.length > 0) {
|
||||
try {
|
||||
// Gather all exception items of all exception lists linked to rule
|
||||
const exceptions = await Promise.all(
|
||||
lists
|
||||
.map(async (list) => {
|
||||
const { id, namespace_type: namespaceType } = list;
|
||||
try {
|
||||
// TODO update once exceptions client `findExceptionListItem`
|
||||
// accepts an array of list ids
|
||||
const foundList = await client.getExceptionList({
|
||||
id,
|
||||
namespaceType,
|
||||
listId: undefined,
|
||||
});
|
||||
|
||||
if (foundList == null) {
|
||||
return [];
|
||||
} else {
|
||||
const items = await client.findExceptionListItem({
|
||||
listId: foundList.list_id,
|
||||
namespaceType,
|
||||
page: 1,
|
||||
perPage: MAX_EXCEPTION_LIST_SIZE,
|
||||
filter: undefined,
|
||||
sortOrder: undefined,
|
||||
sortField: undefined,
|
||||
});
|
||||
return items != null ? items.data : [];
|
||||
}
|
||||
} catch {
|
||||
throw new Error('unable to fetch exception list items');
|
||||
}
|
||||
})
|
||||
.flat()
|
||||
);
|
||||
return exceptions.flat();
|
||||
const listIds = lists.map(({ list_id: listId }) => listId);
|
||||
const namespaceTypes = lists.map(({ namespace_type: namespaceType }) => namespaceType);
|
||||
const items = await client.findExceptionListsItem({
|
||||
listId: listIds,
|
||||
namespaceType: namespaceTypes,
|
||||
page: 1,
|
||||
perPage: MAX_EXCEPTION_LIST_SIZE,
|
||||
filter: [],
|
||||
sortOrder: undefined,
|
||||
sortField: undefined,
|
||||
});
|
||||
return items != null ? items.data : [];
|
||||
} catch {
|
||||
throw new Error('unable to fetch exception list items');
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue