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

[SIEM][Detection Engine][Lists] Adds specific endpoint_list REST API and API for abilities to auto-create the endpoint_list if it gets deleted #71792

Merged
merged 9 commits into from
Jul 15, 2020
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