Skip to content

Commit

Permalink
[Security Solution][Resolver] Fix resolver isStart event bug (elastic…
Browse files Browse the repository at this point in the history
…#73357) (elastic#73448)

* Check if category is array

* Adding more tests and renaming to isStart

* Handling the case where start is not at the front

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
jonathan-buttner and elasticmachine authored Jul 28, 2020
1 parent 9957fe1 commit 258b3d3
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
import {
EndpointDocGenerator,
Event,
Expand Down Expand Up @@ -79,9 +80,9 @@ describe('data generator', () => {
const timestamp = new Date().getTime();
const processEvent = generator.generateEvent({ timestamp });
expect(processEvent['@timestamp']).toEqual(timestamp);
expect(processEvent.event.category).toEqual('process');
expect(processEvent.event.category).toEqual(['process']);
expect(processEvent.event.kind).toEqual('event');
expect(processEvent.event.type).toEqual('start');
expect(processEvent.event.type).toEqual(['start']);
expect(processEvent.agent).not.toBeNull();
expect(processEvent.host).not.toBeNull();
expect(processEvent.process.entity_id).not.toBeNull();
Expand All @@ -94,7 +95,7 @@ describe('data generator', () => {
expect(processEvent['@timestamp']).toEqual(timestamp);
expect(processEvent.event.category).toEqual('dns');
expect(processEvent.event.kind).toEqual('event');
expect(processEvent.event.type).toEqual('start');
expect(processEvent.event.type).toEqual(['start']);
expect(processEvent.agent).not.toBeNull();
expect(processEvent.host).not.toBeNull();
expect(processEvent.process.entity_id).not.toBeNull();
Expand Down Expand Up @@ -332,6 +333,12 @@ describe('data generator', () => {
describe('creates alert ancestor tree', () => {
let events: Event[];

const isCategoryProcess = (event: Event) => {
return (
_.isEqual(event.event.category, ['process']) || _.isEqual(event.event.category, 'process')
);
};

beforeEach(() => {
events = generator.createAlertEventAncestry({
ancestors: 3,
Expand All @@ -343,11 +350,7 @@ describe('data generator', () => {
it('with n-1 process events', () => {
for (let i = events.length - 2; i > 0; ) {
const parentEntityIdOfChild = events[i].process.parent?.entity_id;
for (
;
--i >= -1 && (events[i].event.kind !== 'event' || events[i].event.category !== 'process');

) {
for (; --i >= -1 && (events[i].event.kind !== 'event' || !isCategoryProcess(events[i])); ) {
// related event - skip it
}
expect(i).toBeGreaterThanOrEqual(0);
Expand All @@ -361,7 +364,7 @@ describe('data generator', () => {
;
previousProcessEventIndex >= -1 &&
(events[previousProcessEventIndex].event.kind !== 'event' ||
events[previousProcessEventIndex].event.category !== 'process');
!isCategoryProcess(events[previousProcessEventIndex]));
previousProcessEventIndex--
) {
// related event - skip it
Expand Down
25 changes: 15 additions & 10 deletions x-pack/plugins/security_solution/common/endpoint/generate_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface EventOptions {
timestamp?: number;
entityID?: string;
parentEntityID?: string;
eventType?: string;
eventType?: string | string[];
eventCategory?: string | string[];
processName?: string;
ancestry?: string[];
Expand Down Expand Up @@ -572,9 +572,9 @@ export class EndpointDocGenerator {
},
...detailRecordForEventType,
event: {
category: options.eventCategory ? options.eventCategory : 'process',
category: options.eventCategory ? options.eventCategory : ['process'],
kind: 'event',
type: options.eventType ? options.eventType : 'start',
type: options.eventType ? options.eventType : ['start'],
id: this.seededUUIDv4(),
},
host: this.commonInfo.host,
Expand Down Expand Up @@ -633,7 +633,12 @@ export class EndpointDocGenerator {

// place the event in the right array depending on its category
if (event.event.kind === 'event') {
if (event.event.category === 'process') {
if (
(Array.isArray(event.event.category) &&
event.event.category.length === 1 &&
event.event.category[0] === 'process') ||
event.event.category === 'process'
) {
node.lifecycle.push(event);
} else {
node.relatedEvents.push(event);
Expand Down Expand Up @@ -812,8 +817,8 @@ export class EndpointDocGenerator {
timestamp: timestamp + termProcessDuration * 1000,
entityID: root.process.entity_id,
parentEntityID: root.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
eventCategory: ['process'],
eventType: ['end'],
})
);
}
Expand All @@ -838,8 +843,8 @@ export class EndpointDocGenerator {
timestamp: timestamp + termProcessDuration * 1000,
entityID: ancestor.process.entity_id,
parentEntityID: ancestor.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
eventCategory: ['process'],
eventType: ['end'],
ancestry: ancestor.process.Ext?.ancestry,
ancestryArrayLimit: opts.ancestryArraySize,
})
Expand Down Expand Up @@ -936,8 +941,8 @@ export class EndpointDocGenerator {
timestamp: timestamp + processDuration * 1000,
entityID: child.process.entity_id,
parentEntityID: child.process.parent?.entity_id,
eventCategory: 'process',
eventType: 'end',
eventCategory: ['process'],
eventType: ['end'],
ancestry: child.process.Ext?.ancestry,
ancestryArrayLimit: opts.ancestryArraySize,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,90 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EndpointDocGenerator } from '../generate_data';
import { descriptiveName } from './event';
import { descriptiveName, isStart } from './event';
import { ResolverEvent } from '../types';

describe('Event descriptive names', () => {
describe('Generated documents', () => {
let generator: EndpointDocGenerator;
beforeEach(() => {
generator = new EndpointDocGenerator('seed');
});

it('returns the right name for a registry event', () => {
const extensions = { registry: { key: `HKLM/Windows/Software/abc` } };
const event = generator.generateEvent({ eventCategory: 'registry', extensions });
expect(descriptiveName(event)).toEqual({ subject: `HKLM/Windows/Software/abc` });
});
describe('Event descriptive names', () => {
it('returns the right name for a registry event', () => {
const extensions = { registry: { key: `HKLM/Windows/Software/abc` } };
const event = generator.generateEvent({ eventCategory: 'registry', extensions });
expect(descriptiveName(event)).toEqual({ subject: `HKLM/Windows/Software/abc` });
});

it('returns the right name for a network event', () => {
const randomIP = `${generator.randomIP()}`;
const extensions = { network: { direction: 'outbound', forwarded_ip: randomIP } };
const event = generator.generateEvent({ eventCategory: 'network', extensions });
expect(descriptiveName(event)).toEqual({ subject: `${randomIP}`, descriptor: 'outbound' });
});
it('returns the right name for a network event', () => {
const randomIP = `${generator.randomIP()}`;
const extensions = { network: { direction: 'outbound', forwarded_ip: randomIP } };
const event = generator.generateEvent({ eventCategory: 'network', extensions });
expect(descriptiveName(event)).toEqual({ subject: `${randomIP}`, descriptor: 'outbound' });
});

it('returns the right name for a file event', () => {
const extensions = { file: { path: 'C:\\My Documents\\business\\January\\processName' } };
const event = generator.generateEvent({ eventCategory: 'file', extensions });
expect(descriptiveName(event)).toEqual({
subject: 'C:\\My Documents\\business\\January\\processName',
it('returns the right name for a file event', () => {
const extensions = { file: { path: 'C:\\My Documents\\business\\January\\processName' } };
const event = generator.generateEvent({ eventCategory: 'file', extensions });
expect(descriptiveName(event)).toEqual({
subject: 'C:\\My Documents\\business\\January\\processName',
});
});

it('returns the right name for a dns event', () => {
const extensions = { dns: { question: { name: `${generator.randomIP()}` } } };
const event = generator.generateEvent({ eventCategory: 'dns', extensions });
expect(descriptiveName(event)).toEqual({ subject: extensions.dns.question.name });
});
});

it('returns the right name for a dns event', () => {
const extensions = { dns: { question: { name: `${generator.randomIP()}` } } };
const event = generator.generateEvent({ eventCategory: 'dns', extensions });
expect(descriptiveName(event)).toEqual({ subject: extensions.dns.question.name });
describe('Start events', () => {
it('is a start event when event.type is a string', () => {
const event: ResolverEvent = generator.generateEvent({
eventType: 'start',
});
expect(isStart(event)).toBeTruthy();
});

it('is a start event when event.type is an array of strings', () => {
const event: ResolverEvent = generator.generateEvent({
eventType: ['start'],
});
expect(isStart(event)).toBeTruthy();
});

it('is a start event when event.type is an array of strings and contains start', () => {
let event: ResolverEvent = generator.generateEvent({
eventType: ['bogus', 'start', 'creation'],
});
expect(isStart(event)).toBeTruthy();

event = generator.generateEvent({
eventType: ['start', 'bogus'],
});
expect(isStart(event)).toBeTruthy();
});

it('is not a start event when event.type is not start', () => {
const event: ResolverEvent = generator.generateEvent({
eventType: ['end'],
});
expect(isStart(event)).toBeFalsy();
});

it('is not a start event when event.type is empty', () => {
const event: ResolverEvent = generator.generateEvent({
eventType: [],
});
expect(isStart(event)).toBeFalsy();
});

it('is not a start event when event.type is bogus', () => {
const event: ResolverEvent = generator.generateEvent({
eventType: ['bogus'],
});
expect(isStart(event)).toBeFalsy();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEven
return (event as LegacyEndpointEvent).endgame !== undefined;
}

export function isProcessStart(event: ResolverEvent): boolean {
export function isStart(event: ResolverEvent): boolean {
if (isLegacyEvent(event)) {
return event.event?.type === 'process_start' || event.event?.action === 'fork_event';
}

if (Array.isArray(event.event.type)) {
return event.event.type.includes('start');
}

return event.event.type === 'start';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
TreeNode,
} from '../../../../../common/endpoint/generate_data';
import { ChildrenNodesHelper } from './children_helper';
import { eventId, isProcessStart } from '../../../../../common/endpoint/models/event';
import { eventId, isStart } from '../../../../../common/endpoint/models/event';

function getStartEvents(events: Event[]): Event[] {
const startEvents: Event[] = [];
for (const event of events) {
if (isProcessStart(event)) {
if (isStart(event)) {
startEvents.push(event);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import {
entityId,
parentEntityId,
isProcessStart,
isStart,
getAncestryAsArray,
} from '../../../../../common/endpoint/models/event';
import {
Expand Down Expand Up @@ -99,7 +99,7 @@ export class ChildrenNodesHelper {
for (const event of startEvents) {
const parentID = parentEntityId(event);
const entityID = entityId(event);
if (parentID && entityID && isProcessStart(event)) {
if (parentID && entityID && isStart(event)) {
// don't actually add the start event to the node, because that'll be done in
// a different call
const childNode = this.getOrCreateChildNode(entityID);
Expand Down

0 comments on commit 258b3d3

Please sign in to comment.