[Security Solution] [Resolver] Remove related events api (#79036)

* Removing old related events route

* Removing outer describe block

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Jonathan Buttner 2020-10-12 13:22:33 -04:00 committed by GitHub
parent bc6ba87330
commit 591585df17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 122 additions and 703 deletions

View file

@ -23,23 +23,6 @@ export const validateTree = {
}),
};
/**
* Used to validate GET requests for non process events for a specific event.
*/
export const validateRelatedEvents = {
params: schema.object({ id: schema.string({ minLength: 1 }) }),
query: schema.object({
events: schema.number({ defaultValue: 1000, min: 1, max: 10000 }),
afterEvent: schema.maybe(schema.string()),
legacyEndpointID: schema.maybe(schema.string({ minLength: 1 })),
}),
body: schema.nullable(
schema.object({
filter: schema.maybe(schema.string()),
})
),
};
/**
* Used to validate POST requests for `/resolver/events` api.
*/

View file

@ -197,7 +197,6 @@ export interface SafeResolverTree {
*/
entityID: string;
children: SafeResolverChildren;
relatedEvents: Omit<SafeResolverRelatedEvents, 'entityID'>;
relatedAlerts: Omit<ResolverRelatedAlerts, 'entityID'>;
ancestry: SafeResolverAncestry;
lifecycle: SafeResolverEvent[];
@ -267,15 +266,6 @@ export interface ResolverRelatedEvents {
nextEvent: string | null;
}
/**
* Safe version of `ResolverRelatedEvents`
*/
export interface SafeResolverRelatedEvents {
entityID: string;
events: SafeResolverEvent[];
nextEvent: string | null;
}
/**
* Response structure for the events route.
* `nextEvent` will be set to null when at the time of querying there were no more results to retrieve from ES.

View file

@ -8,14 +8,12 @@ import { IRouter } from 'kibana/server';
import { EndpointAppContext } from '../types';
import {
validateTree,
validateRelatedEvents,
validateEvents,
validateChildren,
validateAncestry,
validateAlerts,
validateEntities,
} from '../../../common/endpoint/schema/resolver';
import { handleRelatedEvents } from './resolver/related_events';
import { handleChildren } from './resolver/children';
import { handleAncestry } from './resolver/ancestry';
import { handleTree } from './resolver/tree';
@ -26,17 +24,6 @@ import { handleEvents } from './resolver/events';
export function registerResolverRoutes(router: IRouter, endpointAppContext: EndpointAppContext) {
const log = endpointAppContext.logFactory.get('resolver');
// this route will be removed in favor of the one below
router.post(
{
// @deprecated use `/resolver/events` instead
path: '/api/endpoint/resolver/{id}/events',
validate: validateRelatedEvents,
options: { authRequired: true },
},
handleRelatedEvents(log, endpointAppContext)
);
router.post(
{
path: '/api/endpoint/resolver/events',

View file

@ -1,35 +0,0 @@
/*
* 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.
*/
/**
* @deprecated use the `events.ts` file's query instead
*/
import { EventsQuery } from './related_events';
import { PaginationBuilder } from '../utils/pagination';
import { legacyEventIndexPattern } from './legacy_event_index_pattern';
describe('Events query', () => {
it('constructs a legacy multi search query', () => {
const query = new EventsQuery(new PaginationBuilder(1), 'index-pattern', 'endpointID');
// using any here because otherwise ts complains that it doesn't know what bool and filter are
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const msearch: any = query.buildMSearch('1234');
expect(msearch[0].index).toBe(legacyEventIndexPattern);
expect(msearch[1].query.bool.filter[0]).toStrictEqual({
terms: { 'endgame.unique_pid': ['1234'] },
});
});
it('constructs a non-legacy multi search query', () => {
const query = new EventsQuery(new PaginationBuilder(1), 'index-pattern');
// using any here because otherwise ts complains that it doesn't know what bool and filter are
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const msearch: any = query.buildMSearch(['1234', '5678']);
expect(msearch[0].index).toBe('index-pattern');
expect(msearch[1].query.bool.filter[0]).toStrictEqual({
terms: { 'process.entity_id': ['1234', '5678'] },
});
});
});

View file

@ -1,92 +0,0 @@
/*
* 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.
*/
/**
* @deprecated use the `events.ts` file's query instead
*/
import { SearchResponse } from 'elasticsearch';
import { esKuery } from '../../../../../../../../src/plugins/data/server';
import { SafeResolverEvent } from '../../../../../common/endpoint/types';
import { ResolverQuery } from './base';
import { PaginationBuilder } from '../utils/pagination';
import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/common';
/**
* Builds a query for retrieving related events for a node.
*/
export class EventsQuery extends ResolverQuery<SafeResolverEvent[]> {
private readonly kqlQuery: JsonObject[] = [];
constructor(
private readonly pagination: PaginationBuilder,
indexPattern: string | string[],
endpointID?: string,
kql?: string
) {
super(indexPattern, endpointID);
if (kql) {
this.kqlQuery.push(esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kql)));
}
}
protected legacyQuery(endpointID: string, uniquePIDs: string[]): JsonObject {
return {
query: {
bool: {
filter: [
...this.kqlQuery,
{
terms: { 'endgame.unique_pid': uniquePIDs },
},
{
term: { 'agent.id': endpointID },
},
{
term: { 'event.kind': 'event' },
},
{
bool: {
must_not: {
term: { 'event.category': 'process' },
},
},
},
],
},
},
...this.pagination.buildQueryFields('endgame.serial_event_id', 'desc'),
};
}
protected query(entityIDs: string[]): JsonObject {
return {
query: {
bool: {
filter: [
...this.kqlQuery,
{
terms: { 'process.entity_id': entityIDs },
},
{
term: { 'event.kind': 'event' },
},
{
bool: {
must_not: {
term: { 'event.category': 'process' },
},
},
},
],
},
},
...this.pagination.buildQueryFields('event.id', 'desc'),
};
}
formatResponse(response: SearchResponse<SafeResolverEvent>): SafeResolverEvent[] {
return this.getResults(response);
}
}

