From 89373490d0dcdc5dd51c33cb4a85b03ebeaf7a86 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Thu, 4 Mar 2021 00:24:58 -0500 Subject: [PATCH] Display multiple copyable fields for process.args in resolver node detail panel (#93280) --- .../common/endpoint/models/event.ts | 4 ++-- .../public/resolver/mocks/endpoint_event.ts | 2 +- .../public/resolver/view/panel.test.tsx | 19 +++++++++++---- .../resolver/view/panels/node_detail.tsx | 24 ++++++++++++++++--- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/models/event.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.ts index 692c1d3757b8a..9c102e77b6946 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/event.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/event.ts @@ -160,12 +160,12 @@ export function md5HashForProcess(event: SafeResolverEvent): string | undefined /** * First non-null value for the `event.process.args` field. */ -export function argsForProcess(event: SafeResolverEvent): string | undefined { +export function argsForProcess(event: SafeResolverEvent): string[] | undefined { if (isLegacyEventSafeVersion(event)) { // There is not currently a key for this on Legacy event types return undefined; } - return firstNonNullValue(event.process?.args); + return values(event.process?.args); } /** diff --git a/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts b/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts index 04541189683a2..59089af4a22f5 100644 --- a/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts +++ b/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts @@ -51,7 +51,7 @@ export function mockEndpointEvent({ process: { entity_id: entityID, executable: 'executable', - args: 'args', + args: ['args0', 'args1', 'args2'], name: processName, pid, hash: { diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx index eeb9d65cd6ace..332f806b59ec7 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx @@ -34,7 +34,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and /** * These are the details we expect to see in the node detail view when the origin is selected. */ - const originEventDetailEntries: ReadonlyMap = new Map([ + const originEventDetailEntries: Array<[string, string]> = [ ['@timestamp', 'Sep 23, 2020 @ 08:25:32.316'], ['process.executable', 'executable'], ['process.pid', '0'], @@ -42,8 +42,10 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and ['user.domain', 'user.domain'], ['process.parent.pid', '0'], ['process.hash.md5', 'hash.md5'], - ['process.args', 'args'], - ]); + ['process.args', 'args0'], + ['process.args', 'args1'], + ['process.args', 'args2'], + ]; beforeEach(() => { // create a mock data access layer @@ -129,11 +131,16 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and describe.each([...originEventDetailEntries])( 'when the user hovers over the description for the field (%p) with their mouse', (fieldTitleText, value) => { + // If there are multiple values for a field, i.e. an array, this is the index for the value we are testing. + const entryIndex = originEventDetailEntries + .filter(([fieldName]) => fieldName === fieldTitleText) + .findIndex(([_, fieldValue]) => fieldValue === value); beforeEach(async () => { const dt = await simulator().resolveWrapper(() => { return simulator() .testSubject('resolver:node-detail:entry-title') - .filterWhere((title) => title.text() === fieldTitleText); + .filterWhere((title) => title.text() === fieldTitleText) + .at(entryIndex); }); expect(dt).toHaveLength(1); @@ -184,7 +191,9 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and ['user.domain', 'user.domain'], ['process.parent.pid', '0'], ['process.hash.md5', 'hash.md5'], - ['process.args', 'args'], + ['process.args', 'args0'], + ['process.args', 'args1'], + ['process.args', 'args2'], ]); }); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx index da52994c4e71e..ed3507d8f4bc3 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx @@ -122,8 +122,12 @@ const NodeDetailView = memo(function ({ description: eventModel.argsForProcess(processEvent), }; - // This is the data in {title, description} form for the EuiDescriptionList to display - const processDescriptionListData = [ + const flattenedEntries: Array<{ + title: string; + description: string | string[] | number | undefined; + }> = []; + + const flattenedDescriptionListData = [ createdEntry, pathEntry, pidEntry, @@ -132,7 +136,21 @@ const NodeDetailView = memo(function ({ parentPidEntry, md5Entry, commandLineEntry, - ] + ].reduce((flattenedList, entry) => { + if (Array.isArray(entry.description)) { + return [ + ...flattenedList, + ...entry.description.map((value) => { + return { title: entry.title, description: value }; + }), + ]; + } else { + return [...flattenedList, entry]; + } + }, flattenedEntries); + + // This is the data in {title, description} form for the EuiDescriptionList to display + const processDescriptionListData = flattenedDescriptionListData .filter((entry) => { return entry.description !== undefined; })