Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Endpoint] Recursive resolver children (#61914) #64960

Merged
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
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 @@ -307,7 +307,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 @@ -645,3 +645,16 @@ export class EndpointDocGenerator {
return uuid.v4({ random: [...this.randomNGenerator(255, 16)] });
}
}

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