View file

@ -1,44 +0,0 @@
/*
* 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.
*/
/**
* @deprecated use the `resolver/events` route and handler instead
*/
import { TypeOf } from '@kbn/config-schema';
import { RequestHandler, Logger } from 'kibana/server';
import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants';
import { validateRelatedEvents } from '../../../../common/endpoint/schema/resolver';
import { Fetcher } from './utils/fetch';
import { EndpointAppContext } from '../../types';
export function handleRelatedEvents(
log: Logger,
endpointAppContext: EndpointAppContext
): RequestHandler<
TypeOf<typeof validateRelatedEvents.params>,
TypeOf<typeof validateRelatedEvents.query>,
TypeOf<typeof validateRelatedEvents.body>
> {
return async (context, req, res) => {
const {
params: { id },
query: { events, afterEvent, legacyEndpointID: endpointID },
body,
} = req;
try {
const client = context.core.elasticsearch.legacy.client;
const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID);
return res.ok({
body: await fetcher.events(events, afterEvent, body?.filter),
});
} catch (err) {
log.warn(err);
return res.internalError({ body: err });
}
};
}

View file

@ -1,96 +0,0 @@
/*
* 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.
*/
/**
* @deprecated msearch functionality for querying events will be removed shortly
*/
import { SearchResponse } from 'elasticsearch';
import { ILegacyScopedClusterClient } from 'kibana/server';
import { SafeResolverRelatedEvents, SafeResolverEvent } from '../../../../../common/endpoint/types';
import { createRelatedEvents } from './node';
import { EventsQuery } from '../queries/related_events';
import { PaginationBuilder } from './pagination';
import { QueryInfo } from '../queries/multi_searcher';
import { SingleQueryHandler } from './fetch';
/**
* Parameters for the RelatedEventsQueryHandler
*/
export interface RelatedEventsParams {
limit: number;
entityID: string;
indexPattern: string;
after?: string;
legacyEndpointID?: string;
filter?: string;
}
/**
* This retrieves the related events for the origin node of a resolver tree.
*/
export class RelatedEventsQueryHandler implements SingleQueryHandler<SafeResolverRelatedEvents> {
private relatedEvents: SafeResolverRelatedEvents | undefined;
private readonly query: EventsQuery;
private readonly limit: number;
private readonly entityID: string;
constructor(options: RelatedEventsParams) {
this.limit = options.limit;
this.entityID = options.entityID;
this.query = new EventsQuery(
PaginationBuilder.createBuilder(this.limit, options.after),
options.indexPattern,
options.legacyEndpointID,
options.filter
);
}
private handleResponse = (response: SearchResponse<SafeResolverEvent>) => {
const results = this.query.formatResponse(response);
this.relatedEvents = createRelatedEvents(
this.entityID,
results,
PaginationBuilder.buildCursorRequestLimit(this.limit, results)
);
};
/**
* Get a query to use in a msearch.
*/
nextQuery(): QueryInfo | undefined {
if (this.getResults()) {
return;
}
return {
query: this.query,
ids: this.entityID,
handler: this.handleResponse,
};
}
/**
* Get the results after an msearch.
*/
getResults() {
return this.relatedEvents;
}
/**
* Perform a normal search and return the related events results.
*
* @param client the elasticsearch client
*/
async search(client: ILegacyScopedClusterClient) {
const results = this.getResults();
if (results) {
return results;
}
this.handleResponse(await this.query.search(client, this.entityID));
return this.getResults() ?? createRelatedEvents(this.entityID);
}
}

