Skip to content

Commit 435cb0b

Browse files
marshallmainoatkillerelasticmachine
authored
[Endpoint] Sample data generator for endpoint app (elastic#58936)
* scaffolding and notes.md * add skeleton event generator to kibana * add optional entityID param to generateEvent * add tree generation * add tests * working tests * fix up tests * fix linting * fix event types * make process parent types consistent * make generator match types * move test resolver node out of common types * fix random string generation * fix typecheck errors * remove extraneous stuff * address PR comments * add test for full resolver tree * cleanup * make tests clearer * add seedrandom to endpoint plugin. contains DONOTMERGE example code * remove robs test * start replacing random with seedrandom * use seeded random for uuidv4 * separate out IP randomization * typecheck fixes Co-authored-by: oatkiller <[email protected]> Co-authored-by: Elastic Machine <[email protected]>
1 parent 0ed7176 commit 435cb0b

File tree

11 files changed

+668
-274
lines changed

11 files changed

+668
-274
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { EndpointDocGenerator, Event } from './generate_data';
7+
8+
interface Node {
9+
events: Event[];
10+
children: Node[];
11+
parent_entity_id?: string;
12+
}
13+
14+
describe('data generator', () => {
15+
let generator: EndpointDocGenerator;
16+
beforeEach(() => {
17+
generator = new EndpointDocGenerator('seed');
18+
});
19+
20+
it('creates the same documents with same random seed', () => {
21+
const generator1 = new EndpointDocGenerator('seed');
22+
const generator2 = new EndpointDocGenerator('seed');
23+
const timestamp = new Date().getTime();
24+
const metadata1 = generator1.generateEndpointMetadata(timestamp);
25+
const metadata2 = generator2.generateEndpointMetadata(timestamp);
26+
expect(metadata1).toEqual(metadata2);
27+
});
28+
29+
it('creates different documents with different random seeds', () => {
30+
const generator1 = new EndpointDocGenerator('seed');
31+
const generator2 = new EndpointDocGenerator('different seed');
32+
const timestamp = new Date().getTime();
33+
const metadata1 = generator1.generateEndpointMetadata(timestamp);
34+
const metadata2 = generator2.generateEndpointMetadata(timestamp);
35+
expect(metadata1).not.toEqual(metadata2);
36+
});
37+
38+
it('creates endpoint metadata documents', () => {
39+
const timestamp = new Date().getTime();
40+
const metadata = generator.generateEndpointMetadata(timestamp);
41+
expect(metadata['@timestamp']).toEqual(timestamp);
42+
expect(metadata.event.created).toEqual(timestamp);
43+
expect(metadata.endpoint).not.toBeNull();
44+
expect(metadata.agent).not.toBeNull();
45+
expect(metadata.host).not.toBeNull();
46+
});
47+
48+
it('creates alert event documents', () => {
49+
const timestamp = new Date().getTime();
50+
const alert = generator.generateAlert(timestamp);
51+
expect(alert['@timestamp']).toEqual(timestamp);
52+
expect(alert.event.action).not.toBeNull();
53+
expect(alert.endpoint).not.toBeNull();
54+
expect(alert.agent).not.toBeNull();
55+
expect(alert.host).not.toBeNull();
56+
expect(alert.process.entity_id).not.toBeNull();
57+
});
58+
59+
it('creates process event documents', () => {
60+
const timestamp = new Date().getTime();
61+
const processEvent = generator.generateEvent({ timestamp });
62+
expect(processEvent['@timestamp']).toEqual(timestamp);
63+
expect(processEvent.event.category).toEqual('process');
64+
expect(processEvent.event.kind).toEqual('event');
65+
expect(processEvent.event.type).toEqual('creation');
66+
expect(processEvent.agent).not.toBeNull();
67+
expect(processEvent.host).not.toBeNull();
68+
expect(processEvent.process.entity_id).not.toBeNull();
69+
});
70+
71+
it('creates other event documents', () => {
72+
const timestamp = new Date().getTime();
73+
const processEvent = generator.generateEvent({ timestamp, eventCategory: 'dns' });
74+
expect(processEvent['@timestamp']).toEqual(timestamp);
75+
expect(processEvent.event.category).toEqual('dns');
76+
expect(processEvent.event.kind).toEqual('event');
77+
expect(processEvent.event.type).toEqual('creation');
78+
expect(processEvent.agent).not.toBeNull();
79+
expect(processEvent.host).not.toBeNull();
80+
expect(processEvent.process.entity_id).not.toBeNull();
81+
});
82+
83+
describe('creates alert ancestor tree', () => {
84+
let events: Event[];
85+
86+
beforeEach(() => {
87+
events = generator.generateAlertEventAncestry(3);
88+
});
89+
90+
it('with n-1 process events', () => {
91+
for (let i = 1; i < events.length - 1; i++) {
92+
expect(events[i].process.parent?.entity_id).toEqual(events[i - 1].process.entity_id);
93+
expect(events[i].event.kind).toEqual('event');
94+
expect(events[i].event.category).toEqual('process');
95+
}
96+
});
97+
98+
it('with a corresponding alert at the end', () => {
99+
// The alert should be last and have the same entity_id as the previous process event
100+
expect(events[events.length - 1].process.entity_id).toEqual(
101+
events[events.length - 2].process.entity_id
102+
);
103+
expect(events[events.length - 1].process.parent?.entity_id).toEqual(
104+
events[events.length - 2].process.parent?.entity_id
105+
);
106+
expect(events[events.length - 1].event.kind).toEqual('alert');
107+
expect(events[events.length - 1].event.category).toEqual('malware');
108+
});
109+
});
110+
111+
function buildResolverTree(events: Event[]): Node {
112+
// First pass we gather up all the events by entity_id
113+
const tree: Record<string, Node> = {};
114+
events.forEach(event => {
115+
if (event.process.entity_id in tree) {
116+
tree[event.process.entity_id].events.push(event);
117+
} else {
118+
tree[event.process.entity_id] = {
119+
events: [event],
120+
children: [],
121+
parent_entity_id: event.process.parent?.entity_id,
122+
};
123+
}
124+
});
125+
// Second pass add child references to each node
126+
for (const value of Object.values(tree)) {
127+
if (value.parent_entity_id) {
128+
tree[value.parent_entity_id].children.push(value);
129+
}
130+
}
131+
// The root node must be first in the array or this fails
132+
return tree[events[0].process.entity_id];
133+
}
134+
135+
function countResolverEvents(rootNode: Node, generations: number): number {
136+
// Start at the root, traverse N levels of the tree and check that we found all nodes
137+
let nodes = [rootNode];
138+
let visitedEvents = 0;
139+
for (let i = 0; i < generations + 1; i++) {
140+
let nextNodes: Node[] = [];
141+
nodes.forEach(node => {
142+
nextNodes = nextNodes.concat(node.children);
143+
visitedEvents += node.events.length;
144+
});
145+
nodes = nextNodes;
146+
}
147+
return visitedEvents;
148+
}
149+
150+
it('creates tree of process children', () => {
151+
const timestamp = new Date().getTime();
152+
const root = generator.generateEvent({ timestamp });
153+
const generations = 2;
154+
const events = generator.generateDescendantsTree(root, generations);
155+
const rootNode = buildResolverTree(events);
156+
const visitedEvents = countResolverEvents(rootNode, generations);
157+
expect(visitedEvents).toEqual(events.length);
158+
});
159+
160+
it('creates full resolver tree', () => {
161+
const alertAncestors = 3;
162+
const generations = 2;
163+
const events = generator.generateFullResolverTree(alertAncestors, generations);
164+
const rootNode = buildResolverTree(events);
165+
const visitedEvents = countResolverEvents(rootNode, alertAncestors + generations);
166+
expect(visitedEvents).toEqual(events.length);
167+
});
168+
});

0 commit comments

Comments
 (0)