mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Validate exception list size when adding new items (#73399)
* Validate exception list size when adding new items * Update comment * Extract list size validation and apply to endpoint route also Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
fea3bfcebc
commit
a6a0937062
8 changed files with 123 additions and 3 deletions
|
@ -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;
|
||||
|
|
|
@ -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 ?? {} });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ?? {} });
|
||||
}
|
||||
}
|
||||
|
|
56
x-pack/plugins/lists/server/routes/validate.ts
Normal file
56
x-pack/plugins/lists/server/routes/validate.ts
Normal file
|
@ -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;
|
||||
};
|
|
@ -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<void> => {
|
||||
const savedObjectType = getSavedObjectType({ namespaceType });
|
||||
await savedObjectsClient.delete(savedObjectType, id);
|
||||
};
|
||||
|
|
|
@ -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<void> => {
|
||||
const { savedObjectsClient } = this;
|
||||
return deleteExceptionListItemById({
|
||||
id,
|
||||
namespaceType,
|
||||
savedObjectsClient,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as "deleteExceptionListItem" except it applies specifically to the endpoint list.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue