[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>
This commit is contained in:
parent
ced455e077
commit
21156d6f18
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
>;
|
|
@ -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;
|
|
@ -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>
|
||||
>;
|
|
@ -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
|
||||
|
|
|
@ -4,15 +4,18 @@
|
|||
* 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';
|
||||
|
@ -20,10 +23,12 @@ 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';
|
||||
|
|
|
@ -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>;
|
|
@ -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;
|
||||
}
|
||||
>;
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import { IRouter } from 'kibana/server';
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { ENDPOINT_LIST_URL } from '../../common/constants';
|
||||
import { buildSiemResponse, transformError } from '../siem_server_deps';
|
||||
import { validate } from '../../common/siem_common_deps';
|
||||
import { exceptionListSchema } from '../../common/schemas';
|
||||
|
||||
import { getExceptionListClient } from './utils/get_exception_list_client';
|
||||
|
||||
/**
|
||||
* This creates the endpoint list if it does not exist. If it does exist,
|
||||
* this will conflict but continue. This is intended to be as fast as possible so it tries
|
||||
* each and every time it is called to create the endpoint_list and just ignores any
|
||||
* conflict so at worse case only one round trip happens per API call. If any error other than conflict
|
||||
* happens this will return that error. If the list already exists this will return an empty
|
||||
* object.
|
||||
* @param router The router to use.
|
||||
*/
|
||||
export const createEndpointListRoute = (router: IRouter): void => {
|
||||
router.post(
|
||||
{
|
||||
options: {
|
||||
tags: ['access:lists'],
|
||||
},
|
||||
path: ENDPOINT_LIST_URL,
|
||||
validate: false,
|
||||
},
|
||||
async (context, _, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
// Our goal is be fast as possible and block the least amount of
|
||||
const exceptionLists = getExceptionListClient(context);
|
||||
const createdList = await exceptionLists.createEndpointList();
|
||||
if (createdList != null) {
|
||||
const [validated, errors] = validate(createdList, t.union([exceptionListSchema, t.null]));
|
||||
if (errors != null) {
|
||||
return siemResponse.error({ body: errors, statusCode: 500 });
|
||||
} else {
|
||||
return response.ok({ body: validated ?? {} });
|
||||
}
|
||||
} else {
|
||||
// We always return ok on a create endpoint list route but with an empty body as
|
||||
// an additional fetch of the full list would be slower and the UI has everything hard coded
|
||||
// within it to get the list if it needs details about it.
|
||||
return response.ok({ body: {} });
|
||||
}
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 {
|
||||
DeleteEndpointListItemSchemaDecoded,
|
||||
deleteEndpointListItemSchema,
|
||||
exceptionListItemSchema,
|
||||
} from '../../common/schemas';
|
||||
|
||||
import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils';
|
||||
|
||||
export const deleteEndpointListItemRoute = (router: IRouter): void => {
|
||||
router.delete(
|
||||
{
|
||||
options: {
|
||||
tags: ['access:lists'],
|
||||
},
|
||||
path: ENDPOINT_LIST_ITEM_URL,
|
||||
validate: {
|
||||
query: buildRouteValidation<
|
||||
typeof deleteEndpointListItemSchema,
|
||||
DeleteEndpointListItemSchemaDecoded
|
||||
>(deleteEndpointListItemSchema),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
const exceptionLists = getExceptionListClient(context);
|
||||
const { item_id: itemId, id } = request.query;
|
||||
if (itemId == null && id == null) {
|
||||
return siemResponse.error({
|
||||
body: 'Either "item_id" or "id" needs to be defined in the request',
|
||||
statusCode: 400,
|
||||
});
|
||||
} else {
|
||||
const deleted = await exceptionLists.deleteEndpointListItem({
|
||||
id,
|
||||
itemId,
|
||||
});
|
||||
if (deleted == null) {
|
||||
return siemResponse.error({
|
||||
body: getErrorMessageExceptionListItem({ id, itemId }),
|
||||
statusCode: 404,
|
||||
});
|
||||
} else {
|
||||
const [validated, errors] = validate(deleted, 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants';
|
||||
import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps';
|
||||
import { validate } from '../../common/siem_common_deps';
|
||||
import {
|
||||
FindEndpointListItemSchemaDecoded,
|
||||
findEndpointListItemSchema,
|
||||
foundExceptionListItemSchema,
|
||||
} from '../../common/schemas';
|
||||
|
||||
import { getExceptionListClient } from './utils';
|
||||
|
||||
export const findEndpointListItemRoute = (router: IRouter): void => {
|
||||
router.get(
|
||||
{
|
||||
options: {
|
||||
tags: ['access:lists'],
|
||||
},
|
||||
path: `${ENDPOINT_LIST_ITEM_URL}/_find`,
|
||||
validate: {
|
||||
query: buildRouteValidation<
|
||||
typeof findEndpointListItemSchema,
|
||||
FindEndpointListItemSchemaDecoded
|
||||
>(findEndpointListItemSchema),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
const exceptionLists = getExceptionListClient(context);
|
||||
const {
|
||||
filter,
|
||||
page,
|
||||
per_page: perPage,
|
||||
sort_field: sortField,
|
||||
sort_order: sortOrder,
|
||||
} = request.query;
|
||||
|
||||
const exceptionListItems = await exceptionLists.findEndpointListItem({
|
||||
filter,
|
||||
page,
|
||||
perPage,
|
||||
sortField,
|
||||
sortOrder,
|
||||
});
|
||||
if (exceptionListItems == null) {
|
||||
// Although I have this line of code here, this is an incredibly rare thing to have
|
||||
// happen as the findEndpointListItem tries to auto-create the endpoint list if
|
||||
// does not exist.
|
||||
return siemResponse.error({
|
||||
body: `list id: "${ENDPOINT_LIST_ID}" does not exist`,
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
const [validated, errors] = validate(exceptionListItems, foundExceptionListItemSchema);
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -4,17 +4,21 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './create_endpoint_list_item_route';
|
||||
export * from './create_endpoint_list_route';
|
||||
export * from './create_exception_list_item_route';
|
||||
export * from './create_exception_list_route';
|
||||
export * from './create_list_index_route';
|
||||
export * from './create_list_item_route';
|
||||
export * from './create_list_route';
|
||||
export * from './delete_endpoint_list_item_route';
|
||||
export * from './delete_exception_list_route';
|
||||
export * from './delete_exception_list_item_route';
|
||||
export * from './delete_list_index_route';
|
||||
export * from './delete_list_item_route';
|
||||
export * from './delete_list_route';
|
||||
export * from './export_list_item_route';
|
||||
export * from './find_endpoint_list_item_route';
|
||||
export * from './find_exception_list_item_route';
|
||||
export * from './find_exception_list_route';
|
||||
export * from './find_list_item_route';
|
||||
|
@ -23,11 +27,14 @@ export * from './import_list_item_route';
|
|||
export * from './init_routes';
|
||||
export * from './patch_list_item_route';
|
||||
export * from './patch_list_route';
|
||||
export * from './read_endpoint_list_item_route';
|
||||
export * from './read_exception_list_item_route';
|
||||
export * from './read_exception_list_route';
|
||||
export * from './read_list_index_route';
|
||||
export * from './read_list_item_route';
|
||||
export * from './read_list_route';
|
||||
export * from './read_privileges_route';
|
||||
export * from './update_endpoint_list_item_route';
|
||||
export * from './update_exception_list_item_route';
|
||||
export * from './update_exception_list_route';
|
||||
export * from './update_list_item_route';
|
||||
|
|
|
@ -9,20 +9,22 @@ import { IRouter } from 'kibana/server';
|
|||
import { SecurityPluginSetup } from '../../../security/server';
|
||||
import { ConfigType } from '../config';
|
||||
|
||||
import { readPrivilegesRoute } from './read_privileges_route';
|
||||
|
||||
import {
|
||||
createEndpointListItemRoute,
|
||||
createEndpointListRoute,
|
||||
createExceptionListItemRoute,
|
||||
createExceptionListRoute,
|
||||
createListIndexRoute,
|
||||
createListItemRoute,
|
||||
createListRoute,
|
||||
deleteEndpointListItemRoute,
|
||||
deleteExceptionListItemRoute,
|
||||
deleteExceptionListRoute,
|
||||
deleteListIndexRoute,
|
||||
deleteListItemRoute,
|
||||
deleteListRoute,
|
||||
exportListItemRoute,
|
||||
findEndpointListItemRoute,
|
||||
findExceptionListItemRoute,
|
||||
findExceptionListRoute,
|
||||
findListItemRoute,
|
||||
|
@ -30,11 +32,14 @@ import {
|
|||
importListItemRoute,
|
||||
patchListItemRoute,
|
||||
patchListRoute,
|
||||
readEndpointListItemRoute,
|
||||
readExceptionListItemRoute,
|
||||
readExceptionListRoute,
|
||||
readListIndexRoute,
|
||||
readListItemRoute,
|
||||
readListRoute,
|
||||
readPrivilegesRoute,
|
||||
updateEndpointListItemRoute,
|
||||
updateExceptionListItemRoute,
|
||||
updateExceptionListRoute,
|
||||
updateListItemRoute,
|
||||
|
@ -83,4 +88,14 @@ export const initRoutes = (
|
|||
updateExceptionListItemRoute(router);
|
||||
deleteExceptionListItemRoute(router);
|
||||
findExceptionListItemRoute(router);
|
||||
|
||||
// endpoint list
|
||||
createEndpointListRoute(router);
|
||||
|
||||
// endpoint list items
|
||||
createEndpointListItemRoute(router);
|
||||
readEndpointListItemRoute(router);
|
||||
updateEndpointListItemRoute(router);
|
||||
deleteEndpointListItemRoute(router);
|
||||
findEndpointListItemRoute(router);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 {
|
||||
ReadEndpointListItemSchemaDecoded,
|
||||
exceptionListItemSchema,
|
||||
readEndpointListItemSchema,
|
||||
} from '../../common/schemas';
|
||||
|
||||
import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils';
|
||||
|
||||
export const readEndpointListItemRoute = (router: IRouter): void => {
|
||||
router.get(
|
||||
{
|
||||
options: {
|
||||
tags: ['access:lists'],
|
||||
},
|
||||
path: ENDPOINT_LIST_ITEM_URL,
|
||||
validate: {
|
||||
query: buildRouteValidation<
|
||||
typeof readEndpointListItemSchema,
|
||||
ReadEndpointListItemSchemaDecoded
|
||||
>(readEndpointListItemSchema),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
const { id, item_id: itemId } = request.query;
|
||||
const exceptionLists = getExceptionListClient(context);
|
||||
if (id != null || itemId != null) {
|
||||
const exceptionListItem = await exceptionLists.getEndpointListItem({
|
||||
id,
|
||||
itemId,
|
||||
});
|
||||
if (exceptionListItem == null) {
|
||||
return siemResponse.error({
|
||||
body: getErrorMessageExceptionListItem({ id, itemId }),
|
||||
statusCode: 404,
|
||||
});
|
||||
} else {
|
||||
const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema);
|
||||
if (errors != null) {
|
||||
return siemResponse.error({ body: errors, statusCode: 500 });
|
||||
} else {
|
||||
return response.ok({ body: validated ?? {} });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return siemResponse.error({ body: 'id or item_id required', statusCode: 400 });
|
||||
}
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 {
|
||||
UpdateEndpointListItemSchemaDecoded,
|
||||
exceptionListItemSchema,
|
||||
updateEndpointListItemSchema,
|
||||
} from '../../common/schemas';
|
||||
|
||||
import { getExceptionListClient } from '.';
|
||||
|
||||
export const updateEndpointListItemRoute = (router: IRouter): void => {
|
||||
router.put(
|
||||
{
|
||||
options: {
|
||||
tags: ['access:lists'],
|
||||
},
|
||||
path: ENDPOINT_LIST_ITEM_URL,
|
||||
validate: {
|
||||
body: buildRouteValidation<
|
||||
typeof updateEndpointListItemSchema,
|
||||
UpdateEndpointListItemSchemaDecoded
|
||||
>(updateEndpointListItemSchema),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
const {
|
||||
description,
|
||||
id,
|
||||
name,
|
||||
meta,
|
||||
type,
|
||||
_tags,
|
||||
comments,
|
||||
entries,
|
||||
item_id: itemId,
|
||||
tags,
|
||||
} = request.body;
|
||||
const exceptionLists = getExceptionListClient(context);
|
||||
const exceptionListItem = await exceptionLists.updateEndpointListItem({
|
||||
_tags,
|
||||
comments,
|
||||
description,
|
||||
entries,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
name,
|
||||
tags,
|
||||
type,
|
||||
});
|
||||
if (exceptionListItem == null) {
|
||||
if (id != null) {
|
||||
return siemResponse.error({
|
||||
body: `list item id: "${id}" not found`,
|
||||
statusCode: 404,
|
||||
});
|
||||
} else {
|
||||
return siemResponse.error({
|
||||
body: `list item item_id: "${itemId}" not found`,
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const [validated, errors] = validate(exceptionListItem, 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -62,10 +62,17 @@ export const updateExceptionListItemRoute = (router: IRouter): void => {
|
|||
type,
|
||||
});
|
||||
if (exceptionListItem == null) {
|
||||
return siemResponse.error({
|
||||
body: `list item id: "${id}" not found`,
|
||||
statusCode: 404,
|
||||
});
|
||||
if (id != null) {
|
||||
return siemResponse.error({
|
||||
body: `list item id: "${id}" not found`,
|
||||
statusCode: 404,
|
||||
});
|
||||
} else {
|
||||
return siemResponse.error({
|
||||
body: `list item item_id: "${itemId}" not found`,
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema);
|
||||
if (errors != null) {
|
||||
|
|
16
x-pack/plugins/lists/server/scripts/delete_endpoint_list_item.sh
Executable file
16
x-pack/plugins/lists/server/scripts/delete_endpoint_list_item.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Example: ./delete_endpoint_list_item.sh ${item_id}
|
||||
curl -s -k \
|
||||
-H 'kbn-xsrf: 123' \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X DELETE "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?item_id=$1" | jq .
|
16
x-pack/plugins/lists/server/scripts/delete_endpoint_list_item_by_id.sh
Executable file
16
x-pack/plugins/lists/server/scripts/delete_endpoint_list_item_by_id.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Example: ./delete_endpoint_list_item_by_id.sh ${list_id}
|
||||
curl -s -k \
|
||||
-H 'kbn-xsrf: 123' \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X DELETE "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?id=$1" | jq .
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"item_id": "simple_list_item",
|
||||
"_tags": ["endpoint", "process", "malware", "os:linux"],
|
||||
"tags": ["user added string for a tag", "malware"],
|
||||
"type": "simple",
|
||||
"description": "This is a sample endpoint type exception",
|
||||
"name": "Sample Endpoint Exception List",
|
||||
"entries": [
|
||||
{
|
||||
"field": "actingProcess.file.signer",
|
||||
"operator": "excluded",
|
||||
"type": "exists"
|
||||
},
|
||||
{
|
||||
"field": "host.name",
|
||||
"operator": "included",
|
||||
"type": "match_any",
|
||||
"value": ["some host", "another host"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"item_id": "endpoint_list_item",
|
||||
"item_id": "simple_list_item",
|
||||
"_tags": ["endpoint", "process", "malware", "os:windows"],
|
||||
"tags": ["user added string for a tag", "malware"],
|
||||
"type": "simple",
|
||||
|
|
20
x-pack/plugins/lists/server/scripts/find_endpoint_list_items.sh
Executable file
20
x-pack/plugins/lists/server/scripts/find_endpoint_list_items.sh
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Optionally, post at least one list item
|
||||
# ./post_endpoint_list_item.sh ./exception_lists/new/endpoint_list_item.json
|
||||
#
|
||||
# Then you can query it as in:
|
||||
# Example: ./find_endpoint_list_item.sh
|
||||
#
|
||||
curl -s -k \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X GET "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items/_find" | jq .
|
15
x-pack/plugins/lists/server/scripts/get_endpoint_list_item.sh
Executable file
15
x-pack/plugins/lists/server/scripts/get_endpoint_list_item.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Example: ./get_endpoint_list_item.sh ${item_id}
|
||||
curl -s -k \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X GET "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?item_id=$1" | jq .
|
18
x-pack/plugins/lists/server/scripts/get_endpoint_list_item_by_id.sh
Executable file
18
x-pack/plugins/lists/server/scripts/get_endpoint_list_item_by_id.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Example: ./get_endpoint_list_item.sh ${id}
|
||||
curl -s -k \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X GET "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?id=$1" | jq .
|
21
x-pack/plugins/lists/server/scripts/post_endpoint_list.sh
Executable file
21
x-pack/plugins/lists/server/scripts/post_endpoint_list.sh
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Uses a default if no argument is specified
|
||||
LISTS=(${@:-./exception_lists/new/exception_list.json})
|
||||
|
||||
# Example: ./post_endpoint_list.sh
|
||||
curl -s -k \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'kbn-xsrf: 123' \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X POST ${KIBANA_URL}${SPACE_URL}/api/endpoint_list \
|
||||
| jq .;
|
30
x-pack/plugins/lists/server/scripts/post_endpoint_list_item.sh
Executable file
30
x-pack/plugins/lists/server/scripts/post_endpoint_list_item.sh
Executable file
|
@ -0,0 +1,30 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Uses a default if no argument is specified
|
||||
LISTS=(${@:-./exception_lists/new/endpoint_list_item.json})
|
||||
|
||||
# Example: ./post_endpoint_list_item.sh
|
||||
# Example: ./post_endpoint_list_item.sh ./exception_lists/new/endpoint_list_item.json
|
||||
for LIST in "${LISTS[@]}"
|
||||
do {
|
||||
[ -e "$LIST" ] || continue
|
||||
curl -s -k \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'kbn-xsrf: 123' \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X POST ${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items \
|
||||
-d @${LIST} \
|
||||
| jq .;
|
||||
} &
|
||||
done
|
||||
|
||||
wait
|
30
x-pack/plugins/lists/server/scripts/update_endpoint_item.sh
Executable file
30
x-pack/plugins/lists/server/scripts/update_endpoint_item.sh
Executable file
|
@ -0,0 +1,30 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Uses a default if no argument is specified
|
||||
LISTS=(${@:-./exception_lists/updates/simple_update_item.json})
|
||||
|
||||
# Example: ./update_endpoint_list_item.sh
|
||||
# Example: ./update_endpoint_list_item.sh ./exception_lists/updates/simple_update_item.json
|
||||
for LIST in "${LISTS[@]}"
|
||||
do {
|
||||
[ -e "$LIST" ] || continue
|
||||
curl -s -k \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'kbn-xsrf: 123' \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X PUT ${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items \
|
||||
-d @${LIST} \
|
||||
| jq .;
|
||||
} &
|
||||
done
|
||||
|
||||
wait
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClientContract } from 'kibana/server';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import {
|
||||
ENDPOINT_LIST_DESCRIPTION,
|
||||
ENDPOINT_LIST_ID,
|
||||
ENDPOINT_LIST_NAME,
|
||||
} from '../../../common/constants';
|
||||
import { ExceptionListSchema, ExceptionListSoSchema } from '../../../common/schemas';
|
||||
|
||||
import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils';
|
||||
|
||||
interface CreateEndpointListOptions {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
user: string;
|
||||
tieBreaker?: string;
|
||||
}
|
||||
|
||||
export const createEndpointList = async ({
|
||||
savedObjectsClient,
|
||||
user,
|
||||
tieBreaker,
|
||||
}: CreateEndpointListOptions): Promise<ExceptionListSchema | null> => {
|
||||
const savedObjectType = getSavedObjectType({ namespaceType: 'agnostic' });
|
||||
const dateNow = new Date().toISOString();
|
||||
try {
|
||||
const savedObject = await savedObjectsClient.create<ExceptionListSoSchema>(
|
||||
savedObjectType,
|
||||
{
|
||||
_tags: [],
|
||||
comments: undefined,
|
||||
created_at: dateNow,
|
||||
created_by: user,
|
||||
description: ENDPOINT_LIST_DESCRIPTION,
|
||||
entries: undefined,
|
||||
item_id: undefined,
|
||||
list_id: ENDPOINT_LIST_ID,
|
||||
list_type: 'list',
|
||||
meta: undefined,
|
||||
name: ENDPOINT_LIST_NAME,
|
||||
tags: [],
|
||||
tie_breaker_id: tieBreaker ?? uuid.v4(),
|
||||
type: 'endpoint',
|
||||
updated_by: user,
|
||||
},
|
||||
{
|
||||
// We intentionally hard coding the id so that there can only be one exception list within the space
|
||||
id: ENDPOINT_LIST_ID,
|
||||
}
|
||||
);
|
||||
return transformSavedObjectToExceptionList({ savedObject });
|
||||
} catch (err) {
|
||||
if (err.status === 409) {
|
||||
return null;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -68,5 +68,5 @@ export const createExceptionList = async ({
|
|||
type,
|
||||
updated_by: user,
|
||||
});
|
||||
return transformSavedObjectToExceptionList({ namespaceType, savedObject });
|
||||
return transformSavedObjectToExceptionList({ savedObject });
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
|
||||
import { ENDPOINT_LIST_ID } from '../../../common/constants';
|
||||
import {
|
||||
ExceptionListItemSchema,
|
||||
ExceptionListSchema,
|
||||
|
@ -15,15 +16,20 @@ import {
|
|||
|
||||
import {
|
||||
ConstructorOptions,
|
||||
CreateEndpointListItemOptions,
|
||||
CreateExceptionListItemOptions,
|
||||
CreateExceptionListOptions,
|
||||
DeleteEndpointListItemOptions,
|
||||
DeleteExceptionListItemOptions,
|
||||
DeleteExceptionListOptions,
|
||||
FindEndpointListItemOptions,
|
||||
FindExceptionListItemOptions,
|
||||
FindExceptionListOptions,
|
||||
FindExceptionListsItemOptions,
|
||||
GetEndpointListItemOptions,
|
||||
GetExceptionListItemOptions,
|
||||
GetExceptionListOptions,
|
||||
UpdateEndpointListItemOptions,
|
||||
UpdateExceptionListItemOptions,
|
||||
UpdateExceptionListOptions,
|
||||
} from './exception_list_client_types';
|
||||
|
@ -38,6 +44,7 @@ import { deleteExceptionListItem } 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';
|
||||
import { createEndpointList } from './create_endpoint_list';
|
||||
|
||||
export class ExceptionListClient {
|
||||
private readonly user: string;
|
||||
|
@ -67,6 +74,103 @@ export class ExceptionListClient {
|
|||
return getExceptionListItem({ id, itemId, namespaceType, savedObjectsClient });
|
||||
};
|
||||
|
||||
/**
|
||||
* This creates an agnostic space endpoint list if it does not exist. This tries to be
|
||||
* as fast as possible by ignoring conflict errors and not returning the contents of the
|
||||
* list if it already exists.
|
||||
* @returns ExceptionListSchema if it created the endpoint list, otherwise null if it already exists
|
||||
*/
|
||||
public createEndpointList = async (): Promise<ExceptionListSchema | null> => {
|
||||
const { savedObjectsClient, user } = this;
|
||||
return createEndpointList({
|
||||
savedObjectsClient,
|
||||
user,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as "createListItem" except it applies specifically to the agnostic endpoint list and will
|
||||
* auto-call the "createEndpointList" for you so that you have the best chance of the agnostic endpoint
|
||||
* being there and existing before the item is inserted into the agnostic endpoint list.
|
||||
*/
|
||||
public createEndpointListItem = async ({
|
||||
_tags,
|
||||
comments,
|
||||
description,
|
||||
entries,
|
||||
itemId,
|
||||
meta,
|
||||
name,
|
||||
tags,
|
||||
type,
|
||||
}: CreateEndpointListItemOptions): Promise<ExceptionListItemSchema> => {
|
||||
const { savedObjectsClient, user } = this;
|
||||
await this.createEndpointList();
|
||||
return createExceptionListItem({
|
||||
_tags,
|
||||
comments,
|
||||
description,
|
||||
entries,
|
||||
itemId,
|
||||
listId: ENDPOINT_LIST_ID,
|
||||
meta,
|
||||
name,
|
||||
namespaceType: 'agnostic',
|
||||
savedObjectsClient,
|
||||
tags,
|
||||
type,
|
||||
user,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as "updateListItem" except it applies specifically to the endpoint list and will
|
||||
* auto-call the "createEndpointList" for you so that you have the best chance of the endpoint
|
||||
* being there if it did not exist before. If the list did not exist before, then creating it here will still cause a
|
||||
* return of null but at least the list exists again.
|
||||
*/
|
||||
public updateEndpointListItem = async ({
|
||||
_tags,
|
||||
comments,
|
||||
description,
|
||||
entries,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
name,
|
||||
tags,
|
||||
type,
|
||||
}: UpdateEndpointListItemOptions): Promise<ExceptionListItemSchema | null> => {
|
||||
const { savedObjectsClient, user } = this;
|
||||
await this.createEndpointList();
|
||||
return updateExceptionListItem({
|
||||
_tags,
|
||||
comments,
|
||||
description,
|
||||
entries,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
name,
|
||||
namespaceType: 'agnostic',
|
||||
savedObjectsClient,
|
||||
tags,
|
||||
type,
|
||||
user,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as "getExceptionListItem" except it applies specifically to the endpoint list.
|
||||
*/
|
||||
public getEndpointListItem = async ({
|
||||
itemId,
|
||||
id,
|
||||
}: GetEndpointListItemOptions): Promise<ExceptionListItemSchema | null> => {
|
||||
const { savedObjectsClient } = this;
|
||||
return getExceptionListItem({ id, itemId, namespaceType: 'agnostic', savedObjectsClient });
|
||||
};
|
||||
|
||||
public createExceptionList = async ({
|
||||
_tags,
|
||||
description,
|
||||
|
@ -209,6 +313,22 @@ export class ExceptionListClient {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as "deleteExceptionListItem" except it applies specifically to the endpoint list.
|
||||
*/
|
||||
public deleteEndpointListItem = async ({
|
||||
id,
|
||||
itemId,
|
||||
}: DeleteEndpointListItemOptions): Promise<ExceptionListItemSchema | null> => {
|
||||
const { savedObjectsClient } = this;
|
||||
return deleteExceptionListItem({
|
||||
id,
|
||||
itemId,
|
||||
namespaceType: 'agnostic',
|
||||
savedObjectsClient,
|
||||
});
|
||||
};
|
||||
|
||||
public findExceptionListItem = async ({
|
||||
listId,
|
||||
filter,
|
||||
|
@ -272,4 +392,33 @@ export class ExceptionListClient {
|
|||
sortOrder,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the same as "findExceptionList" except it applies specifically to the endpoint list and will
|
||||
* auto-call the "createEndpointList" for you so that you have the best chance of the endpoint
|
||||
* being there if it did not exist before. If the list did not exist before, then creating it here should give you
|
||||
* a good guarantee that you will get an empty record set rather than null. I keep the null as the return value in
|
||||
* the off chance that you still might somehow not get into a race condition where the endpoint list does
|
||||
* not exist because someone deleted it in-between the initial create and then the find.
|
||||
*/
|
||||
public findEndpointListItem = async ({
|
||||
filter,
|
||||
perPage,
|
||||
page,
|
||||
sortField,
|
||||
sortOrder,
|
||||
}: FindEndpointListItemOptions): Promise<FoundExceptionListItemSchema | null> => {
|
||||
const { savedObjectsClient } = this;
|
||||
await this.createEndpointList();
|
||||
return findExceptionListItem({
|
||||
filter,
|
||||
listId: ENDPOINT_LIST_ID,
|
||||
namespaceType: 'agnostic',
|
||||
page,
|
||||
perPage,
|
||||
savedObjectsClient,
|
||||
sortField,
|
||||
sortOrder,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -86,12 +86,22 @@ export interface DeleteExceptionListItemOptions {
|
|||
namespaceType: NamespaceType;
|
||||
}
|
||||
|
||||
export interface DeleteEndpointListItemOptions {
|
||||
id: IdOrUndefined;
|
||||
itemId: ItemIdOrUndefined;
|
||||
}
|
||||
|
||||
export interface GetExceptionListItemOptions {
|
||||
itemId: ItemIdOrUndefined;
|
||||
id: IdOrUndefined;
|
||||
namespaceType: NamespaceType;
|
||||
}
|
||||
|
||||
export interface GetEndpointListItemOptions {
|
||||
itemId: ItemIdOrUndefined;
|
||||
id: IdOrUndefined;
|
||||
}
|
||||
|
||||
export interface CreateExceptionListItemOptions {
|
||||
_tags: _Tags;
|
||||
comments: CreateCommentsArray;
|
||||
|
@ -106,6 +116,18 @@ export interface CreateExceptionListItemOptions {
|
|||
type: ExceptionListItemType;
|
||||
}
|
||||
|
||||
export interface CreateEndpointListItemOptions {
|
||||
_tags: _Tags;
|
||||
comments: CreateCommentsArray;
|
||||
entries: EntriesArray;
|
||||
itemId: ItemId;
|
||||
name: Name;
|
||||
description: Description;
|
||||
meta: MetaOrUndefined;
|
||||
tags: Tags;
|
||||
type: ExceptionListItemType;
|
||||
}
|
||||
|
||||
export interface UpdateExceptionListItemOptions {
|
||||
_tags: _TagsOrUndefined;
|
||||
comments: UpdateCommentsArray;
|
||||
|
@ -120,6 +142,19 @@ export interface UpdateExceptionListItemOptions {
|
|||
type: ExceptionListItemTypeOrUndefined;
|
||||
}
|
||||
|
||||
export interface UpdateEndpointListItemOptions {
|
||||
_tags: _TagsOrUndefined;
|
||||
comments: UpdateCommentsArray;
|
||||
entries: EntriesArrayOrUndefined;
|
||||
id: IdOrUndefined;
|
||||
itemId: ItemIdOrUndefined;
|
||||
name: NameOrUndefined;
|
||||
description: DescriptionOrUndefined;
|
||||
meta: MetaOrUndefined;
|
||||
tags: TagsOrUndefined;
|
||||
type: ExceptionListItemTypeOrUndefined;
|
||||
}
|
||||
|
||||
export interface FindExceptionListItemOptions {
|
||||
listId: ListId;
|
||||
namespaceType: NamespaceType;
|
||||
|
@ -130,6 +165,14 @@ export interface FindExceptionListItemOptions {
|
|||
sortOrder: SortOrderOrUndefined;
|
||||
}
|
||||
|
||||
export interface FindEndpointListItemOptions {
|
||||
filter: FilterOrUndefined;
|
||||
perPage: PerPageOrUndefined;
|
||||
page: PageOrUndefined;
|
||||
sortField: SortFieldOrUndefined;
|
||||
sortOrder: SortOrderOrUndefined;
|
||||
}
|
||||
|
||||
export interface FindExceptionListsItemOptions {
|
||||
listId: NonEmptyStringArrayDecoded;
|
||||
namespaceType: NamespaceTypeArray;
|
||||
|
|
|
@ -48,7 +48,7 @@ export const findExceptionList = async ({
|
|||
sortOrder,
|
||||
type: savedObjectType,
|
||||
});
|
||||
return transformSavedObjectsToFoundExceptionList({ namespaceType, savedObjectsFindResponse });
|
||||
return transformSavedObjectsToFoundExceptionList({ savedObjectsFindResponse });
|
||||
};
|
||||
|
||||
export const getExceptionListFilter = ({
|
||||
|
|
|
@ -35,7 +35,7 @@ export const getExceptionList = async ({
|
|||
if (id != null) {
|
||||
try {
|
||||
const savedObject = await savedObjectsClient.get<ExceptionListSoSchema>(savedObjectType, id);
|
||||
return transformSavedObjectToExceptionList({ namespaceType, savedObject });
|
||||
return transformSavedObjectToExceptionList({ savedObject });
|
||||
} catch (err) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
|
||||
return null;
|
||||
|
@ -55,7 +55,6 @@ export const getExceptionList = async ({
|
|||
});
|
||||
if (savedObject.saved_objects[0] != null) {
|
||||
return transformSavedObjectToExceptionList({
|
||||
namespaceType,
|
||||
savedObject: savedObject.saved_objects[0],
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -69,6 +69,6 @@ export const updateExceptionList = async ({
|
|||
updated_by: user,
|
||||
}
|
||||
);
|
||||
return transformSavedObjectUpdateToExceptionList({ exceptionList, namespaceType, savedObject });
|
||||
return transformSavedObjectUpdateToExceptionList({ exceptionList, savedObject });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -93,7 +93,6 @@ export const updateExceptionListItem = async ({
|
|||
);
|
||||
return transformSavedObjectUpdateToExceptionListItem({
|
||||
exceptionListItem,
|
||||
namespaceType,
|
||||
savedObject,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -67,10 +67,8 @@ export const getSavedObjectTypes = ({
|
|||
|
||||
export const transformSavedObjectToExceptionList = ({
|
||||
savedObject,
|
||||
namespaceType,
|
||||
}: {
|
||||
savedObject: SavedObject<ExceptionListSoSchema>;
|
||||
namespaceType: NamespaceType;
|
||||
}): ExceptionListSchema => {
|
||||
const dateNow = new Date().toISOString();
|
||||
const {
|
||||
|
@ -102,7 +100,7 @@ export const transformSavedObjectToExceptionList = ({
|
|||
list_id,
|
||||
meta,
|
||||
name,
|
||||
namespace_type: namespaceType,
|
||||
namespace_type: getExceptionListType({ savedObjectType: savedObject.type }),
|
||||
tags,
|
||||
tie_breaker_id,
|
||||
type: exceptionListType.is(type) ? type : 'detection',
|
||||
|
@ -114,11 +112,9 @@ export const transformSavedObjectToExceptionList = ({
|
|||
export const transformSavedObjectUpdateToExceptionList = ({
|
||||
exceptionList,
|
||||
savedObject,
|
||||
namespaceType,
|
||||
}: {
|
||||
exceptionList: ExceptionListSchema;
|
||||
savedObject: SavedObjectsUpdateResponse<ExceptionListSoSchema>;
|
||||
namespaceType: NamespaceType;
|
||||
}): ExceptionListSchema => {
|
||||
const dateNow = new Date().toISOString();
|
||||
const {
|
||||
|
@ -138,7 +134,7 @@ export const transformSavedObjectUpdateToExceptionList = ({
|
|||
list_id: exceptionList.list_id,
|
||||
meta: meta ?? exceptionList.meta,
|
||||
name: name ?? exceptionList.name,
|
||||
namespace_type: namespaceType,
|
||||
namespace_type: getExceptionListType({ savedObjectType: savedObject.type }),
|
||||
tags: tags ?? exceptionList.tags,
|
||||
tie_breaker_id: exceptionList.tie_breaker_id,
|
||||
type: exceptionListType.is(type) ? type : exceptionList.type,
|
||||
|
@ -200,11 +196,9 @@ export const transformSavedObjectToExceptionListItem = ({
|
|||
export const transformSavedObjectUpdateToExceptionListItem = ({
|
||||
exceptionListItem,
|
||||
savedObject,
|
||||
namespaceType,
|
||||
}: {
|
||||
exceptionListItem: ExceptionListItemSchema;
|
||||
savedObject: SavedObjectsUpdateResponse<ExceptionListSoSchema>;
|
||||
namespaceType: NamespaceType;
|
||||
}): ExceptionListItemSchema => {
|
||||
const dateNow = new Date().toISOString();
|
||||
const {
|
||||
|
@ -239,7 +233,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
|
|||
list_id: exceptionListItem.list_id,
|
||||
meta: meta ?? exceptionListItem.meta,
|
||||
name: name ?? exceptionListItem.name,
|
||||
namespace_type: namespaceType,
|
||||
namespace_type: getExceptionListType({ savedObjectType: savedObject.type }),
|
||||
tags: tags ?? exceptionListItem.tags,
|
||||
tie_breaker_id: exceptionListItem.tie_breaker_id,
|
||||
type: exceptionListItemType.is(type) ? type : exceptionListItem.type,
|
||||
|
@ -265,14 +259,12 @@ export const transformSavedObjectsToFoundExceptionListItem = ({
|
|||
|
||||
export const transformSavedObjectsToFoundExceptionList = ({
|
||||
savedObjectsFindResponse,
|
||||
namespaceType,
|
||||
}: {
|
||||
savedObjectsFindResponse: SavedObjectsFindResponse<ExceptionListSoSchema>;
|
||||
namespaceType: NamespaceType;
|
||||
}): FoundExceptionListSchema => {
|
||||
return {
|
||||
data: savedObjectsFindResponse.saved_objects.map((savedObject) =>
|
||||
transformSavedObjectToExceptionList({ namespaceType, savedObject })
|
||||
transformSavedObjectToExceptionList({ savedObject })
|
||||
),
|
||||
page: savedObjectsFindResponse.page,
|
||||
per_page: savedObjectsFindResponse.per_page,
|
||||
|
|
|
@ -55,6 +55,10 @@ export const addPrepackedRulesRoute = (
|
|||
if (!siemClient || !alertsClient) {
|
||||
return siemResponse.error({ statusCode: 404 });
|
||||
}
|
||||
|
||||
// This will create the endpoint list if it does not exist yet
|
||||
await context.lists?.getExceptionListClient().createEndpointList();
|
||||
|
||||
const rulesFromFileSystem = getPrepackagedRules();
|
||||
const prepackagedRules = await getExistingPrepackagedRules({ alertsClient });
|
||||
const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackagedRules);
|
||||
|
|
|
@ -97,7 +97,6 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void
|
|||
// TODO: Fix these either with an is conversion or by better typing them within io-ts
|
||||
const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[];
|
||||
const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[];
|
||||
|
||||
const alertsClient = context.alerting?.getAlertsClient();
|
||||
const clusterClient = context.core.elasticsearch.legacy.client;
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
|
@ -127,6 +126,8 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void
|
|||
});
|
||||
}
|
||||
}
|
||||
// This will create the endpoint list if it does not exist yet
|
||||
await context.lists?.getExceptionListClient().createEndpointList();
|
||||
|
||||
const createdRule = await createRules({
|
||||
alertsClient,
|
||||
|
|
Loading…
Reference in a new issue