Skip to content

Commit

Permalink
[Endpoint] Encode the index of the alert in the id response (#66919) (#…
Browse files Browse the repository at this point in the history
…66949)

* Encode the index of the alert in the id response

* Fixing tests

* Adding missed test
  • Loading branch information
jonathan-buttner authored May 19, 2020
1 parent 3dbe9dc commit 48e049e
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 25 deletions.
4 changes: 0 additions & 4 deletions x-pack/plugins/endpoint/common/alert_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ export class AlertConstants {
* The path for the Alert's Index Pattern API.
*/
static INDEX_PATTERN_ROUTE = `${AlertConstants.BASE_API_URL}/index_pattern`;
/**
* Alert's Index pattern
*/
static ALERT_INDEX_NAME = 'events-endpoint-1';
/**
* A paramter passed to Alert's Index Pattern.
*/
Expand Down
18 changes: 12 additions & 6 deletions x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import { GetResponse } from 'elasticsearch';
import { KibanaRequest, RequestHandler } from 'kibana/server';
import { AlertEvent } from '../../../../common/types';
import { AlertConstants } from '../../../../common/alert_constants';
import { EndpointAppContext } from '../../../types';
import { AlertDetailsRequestParams } from '../types';
import { AlertDetailsPagination } from './lib';
import { getHostData } from '../../metadata';
import { AlertId, AlertIdError } from '../lib';

export const alertDetailsHandlerWrapper = function(
endpointAppContext: EndpointAppContext
Expand All @@ -21,10 +21,10 @@ export const alertDetailsHandlerWrapper = function(
res
) => {
try {
const alertId = req.params.id;
const alertId = AlertId.fromEncoded(req.params.id);
const response = (await ctx.core.elasticsearch.dataClient.callAsCurrentUser('get', {
index: AlertConstants.ALERT_INDEX_NAME,
id: alertId,
index: alertId.index,
id: alertId.id,
})) as GetResponse<AlertEvent>;

const indexPattern = await endpointAppContext.service
Expand All @@ -50,7 +50,7 @@ export const alertDetailsHandlerWrapper = function(

return res.ok({
body: {
id: response._id,
id: alertId.toString(),
...response._source,
state: {
host_metadata: currentHostInfo?.metadata,
Expand All @@ -60,7 +60,13 @@ export const alertDetailsHandlerWrapper = function(
},
});
} catch (err) {
if (err.status === 404) {
const logger = endpointAppContext.logFactory.get('alerts');
logger.warn(err);

// err will be an AlertIdError if the passed in alert id is not valid
if (err instanceof AlertIdError) {
return res.badRequest({ body: err });
} else if (err.status === 404) {
return res.notFound({ body: err });
}
return res.internalError({ body: err });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { GetResponse, SearchResponse } from 'elasticsearch';
import { AlertEvent, AlertHits, AlertAPIOrdering } from '../../../../../common/types';
import { AlertConstants } from '../../../../../common/alert_constants';
import { EndpointConfigType } from '../../../../config';
import { searchESForAlerts, Pagination } from '../../lib';
import { searchESForAlerts, Pagination, AlertId } from '../../lib';
import { AlertSearchQuery, SearchCursor, AlertDetailsRequestParams } from '../../types';
import { BASE_ALERTS_ROUTE } from '../..';
import { RequestHandlerContext } from '../../../../../../../../src/core/server';
Expand Down Expand Up @@ -59,7 +59,8 @@ export class AlertDetailsPagination extends Pagination<

protected getUrlFromHits(hits: AlertHits): string | null {
if (hits.length > 0) {
return `${BASE_ALERTS_ROUTE}/${hits[0]._id}`;
const id = new AlertId(hits[0]._index, hits[0]._id);
return `${BASE_ALERTS_ROUTE}/${id.toString()}`;
}
return null;
}
Expand Down
48 changes: 48 additions & 0 deletions x-pack/plugins/endpoint/server/routes/alerts/lib/alert_id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { AlertIdError } from './error';

/**
* Abstraction over alert IDs.
*/
export class AlertId {
protected readonly _index: string;
protected readonly _id: string;

constructor(index: string, id: string) {
this._index = index;
this._id = id;
}

public get index() {
return this._index;
}

public get id() {
return this._id;
}

static fromEncoded(encoded: string): AlertId {
try {
const value = encoded.replace(/\-/g, '+').replace(/_/g, '/');
const data = Buffer.from(value, 'base64').toString('utf8');
const { index, id } = JSON.parse(data);
return new AlertId(index, id);
} catch (error) {
throw new AlertIdError(`Unable to decode alert id: ${encoded}`);
}
}

toString(): string {
const value = JSON.stringify({ index: this.index, id: this.id });
// replace invalid URL characters with valid ones
return Buffer.from(value, 'utf8')
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '');
}
}
11 changes: 11 additions & 0 deletions x-pack/plugins/endpoint/server/routes/alerts/lib/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.
*/

export class AlertIdError extends Error {
constructor(message: string) {
super(message);
}
}
2 changes: 2 additions & 0 deletions x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
UndefinedResultPosition,
} from '../types';

export { AlertIdError } from './error';
export { Pagination } from './pagination';
export { AlertId } from './alert_id';

function reverseSortDirection(order: AlertAPIOrdering): AlertAPIOrdering {
if (order === 'asc') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AlertConstants } from '../../../../../common/alert_constants';
import { EndpointAppContext } from '../../../../types';
import { AlertSearchQuery } from '../../types';
import { AlertListPagination } from './pagination';
import { AlertId } from '../../lib';

export const getRequestData = async (
request: KibanaRequest<unknown, AlertingIndexGetQueryResult, unknown>,
Expand Down Expand Up @@ -105,8 +106,9 @@ export async function mapToAlertResultList(
const pagination: AlertListPagination = new AlertListPagination(config, reqCtx, reqData, hits);

function mapHit(entry: AlertHits[0]): AlertData {
const alertId = new AlertId(entry._index, entry._id);
return {
id: entry._id,
id: alertId.toString(),
...entry._source,
prev: null,
next: null,
Expand Down
35 changes: 23 additions & 12 deletions x-pack/test/api_integration/apis/endpoint/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import expect from '@kbn/expect/expect.js';
import { FtrProviderContext } from '../../ftr_provider_context';
import { AlertData } from '../../../../plugins/endpoint/common/types';
import { AlertId } from '../../../../plugins/endpoint/server/routes/alerts/lib/index';

/**
* The number of alert documents in the es archive.
Expand Down Expand Up @@ -65,6 +66,7 @@ export default function({ getService }: FtrProviderContext) {
const nextPrevPrefixOrder = 'order=desc';
const nextPrevPrefixPageSize = 'page_size=10';
const nextPrevPrefix = `${nextPrevPrefixQuery}&${nextPrevPrefixDateRange}&${nextPrevPrefixSort}&${nextPrevPrefixOrder}&${nextPrevPrefixPageSize}`;
const alertIndex = 'events-endpoint-1';

let nullableEventId = '';

Expand All @@ -74,7 +76,7 @@ export default function({ getService }: FtrProviderContext) {
await esArchiver.load('endpoint/alerts/api_feature');
await esArchiver.load('endpoint/alerts/host_api_feature');
const res = await es.search({
index: 'events-endpoint-1',
index: alertIndex,
body: ES_QUERY_MISSING,
});
nullableEventId = res.hits.hits[0]._source.event.id;
Expand Down Expand Up @@ -377,36 +379,45 @@ export default function({ getService }: FtrProviderContext) {
});

it('should return alert details by id, getting last alert', async () => {
const documentID = 'zbNm0HABdD75WLjLYgcB';
const prevDocumentID = '2rNm0HABdD75WLjLYgcU';
const documentID = new AlertId(alertIndex, 'zbNm0HABdD75WLjLYgcB');
const prevDocumentID = new AlertId(alertIndex, '2rNm0HABdD75WLjLYgcU');
const { body } = await supertest
.get(`/api/endpoint/alerts/${documentID}`)
.get(`/api/endpoint/alerts/${documentID.toString()}`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.id).to.eql(documentID);
expect(body.prev).to.eql(`/api/endpoint/alerts/${prevDocumentID}`);
expect(body.id).to.eql(documentID.toString());
expect(body.prev).to.eql(`/api/endpoint/alerts/${prevDocumentID.toString()}`);
expect(body.next).to.eql(null); // last alert, no more beyond this
expect(body.state.host_metadata.host.id).to.eql(body.host.id);
});

it('should return alert details by id, getting first alert', async () => {
const documentID = 'p7Nm0HABdD75WLjLYghv';
const nextDocumentID = 'mbNm0HABdD75WLjLYgho';
const documentID = new AlertId(alertIndex, 'p7Nm0HABdD75WLjLYghv');
const nextDocumentID = new AlertId(alertIndex, 'mbNm0HABdD75WLjLYgho');
const { body } = await supertest
.get(`/api/endpoint/alerts/${documentID}`)
.get(`/api/endpoint/alerts/${documentID.toString()}`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.id).to.eql(documentID);
expect(body.next).to.eql(`/api/endpoint/alerts/${nextDocumentID}`);
expect(body.id).to.eql(documentID.toString());
expect(body.next).to.eql(`/api/endpoint/alerts/${nextDocumentID.toString()}`);
expect(body.prev).to.eql(null); // first alert, no more before this
});

it('should return 404 when alert is not found', async () => {
const documentID = new AlertId(alertIndex, 'does-not-exit');

await supertest
.get('/api/endpoint/alerts/does-not-exist')
.get(`/api/endpoint/alerts/${documentID.toString()}`)
.set('kbn-xsrf', 'xxx')
.expect(404);
});

it('should return 400 when alert id is not valid', async () => {
await supertest
.get('/api/endpoint/alerts/does-not-exist')
.set('kbn-xsrf', 'xxx')
.expect(400);
});
});
});
}

0 comments on commit 48e049e

Please sign in to comment.