diff --git a/x-pack/plugins/lists/common/constants.ts b/x-pack/plugins/lists/common/constants.ts index df16085b53405b..6c73dc16563022 100644 --- a/x-pack/plugins/lists/common/constants.ts +++ b/x-pack/plugins/lists/common/constants.ts @@ -48,3 +48,5 @@ export const ENDPOINT_LIST_NAME = 'Elastic Endpoint Security Exception List'; /** The description of the single global space agnostic endpoint list */ export const ENDPOINT_LIST_DESCRIPTION = 'Elastic Endpoint Security Exception List'; + +export const MAX_EXCEPTION_LIST_SIZE = 10000; diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts index 5ff2a9d9df9f47..22aa1fb59858b4 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts @@ -6,7 +6,7 @@ import { IRouter } from 'kibana/server'; -import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; +import { ENDPOINT_LIST_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/siem_common_deps'; import { @@ -16,6 +16,7 @@ import { } from '../../common/schemas'; import { getExceptionListClient } from './utils/get_exception_list_client'; +import { validateExceptionListSize } from './validate'; export const createEndpointListItemRoute = (router: IRouter): void => { router.post( @@ -71,6 +72,18 @@ export const createEndpointListItemRoute = (router: IRouter): void => { if (errors != null) { return siemResponse.error({ body: errors, statusCode: 500 }); } else { + const listSizeError = await validateExceptionListSize( + exceptionLists, + ENDPOINT_LIST_ID, + 'agnostic' + ); + if (listSizeError != null) { + await exceptionLists.deleteExceptionListItemById({ + id: createdList.id, + namespaceType: 'agnostic', + }); + return siemResponse.error(listSizeError); + } return response.ok({ body: validated ?? {} }); } } diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts index e4885c7393bd4a..ed58621dae973b 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts @@ -17,6 +17,7 @@ import { import { getExceptionListClient } from './utils/get_exception_list_client'; import { endpointDisallowedFields } from './endpoint_disallowed_fields'; +import { validateExceptionListSize } from './validate'; export const createExceptionListItemRoute = (router: IRouter): void => { router.post( @@ -104,6 +105,18 @@ export const createExceptionListItemRoute = (router: IRouter): void => { if (errors != null) { return siemResponse.error({ body: errors, statusCode: 500 }); } else { + const listSizeError = await validateExceptionListSize( + exceptionLists, + listId, + namespaceType + ); + if (listSizeError != null) { + await exceptionLists.deleteExceptionListItemById({ + id: createdList.id, + namespaceType, + }); + return siemResponse.error(listSizeError); + } return response.ok({ body: validated ?? {} }); } } diff --git a/x-pack/plugins/lists/server/routes/validate.ts b/x-pack/plugins/lists/server/routes/validate.ts new file mode 100644 index 00000000000000..bbd4b0eaf0e33d --- /dev/null +++ b/x-pack/plugins/lists/server/routes/validate.ts @@ -0,0 +1,56 @@ +/* + * 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. + */ + +import { ExceptionListClient } from '../services/exception_lists/exception_list_client'; +import { MAX_EXCEPTION_LIST_SIZE } from '../../common/constants'; +import { foundExceptionListItemSchema } from '../../common/schemas'; +import { NamespaceType } from '../../common/schemas/types'; +import { validate } from '../../common/siem_common_deps'; + +export const validateExceptionListSize = async ( + exceptionLists: ExceptionListClient, + listId: string, + namespaceType: NamespaceType +): Promise<{ body: string; statusCode: number } | null> => { + const exceptionListItems = await exceptionLists.findExceptionListItem({ + filter: undefined, + listId, + namespaceType, + page: undefined, + perPage: undefined, + sortField: undefined, + sortOrder: undefined, + }); + if (exceptionListItems == null) { + // If exceptionListItems is null then we couldn't find the list so it may have been deleted + return { + body: `Unable to find list id: ${listId} to verify max exception list size`, + statusCode: 500, + }; + } + const [validatedItems, err] = validate(exceptionListItems, foundExceptionListItemSchema); + if (err != null) { + return { + body: err, + statusCode: 500, + }; + } + // Unnecessary since validatedItems comes from exceptionListItems which is already + // checked for null, but typescript fails to detect that + if (validatedItems == null) { + return { + body: `Unable to find list id: ${listId} to verify max exception list size`, + statusCode: 500, + }; + } + if (validatedItems.total > MAX_EXCEPTION_LIST_SIZE) { + return { + body: `Failed to add exception item, exception list would exceed max size of ${MAX_EXCEPTION_LIST_SIZE}`, + statusCode: 400, + }; + } + return null; +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts index 8dce1f1f79e358..ee85cf36a48b5c 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts @@ -8,6 +8,7 @@ import { SavedObjectsClientContract } from 'kibana/server'; import { ExceptionListItemSchema, + Id, IdOrUndefined, ItemIdOrUndefined, NamespaceType, @@ -23,6 +24,12 @@ interface DeleteExceptionListItemOptions { savedObjectsClient: SavedObjectsClientContract; } +interface DeleteExceptionListItemByIdOptions { + id: Id; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; +} + export const deleteExceptionListItem = async ({ itemId, id, @@ -43,3 +50,12 @@ export const deleteExceptionListItem = async ({ return exceptionListItem; } }; + +export const deleteExceptionListItemById = async ({ + id, + namespaceType, + savedObjectsClient, +}: DeleteExceptionListItemByIdOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + await savedObjectsClient.delete(savedObjectType, id); +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts index 11302e64b35387..83b44ababf9dec 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -20,6 +20,7 @@ import { CreateExceptionListItemOptions, CreateExceptionListOptions, DeleteEndpointListItemOptions, + DeleteExceptionListItemByIdOptions, DeleteExceptionListItemOptions, DeleteExceptionListOptions, FindEndpointListItemOptions, @@ -40,7 +41,7 @@ import { createExceptionListItem } from './create_exception_list_item'; import { updateExceptionList } from './update_exception_list'; import { updateExceptionListItem } from './update_exception_list_item'; import { deleteExceptionList } from './delete_exception_list'; -import { deleteExceptionListItem } from './delete_exception_list_item'; +import { deleteExceptionListItem, deleteExceptionListItemById } from './delete_exception_list_item'; import { findExceptionListItem } from './find_exception_list_item'; import { findExceptionList } from './find_exception_list'; import { findExceptionListsItem } from './find_exception_list_items'; @@ -326,6 +327,18 @@ export class ExceptionListClient { }); }; + public deleteExceptionListItemById = async ({ + id, + namespaceType, + }: DeleteExceptionListItemByIdOptions): Promise => { + const { savedObjectsClient } = this; + return deleteExceptionListItemById({ + id, + namespaceType, + savedObjectsClient, + }); + }; + /** * This is the same as "deleteExceptionListItem" except it applies specifically to the endpoint list. */ diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index 555b9c5e95a77d..963716b55ea771 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -19,6 +19,7 @@ import { ExceptionListType, ExceptionListTypeOrUndefined, FilterOrUndefined, + Id, IdOrUndefined, Immutable, ItemId, @@ -93,6 +94,11 @@ export interface DeleteExceptionListItemOptions { namespaceType: NamespaceType; } +export interface DeleteExceptionListItemByIdOptions { + id: Id; + namespaceType: NamespaceType; +} + export interface DeleteEndpointListItemOptions { id: IdOrUndefined; itemId: ItemIdOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 90373ee6761215..ae4274f31e1455 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -15,6 +15,7 @@ import { ListArrayOrUndefined } from '../../../../common/detection_engine/schema import { BulkResponse, BulkResponseErrorAggregation, isValidUnit } from './types'; import { BuildRuleMessage } from './rule_messages'; import { hasLargeValueList } from '../../../../common/detection_engine/utils'; +import { MAX_EXCEPTION_LIST_SIZE } from '../../../../../lists/common/constants'; interface SortExceptionsReturn { exceptionsWithValueLists: ExceptionListItemSchema[]; @@ -183,7 +184,7 @@ export const getExceptions = async ({ listId: foundList.list_id, namespaceType, page: 1, - perPage: 10000, + perPage: MAX_EXCEPTION_LIST_SIZE, filter: undefined, sortOrder: undefined, sortField: undefined,