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

Merge/restyle nodes table #69098

Merged
merged 82 commits into from
Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
39b3aa6
linted rebase
Jun 12, 2020
3596269
remove unused const
Jun 12, 2020
ab02953
Merge remote-tracking branch 'upstream/master' into merge/restyle-nod…
Jun 13, 2020
94b0125
fixing upstream conflicts
Jun 13, 2020
ebaa753
fix import
Jun 13, 2020
40ae3d7
Merge remote-tracking branch 'upstream/master' into merge/restyle-nod…
Jun 13, 2020
867b977
fix import / conflicts
Jun 13, 2020
23f8e6a
fix asset bug with process detail
Jun 13, 2020
b3c542f
fix conflicts: List related by type
Jun 13, 2020
5d096be
repair conflicts: related detail
Jun 13, 2020
10ee8ec
style conflict
Jun 13, 2020
0fa1f07
style touchups
Jun 13, 2020
3d16dab
add counts
Jun 13, 2020
23837dc
linting
Jun 13, 2020
8f6a903
delete unused view
Jun 13, 2020
74e9bec
fixing deps
Jun 13, 2020
680b152
lint
Jun 13, 2020
67eb433
that wasn't really a selector
Jun 14, 2020
f13aa66
comments and fixes
Jun 14, 2020
e3047dc
theming adjustments
Jun 14, 2020
e5ea5af
consistent counts
Jun 14, 2020
3f7a70f
lint
Jun 14, 2020
e618094
i18n domain fixes
Jun 14, 2020
877d168
React key warnings
Jun 14, 2020
d4202e9
fixing names
Jun 15, 2020
0ee1886
J. Buttner review: name changes for process
Jun 15, 2020
499d942
J Buttner review: annotate return type
Jun 15, 2020
03da2ea
lint
Jun 15, 2020
90a9fc1
M Olorunnisola review: change prop name to be more clear
Jun 15, 2020
5824100
M Olorunnisola review: fix idFromParams to be more radable
Jun 15, 2020
be839a2
M Olorunnisola review: more reable descriptiveName
Jun 15, 2020
96732ae
M Olorunnisola review: parameter readability in middleware
Jun 15, 2020
0548b07
J Buttner review: set domain as optional type
Jun 15, 2020
bef40b6
R Austin review: does not comport
Jun 15, 2020
6772323
J Buttner review: remove unnecessary pid generation
Jun 15, 2020
ee1f0e6
R Austin review: Use broader i18n input
Jun 15, 2020
7c7fd1f
R Austin review: i18n on list comp.
Jun 15, 2020
06c5896
Merge branch 'master' into merge/restyle-nodes-table
elasticmachine Jun 15, 2020
48347d1
R Austin review: remove stray line
Jun 16, 2020
54a451f
R Austin review: move check inside middleware
Jun 16, 2020
8b4a3b3
R Austin review: use String() instead
Jun 16, 2020
951ef24
R Austin review: return number instead
Jun 16, 2020
efb24f1
R Austin review: format compound message
Jun 16, 2020
532233a
R Austin review: recomport for readability
Jun 16, 2020
ef9b028
R Austin review: name to primaryEventCategory
Jun 17, 2020
cf1fff0
R Austin review: move formatting out of model
Jun 17, 2020
1659805
R Austin review: chip shot at model data
Jun 17, 2020
6ca1004
R Austin review: remove unnecessary guard
Jun 17, 2020
81a865d
R Austin review: prefer const
Jun 17, 2020
a7cdb88
R Austin review: reorganize/rename actions
Jun 17, 2020
2445aba
K Qualters review: fix pid/ppid references
Jun 17, 2020
28c8e92
R Austin review: decouple view selection from render
Jun 17, 2020
8862823
Bug fix: No undefined siblings
Jun 17, 2020
feeadf3
Bug fix: reference error
Jun 17, 2020
fd47388
R Austin review: use App- actions to sync state
Jun 17, 2020
07f9082
change descriptive name code to use deep partial
Jun 17, 2020
66ab251
TS type
Jun 17, 2020
0ca4fb1
K Qualters review: fix pid/ppid again
Jun 17, 2020
6d76057
R Austin review: fix ecsEventType
Jun 17, 2020
89bd6a8
R Austin review: refactor state change
Jun 17, 2020
1058e68
R Austin review: new action for missing relateds
Jun 17, 2020
318eba2
R Austin review: Add comments to exports
Jun 17, 2020
6c20672
R Austin review: Comment on aggregate totals
Jun 17, 2020
7a0c375
R Austin review: export BoldCode
Jun 17, 2020
a75d74d
R Austin review: add comment to description list helper
Jun 17, 2020
436ae12
R Austin review: debride event model function
Jun 17, 2020
7229e50
CI: Typecheck
Jun 17, 2020
cc5858a
R Austin review: allow 0 as pid
Jun 18, 2020
ef3a539
R Austin review: remove EndpointEvent cast
Jun 18, 2020
074bfc3
R Austin review: allow 0 as parentPid
Jun 18, 2020
44a4d56
R Austin review: simplify ppid return
Jun 18, 2020
531070a
R Austin review: make processPid consistent
Jun 18, 2020
56db019
test file got lost in rebase
Jun 18, 2020
b203e41
fix test
Jun 18, 2020
35e327c
Merge branch 'master' into merge/restyle-nodes-table
elasticmachine Jun 18, 2020
7cc3d72
CI: typecheck events
Jun 18, 2020
4fef503
CI: typecheck fix
Jun 18, 2020
ed73b46
R Austin review: spelling in translation titles
Jun 18, 2020
14db86c
R Austin review: spelling / i18n
Jun 18, 2020
03476a9
CI: dup i18n title
Jun 18, 2020
1c76b99
CI: dup i18n title
Jun 18, 2020
1f05fdd
CI: derped the name
Jun 18, 2020
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
54 changes: 50 additions & 4 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;
oatkiller marked this conversation as resolved.
Show resolved Hide resolved
}

