Skip to content

Commit

Permalink
[Security Solution] Validate exception list size when adding new items (
Browse files Browse the repository at this point in the history
elastic#73399) (elastic#73600)

* 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>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
marshallmain and elasticmachine authored Jul 29, 2020
1 parent 5e2bde2 commit 58d97f5
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 3 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/lists/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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 ?? {} });
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 ?? {} });
}
}
Expand Down
56 changes: 56 additions & 0 deletions x-pack/plugins/lists/server/routes/validate.ts
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SavedObjectsClientContract } from 'kibana/server';

import {
ExceptionListItemSchema,
Id,
IdOrUndefined,
ItemIdOrUndefined,
NamespaceType,
Expand All @@ -23,6 +24,12 @@ interface DeleteExceptionListItemOptions {
savedObjectsClient: SavedObjectsClientContract;
}

interface DeleteExceptionListItemByIdOptions {
id: Id;
namespaceType: NamespaceType;
savedObjectsClient: SavedObjectsClientContract;
}

export const deleteExceptionListItem = async ({
itemId,
id,
Expand All @@ -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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CreateExceptionListItemOptions,
CreateExceptionListOptions,
DeleteEndpointListItemOptions,
DeleteExceptionListItemByIdOptions,
DeleteExceptionListItemOptions,
DeleteExceptionListOptions,
FindEndpointListItemOptions,
Expand All @@ -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';
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ExceptionListType,
ExceptionListTypeOrUndefined,
FilterOrUndefined,
Id,
IdOrUndefined,
Immutable,
ItemId,
Expand Down Expand Up @@ -93,6 +94,11 @@ export interface DeleteExceptionListItemOptions {
namespaceType: NamespaceType;
}

export interface DeleteExceptionListItemByIdOptions {
id: Id;
namespaceType: NamespaceType;
}

export interface DeleteEndpointListItemOptions {
id: IdOrUndefined;
itemId: ItemIdOrUndefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 58d97f5

Please sign in to comment.