Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -715,7 +730,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 @@ -740,6 +755,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 @@ -755,6 +772,7 @@ export class EndpointDocGenerator {
parentEntityID: ancestor.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
ancestry: ancestor.process.Ext.ancestry,
})
);
}
Expand All @@ -773,7 +791,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 @@ -828,6 +851,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 @@ -849,6 +876,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 @@ -893,6 +921,7 @@ export class EndpointDocGenerator {
parentEntityID: node.process.parent?.entity_id,
eventCategory: eventInfo.category,
eventType: eventInfo.creationType,
ancestry: node.process.Ext.ancestry,
});
}
}
Expand All @@ -911,7 +940,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