const Windows: HostOS[] = [
Expand Down Expand Up @@ -446,12 +449,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 =
oatkiller marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -460,9 +487,26 @@ export class EndpointDocGenerator {
},
host: this.commonInfo.host,
process: {
pid: options.pid ? options.pid : this.randomN(5000),
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
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: options.parentPid ? options.parentPid : this.randomN(5000),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, I think 'parentPid' in options would be a better check here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
: undefined,
name: processName,
},
user: {
domain: this.randomString(10),
name: this.randomString(10),
},
};
}
Expand Down Expand Up @@ -633,7 +677,7 @@ export class EndpointDocGenerator {
): Event[] {
const events = [];
const startDate = new Date().getTime();
const root = this.generateEvent({ timestamp: startDate + 1000 });
const root = this.generateEvent({ timestamp: startDate + 1000, pid: this.randomN(5000) });
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
events.push(root);
let ancestor = root;
let timestamp = root['@timestamp'] + 1000;
Expand Down Expand Up @@ -668,6 +712,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 @@ -1072,7 +1118,7 @@ export class EndpointDocGenerator {
return [...this.randomNGenerator(255, 6)].map((x) => x.toString(16)).join('-');
}

private randomIP(): string {
public randomIP(): string {
oatkiller marked this conversation as resolved.
Show resolved Hide resolved
return [10, ...this.randomNGenerator(255, 3)].map((x) => x.toString()).join('.');
}

Expand Down
120 changes: 120 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,123 @@ export function parentEntityId(event: ResolverEvent): string | undefined {
}
return event.process.parent?.entity_id;
}

oatkiller marked this conversation as resolved.
Show resolved Hide resolved
/**
* @param event The event to get the category for
*/
export function eventCategory(event: ResolverEvent): string {
// Returning "Process" as a catch-all here because it seems pretty general
let eventCategoryToReturn: string = 'Process';
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
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] || '';
bkimmel marked this conversation as resolved.
Show resolved Hide resolved

if (category) {
eventCategoryToReturn = category;
}
}
return eventCategoryToReturn;
}

/**
* 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): string {
if (isLegacyEvent(event)) {
return event.endgame.event_subtype_full || '';
}
return typeof event.event.type === 'string' ? event.event.type : event.event.type.join('/');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of joining the array w/ /, maybe you could use some semantic HTML like an ol.

Copy link
Contributor Author

@bkimmel bkimmel Jun 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎗️ pull the join-y stuff out

}

/**
* #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.
*/

/**
* A type that annotates the basic requirements for giving the event a `descriptive name`
*/
type detailRecord<T> = T extends 'registry'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per our discussion, let's replace this w/ c14226e

? { path: unknown; key: unknown }
: T extends 'dns'
? { question: { name: unknown } }
: T extends 'network'
? { direction: unknown; forwarded_ip: unknown }
: T extends 'file'
? { path: unknown }
: unknown;
type ecsRecordWithType<T extends string> = Record<T, detailRecord<T>>;

/**
* Verify that the `ecsCategory` exists as a key on the event record. i.e. if ecsCategory is `registry`, the event is shaped like {registry: object}
*
* @param event
* @param ecsCategory
*/
export function isRecordNamable<T extends string>(
event: object,
ecsCategory: T
): event is ecsRecordWithType<T> {
// The goal here is to reach in and grab a better name for the event (if one exists) without being too obtrusive/assertive about ECS
return typeof (event as ecsRecordWithType<T>)[ecsCategory] === 'object';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following this. Let's talk it over maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well that sucks, because I was planning on asking you for help with this. Maybe I'll explain what's happening here better and see if we can come up with a more terse solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per our discussion: let's pair on this.

}

