Skip to content

Commit

Permalink
[7.x] [Endpoint] Recursive resolver children (#61914) (#64960)
Browse files Browse the repository at this point in the history
  • Loading branch information
kqualters-elastic authored May 5, 2020
1 parent d973729 commit 254757b
Show file tree
Hide file tree
Showing 35 changed files with 1,293 additions and 574 deletions.
15 changes: 14 additions & 1 deletion x-pack/plugins/endpoint/common/generate_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export class EndpointDocGenerator {
process: {
entity_id: options.entityID ? options.entityID : this.randomString(10),
parent: options.parentEntityID ? { entity_id: options.parentEntityID } : undefined,
name: options.processName ? options.processName : 'powershell.exe',
name: options.processName ? options.processName : randomProcessName(),
},
};
}
Expand Down Expand Up @@ -720,3 +720,16 @@ export class EndpointDocGenerator {
]);
}
}

const fakeProcessNames = [
'lsass.exe',
'notepad.exe',
'mimikatz.exe',
'powershell.exe',
'iexlorer.exe',
'explorer.exe',
];
/** Return a random fake process name */
function randomProcessName(): string {
return fakeProcessNames[Math.floor(Math.random() * fakeProcessNames.length)];
}
33 changes: 25 additions & 8 deletions x-pack/plugins/endpoint/common/models/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,45 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EndpointEvent, LegacyEndpointEvent } from '../types';
import { LegacyEndpointEvent, ResolverEvent } from '../types';

