Skip to content

Commit

Permalink
[SIEM][Detection Engine][Lists] Adds specific endpoint_list REST API …
Browse files Browse the repository at this point in the history
…and API for abilities to auto-create the endpoint_list if it gets deleted (#71792)

* Adds specific endpoint_list REST API and API for abilities to autocreate the endpoint_list if it gets deleted

* Added the check against prepackaged list

* Updated to use LIST names

* Removed the namespace where it does not belong

* Updates per code review an extra space that was added

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
FrankHassanabad and elasticmachine authored Jul 15, 2020
1 parent ced455e commit 21156d6
Show file tree
Hide file tree
Showing 38 changed files with 1,204 additions and 28 deletions.
25 changes: 25 additions & 0 deletions x-pack/plugins/lists/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,28 @@ export const EXCEPTION_LIST_ITEM_URL = '/api/exception_lists/items';
*/
export const EXCEPTION_LIST_NAMESPACE_AGNOSTIC = 'exception-list-agnostic';
export const EXCEPTION_LIST_NAMESPACE = 'exception-list';

/**
* Specific routes for the single global space agnostic endpoint list
*/
export const ENDPOINT_LIST_URL = '/api/endpoint_list';

/**
* Specific routes for the single global space agnostic endpoint list. These are convenience
* routes where they are going to try and create the global space agnostic endpoint list if it
* does not exist yet or if it was deleted at some point and re-create it before adding items to
* the list
*/
export const ENDPOINT_LIST_ITEM_URL = '/api/endpoint_list/items';

/**
* This ID is used for _both_ the Saved Object ID and for the list_id
* for the single global space agnostic endpoint list
*/
export const ENDPOINT_LIST_ID = 'endpoint_list';

/** The name of the single global space agnostic endpoint list */
export const ENDPOINT_LIST_NAME = 'Elastic Endpoint Exception List';

/** The description of the single global space agnostic endpoint list */
export const ENDPOINT_LIST_DESCRIPTION = 'Elastic Endpoint Exception List';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.
*/

/* eslint-disable @typescript-eslint/camelcase */

import * as t from 'io-ts';

import {
ItemId,
Tags,
_Tags,
_tags,
description,
exceptionListItemType,
meta,
name,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { CreateCommentsArray, DefaultCreateCommentsArray, DefaultEntryArray } from '../types';
import { EntriesArray } from '../types/entries';
import { DefaultUuid } from '../../siem_common_deps';

export const createEndpointListItemSchema = t.intersection([
t.exact(
t.type({
description,
name,
type: exceptionListItemType,
})
),
t.exact(
t.partial({
_tags, // defaults to empty array if not set during decode
comments: DefaultCreateCommentsArray, // defaults to empty array if not set during decode
entries: DefaultEntryArray, // defaults to empty array if not set during decode
item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode
meta, // defaults to undefined if not set during decode
tags, // defaults to empty array if not set during decode
})
),
]);

export type CreateEndpointListItemSchemaPartial = Identity<
t.TypeOf<typeof createEndpointListItemSchema>
>;
export type CreateEndpointListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof createEndpointListItemSchema>
>;

// This type is used after a decode since some things are defaults after a decode.
export type CreateEndpointListItemSchemaDecoded = Identity<
Omit<CreateEndpointListItemSchema, '_tags' | 'tags' | 'item_id' | 'entries' | 'comments'> & {
_tags: _Tags;
comments: CreateCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
}
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.
*/

/* eslint-disable @typescript-eslint/camelcase */

import * as t from 'io-ts';

import { id, item_id } from '../common/schemas';

export const deleteEndpointListItemSchema = t.exact(
t.partial({
id,
item_id,
})
);

export type DeleteEndpointListItemSchema = t.TypeOf<typeof deleteEndpointListItemSchema>;

// This type is used after a decode since some things are defaults after a decode.
export type DeleteEndpointListItemSchemaDecoded = DeleteEndpointListItemSchema;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.
*/

/* eslint-disable @typescript-eslint/camelcase */

import * as t from 'io-ts';

import { filter, sort_field, sort_order } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
import { StringToPositiveNumber } from '../types/string_to_positive_number';

export const findEndpointListItemSchema = t.exact(
t.partial({
filter, // defaults to undefined if not set during decode
page: StringToPositiveNumber, // defaults to undefined if not set during decode
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode
sort_field, // defaults to undefined if not set during decode
sort_order, // defaults to undefined if not set during decode
})
);

export type FindEndpointListItemSchemaPartial = t.OutputOf<typeof findEndpointListItemSchema>;

// This type is used after a decode since some things are defaults after a decode.
export type FindEndpointListItemSchemaPartialDecoded = t.TypeOf<typeof findEndpointListItemSchema>;

// This type is used after a decode since some things are defaults after a decode.
export type FindEndpointListItemSchemaDecoded = RequiredKeepUndefined<
FindEndpointListItemSchemaPartialDecoded
>;

export type FindEndpointListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof findEndpointListItemSchema>
>;
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const findExceptionListItemSchema = t.intersection([
),
t.exact(
t.partial({
filter: EmptyStringArray, // defaults to undefined if not set during decode
filter: EmptyStringArray, // defaults to an empty array [] if not set during decode
namespace_type: DefaultNamespaceArray, // defaults to ['single'] if not set during decode
page: StringToPositiveNumber, // defaults to undefined if not set during decode
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/lists/common/schemas/request/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/

export * from './create_endpoint_list_item_schema';
export * from './create_exception_list_item_schema';
export * from './create_exception_list_schema';
export * from './create_list_item_schema';
export * from './create_list_schema';
export * from './delete_endpoint_list_item_schema';
export * from './delete_exception_list_item_schema';
export * from './delete_exception_list_schema';
export * from './delete_list_item_schema';
export * from './delete_list_schema';
export * from './export_list_item_query_schema';
export * from './find_endpoint_list_item_schema';
export * from './find_exception_list_item_schema';
export * from './find_exception_list_schema';
export * from './find_list_item_schema';
export * from './find_list_schema';
export * from './import_list_item_schema';
export * from './patch_list_item_schema';
export * from './patch_list_schema';
export * from './read_exception_list_item_schema';
export * from './read_endpoint_list_item_schema';
export * from './read_exception_list_schema';
export * from './read_exception_list_item_schema';
export * from './read_list_item_schema';
export * from './read_list_schema';
export * from './update_endpoint_list_item_schema';
export * from './update_exception_list_item_schema';
export * from './update_exception_list_schema';
export * from './import_list_item_query_schema';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.
*/

/* eslint-disable @typescript-eslint/camelcase */

import * as t from 'io-ts';

import { id, item_id } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';

export const readEndpointListItemSchema = t.exact(
t.partial({
id,
item_id,
})
);

export type ReadEndpointListItemSchemaPartial = t.TypeOf<typeof readEndpointListItemSchema>;

// This type is used after a decode since some things are defaults after a decode.
export type ReadEndpointListItemSchemaPartialDecoded = ReadEndpointListItemSchemaPartial;

// This type is used after a decode since some things are defaults after a decode.
export type ReadEndpointListItemSchemaDecoded = RequiredKeepUndefined<
ReadEndpointListItemSchemaPartialDecoded
>;

export type ReadEndpointListItemSchema = RequiredKeepUndefined<ReadEndpointListItemSchemaPartial>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.
*/

/* eslint-disable @typescript-eslint/camelcase */

import * as t from 'io-ts';

import {
Tags,
_Tags,
_tags,
description,
exceptionListItemType,
id,
meta,
name,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import {
DefaultEntryArray,
DefaultUpdateCommentsArray,
EntriesArray,
UpdateCommentsArray,
} from '../types';

export const updateEndpointListItemSchema = t.intersection([
t.exact(
t.type({
description,
name,
type: exceptionListItemType,
})
),
t.exact(
t.partial({
_tags, // defaults to empty array if not set during decode
comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode
entries: DefaultEntryArray, // defaults to empty array if not set during decode
id, // defaults to undefined if not set during decode
item_id: t.union([t.string, t.undefined]),
meta, // defaults to undefined if not set during decode
tags, // defaults to empty array if not set during decode
})
),
]);

export type UpdateEndpointListItemSchemaPartial = Identity<
t.TypeOf<typeof updateEndpointListItemSchema>
>;
export type UpdateEndpointListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof updateEndpointListItemSchema>
>;

// This type is used after a decode since some things are defaults after a decode.
export type UpdateEndpointListItemSchemaDecoded = Identity<
Omit<UpdateEndpointListItemSchema, '_tags' | 'tags' | 'entries' | 'comments'> & {
_tags: _Tags;
comments: UpdateCommentsArray;
tags: Tags;
entries: EntriesArray;
}
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 { IRouter } from 'kibana/server';

import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants';
import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps';
import { validate } from '../../common/siem_common_deps';
import {
CreateEndpointListItemSchemaDecoded,
createEndpointListItemSchema,
exceptionListItemSchema,
} from '../../common/schemas';

import { getExceptionListClient } from './utils/get_exception_list_client';

export const createEndpointListItemRoute = (router: IRouter): void => {
router.post(
{
options: {
tags: ['access:lists'],
},
path: ENDPOINT_LIST_ITEM_URL,
validate: {
body: buildRouteValidation<
typeof createEndpointListItemSchema,
CreateEndpointListItemSchemaDecoded
>(createEndpointListItemSchema),
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const {
name,
_tags,
tags,
meta,
comments,
description,
entries,
item_id: itemId,
type,
} = request.body;
const exceptionLists = getExceptionListClient(context);
const exceptionListItem = await exceptionLists.getEndpointListItem({
id: undefined,
itemId,
});
if (exceptionListItem != null) {
return siemResponse.error({
body: `exception list item id: "${itemId}" already exists`,
statusCode: 409,
});
} else {
const createdList = await exceptionLists.createEndpointListItem({
_tags,
comments,
description,
entries,
itemId,
meta,
name,
tags,
type,
});
const [validated, errors] = validate(createdList, exceptionListItemSchema);
if (errors != null) {
return siemResponse.error({ body: errors, statusCode: 500 });
} else {
return response.ok({ body: validated ?? {} });
}
}
} catch (err) {
const error = transformError(err);
return siemResponse.error({
body: error.message,
statusCode: error.statusCode,
});
}
}
);
};
Loading

0 comments on commit 21156d6

Please sign in to comment.