From 8398a1c77cde0e190126ff13804d182fda96e74d Mon Sep 17 00:00:00 2001 From: Dan Panzarella Date: Thu, 30 Jan 2020 14:25:56 -0500 Subject: [PATCH] [Endpoint] Add Endpoint Details route (#55746) * Add Endpoint Details route * add Endpoint Details tests * sacrifices to the Type gods * update to latest endpoint schema Co-authored-by: Elastic Machine --- .../endpoint/server/routes/endpoints.test.ts | 78 +++++++++++++++++++ .../endpoint/server/routes/endpoints.ts | 32 +++++++- .../endpoint/endpoint_query_builders.test.ts | 32 +++++++- .../endpoint/endpoint_query_builders.ts | 24 ++++++ 4 files changed, 162 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.test.ts b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts index 04a38972401ed..be14554f128c3 100644 --- a/x-pack/plugins/endpoint/server/routes/endpoints.test.ts +++ b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts @@ -120,4 +120,82 @@ describe('test endpoint route', () => { expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); }); + + describe('Endpoint Details route', () => { + it('should return 404 on no results', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } }); + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => + Promise.resolve({ + took: 3, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 9, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + }) + ); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/endpoints') + )!; + + await routeHandler( + ({ + core: { + elasticsearch: { + dataClient: mockScopedClient, + }, + }, + } as unknown) as RequestHandlerContext, + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.notFound).toBeCalled(); + const message = mockResponse.notFound.mock.calls[0][0]?.body; + expect(message).toEqual('Endpoint Not Found'); + }); + + it('should return a single endpoint', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ + params: { id: (data as any).hits.hits[0]._id }, + }); + const response: SearchResponse = (data as unknown) as SearchResponse< + EndpointMetadata + >; + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/endpoints') + )!; + + await routeHandler( + ({ + core: { + elasticsearch: { + dataClient: mockScopedClient, + }, + }, + } as unknown) as RequestHandlerContext, + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.ok).toBeCalled(); + const result = mockResponse.ok.mock.calls[0][0]?.body as EndpointMetadata; + expect(result).toHaveProperty('endpoint'); + }); + }); }); diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.ts b/x-pack/plugins/endpoint/server/routes/endpoints.ts index 4fc3e653f9426..24ad8e3941f5e 100644 --- a/x-pack/plugins/endpoint/server/routes/endpoints.ts +++ b/x-pack/plugins/endpoint/server/routes/endpoints.ts @@ -8,7 +8,10 @@ import { IRouter } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { schema } from '@kbn/config-schema'; -import { kibanaRequestToEndpointListQuery } from '../services/endpoint/endpoint_query_builders'; +import { + kibanaRequestToEndpointListQuery, + kibanaRequestToEndpointFetchQuery, +} from '../services/endpoint/endpoint_query_builders'; import { EndpointMetadata, EndpointResultList } from '../../common/types'; import { EndpointAppContext } from '../types'; @@ -51,6 +54,33 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp } } ); + + router.get( + { + path: '/api/endpoint/endpoints/{id}', + validate: { + params: schema.object({ id: schema.string() }), + }, + options: { authRequired: true }, + }, + async (context, req, res) => { + try { + const query = kibanaRequestToEndpointFetchQuery(req, endpointAppContext); + const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( + 'search', + query + )) as SearchResponse; + + if (response.hits.hits.length === 0) { + return res.notFound({ body: 'Endpoint Not Found' }); + } + + return res.ok({ body: response.hits.hits[0]._source }); + } catch (err) { + return res.internalError({ body: err }); + } + } + ); } function mapToEndpointResultList( diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts index 3c931a251d697..e453f777fbd50 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts @@ -5,10 +5,13 @@ */ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; import { EndpointConfigSchema } from '../../config'; -import { kibanaRequestToEndpointListQuery } from './endpoint_query_builders'; +import { + kibanaRequestToEndpointListQuery, + kibanaRequestToEndpointFetchQuery, +} from './endpoint_query_builders'; -describe('test query builder', () => { - describe('test query builder request processing', () => { +describe('query builder', () => { + describe('EndpointListQuery', () => { it('test default query params for all endpoints when no params or body is provided', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {}, @@ -51,4 +54,27 @@ describe('test query builder', () => { } as Record); }); }); + + describe('EndpointFetchQuery', () => { + it('searches for the correct ID', () => { + const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; + const mockRequest = httpServerMock.createKibanaRequest({ + params: { + id: mockID, + }, + }); + const query = kibanaRequestToEndpointFetchQuery(mockRequest, { + logFactory: loggingServiceMock.create(), + config: () => Promise.resolve(EndpointConfigSchema.validate({})), + }); + expect(query).toEqual({ + body: { + query: { match: { 'host.id.keyword': mockID } }, + sort: [{ 'event.created': { order: 'desc' } }], + size: 1, + }, + index: 'endpoint-agent*', + }); + }); + }); }); diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts index 102c268cf9ec4..b4f295a64b6ea 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts @@ -65,3 +65,27 @@ async function getPagingProperties( pageIndex: pagingProperties.page_index || config.endpointResultListDefaultFirstPageIndex, }; } + +export const kibanaRequestToEndpointFetchQuery = ( + request: KibanaRequest, + endpointAppContext: EndpointAppContext +) => { + return { + body: { + query: { + match: { + 'host.id.keyword': request.params.id, + }, + }, + sort: [ + { + 'event.created': { + order: 'desc', + }, + }, + ], + size: 1, + }, + index: EndpointAppConstants.ENDPOINT_INDEX_NAME, + }; +};