-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Endpoint] Sample data generator for endpoint app (#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 <robert.austin@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
- Loading branch information
1 parent
0ed7176
commit 435cb0b
Showing
11 changed files
with
668 additions
and
274 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/* | ||
* 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 { EndpointDocGenerator, Event } from './generate_data'; | ||
|
||
interface Node { | ||
events: Event[]; | ||
children: Node[]; | ||
parent_entity_id?: string; | ||
} | ||
|
||
describe('data generator', () => { | ||
let generator: EndpointDocGenerator; | ||
beforeEach(() => { | ||
generator = new EndpointDocGenerator('seed'); | ||
}); | ||
|
||
it('creates the same documents with same random seed', () => { | ||
const generator1 = new EndpointDocGenerator('seed'); | ||
const generator2 = new EndpointDocGenerator('seed'); | ||
const timestamp = new Date().getTime(); | ||
const metadata1 = generator1.generateEndpointMetadata(timestamp); | ||
const metadata2 = generator2.generateEndpointMetadata(timestamp); | ||
expect(metadata1).toEqual(metadata2); | ||
}); | ||
|
||
it('creates different documents with different random seeds', () => { | ||
const generator1 = new EndpointDocGenerator('seed'); | ||
const generator2 = new EndpointDocGenerator('different seed'); | ||
const timestamp = new Date().getTime(); | ||
const metadata1 = generator1.generateEndpointMetadata(timestamp); | ||
const metadata2 = generator2.generateEndpointMetadata(timestamp); | ||
expect(metadata1).not.toEqual(metadata2); | ||
}); | ||
|
||
it('creates endpoint metadata documents', () => { | ||
const timestamp = new Date().getTime(); | ||
const metadata = generator.generateEndpointMetadata(timestamp); | ||
expect(metadata['@timestamp']).toEqual(timestamp); | ||
expect(metadata.event.created).toEqual(timestamp); | ||
expect(metadata.endpoint).not.toBeNull(); | ||
expect(metadata.agent).not.toBeNull(); | ||
expect(metadata.host).not.toBeNull(); | ||
}); | ||
|
||
it('creates alert event documents', () => { | ||
const timestamp = new Date().getTime(); | ||
const alert = generator.generateAlert(timestamp); | ||
expect(alert['@timestamp']).toEqual(timestamp); | ||
expect(alert.event.action).not.toBeNull(); | ||
expect(alert.endpoint).not.toBeNull(); | ||
expect(alert.agent).not.toBeNull(); | ||
expect(alert.host).not.toBeNull(); | ||
expect(alert.process.entity_id).not.toBeNull(); | ||
}); | ||
|
||
it('creates process event documents', () => { | ||
const timestamp = new Date().getTime(); | ||
const processEvent = generator.generateEvent({ timestamp }); | ||
expect(processEvent['@timestamp']).toEqual(timestamp); | ||
expect(processEvent.event.category).toEqual('process'); | ||
expect(processEvent.event.kind).toEqual('event'); | ||
expect(processEvent.event.type).toEqual('creation'); | ||
expect(processEvent.agent).not.toBeNull(); | ||
expect(processEvent.host).not.toBeNull(); | ||
expect(processEvent.process.entity_id).not.toBeNull(); | ||
}); | ||
|
||
it('creates other event documents', () => { | ||
const timestamp = new Date().getTime(); | ||
const processEvent = generator.generateEvent({ timestamp, eventCategory: 'dns' }); | ||
expect(processEvent['@timestamp']).toEqual(timestamp); | ||
expect(processEvent.event.category).toEqual('dns'); | ||
expect(processEvent.event.kind).toEqual('event'); | ||
expect(processEvent.event.type).toEqual('creation'); | ||
expect(processEvent.agent).not.toBeNull(); | ||
expect(processEvent.host).not.toBeNull(); | ||
expect(processEvent.process.entity_id).not.toBeNull(); | ||
}); | ||
|
||
describe('creates alert ancestor tree', () => { | ||
let events: Event[]; | ||
|
||
beforeEach(() => { | ||
events = generator.generateAlertEventAncestry(3); | ||
}); | ||
|
||
it('with n-1 process events', () => { | ||
for (let i = 1; i < events.length - 1; i++) { | ||
expect(events[i].process.parent?.entity_id).toEqual(events[i - 1].process.entity_id); | ||
expect(events[i].event.kind).toEqual('event'); | ||
expect(events[i].event.category).toEqual('process'); | ||
} | ||
}); | ||
|
||
it('with a corresponding alert at the end', () => { | ||
// The alert should be last and have the same entity_id as the previous process event | ||
expect(events[events.length - 1].process.entity_id).toEqual( | ||
events[events.length - 2].process.entity_id | ||
); | ||
expect(events[events.length - 1].process.parent?.entity_id).toEqual( | ||
events[events.length - 2].process.parent?.entity_id | ||
); | ||
expect(events[events.length - 1].event.kind).toEqual('alert'); | ||
expect(events[events.length - 1].event.category).toEqual('malware'); | ||
}); | ||
}); | ||
|
||
function buildResolverTree(events: Event[]): Node { | ||
// First pass we gather up all the events by entity_id | ||
const tree: Record<string, Node> = {}; | ||
events.forEach(event => { | ||
if (event.process.entity_id in tree) { | ||
tree[event.process.entity_id].events.push(event); | ||
} else { | ||
tree[event.process.entity_id] = { | ||
events: [event], | ||
children: [], | ||
parent_entity_id: event.process.parent?.entity_id, | ||
}; | ||
} | ||
}); | ||
// Second pass add child references to each node | ||
for (const value of Object.values(tree)) { | ||
if (value.parent_entity_id) { | ||
tree[value.parent_entity_id].children.push(value); | ||
} | ||
} | ||
// The root node must be first in the array or this fails | ||
return tree[events[0].process.entity_id]; | ||
} | ||
|
||
function countResolverEvents(rootNode: Node, generations: number): number { | ||
// Start at the root, traverse N levels of the tree and check that we found all nodes | ||
let nodes = [rootNode]; | ||
let visitedEvents = 0; | ||
for (let i = 0; i < generations + 1; i++) { | ||
let nextNodes: Node[] = []; | ||
nodes.forEach(node => { | ||
nextNodes = nextNodes.concat(node.children); | ||
visitedEvents += node.events.length; | ||
}); | ||
nodes = nextNodes; | ||
} | ||
return visitedEvents; | ||
} | ||
|
||
it('creates tree of process children', () => { | ||
const timestamp = new Date().getTime(); | ||
const root = generator.generateEvent({ timestamp }); | ||
const generations = 2; | ||
const events = generator.generateDescendantsTree(root, generations); | ||
const rootNode = buildResolverTree(events); | ||
const visitedEvents = countResolverEvents(rootNode, generations); | ||
expect(visitedEvents).toEqual(events.length); | ||
}); | ||
|
||
it('creates full resolver tree', () => { | ||
const alertAncestors = 3; | ||
const generations = 2; | ||
const events = generator.generateFullResolverTree(alertAncestors, generations); | ||
const rootNode = buildResolverTree(events); | ||
const visitedEvents = countResolverEvents(rootNode, alertAncestors + generations); | ||
expect(visitedEvents).toEqual(events.length); | ||
}); | ||
}); |
Oops, something went wrong.