From 51e773fad22e5b2222a31917395ab3cbfed07e39 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 Oct 2021 11:37:03 -0400 Subject: [PATCH] [Security Solution] Improves the formatting of array values and JSON in the Event and Alert Details panels (#115141) (#115532) ## [Security Solution] Improves the formatting of array values and JSON in the Event and Alert Details panels This PR improves the formatting of array values and JSON in the Event and Alert details panels by: - in the `Table` tab, formatting array values such that each value appears on a separate line, (instead of joining the values on a single line) - in the `JSON` tab, displaying the raw search hit JSON, instead displaying a JSON representation based on the `Fields` API ### Table value formatting In the Event and Alert details `Table` tab, array values were joined on a single line, as shown in the _before_ screenshot below: ![event-details-value-formatting-before](https://user-images.githubusercontent.com/4459398/137524968-6450cd73-3154-457d-b850-32a3e7faaab2.png) _Above: (before) array values were joined on a single line_ Array values are now formatted such that each value appears on a separate line, as shown in the _after_ screenshot below: ![event-details-value-formatting-after](https://user-images.githubusercontent.com/4459398/137436705-b0bec735-5a83-402e-843a-2776e1c80da9.png) _Above: (after) array values each appear on a separte line_ ### JSON formatting The `JSON` tab previously displayed a JSON representation based on the `Fields` API. Array values were previously represented as a joined string, as shown in the _before_ screenshot below: ![event-details-json-formatting-before](https://user-images.githubusercontent.com/4459398/137525039-d1b14f21-5f9c-4201-905e-8b08f00bb5a0.png) _Above: (before) array values were previously represented as a joined string_ The `JSON` tab now displays the raw search hit JSON, per the _after_ screenshot below: ![event-details-json-formatting-after](https://user-images.githubusercontent.com/4459398/137437257-330c5b49-a4ad-418e-a976-923f7a35c0cf.png) _Above: (after) the `JSON` tab displays the raw search hit_ CC @monina-n @paulewing Co-authored-by: Andrew Goldstein --- .../detection_alerts/alerts_details.spec.ts | 20 +- .../detection_alerts/cti_enrichments.spec.ts | 49 ++- .../cypress/screens/alerts_details.ts | 2 + .../alert_summary_view.test.tsx.snap | 120 ++++-- .../__snapshots__/json_view.test.tsx.snap | 343 ++++++++++++++++-- .../event_details/event_details.test.tsx | 3 +- .../event_details/event_details.tsx | 6 +- .../event_details/json_view.test.tsx | 49 +-- .../components/event_details/json_view.tsx | 23 +- .../table/field_value_cell.test.tsx | 193 ++++++++++ .../event_details/table/field_value_cell.tsx | 26 +- .../public/common/mock/mock_detail_item.ts | 188 ++++++++++ .../event_details/expandable_event.tsx | 3 + .../side_panel/event_details/index.tsx | 4 +- .../timelines/containers/details/index.tsx | 7 +- .../timeline/events/details/index.ts | 1 + .../timeline/factory/events/details/index.ts | 4 + 17 files changed, 872 insertions(+), 169 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 8908adb42f202..d211a5914656b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { ALERT_FLYOUT, CELL_TEXT, JSON_LINES, TABLE_ROWS } from '../../screens/alerts_details'; +import { ALERT_FLYOUT, CELL_TEXT, JSON_TEXT, TABLE_ROWS } from '../../screens/alerts_details'; import { expandFirstAlert, waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded, } from '../../tasks/alerts'; -import { openJsonView, openTable, scrollJsonViewToBottom } from '../../tasks/alerts_details'; +import { openJsonView, openTable } from '../../tasks/alerts_details'; import { createCustomRuleActivated } from '../../tasks/api_calls/rules'; import { cleanKibana } from '../../tasks/common'; import { esArchiverLoad } from '../../tasks/es_archiver'; @@ -36,20 +36,14 @@ describe('Alert details with unmapped fields', () => { }); it('Displays the unmapped field on the JSON view', () => { - const expectedUnmappedField = { line: 2, text: ' "unmapped": "This is the unmapped field"' }; + const expectedUnmappedValue = 'This is the unmapped field'; openJsonView(); - scrollJsonViewToBottom(); - cy.get(ALERT_FLYOUT) - .find(JSON_LINES) - .then((elements) => { - const length = elements.length; - cy.wrap(elements) - .eq(length - expectedUnmappedField.line) - .invoke('text') - .should('include', expectedUnmappedField.text); - }); + cy.get(JSON_TEXT).then((x) => { + const parsed = JSON.parse(x.text()); + expect(parsed._source.unmapped).to.equal(expectedUnmappedValue); + }); }); it('Displays the unmapped field on the table', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts index b3c6abcd8e426..f15e7adbbca44 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts @@ -10,7 +10,7 @@ import { cleanKibana, reload } from '../../tasks/common'; import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { - JSON_LINES, + JSON_TEXT, TABLE_CELL, TABLE_ROWS, THREAT_DETAILS_VIEW, @@ -28,11 +28,7 @@ import { viewThreatIntelTab, } from '../../tasks/alerts'; import { createCustomIndicatorRule } from '../../tasks/api_calls/rules'; -import { - openJsonView, - openThreatIndicatorDetails, - scrollJsonViewToBottom, -} from '../../tasks/alerts_details'; +import { openJsonView, openThreatIndicatorDetails } from '../../tasks/alerts_details'; import { ALERTS_URL } from '../../urls/navigation'; import { addsFieldsToTimeline } from '../../tasks/rule_details'; @@ -76,26 +72,39 @@ describe('CTI Enrichment', () => { it('Displays persisted enrichments on the JSON view', () => { const expectedEnrichment = [ - { line: 4, text: ' "threat": {' }, { - line: 3, - text: ' "enrichments": "{\\"indicator\\":{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"logs-ti_abusech.malware\\",\\"type\\":\\"indicator_match_rule\\"}}"', + indicator: { + first_seen: '2021-03-10T08:02:14.000Z', + file: { + size: 80280, + pe: {}, + type: 'elf', + hash: { + sha256: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', + tlsh: '6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE', + ssdeep: + '1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL', + md5: '9b6c3518a91d23ed77504b5416bfb5b3', + }, + }, + type: 'file', + }, + matched: { + atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', + field: 'myhash.mysha256', + id: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f', + index: 'logs-ti_abusech.malware', + type: 'indicator_match_rule', + }, }, - { line: 2, text: ' }' }, ]; expandFirstAlert(); openJsonView(); - scrollJsonViewToBottom(); - - cy.get(JSON_LINES).then((elements) => { - const length = elements.length; - expectedEnrichment.forEach((enrichment) => { - cy.wrap(elements) - .eq(length - enrichment.line) - .invoke('text') - .should('include', enrichment.text); - }); + + cy.get(JSON_TEXT).then((x) => { + const parsed = JSON.parse(x.text()); + expect(parsed._source.threat.enrichments).to.deep.equal(expectedEnrichment); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts index c740a669d059a..584fba05452f0 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts @@ -28,6 +28,8 @@ export const JSON_LINES = '.euiCodeBlock__line'; export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]'; +export const JSON_TEXT = '[data-test-subj="jsonView"]'; + export const TABLE_CELL = '.euiTableRowCell'; export const TABLE_TAB = '[data-test-subj="tableTab"]'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap index d367c68586be1..930e1282ebca5 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap @@ -138,12 +138,17 @@ exports[`AlertSummaryView Behavior event code renders additional summary rows 1` class="euiTableCellContent flyoutOverviewDescription euiTableCellContent--overflowingContent" >
- open +
+ open +
- xxx +
+ xxx +
- low +
+ low +
- 21 +
+ 21 +
- windows-native +
+ windows-native +
- administrator +
+ administrator +
- open +
+ open +
- xxx +
+ xxx +
- low +
+ low +
- 21 +
+ 21 +
- windows-native +
+ windows-native +
- administrator +
+ administrator +
{ - "_id": "pEMaMmkBUV60JmNWmWVi", - "_index": "filebeat-8.0.0-2019.02.19-000001", + "_index": ".ds-logs-endpoint.events.network-default-2021.09.28-000001", + "_id": "TUWyf3wBFCFU0qRJTauW", "_score": 1, - "_type": "_doc", - "@timestamp": "2019-02-28T16:50:54.621Z", - "agent": { - "ephemeral_id": "9d391ef2-a734-4787-8891-67031178c641", - "hostname": "siem-kibana", - "id": "5de03d5f-52f3-482e-91d4-853c7de073c3", - "type": "filebeat", - "version": "8.0.0" - }, - "cloud": { - "availability_zone": "projects/189716325846/zones/us-east1-b", - "instance": { - "id": "5412578377715150143", - "name": "siem-kibana" + "_source": { + "agent": { + "id": "2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624", + "type": "endpoint", + "version": "8.0.0-SNAPSHOT" }, - "machine": { - "type": "projects/189716325846/machineTypes/n1-standard-1" + "process": { + "Ext": { + "ancestry": [ + "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=", + "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA==" + ] + }, + "name": "filebeat", + "pid": 22535, + "entity_id": "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA=", + "executable": "/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat" }, - "project": { - "id": "elastic-beats" + "destination": { + "address": "127.0.0.1", + "port": 9200, + "ip": "127.0.0.1" }, - "provider": "gce" - }, - "destination": { - "bytes": 584, - "ip": "10.47.8.200", - "packets": 4, - "port": 902 + "source": { + "address": "127.0.0.1", + "port": 54146, + "ip": "127.0.0.1" + }, + "message": "Endpoint network event", + "network": { + "transport": "tcp", + "type": "ipv4" + }, + "@timestamp": "2021-10-14T16:45:58.0310772Z", + "ecs": { + "version": "1.11.0" + }, + "data_stream": { + "namespace": "default", + "type": "logs", + "dataset": "endpoint.events.network" + }, + "elastic": { + "agent": { + "id": "12345" + } + }, + "host": { + "hostname": "test-linux-1", + "os": { + "Ext": { + "variant": "Debian" + }, + "kernel": "4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)", + "name": "Linux", + "family": "debian", + "type": "linux", + "version": "10", + "platform": "debian", + "full": "Debian 10" + }, + "ip": [ + "127.0.0.1", + "::1", + "10.1.2.3", + "2001:0DB8:AC10:FE01::" + ], + "name": "test-linux-1", + "id": "76ea303129f249aa7382338e4263eac1", + "mac": [ + "aa:bb:cc:dd:ee:ff" + ], + "architecture": "x86_64" + }, + "event": { + "agent_id_status": "verified", + "sequence": 44872, + "ingested": "2021-10-14T16:46:04Z", + "created": "2021-10-14T16:45:58.0310772Z", + "kind": "event", + "module": "endpoint", + "action": "connection_attempted", + "id": "MKPXftjGeHiQzUNj++++nn6R", + "category": [ + "network" + ], + "type": [ + "start" + ], + "dataset": "endpoint.events.network", + "outcome": "unknown" + }, + "user": { + "Ext": { + "real": { + "name": "root", + "id": 0 + } + }, + "name": "root", + "id": 0 + }, + "group": { + "Ext": { + "real": { + "name": "root", + "id": 0 + } + }, + "name": "root", + "id": 0 + } }, - "event": { - "kind": "event" + "fields": { + "host.os.full.text": [ + "Debian 10" + ], + "event.category": [ + "network" + ], + "process.name.text": [ + "filebeat" + ], + "host.os.name.text": [ + "Linux" + ], + "host.os.full": [ + "Debian 10" + ], + "host.hostname": [ + "test-linux-1" + ], + "process.pid": [ + 22535 + ], + "host.mac": [ + "42:01:0a:c8:00:32" + ], + "elastic.agent.id": [ + "abcdefg-f6d5-4ce6-915d-8f1f8f413624" + ], + "host.os.version": [ + "10" + ], + "host.os.name": [ + "Linux" + ], + "source.ip": [ + "127.0.0.1" + ], + "destination.address": [ + "127.0.0.1" + ], + "host.name": [ + "test-linux-1" + ], + "event.agent_id_status": [ + "verified" + ], + "event.kind": [ + "event" + ], + "event.outcome": [ + "unknown" + ], + "group.name": [ + "root" + ], + "user.id": [ + "0" + ], + "host.os.type": [ + "linux" + ], + "process.Ext.ancestry": [ + "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=", + "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA==" + ], + "user.Ext.real.id": [ + "0" + ], + "data_stream.type": [ + "logs" + ], + "host.architecture": [ + "x86_64" + ], + "process.name": [ + "filebeat" + ], + "agent.id": [ + "2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624" + ], + "source.port": [ + 54146 + ], + "ecs.version": [ + "1.11.0" + ], + "event.created": [ + "2021-10-14T16:45:58.031Z" + ], + "agent.version": [ + "8.0.0-SNAPSHOT" + ], + "host.os.family": [ + "debian" + ], + "destination.port": [ + 9200 + ], + "group.id": [ + "0" + ], + "user.name": [ + "root" + ], + "source.address": [ + "127.0.0.1" + ], + "process.entity_id": [ + "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA=" + ], + "host.ip": [ + "127.0.0.1", + "::1", + "10.1.2.3", + "2001:0DB8:AC10:FE01::" + ], + "process.executable.caseless": [ + "/opt/elastic/agent/data/elastic-agent-058c40/install/filebeat-8.0.0-snapshot-linux-x86_64/filebeat" + ], + "event.sequence": [ + 44872 + ], + "agent.type": [ + "endpoint" + ], + "process.executable.text": [ + "/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat" + ], + "group.Ext.real.name": [ + "root" + ], + "event.module": [ + "endpoint" + ], + "host.os.kernel": [ + "4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)" + ], + "host.os.full.caseless": [ + "debian 10" + ], + "host.id": [ + "76ea303129f249aa7382338e4263eac1" + ], + "process.name.caseless": [ + "filebeat" + ], + "network.type": [ + "ipv4" + ], + "process.executable": [ + "/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat" + ], + "user.Ext.real.name": [ + "root" + ], + "data_stream.namespace": [ + "default" + ], + "message": [ + "Endpoint network event" + ], + "destination.ip": [ + "127.0.0.1" + ], + "network.transport": [ + "tcp" + ], + "host.os.Ext.variant": [ + "Debian" + ], + "group.Ext.real.id": [ + "0" + ], + "event.ingested": [ + "2021-10-14T16:46:04.000Z" + ], + "event.action": [ + "connection_attempted" + ], + "@timestamp": [ + "2021-10-14T16:45:58.031Z" + ], + "host.os.platform": [ + "debian" + ], + "data_stream.dataset": [ + "endpoint.events.network" + ], + "event.type": [ + "start" + ], + "event.id": [ + "MKPXftjGeHiQzUNj++++nn6R" + ], + "host.os.name.caseless": [ + "linux" + ], + "event.dataset": [ + "endpoint.events.network" + ], + "user.name.text": [ + "root" + ] } } diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index a8ba536a75541..37ca3b0b897a6 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import '../../mock/match_media'; import '../../mock/react_beautiful_dnd'; -import { mockDetailItemData, mockDetailItemDataId, TestProviders } from '../../mock'; +import { mockDetailItemData, mockDetailItemDataId, rawEventData, TestProviders } from '../../mock'; import { EventDetails, EventsViewType } from './event_details'; import { mockBrowserFields } from '../../containers/source/mock'; @@ -48,6 +48,7 @@ describe('EventDetails', () => { timelineId: 'test', eventView: EventsViewType.summaryView, hostRisk: { fields: [], loading: true }, + rawEventData, }; const alertsProps = { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index e7092d9d6f466..a8305a635f157 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -61,6 +61,7 @@ interface Props { id: string; isAlert: boolean; isDraggable?: boolean; + rawEventData: object | undefined; timelineTabType: TimelineTabs | 'flyout'; timelineId: string; hostRisk: HostRisk | null; @@ -106,6 +107,7 @@ const EventDetailsComponent: React.FC = ({ id, isAlert, isDraggable, + rawEventData, timelineId, timelineTabType, hostRisk, @@ -278,12 +280,12 @@ const EventDetailsComponent: React.FC = ({ <> - + ), }), - [data] + [rawEventData] ); const tabs = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx index 696fac6016603..b20270266602d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx @@ -8,58 +8,15 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { mockDetailItemData } from '../../mock'; +import { rawEventData } from '../../mock'; -import { buildJsonView, JsonView } from './json_view'; +import { JsonView } from './json_view'; describe('JSON View', () => { describe('rendering', () => { test('should match snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); - - describe('buildJsonView', () => { - test('should match a json', () => { - const expectedData = { - '@timestamp': '2019-02-28T16:50:54.621Z', - _id: 'pEMaMmkBUV60JmNWmWVi', - _index: 'filebeat-8.0.0-2019.02.19-000001', - _score: 1, - _type: '_doc', - agent: { - ephemeral_id: '9d391ef2-a734-4787-8891-67031178c641', - hostname: 'siem-kibana', - id: '5de03d5f-52f3-482e-91d4-853c7de073c3', - type: 'filebeat', - version: '8.0.0', - }, - cloud: { - availability_zone: 'projects/189716325846/zones/us-east1-b', - instance: { - id: '5412578377715150143', - name: 'siem-kibana', - }, - machine: { - type: 'projects/189716325846/machineTypes/n1-standard-1', - }, - project: { - id: 'elastic-beats', - }, - provider: 'gce', - }, - destination: { - bytes: 584, - ip: '10.47.8.200', - packets: 4, - port: 902, - }, - event: { - kind: 'event', - }, - }; - expect(buildJsonView(mockDetailItemData)).toEqual(expectedData); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx index 0614f131bcd10..0227d44f32305 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx @@ -6,15 +6,13 @@ */ import { EuiCodeBlock } from '@elastic/eui'; -import { set } from '@elastic/safer-lodash-set/fp'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { omitTypenameAndEmpty } from '../../../timelines/components/timeline/body/helpers'; interface Props { - data: TimelineEventsDetailsItem[]; + rawEventData: object | undefined; } const EuiCodeEditorContainer = styled.div` @@ -23,15 +21,15 @@ const EuiCodeEditorContainer = styled.div` } `; -export const JsonView = React.memo(({ data }) => { +export const JsonView = React.memo(({ rawEventData }) => { const value = useMemo( () => JSON.stringify( - buildJsonView(data), + rawEventData, omitTypenameAndEmpty, 2 // indent level ), - [data] + [rawEventData] ); return ( @@ -50,16 +48,3 @@ export const JsonView = React.memo(({ data }) => { }); JsonView.displayName = 'JsonView'; - -export const buildJsonView = (data: TimelineEventsDetailsItem[]) => - data - .sort((a, b) => a.field.localeCompare(b.field)) - .reduce( - (accumulator, item) => - set( - item.field, - Array.isArray(item.originalValue) ? item.originalValue.join() : item.originalValue, - accumulator - ), - {} - ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx new file mode 100644 index 0000000000000..f6c43da2da8ac --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { BrowserField } from '../../../containers/source'; +import { FieldValueCell } from './field_value_cell'; +import { TestProviders } from '../../../mock'; +import { EventFieldsData } from '../types'; + +const contextId = 'test'; + +const eventId = 'TUWyf3wBFCFU0qRJTauW'; + +const hostIpData: EventFieldsData = { + aggregatable: true, + ariaRowindex: 35, + category: 'host', + description: 'Host ip addresses.', + example: '127.0.0.1', + field: 'host.ip', + fields: {}, + format: '', + indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], + isObjectArray: false, + name: 'host.ip', + originalValue: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], + searchable: true, + type: 'ip', + values: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], +}; +const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', 'fe80::4001:aff:fec8:32']; + +describe('FieldValueCell', () => { + describe('common behavior', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it formats multiple values such that each value is displayed on a single line', () => { + expect(screen.getByTestId(`event-field-${hostIpData.field}`)).toHaveClass( + 'euiFlexGroup--directionColumn' + ); + }); + }); + + describe('when `BrowserField` metadata is NOT available', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it renders each of the expected values when `fieldFromBrowserField` is undefined', () => { + hostIpValues.forEach((value) => { + expect(screen.getByText(value)).toBeInTheDocument(); + }); + }); + + test('it renders values formatted as plain text (without `eventFieldsTable__fieldValue` formatting)', () => { + expect(screen.getByTestId(`event-field-${hostIpData.field}`).firstChild).not.toHaveClass( + 'eventFieldsTable__fieldValue' + ); + }); + }); + + describe('`message` field formatting', () => { + const messageData: EventFieldsData = { + aggregatable: false, + ariaRowindex: 50, + category: 'base', + description: + 'For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.', + example: 'Hello World', + field: 'message', + fields: {}, + format: '', + indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], + isObjectArray: false, + name: 'message', + originalValue: ['Endpoint network event'], + searchable: true, + type: 'string', + values: ['Endpoint network event'], + }; + const messageValues = ['Endpoint network event']; + + const messageFieldFromBrowserField: BrowserField = { + aggregatable: false, + category: 'base', + description: + 'For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.', + example: 'Hello World', + fields: {}, + format: '', + indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], + name: 'message', + searchable: true, + type: 'string', + }; + + beforeEach(() => { + render( + + + + ); + }); + + test('it renders special formatting for the `message` field', () => { + expect(screen.getByTestId('event-field-message')).toBeInTheDocument(); + }); + + test('it renders the expected message value', () => { + messageValues.forEach((value) => { + expect(screen.getByText(value)).toBeInTheDocument(); + }); + }); + }); + + describe('when `BrowserField` metadata IS available', () => { + const hostIpFieldFromBrowserField: BrowserField = { + aggregatable: true, + category: 'host', + description: 'Host ip addresses.', + example: '127.0.0.1', + fields: {}, + format: '', + indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], + name: 'host.ip', + searchable: true, + type: 'ip', + }; + + beforeEach(() => { + render( + + + + ); + }); + + test('it renders values formatted with the expected class', () => { + expect(screen.getByTestId(`event-field-${hostIpData.field}`).firstChild).toHaveClass( + 'eventFieldsTable__fieldValue' + ); + }); + + test('it renders link buttons for each of the host ip addresses', () => { + expect(screen.getAllByRole('button').length).toBe(hostIpValues.length); + }); + + test('it renders each of the expected values when `fieldFromBrowserField` is provided', () => { + hostIpValues.forEach((value) => { + expect(screen.getByText(value)).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx index fc20f84d3650d..dc6c84b8138fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { BrowserField } from '../../../containers/source'; import { OverflowField } from '../../tables/helpers'; import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field'; @@ -36,18 +36,28 @@ export const FieldValueCell = React.memo( values, }: FieldValueCellProps) => { return ( -
+ {values != null && values.map((value, i) => { if (fieldFromBrowserField == null) { return ( - - {value} - + + + {value} + + ); } return ( -
+ {data.field === MESSAGE_FIELD_NAME ? ( ) : ( @@ -63,10 +73,10 @@ export const FieldValueCell = React.memo( linkValue={(getLinkValue && getLinkValue(data.field)) ?? linkValue} /> )} -
+ ); })} -
+ ); } ); diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts index 3712d389edeb1..035bdbbceff88 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts +++ b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts @@ -139,3 +139,191 @@ export const generateMockDetailItemData = (): TimelineEventsDetailsItem[] => [ ]; export const mockDetailItemData: TimelineEventsDetailsItem[] = generateMockDetailItemData(); + +export const rawEventData = { + _index: '.ds-logs-endpoint.events.network-default-2021.09.28-000001', + _id: 'TUWyf3wBFCFU0qRJTauW', + _score: 1, + _source: { + agent: { + id: '2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624', + type: 'endpoint', + version: '8.0.0-SNAPSHOT', + }, + process: { + Ext: { + ancestry: [ + 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=', + 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA==', + ], + }, + name: 'filebeat', + pid: 22535, + entity_id: 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA=', + executable: + '/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat', + }, + destination: { + address: '127.0.0.1', + port: 9200, + ip: '127.0.0.1', + }, + source: { + address: '127.0.0.1', + port: 54146, + ip: '127.0.0.1', + }, + message: 'Endpoint network event', + network: { + transport: 'tcp', + type: 'ipv4', + }, + '@timestamp': '2021-10-14T16:45:58.0310772Z', + ecs: { + version: '1.11.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'endpoint.events.network', + }, + elastic: { + agent: { + id: '12345', + }, + }, + host: { + hostname: 'test-linux-1', + os: { + Ext: { + variant: 'Debian', + }, + kernel: '4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)', + name: 'Linux', + family: 'debian', + type: 'linux', + version: '10', + platform: 'debian', + full: 'Debian 10', + }, + ip: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], + name: 'test-linux-1', + id: '76ea303129f249aa7382338e4263eac1', + mac: ['aa:bb:cc:dd:ee:ff'], + architecture: 'x86_64', + }, + event: { + agent_id_status: 'verified', + sequence: 44872, + ingested: '2021-10-14T16:46:04Z', + created: '2021-10-14T16:45:58.0310772Z', + kind: 'event', + module: 'endpoint', + action: 'connection_attempted', + id: 'MKPXftjGeHiQzUNj++++nn6R', + category: ['network'], + type: ['start'], + dataset: 'endpoint.events.network', + outcome: 'unknown', + }, + user: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + group: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + }, + fields: { + 'host.os.full.text': ['Debian 10'], + 'event.category': ['network'], + 'process.name.text': ['filebeat'], + 'host.os.name.text': ['Linux'], + 'host.os.full': ['Debian 10'], + 'host.hostname': ['test-linux-1'], + 'process.pid': [22535], + 'host.mac': ['42:01:0a:c8:00:32'], + 'elastic.agent.id': ['abcdefg-f6d5-4ce6-915d-8f1f8f413624'], + 'host.os.version': ['10'], + 'host.os.name': ['Linux'], + 'source.ip': ['127.0.0.1'], + 'destination.address': ['127.0.0.1'], + 'host.name': ['test-linux-1'], + 'event.agent_id_status': ['verified'], + 'event.kind': ['event'], + 'event.outcome': ['unknown'], + 'group.name': ['root'], + 'user.id': ['0'], + 'host.os.type': ['linux'], + 'process.Ext.ancestry': [ + 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=', + 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA==', + ], + 'user.Ext.real.id': ['0'], + 'data_stream.type': ['logs'], + 'host.architecture': ['x86_64'], + 'process.name': ['filebeat'], + 'agent.id': ['2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624'], + 'source.port': [54146], + 'ecs.version': ['1.11.0'], + 'event.created': ['2021-10-14T16:45:58.031Z'], + 'agent.version': ['8.0.0-SNAPSHOT'], + 'host.os.family': ['debian'], + 'destination.port': [9200], + 'group.id': ['0'], + 'user.name': ['root'], + 'source.address': ['127.0.0.1'], + 'process.entity_id': [ + 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA=', + ], + 'host.ip': ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], + 'process.executable.caseless': [ + '/opt/elastic/agent/data/elastic-agent-058c40/install/filebeat-8.0.0-snapshot-linux-x86_64/filebeat', + ], + 'event.sequence': [44872], + 'agent.type': ['endpoint'], + 'process.executable.text': [ + '/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat', + ], + 'group.Ext.real.name': ['root'], + 'event.module': ['endpoint'], + 'host.os.kernel': ['4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)'], + 'host.os.full.caseless': ['debian 10'], + 'host.id': ['76ea303129f249aa7382338e4263eac1'], + 'process.name.caseless': ['filebeat'], + 'network.type': ['ipv4'], + 'process.executable': [ + '/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat', + ], + 'user.Ext.real.name': ['root'], + 'data_stream.namespace': ['default'], + message: ['Endpoint network event'], + 'destination.ip': ['127.0.0.1'], + 'network.transport': ['tcp'], + 'host.os.Ext.variant': ['Debian'], + 'group.Ext.real.id': ['0'], + 'event.ingested': ['2021-10-14T16:46:04.000Z'], + 'event.action': ['connection_attempted'], + '@timestamp': ['2021-10-14T16:45:58.031Z'], + 'host.os.platform': ['debian'], + 'data_stream.dataset': ['endpoint.events.network'], + 'event.type': ['start'], + 'event.id': ['MKPXftjGeHiQzUNj++++nn6R'], + 'host.os.name.caseless': ['linux'], + 'event.dataset': ['endpoint.events.network'], + 'user.name.text': ['root'], + }, +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index 17d43d80a5a9a..6a7f0602c3675 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -33,6 +33,7 @@ interface Props { isDraggable?: boolean; loading: boolean; messageHeight?: number; + rawEventData: object | undefined; timelineTabType: TimelineTabs | 'flyout'; timelineId: string; hostRisk: HostRisk | null; @@ -93,6 +94,7 @@ export const ExpandableEvent = React.memo( loading, detailsData, hostRisk, + rawEventData, }) => { if (!event.eventId) { return {i18n.EVENT_DETAILS_PLACEHOLDER}; @@ -111,6 +113,7 @@ export const ExpandableEvent = React.memo( id={event.eventId} isAlert={isAlert} isDraggable={isDraggable} + rawEventData={rawEventData} timelineId={timelineId} timelineTabType={timelineTabType} hostRisk={hostRisk} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index f8786e0706834..b9d7e0a8c024f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -79,7 +79,7 @@ const EventDetailsPanelComponent: React.FC = ({ tabType, timelineId, }) => { - const [loading, detailsData] = useTimelineEventsDetails({ + const [loading, detailsData, rawEventData] = useTimelineEventsDetails({ docValueFields, entityType, indexName: expandedEvent.indexName ?? '', @@ -195,6 +195,7 @@ const EventDetailsPanelComponent: React.FC = ({ isAlert={isAlert} isDraggable={isDraggable} loading={loading} + rawEventData={rawEventData} timelineId={timelineId} timelineTabType="flyout" hostRisk={hostRisk} @@ -228,6 +229,7 @@ const EventDetailsPanelComponent: React.FC = ({ isAlert={isAlert} isDraggable={isDraggable} loading={loading} + rawEventData={rawEventData} timelineId={timelineId} timelineTabType={tabType} hostRisk={hostRisk} diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx index e59eaeed4f2a6..f05966bd97870 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx @@ -42,7 +42,7 @@ export const useTimelineEventsDetails = ({ indexName, eventId, skip, -}: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData']] => { +}: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData'], object | undefined] => { const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); @@ -55,6 +55,8 @@ export const useTimelineEventsDetails = ({ const [timelineDetailsResponse, setTimelineDetailsResponse] = useState(null); + const [rawEventData, setRawEventData] = useState(undefined); + const timelineDetailsSearch = useCallback( (request: TimelineEventsDetailsRequestOptions | null) => { if (request == null || skip || isEmpty(request.eventId)) { @@ -78,6 +80,7 @@ export const useTimelineEventsDetails = ({ if (isCompleteResponse(response)) { setLoading(false); setTimelineDetailsResponse(response.data || []); + setRawEventData(response.rawResponse.hits.hits[0]); searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); @@ -125,5 +128,5 @@ export const useTimelineEventsDetails = ({ }; }, [timelineDetailsRequest, timelineDetailsSearch]); - return [loading, timelineDetailsResponse]; + return [loading, timelineDetailsResponse, rawEventData]; }; diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts index 5bceb31081687..f9f6a2ea57917 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/details/index.ts @@ -24,6 +24,7 @@ export interface TimelineEventsDetailsItem { export interface TimelineEventsDetailsStrategyResponse extends IEsSearchResponse { data?: Maybe; inspect?: Maybe; + rawEventData?: Maybe; } export interface TimelineEventsDetailsRequestOptions diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts index c82d9af938a98..b60add2515ec9 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts @@ -57,10 +57,14 @@ export const timelineEventsDetails: TimelineFactory