Skip to content

Commit 21156d6

Browse files
[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)
* 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>
1 parent ced455e commit 21156d6

38 files changed

+1204
-28
lines changed

x-pack/plugins/lists/common/constants.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,28 @@ export const EXCEPTION_LIST_ITEM_URL = '/api/exception_lists/items';
2323
*/
2424
export const EXCEPTION_LIST_NAMESPACE_AGNOSTIC = 'exception-list-agnostic';
2525
export const EXCEPTION_LIST_NAMESPACE = 'exception-list';
26+
27+
/**
28+
* Specific routes for the single global space agnostic endpoint list
29+
*/
30+
export const ENDPOINT_LIST_URL = '/api/endpoint_list';
31+
32+
/**
33+
* Specific routes for the single global space agnostic endpoint list. These are convenience
34+
* routes where they are going to try and create the global space agnostic endpoint list if it
35+
* does not exist yet or if it was deleted at some point and re-create it before adding items to
36+
* the list
37+
*/
38+
export const ENDPOINT_LIST_ITEM_URL = '/api/endpoint_list/items';
39+
40+
/**
41+
* This ID is used for _both_ the Saved Object ID and for the list_id
42+
* for the single global space agnostic endpoint list
43+
*/
44+
export const ENDPOINT_LIST_ID = 'endpoint_list';
45+
46+
/** The name of the single global space agnostic endpoint list */
47+
export const ENDPOINT_LIST_NAME = 'Elastic Endpoint Exception List';
48+
49+
/** The description of the single global space agnostic endpoint list */
50+
export const ENDPOINT_LIST_DESCRIPTION = 'Elastic Endpoint Exception List';
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
/* eslint-disable @typescript-eslint/camelcase */
8+
9+
import * as t from 'io-ts';
10+
11+
import {
12+
ItemId,
13+
Tags,
14+
_Tags,
15+
_tags,
16+
description,
17+
exceptionListItemType,
18+
meta,
19+
name,
20+
tags,
21+
} from '../common/schemas';
22+
import { Identity, RequiredKeepUndefined } from '../../types';
23+
import { CreateCommentsArray, DefaultCreateCommentsArray, DefaultEntryArray } from '../types';
24+
import { EntriesArray } from '../types/entries';
25+
import { DefaultUuid } from '../../siem_common_deps';
26+
27+
export const createEndpointListItemSchema = t.intersection([
28+
t.exact(
29+
t.type({
30+
description,
31+
name,
32+
type: exceptionListItemType,
33+
})
34+
),
35+
t.exact(
36+
t.partial({
37+
_tags, // defaults to empty array if not set during decode
38+
comments: DefaultCreateCommentsArray, // defaults to empty array if not set during decode
39+
entries: DefaultEntryArray, // defaults to empty array if not set during decode
40+
item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode
41+
meta, // defaults to undefined if not set during decode
42+
tags, // defaults to empty array if not set during decode
43+
})
44+
),
45+
]);
46+
47+
export type CreateEndpointListItemSchemaPartial = Identity<
48+
t.TypeOf<typeof createEndpointListItemSchema>
49+
>;
50+
export type CreateEndpointListItemSchema = RequiredKeepUndefined<
51+
t.TypeOf<typeof createEndpointListItemSchema>
52+
>;
53+
54+
// This type is used after a decode since some things are defaults after a decode.
55+
export type CreateEndpointListItemSchemaDecoded = Identity<
56+
Omit<CreateEndpointListItemSchema, '_tags' | 'tags' | 'item_id' | 'entries' | 'comments'> & {
57+
_tags: _Tags;
58+
comments: CreateCommentsArray;
59+
tags: Tags;
60+
item_id: ItemId;
61+
entries: EntriesArray;
62+
}
63+
>;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
/* eslint-disable @typescript-eslint/camelcase */
8+
9+
import * as t from 'io-ts';
10+
11+
import { id, item_id } from '../common/schemas';
12+
13+
export const deleteEndpointListItemSchema = t.exact(
14+
t.partial({
15+
id,
16+
item_id,
17+
})
18+
);
19+
20+
export type DeleteEndpointListItemSchema = t.TypeOf<typeof deleteEndpointListItemSchema>;
21+
22+
// This type is used after a decode since some things are defaults after a decode.
23+
export type DeleteEndpointListItemSchemaDecoded = DeleteEndpointListItemSchema;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
/* eslint-disable @typescript-eslint/camelcase */
8+
9+
import * as t from 'io-ts';
10+
11+
import { filter, sort_field, sort_order } from '../common/schemas';
12+
import { RequiredKeepUndefined } from '../../types';
13+
import { StringToPositiveNumber } from '../types/string_to_positive_number';
14+
15+
export const findEndpointListItemSchema = t.exact(
16+
t.partial({
17+
filter, // defaults to undefined if not set during decode
18+
page: StringToPositiveNumber, // defaults to undefined if not set during decode
19+
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode
20+
sort_field, // defaults to undefined if not set during decode
21+
sort_order, // defaults to undefined if not set during decode
22+
})
23+
);
24+
25+
export type FindEndpointListItemSchemaPartial = t.OutputOf<typeof findEndpointListItemSchema>;
26+
27+
// This type is used after a decode since some things are defaults after a decode.
28+
export type FindEndpointListItemSchemaPartialDecoded = t.TypeOf<typeof findEndpointListItemSchema>;
29+
30+
// This type is used after a decode since some things are defaults after a decode.
31+
export type FindEndpointListItemSchemaDecoded = RequiredKeepUndefined<
32+
FindEndpointListItemSchemaPartialDecoded
33+
>;
34+
35+
export type FindEndpointListItemSchema = RequiredKeepUndefined<
36+
t.TypeOf<typeof findEndpointListItemSchema>
37+
>;