export function isLegacyEvent(
event: EndpointEvent | LegacyEndpointEvent
): event is LegacyEndpointEvent {
export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEvent {
return (event as LegacyEndpointEvent).endgame !== undefined;
}

export function eventTimestamp(
event: EndpointEvent | LegacyEndpointEvent
): string | undefined | number {
export function eventTimestamp(event: ResolverEvent): string | undefined | number {
if (isLegacyEvent(event)) {
return event.endgame.timestamp_utc;
} else {
return event['@timestamp'];
}
}

export function eventName(event: EndpointEvent | LegacyEndpointEvent): string {
export function eventName(event: ResolverEvent): string {
if (isLegacyEvent(event)) {
return event.endgame.process_name ? event.endgame.process_name : '';
} else {
return event.process.name;
}
}

export function eventId(event: ResolverEvent): string {
if (isLegacyEvent(event)) {
return event.endgame.serial_event_id ? String(event.endgame.serial_event_id) : '';
}
return event.event.id;
}

export function entityId(event: ResolverEvent): string {
if (isLegacyEvent(event)) {
return event.endgame.unique_pid ? String(event.endgame.unique_pid) : '';
}
return event.process.entity_id;
}

export function parentEntityId(event: ResolverEvent): string | undefined {
if (isLegacyEvent(event)) {
return event.endgame.unique_ppid ? String(event.endgame.unique_ppid) : undefined;
}
return event.process.parent?.entity_id;
}
59 changes: 59 additions & 0 deletions x-pack/plugins/endpoint/common/schema/resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { schema } from '@kbn/config-schema';

/**
* Used to validate GET requests for a complete resolver tree.
*/
export const validateTree = {
params: schema.object({ id: schema.string() }),
query: schema.object({
children: schema.number({ defaultValue: 10, min: 0, max: 100 }),
generations: schema.number({ defaultValue: 3, min: 0, max: 3 }),
ancestors: schema.number({ defaultValue: 3, min: 0, max: 5 }),
events: schema.number({ defaultValue: 100, min: 0, max: 1000 }),
afterEvent: schema.maybe(schema.string()),
afterChild: schema.maybe(schema.string()),
legacyEndpointID: schema.maybe(schema.string()),
}),
};

/**
* Used to validate GET requests for non process events for a specific event.
*/
export const validateEvents = {
params: schema.object({ id: schema.string() }),
query: schema.object({
events: schema.number({ defaultValue: 100, min: 1, max: 1000 }),
afterEvent: schema.maybe(schema.string()),
legacyEndpointID: schema.maybe(schema.string()),
}),
};

/**
* Used to validate GET requests for the ancestors of a process event.
*/
export const validateAncestry = {
params: schema.object({ id: schema.string() }),
query: schema.object({
ancestors: schema.number({ defaultValue: 0, min: 0, max: 10 }),
legacyEndpointID: schema.maybe(schema.string()),
}),
};

/**
* Used to validate GET requests for children of a specified process event.
*/
export const validateChildren = {
params: schema.object({ id: schema.string() }),
query: schema.object({
children: schema.number({ defaultValue: 10, min: 10, max: 100 }),
generations: schema.number({ defaultValue: 3, min: 0, max: 3 }),
afterChild: schema.maybe(schema.string()),
legacyEndpointID: schema.maybe(schema.string()),
}),
};
25 changes: 25 additions & 0 deletions x-pack/plugins/endpoint/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
*/
export type AlertAPIOrdering = 'asc' | 'desc';

export interface ResolverNodeStats {
totalEvents: number;
totalAlerts: number;
}

export interface ResolverNodePagination {
nextChild?: string | null;
nextEvent?: string | null;
nextAncestor?: string | null;
nextAlert?: string | null;
}

/**
* A node that contains pointers to other nodes, arrrays of resolver events, and any metadata associated with resolver specific data
*/
export interface ResolverNode {
id: string;
children: ResolverNode[];
events: ResolverEvent[];
lifecycle: ResolverEvent[];
ancestors?: ResolverNode[];
pagination: ResolverNodePagination;
stats?: ResolverNodeStats;
}

/**
* Returned by 'api/endpoint/alerts'
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ export const AlertDetailResolver = styled(
width: 100%;
display: flex;
flex-grow: 1;
min-height: 500px;
/* gross demo hack */
min-height: calc(100vh - 505px);
`;
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import { ResolverEvent } from '../../../../../common/types';

interface ServerReturnedResolverData {
readonly type: 'serverReturnedResolverData';
readonly payload: {
readonly data: {
readonly result: {
readonly search_results: readonly ResolverEvent[];
};
};
};
readonly payload: ResolverEvent[];
}

export type DataAction = ServerReturnedResolverData;
interface ServerFailedToReturnResolverData {
readonly type: 'serverFailedToReturnResolverData';
}

export type DataAction = ServerReturnedResolverData | ServerFailedToReturnResolverData;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Store, createStore } from 'redux';
import { DataAction } from './action';
import { dataReducer } from './reducer';
import { DataState } from '../../types';
import { LegacyEndpointEvent } from '../../../../../common/types';
import { LegacyEndpointEvent, ResolverEvent } from '../../../../../common/types';
import { graphableProcesses, processNodePositionsAndEdgeLineSegments } from './selectors';
import { mockProcessEvent } from '../../models/process_event_test_helpers';

Expand Down Expand Up @@ -113,13 +113,7 @@ describe('resolver graph layout', () => {
});
describe('when rendering no nodes', () => {
beforeEach(() => {
const payload = {
data: {
result: {
search_results: [],
},
},
};
const payload: ResolverEvent[] = [];
const action: DataAction = { type: 'serverReturnedResolverData', payload };
store.dispatch(action);
});
Expand All @@ -133,13 +127,7 @@ describe('resolver graph layout', () => {
});
describe('when rendering one node', () => {
beforeEach(() => {
const payload = {
data: {
result: {
search_results: [processA],
},
},
};
const payload = [processA];
const action: DataAction = { type: 'serverReturnedResolverData', payload };
store.dispatch(action);
});
Expand All @@ -153,13 +141,7 @@ describe('resolver graph layout', () => {
});
describe('when rendering two nodes, one being the parent of the other', () => {
beforeEach(() => {
const payload = {
data: {
result: {
search_results: [processA, processB],
},
},
};
const payload = [processA, processB];
const action: DataAction = { type: 'serverReturnedResolverData', payload };
store.dispatch(action);
});
Expand All @@ -173,23 +155,17 @@ describe('resolver graph layout', () => {
});
describe('when rendering two forks, and one fork has an extra long tine', () => {
beforeEach(() => {
const payload = {
data: {
result: {
search_results: [
processA,
processB,
processC,
processD,
processE,
processF,
processG,
processH,
processI,
],
},
},
};
const payload = [
processA,
processB,
processC,
processD,
processE,
processF,
processG,
processH,
processI,
];
const action: DataAction = { type: 'serverReturnedResolverData', payload };
store.dispatch(action);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,28 @@ function initialState(): DataState {
return {
results: [],
isLoading: false,
hasError: false,
};
}

export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialState(), action) => {
if (action.type === 'serverReturnedResolverData') {
const {
data: {
result: { search_results },
},
} = action.payload;
return {
...state,
results: search_results,
results: action.payload,
isLoading: false,
hasError: false,
};
} else if (action.type === 'appRequestedResolverData') {
return {
...state,
isLoading: true,
hasError: false,
};
} else if (action.type === 'serverFailedToReturnResolverData') {
return {
...state,
hasError: true,
};
} else {
return state;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export function isLoading(state: DataState) {
return state.isLoading;
}

export function hasError(state: DataState) {
return state.hasError;
}

/**
* An isometric projection is a method for representing three dimensional objects in 2 dimensions.
* More information about isometric projections can be found here https://en.wikipedia.org/wiki/Isometric_projection.
Expand Down Expand Up @@ -293,7 +297,7 @@ function* levelOrderWithWidths(
metadata.firstChildWidth = width;
} else {
const firstChildWidth = widths.get(siblings[0]);
const lastChildWidth = widths.get(siblings[0]);
const lastChildWidth = widths.get(siblings[siblings.length - 1]);
if (firstChildWidth === undefined || lastChildWidth === undefined) {
/**
* All widths have been precalcluated, so this will not happen.
Expand Down
Loading

0 comments on commit 254757b

Please sign in to comment.