Skip to content

Commit

Permalink
[Security_Solution][Endpoint] Resolver leverage ancestry array for qu…
Browse files Browse the repository at this point in the history
…eries (#69264)

* Adding alerts route

* Adding related alerts generator changes, tests, and script updates

* Fixing missed parameter

* Aligning the AlertEvent and ResolverEvent definition

* Fixing type errors

* Fixing import error

* Adding ancestry functionality in generator

* Creating some tests for ancestry field

* Making progress on the ancestry

* Fixing the ancestry verification

* Fixing existing tests

* Removing unused code and fixing test

* Adding more comments

* Fixing endgame queries

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
jonathan-buttner and elasticmachine authored Jun 19, 2020
1 parent 7ec8836 commit 7c7191f
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TreeNode,
RelatedEventCategory,
ECSCategory,
ANCESTRY_LIMIT,
} from './generate_data';

interface Node {
Expand Down Expand Up @@ -113,6 +114,7 @@ describe('data generator', () => {
relatedEvents: 0,
relatedAlerts: 0,
});
tree.ancestry.delete(tree.origin.id);
});

it('creates an alert for the origin node but no other nodes', () => {
Expand Down Expand Up @@ -159,6 +161,30 @@ describe('data generator', () => {
return (inRelated || inRelatedAlerts || inLifecycle) && event.process.entity_id === node.id;
};

const verifyAncestry = (event: Event, genTree: Tree) => {
if (event.process.Ext.ancestry.length > 0) {
expect(event.process.parent?.entity_id).toBe(event.process.Ext.ancestry[0]);
}
for (let i = 0; i < event.process.Ext.ancestry.length; i++) {
const ancestor = event.process.Ext.ancestry[i];
const parent = genTree.children.get(ancestor) || genTree.ancestry.get(ancestor);
expect(ancestor).toBe(parent?.lifecycle[0].process.entity_id);

// the next ancestor should be the grandparent
if (i + 1 < event.process.Ext.ancestry.length) {
const grandparent = event.process.Ext.ancestry[i + 1];
expect(grandparent).toBe(parent?.lifecycle[0].process.parent?.entity_id);
}
}
};

it('has ancestry array defined', () => {
expect(tree.origin.lifecycle[0].process.Ext.ancestry.length).toBe(ANCESTRY_LIMIT);
for (const event of tree.allEvents) {
verifyAncestry(event, tree);
}
});

it('has the right related events for each node', () => {
const checkRelatedEvents = (node: TreeNode) => {
expect(node.relatedEvents.length).toEqual(4);
Expand All @@ -185,8 +211,6 @@ describe('data generator', () => {
for (const node of tree.children.values()) {
checkRelatedEvents(node);
}

checkRelatedEvents(tree.origin);
});

it('has the right number of related alerts for each node', () => {
Expand All @@ -202,7 +226,8 @@ describe('data generator', () => {
});

it('has the right number of ancestors', () => {
expect(tree.ancestry.size).toEqual(ancestors);
// +1 for the origin node
expect(tree.ancestry.size).toEqual(ancestors + 1);
});

it('has the right number of total children', () => {
Expand Down Expand Up @@ -239,10 +264,7 @@ describe('data generator', () => {
const children = tree.children.get(event.process.entity_id);
if (children) {
expect(eventInNode(event, children)).toBeTruthy();
return;
}

expect(eventInNode(event, tree.origin)).toBeTruthy();
});
});

Expand All @@ -260,8 +282,6 @@ describe('data generator', () => {
total += nodeEventCount(node);
}

total += nodeEventCount(tree.origin);

expect(tree.allEvents.length).toEqual(total);
});
});
Expand Down
48 changes: 41 additions & 7 deletions x-pack/plugins/security_solution/common/endpoint/generate_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ import {
import { factory as policyFactory } from './models/policy_config';

export type Event = AlertEvent | EndpointEvent;
/**
* This value indicates the limit for the size of the ancestry array. The endpoint currently saves up to 20 values
* in its messages. To simulate a limit on the array size I'm using 2 here so that we can't rely on there being a large
* number like 20. The ancestry array contains entity_ids for the ancestors of a particular process.
*
* The array has a special format. The entity_ids towards the beginning of the array are closer ancestors and the
* values towards the end of the array are more distant ancestors (grandparents). Therefore
* ancestry_array[0] == process.parent.entity_id and ancestry_array[1] == process.parent.parent.entity_id
*/
export const ANCESTRY_LIMIT: number = 2;

interface EventOptions {
timestamp?: number;
Expand All @@ -26,6 +36,7 @@ interface EventOptions {
eventType?: string;
eventCategory?: string | string[];
processName?: string;
ancestry?: string[];
pid?: number;
parentPid?: number;
extensions?: object;
Expand Down Expand Up @@ -352,7 +363,8 @@ export class EndpointDocGenerator {
public generateAlert(
ts = new Date().getTime(),
entityID = this.randomString(10),
parentEntityID?: string
parentEntityID?: string,
ancestryArray: string[] = []
): AlertEvent {
return {
...this.commonInfo,
Expand Down Expand Up @@ -412,6 +424,9 @@ export class EndpointDocGenerator {
sha256: 'fake sha256',
},
Ext: {
// simulate a finite ancestry array size, the endpoint limits the ancestry array to 20 entries we'll use
// 2 so that the backend can handle that case
ancestry: ancestryArray.slice(0, ANCESTRY_LIMIT),
code_signature: [
{
trusted: false,
Expand Down Expand Up @@ -532,6 +547,9 @@ export class EndpointDocGenerator {
}
: undefined,
name: processName,
// simulate a finite ancestry array size, the endpoint limits the ancestry array to 20 entries we'll use
// 2 so that the backend can handle that case
Ext: { ancestry: options.ancestry?.slice(0, ANCESTRY_LIMIT) || [] },
},
user: {
domain: this.randomString(10),
Expand Down Expand Up @@ -589,9 +607,6 @@ export class EndpointDocGenerator {
throw Error(`could not find origin while building tree: ${alert.process.entity_id}`);
}

// remove the origin node from the ancestry array
ancestryNodes.delete(alert.process.entity_id);

const children = Array.from(
this.descendantsTreeGenerator(
alert,
Expand Down Expand Up @@ -716,7 +731,7 @@ export class EndpointDocGenerator {
}
};

// generate related alerts for rootW
// generate related alerts for root
const processDuration: number = 6 * 3600;
if (this.randomN(100) < pctWithRelated) {
addRelatedEvents(ancestor, processDuration, events);
Expand All @@ -741,6 +756,8 @@ export class EndpointDocGenerator {
ancestor = this.generateEvent({
timestamp,
parentEntityID: ancestor.process.entity_id,
// add the parent to the ancestry array
ancestry: [ancestor.process.entity_id, ...ancestor.process.Ext.ancestry],
parentPid: ancestor.process.pid,
pid: this.randomN(5000),
});
Expand All @@ -756,6 +773,7 @@ export class EndpointDocGenerator {
parentEntityID: ancestor.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
ancestry: ancestor.process.Ext.ancestry,
})
);
}
Expand All @@ -774,7 +792,12 @@ export class EndpointDocGenerator {
}
}
events.push(
this.generateAlert(timestamp, ancestor.process.entity_id, ancestor.process.parent?.entity_id)
this.generateAlert(
timestamp,
ancestor.process.entity_id,
ancestor.process.parent?.entity_id,
ancestor.process.Ext.ancestry
)
);
return events;
}
Expand Down Expand Up @@ -829,6 +852,10 @@ export class EndpointDocGenerator {
const child = this.generateEvent({
timestamp,
parentEntityID: currentState.event.process.entity_id,
ancestry: [
currentState.event.process.entity_id,
...currentState.event.process.Ext.ancestry,
],
});

maxChildren = this.randomN(maxChildrenPerNode + 1);
Expand All @@ -850,6 +877,7 @@ export class EndpointDocGenerator {
parentEntityID: child.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
ancestry: child.process.Ext.ancestry,
});
}
if (this.randomN(100) < percentNodesWithRelated) {
Expand Down Expand Up @@ -894,6 +922,7 @@ export class EndpointDocGenerator {
parentEntityID: node.process.parent?.entity_id,
eventCategory: eventInfo.category,
eventType: eventInfo.creationType,
ancestry: node.process.Ext.ancestry,
});
}
}
Expand All @@ -912,7 +941,12 @@ export class EndpointDocGenerator {
) {
for (let i = 0; i < relatedAlerts; i++) {
const ts = node['@timestamp'] + this.randomN(alertCreationTime) * 1000;
yield this.generateAlert(ts, node.process.entity_id, node.process.parent?.entity_id);
yield this.generateAlert(
ts,
node.process.entity_id,
node.process.parent?.entity_id,
node.process.Ext.ancestry
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export function parentEntityId(event: ResolverEvent): string | undefined {
return event.process.parent?.entity_id;
}

export function ancestryArray(event: ResolverEvent): string[] | undefined {
if (isLegacyEvent(event)) {
return undefined;
}
return event.process.Ext.ancestry;
}

/**
* @param event The event to get the category for
*/
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ export interface AlertEvent {
thread?: ThreadFields[];
uptime: number;
Ext: {
/*
* The array has a special format. The entity_ids towards the beginning of the array are closer ancestors and the
* values towards the end of the array are more distant ancestors (grandparents). Therefore
* ancestry_array[0] == process.parent.entity_id and ancestry_array[1] == process.parent.parent.entity_id
*/
ancestry: string[];
code_signature: Array<{
subject_name: string;
trusted: boolean;
Expand Down Expand Up @@ -469,6 +475,14 @@ export interface EndpointEvent {
name?: string;
pid?: number;
};
/*
* The array has a special format. The entity_ids towards the beginning of the array are closer ancestors and the
* values towards the end of the array are more distant ancestors (grandparents). Therefore
* ancestry_array[0] == process.parent.entity_id and ancestry_array[1] == process.parent.parent.entity_id
*/
Ext: {
ancestry: string[];
};
};
user?: {
domain?: string;
Expand Down
Loading

0 comments on commit 7c7191f

Please sign in to comment.