/**
* 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.).
* 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): string {
if (isLegacyEvent(event)) {
return eventName(event);
}
// For the purposes of providing a descriptive name, we're taking the first entry in the `event.type`
const ecsCategory = eventCategory(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 (ecsCategory === 'network' && isRecordNamable(event, ecsCategory)) {
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
if (event?.network?.forwarded_ip) {
return `${event?.network?.direction ? `${event?.network?.direction} ` : ''}${
event.network.forwarded_ip
}`;
}
}
if (ecsCategory === 'file' && isRecordNamable(event, ecsCategory)) {
if (event?.file?.path) {
return `${event.file.path}`;
}
}

// Extended categories (per ECS 1.5):
if (ecsCategory === 'registry' && isRecordNamable(event, ecsCategory)) {
const pathOrKey = event?.registry?.path || event?.registry?.key;
if (pathOrKey) {
return `${pathOrKey}`;
}
}
if (ecsCategory === 'dns' && isRecordNamable(event, ecsCategory)) {
if (event?.dns?.question?.name) {
return `${event.dns.question.name}`;
}
}

// Fall back on entityId if we can't fish a more descriptive name out.
return entityId(event);
}
15 changes: 15 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,26 @@ export interface EndpointEvent {
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;
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
name: string;
};
}

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

bkimmel marked this conversation as resolved.
Show resolved Hide resolved
import {
isOnAlertPage,
apiQueryParams,
hasSelectedAlert,
uiQueryParams,
hasSelectedAlert,
isAlertPageTabChange,
} from './selectors';

let lastSelectedAlert: string | null = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you move this into alertMiddlewareFactory so that the binding has a 1:1 relationship with middleware instances.

For example, each unit test in a suite might instantiate this middleware. The state of the middleware should be independent of previous test runs.

/**
* @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;
if (typeof selectedAlert !== 'string') {
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
if (selectedAlert === lastSelectedAlert) {
return false;
}
lastSelectedAlert = selectedAlert;
return true;
};

export const alertMiddlewareFactory: ImmutableMiddlewareFactory<AlertListState> = (
coreStart,
depsStart
Expand Down Expand Up @@ -53,7 +73,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 @@ -78,6 +78,17 @@ export function uniquePidForProcess(passedEvent: ResolverEvent): string {
}
}

/**
* Returns the pid for the process on the host
*/
export function processPid(passedEvent: ResolverEvent): string {
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
if (event.isLegacyEvent(passedEvent)) {
return String(passedEvent.endgame.unique_pid);
} else {
return String(passedEvent.process.pid);
}
}

/**
* Returns the process event's parent pid
*/
Expand All @@ -88,3 +99,62 @@ export function uniqueParentPidForProcess(passedEvent: ResolverEvent): string |
return passedEvent.process.parent?.entity_id;
}
}

/**
* Returns the process event's parent pid
*/
export function processParentPid(passedEvent: ResolverEvent): string | undefined {
if (event.isLegacyEvent(passedEvent)) {
return String(passedEvent.endgame.unique_ppid);
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
} else {
const ppid = passedEvent.process.parent?.pid;
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
return ppid ? String(ppid) : undefined;
}
}

/**
* Returns the process event's path on its host
*/
export function processPath(passedEvent: ResolverEvent): string | undefined {
if (event.isLegacyEvent(passedEvent)) {
return passedEvent.endgame.process_path;
} else {
return passedEvent.process.executable;
}
}

/**
* Returns the username for the account that ran the process
*/
export function userInfoForProcess(
passedEvent: ResolverEvent
): { user?: string; domain?: string } | undefined {
return passedEvent.user;
}

/**
* Returns the MD5 hash for the `passedEvent` param, or undefined if it can't be located
* @param {ResolverEvent} passedEvent The `ResolverEvent` to get the MD5 value for
* @returns {string | undefined} The MD5 string for the event
*/
export function md5HashForProcess(passedEvent: ResolverEvent): string | undefined {
if (event.isLegacyEvent(passedEvent)) {
// There is not currently a key for this on Legacy event types
return undefined;
}
return passedEvent?.process?.hash?.md5;
}

/**
* Returns the command line path and arguments used to run the `passedEvent` if any
*
* @param {ResolverEvent} passedEvent The `ResolverEvent` to get the arguemnts value for
* @returns {string | undefined} The arguments (including the path) used to run the process
*/
export function argsForProcess(passedEvent: ResolverEvent): string | undefined {
if (event.isLegacyEvent(passedEvent)) {
// There is not currently a key for this on Legacy event types
return undefined;
}
return passedEvent?.process?.args;
}
Loading