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] Merge/restyle nodes table (#69098) #69588

Merged
merged 1 commit into from
Jun 18, 2020
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
56 changes: 53 additions & 3 deletions x-pack/plugins/security_solution/common/endpoint/generate_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ interface EventOptions {
eventType?: string;
eventCategory?: string | string[];
processName?: string;
pid?: number;
parentPid?: number;
extensions?: object;
}

const Windows: HostOS[] = [
Expand Down Expand Up @@ -452,12 +455,36 @@ export class EndpointDocGenerator {
* @param options - Allows event field values to be specified
*/
public generateEvent(options: EventOptions = {}): EndpointEvent {
const processName = options.processName ? options.processName : randomProcessName();
const detailRecordForEventType =
options.extensions ||
((eventCategory) => {
if (eventCategory === 'registry') {
return { registry: { key: `HKLM/Windows/Software/${this.randomString(5)}` } };
}
if (eventCategory === 'network') {
return {
network: {
direction: this.randomChoice(['inbound', 'outbound']),
forwarded_ip: `${this.randomIP()}`,
},
};
}
if (eventCategory === 'file') {
return { file: { path: 'C:\\My Documents\\business\\January\\processName' } };
}
if (eventCategory === 'dns') {
return { dns: { question: { name: `${this.randomIP()}` } } };
}
return {};
})(options.eventCategory);
return {
'@timestamp': options.timestamp ? options.timestamp : new Date().getTime(),
agent: { ...this.commonInfo.agent, type: 'endpoint' },
ecs: {
version: '1.4.0',
},
...detailRecordForEventType,
event: {
category: options.eventCategory ? options.eventCategory : 'process',
kind: 'event',
Expand All @@ -466,9 +493,30 @@ export class EndpointDocGenerator {
},
host: this.commonInfo.host,
process: {
pid:
'pid' in options && typeof options.pid !== 'undefined' ? options.pid : this.randomN(5000),
executable: `C:\\${processName}`,
args: `"C:\\${processName}" \\${this.randomString(3)}`,
code_signature: {
status: 'trusted',
subject_name: 'Microsoft',
},
hash: { md5: this.seededUUIDv4() },
entity_id: options.entityID ? options.entityID : this.randomString(10),
parent: options.parentEntityID ? { entity_id: options.parentEntityID } : undefined,
name: options.processName ? options.processName : randomProcessName(),
parent: options.parentEntityID
? {
entity_id: options.parentEntityID,
pid:
'parentPid' in options && typeof options.parentPid !== 'undefined'
? options.parentPid
: this.randomN(5000),
}
: undefined,
name: processName,
},
user: {
domain: this.randomString(10),
name: this.randomString(10),
},
};
}
Expand Down Expand Up @@ -701,6 +749,8 @@ export class EndpointDocGenerator {
ancestor = this.generateEvent({
timestamp,
parentEntityID: ancestor.process.entity_id,
parentPid: ancestor.process.pid,
pid: this.randomN(5000),
});
events.push(ancestor);
timestamp = timestamp + 1000;
Expand Down Expand Up @@ -1126,7 +1176,7 @@ export class EndpointDocGenerator {
return [...this.randomNGenerator(255, 6)].map((x) => x.toString(16)).join('-');
}

private randomIP(): string {
public randomIP(): string {
return [10, ...this.randomNGenerator(255, 3)].map((x) => x.toString()).join('.');
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 } from '../generate_data';
import { descriptiveName } from './event';

describe('Event descriptive names', () => {
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` });
});

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 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 });
});
});
92 changes: 92 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/models/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,95 @@ export function parentEntityId(event: ResolverEvent): string | undefined {
}
return event.process.parent?.entity_id;
}

/**
* @param event The event to get the category for
*/
export function primaryEventCategory(event: ResolverEvent): string | undefined {
// Returning "Process" as a catch-all here because it seems pretty general
if (isLegacyEvent(event)) {
const legacyFullType = event.endgame.event_type_full;
if (legacyFullType) {
return legacyFullType;
}
} else {
const eventCategories = event.event.category;
const category = typeof eventCategories === 'string' ? eventCategories : eventCategories[0];

return category;
}
}

/**
* ECS event type will be things like 'creation', 'deletion', 'access', etc.
* see: https://www.elastic.co/guide/en/ecs/current/ecs-event.html
* @param event The ResolverEvent to get the ecs type for
*/
export function ecsEventType(event: ResolverEvent): Array<string | undefined> {
if (isLegacyEvent(event)) {
return [event.endgame.event_subtype_full];
}
return typeof event.event.type === 'string' ? [event.event.type] : event.event.type;
}

/**
* #Descriptive Names For Related Events:
*
* The following section provides facilities for deriving **Descriptive Names** for ECS-compliant event data.
* There are drawbacks to trying to do this: It *will* require ongoing maintenance. It presents temptations to overarticulate.
* On balance, however, it seems that the benefit of giving the user some form of information they can recognize & scan outweighs the drawbacks.
*/
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
/**
* Based on the ECS category of the event, attempt to provide a more descriptive name
* (e.g. the `event.registry.key` for `registry` or the `dns.question.name` for `dns`, etc.).
* This function returns the data in the form of `{subject, descriptor}` where `subject` will
* tend to be the more distinctive term (e.g. 137.213.212.7 for a network event) and the
* `descriptor` can be used to present more useful/meaningful view (e.g. `inbound 137.213.212.7`
* in the example above).
* see: https://www.elastic.co/guide/en/ecs/current/ecs-field-reference.html
* @param event The ResolverEvent to get the descriptive name for
* @returns { descriptiveName } An attempt at providing a readable name to the user
*/
export function descriptiveName(event: ResolverEvent): { subject: string; descriptor?: string } {
if (isLegacyEvent(event)) {
return { subject: eventName(event) };
}

// To be somewhat defensive, we'll check for the presence of these.
const partialEvent: DeepPartial<ResolverEvent> = event;

/**
* This list of attempts can be expanded/adjusted as the underlying model changes over time:
*/

// Stable, per ECS 1.5: https://www.elastic.co/guide/en/ecs/current/ecs-allowed-values-event-category.html

if (partialEvent.network?.forwarded_ip) {
return {
subject: String(partialEvent.network?.forwarded_ip),
descriptor: String(partialEvent.network?.direction),
};
}

if (partialEvent.file?.path) {
return {
subject: String(partialEvent.file?.path),
};
}

// Extended categories (per ECS 1.5):
const pathOrKey = partialEvent.registry?.path || partialEvent.registry?.key;
if (pathOrKey) {
return {
subject: String(pathOrKey),
};
}

if (partialEvent.dns?.question?.name) {
return { subject: String(partialEvent.dns?.question?.name) };
}

// Fall back on entityId if we can't fish a more descriptive name out.
return { subject: entityId(event) };
}
24 changes: 24 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,14 +438,38 @@ export interface EndpointEvent {
kind: string;
};
host: Host;
network?: {
direction: unknown;
forwarded_ip: unknown;
};
dns?: {
question: { name: unknown };
};
process: {
entity_id: string;
name: string;
executable?: string;
args?: string;
code_signature?: {
status?: string;
subject_name: string;
};
pid?: number;
hash?: {
md5: string;
};
parent?: {
entity_id: string;
name?: string;
pid?: number;
};
};
user?: {
domain?: string;
name: string;
};
file?: { path: unknown };
registry?: { path: unknown; key: unknown };
}

export type ResolverEvent = EndpointEvent | LegacyEndpointEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,39 @@ import {
} from '../../../common/endpoint_alerts/types';
import { ImmutableMiddlewareFactory } from '../../common/store';
import { cloneHttpFetchQuery } from '../../common/utils/clone_http_fetch_query';

import {
isOnAlertPage,
apiQueryParams,
hasSelectedAlert,
uiQueryParams,
hasSelectedAlert,
isAlertPageTabChange,
} from './selectors';

export const alertMiddlewareFactory: ImmutableMiddlewareFactory<AlertListState> = (
coreStart,
depsStart
) => {
let lastSelectedAlert: string | null = null;
/**
* @returns <boolean> true once per change of `selectedAlert` in query params.
*
* As opposed to `hasSelectedAlert` which always returns true if the alert is present
* query params, which can cause unnecessary requests and re-renders in some cases.
*/
const selectedAlertHasChanged = (params: ReturnType<typeof uiQueryParams>): boolean => {
const { selected_alert: selectedAlert } = params;
const shouldNotChange = selectedAlert === lastSelectedAlert;
if (shouldNotChange) {
return false;
}
if (typeof selectedAlert !== 'string') {
return false;
}
lastSelectedAlert = selectedAlert;
return true;
};

async function fetchIndexPatterns(): Promise<IIndexPattern[]> {
const { indexPatterns } = depsStart.data;
const fields = await indexPatterns.getFieldsForWildcard({
Expand All @@ -50,7 +71,7 @@ export const alertMiddlewareFactory: ImmutableMiddlewareFactory<AlertListState>
});
api.dispatch({ type: 'serverReturnedAlertsData', payload: listResponse });

if (hasSelectedAlert(state)) {
if (hasSelectedAlert(state) && selectedAlertHasChanged(uiQueryParams(state))) {
const uiParams = uiQueryParams(state);
const detailsResponse: AlertDetails = await coreStart.http.get(
`/api/endpoint/alerts/${uiParams.selected_alert}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ export function factory(processes: ResolverEvent[]): IndexedProcessTree {
currentProcessAdjacencyMap.parent = uniqueParentPid;
}
} else {
idToChildren.set(uniqueParentPid, [process]);

if (uniqueParentPid) {
idToChildren.set(uniqueParentPid, [process]);
/**
* Get the parent's map, otherwise set an empty one
*/
Expand Down
Loading