x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const findExceptionListItemSchema = t.intersection([
2626
),
2727
t.exact(
2828
t.partial({
29-
filter: EmptyStringArray, // defaults to undefined if not set during decode
29+
filter: EmptyStringArray, // defaults to an empty array [] if not set during decode
3030
namespace_type: DefaultNamespaceArray, // defaults to ['single'] if not set during decode
3131
page: StringToPositiveNumber, // defaults to undefined if not set during decode
3232
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode

x-pack/plugins/lists/common/schemas/request/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,31 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
export * from './create_endpoint_list_item_schema';
78
export * from './create_exception_list_item_schema';
89
export * from './create_exception_list_schema';
910
export * from './create_list_item_schema';
1011
export * from './create_list_schema';
12+
export * from './delete_endpoint_list_item_schema';
1113
export * from './delete_exception_list_item_schema';
1214
export * from './delete_exception_list_schema';
1315
export * from './delete_list_item_schema';
1416
export * from './delete_list_schema';
1517
export * from './export_list_item_query_schema';
18+
export * from './find_endpoint_list_item_schema';
1619
export * from './find_exception_list_item_schema';
1720
export * from './find_exception_list_schema';
1821
export * from './find_list_item_schema';
1922
export * from './find_list_schema';
2023
export * from './import_list_item_schema';
2124
export * from './patch_list_item_schema';
2225
export * from './patch_list_schema';
23-
export * from './read_exception_list_item_schema';
26+
export * from './read_endpoint_list_item_schema';
2427
export * from './read_exception_list_schema';
28+
export * from './read_exception_list_item_schema';
2529
export * from './read_list_item_schema';
2630
export * from './read_list_schema';
31+
export * from './update_endpoint_list_item_schema';
2732
export * from './update_exception_list_item_schema';
2833
export * from './update_exception_list_schema';
2934
export * from './import_list_item_query_schema';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
/* eslint-disable @typescript-eslint/camelcase */
8+
9+
import * as t from 'io-ts';
10+
11+
import { id, item_id } from '../common/schemas';
12+
import { RequiredKeepUndefined } from '../../types';
13+
14+
export const readEndpointListItemSchema = t.exact(
15+
t.partial({
16+
id,
17+
item_id,
18+
})
19+
);
20+
21+
export type ReadEndpointListItemSchemaPartial = t.TypeOf<typeof readEndpointListItemSchema>;
22+
23+
// This type is used after a decode since some things are defaults after a decode.
24+
export type ReadEndpointListItemSchemaPartialDecoded = ReadEndpointListItemSchemaPartial;
25+
26+
// This type is used after a decode since some things are defaults after a decode.
27+
export type ReadEndpointListItemSchemaDecoded = RequiredKeepUndefined<
28+
ReadEndpointListItemSchemaPartialDecoded
29+
>;
30+
31+
export type ReadEndpointListItemSchema = RequiredKeepUndefined<ReadEndpointListItemSchemaPartial>;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
/* eslint-disable @typescript-eslint/camelcase */
8+
9+
import * as t from 'io-ts';
10+
11+
import {
12+
Tags,
13+
_Tags,
14+
_tags,
15+
description,
16+
exceptionListItemType,
17+
id,
18+
meta,
19+
name,
20+
tags,
21+
} from '../common/schemas';
22+
import { Identity, RequiredKeepUndefined } from '../../types';
23+
import {
24+
DefaultEntryArray,
25+
DefaultUpdateCommentsArray,
26+
EntriesArray,
27+
UpdateCommentsArray,
28+
} from '../types';
29+
30+
export const updateEndpointListItemSchema = t.intersection([
31+
t.exact(
32+
t.type({
33+
description,
34+
name,
35+
type: exceptionListItemType,
36+
})
37+
),
38+
t.exact(
39+
t.partial({
40+
_tags, // defaults to empty array if not set during decode
41+
comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode
42+
entries: DefaultEntryArray, // defaults to empty array if not set during decode
43+
id, // defaults to undefined if not set during decode
44+
item_id: t.union([t.string, t.undefined]),
45+
meta, // defaults to undefined if not set during decode
46+
tags, // defaults to empty array if not set during decode
47+
})
48+
),
49+
]);
50+
51+
export type UpdateEndpointListItemSchemaPartial = Identity<
52+
t.TypeOf<typeof updateEndpointListItemSchema>
53+
>;
54+
export type UpdateEndpointListItemSchema = RequiredKeepUndefined<
55+
t.TypeOf<typeof updateEndpointListItemSchema>
56+
>;
57+
58+
// This type is used after a decode since some things are defaults after a decode.
59+
export type UpdateEndpointListItemSchemaDecoded = Identity<
60+
Omit<UpdateEndpointListItemSchema, '_tags' | 'tags' | 'entries' | 'comments'> & {
61+
_tags: _Tags;
62+
comments: UpdateCommentsArray;
63+
tags: Tags;
64+
entries: EntriesArray;
65+
}
66+
>;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { IRouter } from 'kibana/server';
8+
9+
import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants';
10+
import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps';
11+
import { validate } from '../../common/siem_common_deps';
12+
import {
13+
CreateEndpointListItemSchemaDecoded,
14+
createEndpointListItemSchema,
15+
exceptionListItemSchema,
16+
} from '../../common/schemas';
17+
18+
import { getExceptionListClient } from './utils/get_exception_list_client';
19+
20+
export const createEndpointListItemRoute = (router: IRouter): void => {
21+
router.post(
22+
{
23+
options: {
24+
tags: ['access:lists'],
25+
},
26+
path: ENDPOINT_LIST_ITEM_URL,
27+
validate: {
28+
body: buildRouteValidation<
29+
typeof createEndpointListItemSchema,
30+
CreateEndpointListItemSchemaDecoded
31+
>(createEndpointListItemSchema),
32+
},
33+
},
34+
async (context, request, response) => {
35+
const siemResponse = buildSiemResponse(response);
36+
try {
37+
const {
38+
name,
39+
_tags,
40+
tags,
41+
meta,
42+
comments,
43+
description,
44+
entries,
45+
item_id: itemId,
46+
type,
47+
} = request.body;
48+
const exceptionLists = getExceptionListClient(context);
49+
const exceptionListItem = await exceptionLists.getEndpointListItem({
50+
id: undefined,
51+
itemId,
52+
});
53+
if (exceptionListItem != null) {
54+
return siemResponse.error({
55+
body: `exception list item id: "${itemId}" already exists`,
56+
statusCode: 409,
57+
});
58+
} else {
59+
const createdList = await exceptionLists.createEndpointListItem({
60+
_tags,
61+
comments,
62+
description,
63+
entries,
64+
itemId,
65+
meta,
66+
name,
67+
tags,
68+
type,
69+
});
70+
const [validated, errors] = validate(createdList, exceptionListItemSchema);
71+
if (errors != null) {
72+
return siemResponse.error({ body: errors, statusCode: 500 });
73+
} else {
74+
return response.ok({ body: validated ?? {} });
75+
}
76+
}
77+
} catch (err) {
78+
const error = transformError(err);
79+
return siemResponse.error({
80+
body: error.message,
81+
statusCode: error.statusCode,
82+
});
83+
}
84+
}
85+
);
86+
};

0 commit comments

Comments
 (0)