View file

@ -7,7 +7,6 @@
import { ILegacyScopedClusterClient } from 'kibana/server';
import {
SafeResolverChildren,
SafeResolverRelatedEvents,
SafeResolverAncestry,
ResolverRelatedAlerts,
SafeResolverLifecycleNode,
@ -18,7 +17,6 @@ import { StatsQuery } from '../queries/stats';
import { createLifecycle } from './node';
import { MultiSearcher, QueryInfo } from '../queries/multi_searcher';
import { AncestryQueryHandler } from './ancestry_query_handler';
import { RelatedEventsQueryHandler } from './events_query_handler';
import { RelatedAlertsQueryHandler } from './alerts_query_handler';
import { ChildrenStartQueryHandler } from './children_start_query_handler';
import { ChildrenLifecycleQueryHandler } from './children_lifecycle_query_handler';
@ -110,14 +108,6 @@ export class Fetcher {
this.endpointID
);
const eventsHandler = new RelatedEventsQueryHandler({
limit: options.events,
entityID: this.id,
after: options.afterEvent,
indexPattern: this.eventsIndexPattern,
legacyEndpointID: this.endpointID,
});
const alertsHandler = new RelatedAlertsQueryHandler({
limit: options.alerts,
entityID: this.id,
@ -139,7 +129,6 @@ export class Fetcher {
const msearch = new MultiSearcher(this.client);
let queries: QueryInfo[] = [];
addQueryToList(eventsHandler, queries);
addQueryToList(alertsHandler, queries);
addQueryToList(childrenHandler, queries);
addQueryToList(originHandler, queries);
@ -176,7 +165,6 @@ export class Fetcher {
const tree = new Tree(this.id, {
ancestry: ancestryHandler.getResults(),
relatedEvents: eventsHandler.getResults(),
relatedAlerts: alertsHandler.getResults(),
children: childrenLifecycleHandler.getResults(),
});
@ -225,31 +213,6 @@ export class Fetcher {
return childrenLifecycleHandler.search(this.client);
}
/**
* Retrieves the related events for the origin node.
*
* @param limit the upper bound number of related events to return. The limit is applied after the cursor is used to
* skip the previous results.
* @param after a cursor to use as the starting point for retrieving related events
* @param filter a kql query for filtering the results
*/
public async events(
limit: number,
after?: string,
filter?: string
): Promise<SafeResolverRelatedEvents> {
const eventsHandler = new RelatedEventsQueryHandler({
limit,
entityID: this.id,
after,
indexPattern: this.eventsIndexPattern,
legacyEndpointID: this.endpointID,
filter,
});
return eventsHandler.search(this.client);
}
/**
* Retrieves the alerts for the origin node.
*

View file

@ -12,25 +12,9 @@ import {
SafeResolverLifecycleNode,
SafeResolverEvent,
SafeResolverChildNode,
SafeResolverRelatedEvents,
ResolverPaginatedEvents,
} from '../../../../../common/endpoint/types';
/**
* Creates a related event object that the related events handler would return
*
* @param entityID the entity_id for these related events
* @param events array of related events
* @param nextEvent the cursor to retrieve the next related event
*/
export function createRelatedEvents(
entityID: string,
events: SafeResolverEvent[] = [],
nextEvent: string | null = null
): SafeResolverRelatedEvents {
return { entityID, events, nextEvent };
}
/**
* Creates an object that the events handler would return
*
@ -116,10 +100,6 @@ export function createTree(entityID: string): SafeResolverTree {
childNodes: [],
nextChild: null,
},
relatedEvents: {
events: [],
nextEvent: null,
},
relatedAlerts: {
alerts: [],
nextAlert: null,

View file

@ -6,11 +6,7 @@
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
import { Tree } from './tree';
import {
SafeResolverAncestry,
SafeResolverEvent,
SafeResolverRelatedEvents,
} from '../../../../../common/endpoint/types';
import { SafeResolverAncestry, SafeResolverEvent } from '../../../../../common/endpoint/types';
import { entityIDSafeVersion } from '../../../../../common/endpoint/models/event';
describe('Tree', () => {
@ -46,19 +42,4 @@ describe('Tree', () => {
expect(tree.render().ancestry.nextAncestor).toEqual('hello');
});
});
describe('related events', () => {
it('adds related events to the tree', () => {
const root = generator.generateEvent();
const events: SafeResolverRelatedEvents = {
entityID: entityIDSafeVersion(root) ?? '',
events: Array.from(generator.relatedEventsGenerator(root)),
nextEvent: null,
};
const tree = new Tree(entityIDSafeVersion(root) ?? '', { relatedEvents: events });
const rendered = tree.render();
expect(rendered.relatedEvents.nextEvent).toBeNull();
expect(rendered.relatedEvents.events).toStrictEqual(events.events);
});
});
});

View file

@ -8,7 +8,6 @@ import _ from 'lodash';
import {
SafeResolverEvent,
ResolverNodeStats,
SafeResolverRelatedEvents,
SafeResolverAncestry,
SafeResolverTree,
SafeResolverChildren,
@ -23,7 +22,6 @@ interface Node {
}
export interface Options {
relatedEvents?: SafeResolverRelatedEvents;
ancestry?: SafeResolverAncestry;
children?: SafeResolverChildren;
relatedAlerts?: ResolverRelatedAlerts;
@ -44,7 +42,6 @@ export class Tree {
this.tree = tree;
this.cache.set(id, tree);
this.addRelatedEvents(options.relatedEvents);
this.addAncestors(options.ancestry);
this.addChildren(options.children);
this.addRelatedAlerts(options.relatedAlerts);
@ -68,20 +65,6 @@ export class Tree {
return [...this.cache.keys()];
}
/**
* Add related events for the tree's origin node. Related events cannot be added for other nodes.
*
* @param relatedEventsInfo is the related events and pagination information to add to the tree.
*/
private addRelatedEvents(relatedEventsInfo: SafeResolverRelatedEvents | undefined) {
if (!relatedEventsInfo) {
return;
}
this.tree.relatedEvents.events = relatedEventsInfo.events;
this.tree.relatedEvents.nextEvent = relatedEventsInfo.nextEvent;
}
/**
* Add alerts for the tree's origin node. Alerts cannot be added for other nodes.
*

View file

@ -5,10 +5,7 @@
*/
import expect from '@kbn/expect';
import { eventIDSafeVersion } from '../../../../plugins/security_solution/common/endpoint/models/event';
import {
ResolverPaginatedEvents,
SafeResolverRelatedEvents,
} from '../../../../plugins/security_solution/common/endpoint/types';
import { ResolverPaginatedEvents } from '../../../../plugins/security_solution/common/endpoint/types';
import { FtrProviderContext } from '../../ftr_provider_context';
import {
Tree,
@ -20,7 +17,6 @@ import { compareArrays } from './common';
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const resolver = getService('resolverGenerator');
const esArchiver = getService('esArchiver');
const relatedEventsToGen = [
{ category: RelatedEventCategory.Driver, count: 2 },
@ -44,308 +40,136 @@ export default function ({ getService }: FtrProviderContext) {
ancestryArraySize: 2,
};
describe('event routes', () => {
describe('related events route', () => {
before(async () => {
await esArchiver.load('endpoint/resolver/api_feature');
resolverTrees = await resolver.createTrees(treeOptions);
// we only requested a single alert so there's only 1 tree
tree = resolverTrees.trees[0];
});
after(async () => {
await resolver.deleteData(resolverTrees);
await esArchiver.unload('endpoint/resolver/api_feature');
});
describe('legacy events', () => {
const endpointID = '5a0c957f-b8e7-4538-965e-57e8bb86ad3a';
const entityID = '94042';
const cursor = 'eyJ0aW1lc3RhbXAiOjE1ODE0NTYyNTUwMDAsImV2ZW50SUQiOiI5NDA0MyJ9';
it('should return details for the root node', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/${entityID}/events?legacyEndpointID=${endpointID}`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.events.length).to.eql(1);
expect(body.entityID).to.eql(entityID);
expect(body.nextEvent).to.eql(null);
});
it('returns no values when there is no more data', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
// after is set to the document id of the last event so there shouldn't be any more after it
.post(
`/api/endpoint/resolver/${entityID}/events?legacyEndpointID=${endpointID}&afterEvent=${cursor}`
)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.events).be.empty();
expect(body.entityID).to.eql(entityID);
expect(body.nextEvent).to.eql(null);
});
it('should return the first page of information when the cursor is invalid', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(
`/api/endpoint/resolver/${entityID}/events?legacyEndpointID=${endpointID}&afterEvent=blah`
)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.entityID).to.eql(entityID);
expect(body.nextEvent).to.eql(null);
});
it('should return no results for an invalid endpoint ID', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/${entityID}/events?legacyEndpointID=foo`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.nextEvent).to.eql(null);
expect(body.entityID).to.eql(entityID);
expect(body.events).to.be.empty();
});
it('should error on invalid pagination values', async () => {
await supertest
.post(`/api/endpoint/resolver/${entityID}/events?events=0`)
.set('kbn-xsrf', 'xxx')
.expect(400);
await supertest
.post(`/api/endpoint/resolver/${entityID}/events?events=20000`)
.set('kbn-xsrf', 'xxx')
.expect(400);
await supertest
.post(`/api/endpoint/resolver/${entityID}/events?events=-1`)
.set('kbn-xsrf', 'xxx')
.expect(400);
});
});
describe('endpoint events', () => {
it('should not find any events', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/5555/events`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.nextEvent).to.eql(null);
expect(body.events).to.be.empty();
});
it('should return details for the root node', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/${tree.origin.id}/events`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.events.length).to.eql(4);
compareArrays(tree.origin.relatedEvents, body.events, true);
expect(body.nextEvent).to.eql(null);
});
it('should allow for the events to be filtered', async () => {
const filter = `event.category:"${RelatedEventCategory.Driver}"`;
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/${tree.origin.id}/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter,
})
.expect(200);
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).to.eql(null);
for (const event of body.events) {
expect(event.event?.category).to.be(RelatedEventCategory.Driver);
}
});
it('should return paginated results for the root node', async () => {
let { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/${tree.origin.id}/events?events=2`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).not.to.eql(null);
({ body } = await supertest
.post(
`/api/endpoint/resolver/${tree.origin.id}/events?events=2&afterEvent=${body.nextEvent}`
)
.set('kbn-xsrf', 'xxx')
.expect(200));
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).to.not.eql(null);
({ body } = await supertest
.post(
`/api/endpoint/resolver/${tree.origin.id}/events?events=2&afterEvent=${body.nextEvent}`
)
.set('kbn-xsrf', 'xxx')
.expect(200));
expect(body.events).to.be.empty();
expect(body.nextEvent).to.eql(null);
});
it('should return the first page of information when the cursor is invalid', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/${tree.origin.id}/events?afterEvent=blah`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.events.length).to.eql(4);
compareArrays(tree.origin.relatedEvents, body.events, true);
expect(body.nextEvent).to.eql(null);
});
it('should sort the events in descending order', async () => {
const { body }: { body: SafeResolverRelatedEvents } = await supertest
.post(`/api/endpoint/resolver/${tree.origin.id}/events`)
.set('kbn-xsrf', 'xxx')
.expect(200);
expect(body.events.length).to.eql(4);
// these events are created in the order they are defined in the array so the newest one is
// the last element in the array so let's reverse it
const relatedEvents = tree.origin.relatedEvents.reverse();
for (let i = 0; i < body.events.length; i++) {
expect(body.events[i].event?.category).to.equal(relatedEvents[i].event?.category);
expect(eventIDSafeVersion(body.events[i])).to.equal(relatedEvents[i].event?.id);
}
});
});
describe('event route', () => {
let entityIDFilter: string | undefined;
before(async () => {
resolverTrees = await resolver.createTrees(treeOptions);
// we only requested a single alert so there's only 1 tree
tree = resolverTrees.trees[0];
entityIDFilter = `process.entity_id:"${tree.origin.id}" and not event.category:"process"`;
});
after(async () => {
await resolver.deleteData(resolverTrees);
});
describe('kql events route', () => {
let entityIDFilter: string | undefined;
before(async () => {
resolverTrees = await resolver.createTrees(treeOptions);
// we only requested a single alert so there's only 1 tree
tree = resolverTrees.trees[0];
entityIDFilter = `process.entity_id:"${tree.origin.id}" and not event.category:"process"`;
});
after(async () => {
await resolver.deleteData(resolverTrees);
});
it('should filter events by event.id', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: `event.id:"${tree.origin.relatedEvents[0]?.event?.id}"`,
})
.expect(200);
expect(body.events.length).to.eql(1);
expect(tree.origin.relatedEvents[0]?.event?.id).to.eql(body.events[0].event?.id);
expect(body.nextEvent).to.eql(null);
});
it('should filter events by event.id', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: `event.id:"${tree.origin.relatedEvents[0]?.event?.id}"`,
})
.expect(200);
expect(body.events.length).to.eql(1);
expect(tree.origin.relatedEvents[0]?.event?.id).to.eql(body.events[0].event?.id);
expect(body.nextEvent).to.eql(null);
});
it('should not find any events when given an invalid entity id', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: 'process.entity_id:"5555"',
})
.expect(200);
expect(body.nextEvent).to.eql(null);
expect(body.events).to.be.empty();
});
it('should not find any events when given an invalid entity id', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: 'process.entity_id:"5555"',
})
.expect(200);
expect(body.nextEvent).to.eql(null);
expect(body.events).to.be.empty();
});
it('should return related events for the root node', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(4);
compareArrays(tree.origin.relatedEvents, body.events, true);
expect(body.nextEvent).to.eql(null);
});
it('should return related events for the root node', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(4);
compareArrays(tree.origin.relatedEvents, body.events, true);
expect(body.nextEvent).to.eql(null);
});
it('should allow for the events to be filtered', async () => {
const filter = `event.category:"${RelatedEventCategory.Driver}" and ${entityIDFilter}`;
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter,
})
.expect(200);
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).to.eql(null);
for (const event of body.events) {
expect(event.event?.category).to.be(RelatedEventCategory.Driver);
}
});
it('should allow for the events to be filtered', async () => {
const filter = `event.category:"${RelatedEventCategory.Driver}" and ${entityIDFilter}`;
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter,
})
.expect(200);
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).to.eql(null);
for (const event of body.events) {
expect(event.event?.category).to.be(RelatedEventCategory.Driver);
}
});
it('should return paginated results for the root node', async () => {
let { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events?limit=2`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).not.to.eql(null);
it('should return paginated results for the root node', async () => {
let { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events?limit=2`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).not.to.eql(null);
({ body } = await supertest
.post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200));
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).to.not.eql(null);
({ body } = await supertest
.post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200));
expect(body.events.length).to.eql(2);
compareArrays(tree.origin.relatedEvents, body.events);
expect(body.nextEvent).to.not.eql(null);
({ body } = await supertest
.post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200));
expect(body.events).to.be.empty();
expect(body.nextEvent).to.eql(null);
});
({ body } = await supertest
.post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200));
expect(body.events).to.be.empty();
expect(body.nextEvent).to.eql(null);
});
it('should return the first page of information when the cursor is invalid', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events?afterEvent=blah`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(4);
compareArrays(tree.origin.relatedEvents, body.events, true);
expect(body.nextEvent).to.eql(null);
});
it('should return the first page of information when the cursor is invalid', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events?afterEvent=blah`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(4);
compareArrays(tree.origin.relatedEvents, body.events, true);
expect(body.nextEvent).to.eql(null);
});
it('should sort the events in descending order', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(4);
// these events are created in the order they are defined in the array so the newest one is
// the last element in the array so let's reverse it
const relatedEvents = tree.origin.relatedEvents.reverse();
for (let i = 0; i < body.events.length; i++) {
expect(body.events[i].event?.category).to.equal(relatedEvents[i].event?.category);
expect(eventIDSafeVersion(body.events[i])).to.equal(relatedEvents[i].event?.id);
}
});
it('should sort the events in descending order', async () => {
const { body }: { body: ResolverPaginatedEvents } = await supertest
.post(`/api/endpoint/resolver/events`)
.set('kbn-xsrf', 'xxx')
.send({
filter: entityIDFilter,
})
.expect(200);
expect(body.events.length).to.eql(4);
// these events are created in the order they are defined in the array so the newest one is
// the last element in the array so let's reverse it
const relatedEvents = tree.origin.relatedEvents.reverse();
for (let i = 0; i < body.events.length; i++) {
expect(body.events[i].event?.category).to.equal(relatedEvents[i].event?.category);
expect(eventIDSafeVersion(body.events[i])).to.equal(relatedEvents[i].event?.id);
}
});
});
}

View file

@ -340,10 +340,8 @@ export default function ({ getService }: FtrProviderContext) {
.get(`/api/endpoint/resolver/93933?legacyEndpointID=${endpointID}`)
.expect(200);
expect(body.ancestry.nextAncestor).to.equal(null);
expect(body.relatedEvents.nextEvent).to.equal(null);
expect(body.children.nextChild).to.equal(null);
expect(body.children.childNodes.length).to.equal(0);
expect(body.relatedEvents.events.length).to.equal(0);
expect(body.lifecycle.length).to.equal(2);
});
});
@ -365,9 +363,6 @@ export default function ({ getService }: FtrProviderContext) {
verifyAncestry(body.ancestry.ancestors, tree, true);
verifyLifecycleStats(body.ancestry.ancestors, relatedEventsToGen, relatedAlerts);
expect(body.relatedEvents.nextEvent).to.equal(null);
compareArrays(tree.origin.relatedEvents, body.relatedEvents.events, true);
expect(body.relatedAlerts.nextAlert).to.equal(null);
compareArrays(tree.origin.relatedAlerts, body.relatedAlerts.alerts, true);