Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Security Solution] Validate exception list size when adding new items (#73399) #73600

Merged
merged 1 commit into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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