From df0c1d0c3c981d26a01fcc6d978e50768d73ed87 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Thu, 16 Jan 2020 17:13:39 -0700 Subject: [PATCH] [SIEM] Adds support for apm-* to the network map (#54876) ## Summary Resolves https://github.com/elastic/kibana/issues/52297, https://github.com/elastic/kibana/issues/52565 To improve the display of APM data within SIEM (specifically the `HTTP Table` and `Network Map`), this PR adds `apm-*-transcation*` to `siem:defaultIndex`, and additional support for showing `client`/`server` layers on the `Network Map` when a matching `apm-*` index pattern is present. The map now supports pattern matching when checking for available Kibana Index Patterns, and so matches `apm-*-transcation*` -> `apm-*` (if exists). Additionally, the map config was updated to generate layers for client/server geo fields (instead of the usual source/dest) since these are the fields Transactions use. ![image](https://user-images.githubusercontent.com/2946766/72573225-2a038880-3882-11ea-9590-a545d726dbf9.png) Screen Shot 2020-01-14 at 18 22 11 ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. - [ ] ~This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~ - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - Will work with @benskelker on updating the maps docs - [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios - [ ] ~This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~ ### For maintainers - [ ] ~This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~ - [ ] ~This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~ --- .../plugins/siem/default_index_pattern.ts | 1 + .../drag_drop_context_wrapper.test.tsx.snap | 1 + .../components/embeddables/__mocks__/mock.ts | 248 ++++++++++++++++++ .../components/embeddables/embedded_map.tsx | 12 +- .../embeddables/embedded_map_helpers.test.tsx | 64 ++++- .../embeddables/embedded_map_helpers.tsx | 24 ++ .../components/embeddables/map_config.test.ts | 54 +++- .../components/embeddables/map_config.ts | 116 ++++++-- .../line_tool_tip_content.test.tsx.snap | 50 ++++ .../line_tool_tip_content.test.tsx | 28 +- .../map_tool_tip/line_tool_tip_content.tsx | 19 +- .../components/embeddables/translations.ts | 56 ++++ .../public/components/embeddables/types.ts | 16 ++ .../__snapshots__/event_details.test.tsx.snap | 2 + .../__snapshots__/index.test.tsx.snap | 1 + .../suricata_row_renderer.test.tsx.snap | 1 + .../__snapshots__/zeek_details.test.tsx.snap | 1 + .../zeek_row_renderer.test.tsx.snap | 1 + 18 files changed, 661 insertions(+), 34 deletions(-) diff --git a/x-pack/legacy/plugins/siem/default_index_pattern.ts b/x-pack/legacy/plugins/siem/default_index_pattern.ts index 6719e245e0289..4d53aeb000c55 100644 --- a/x-pack/legacy/plugins/siem/default_index_pattern.ts +++ b/x-pack/legacy/plugins/siem/default_index_pattern.ts @@ -6,6 +6,7 @@ /** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ export const defaultIndexPattern = [ + 'apm-*-transaction*', 'auditbeat-*', 'endgame-*', 'filebeat-*', diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap index 666a8249c27d8..07cbd6dfe0370 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap @@ -365,6 +365,7 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts index 2a3e69787c05c..1f06385e12c94 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts @@ -5,11 +5,16 @@ */ import { IndexPatternMapping } from '../types'; +import { IndexPatternSavedObject } from '../../ml_popover/types'; export const mockIndexPatternIds: IndexPatternMapping[] = [ { title: 'filebeat-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' }, ]; +export const mockAPMIndexPatternIds: IndexPatternMapping[] = [ + { title: 'apm-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' }, +]; + export const mockSourceLayer = { sourceDescriptor: { id: 'uuid.v4()', @@ -113,6 +118,109 @@ export const mockDestinationLayer = { query: { query: '', language: 'kuery' }, }; +export const mockClientLayer = { + sourceDescriptor: { + id: 'uuid.v4()', + type: 'ES_SEARCH', + applyGlobalQuery: true, + geoField: 'client.geo.location', + filterByMapBounds: false, + tooltipProperties: [ + 'host.name', + 'client.ip', + 'client.domain', + 'client.geo.country_iso_code', + 'client.as.organization.name', + ], + useTopHits: false, + topHitsTimeField: '@timestamp', + topHitsSize: 1, + indexPatternId: '8c7323ac-97ad-4b53-ac0a-40f8f691a918', + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: '#6092C0' }, + }, + lineColor: { + type: 'STATIC', + options: { color: '#FFFFFF' }, + }, + lineWidth: { type: 'STATIC', options: { size: 2 } }, + iconSize: { type: 'STATIC', options: { size: 8 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbol: { + options: { symbolizeAs: 'icon', symbolId: 'home' }, + }, + }, + }, + id: 'uuid.v4()', + label: `apm-* | Client Point`, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + type: 'VECTOR', + query: { query: '', language: 'kuery' }, + joins: [], +}; + +export const mockServerLayer = { + sourceDescriptor: { + id: 'uuid.v4()', + type: 'ES_SEARCH', + applyGlobalQuery: true, + geoField: 'server.geo.location', + filterByMapBounds: true, + tooltipProperties: [ + 'host.name', + 'server.ip', + 'server.domain', + 'server.geo.country_iso_code', + 'server.as.organization.name', + ], + useTopHits: false, + topHitsTimeField: '@timestamp', + topHitsSize: 1, + indexPatternId: '8c7323ac-97ad-4b53-ac0a-40f8f691a918', + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: '#D36086' }, + }, + lineColor: { + type: 'STATIC', + options: { color: '#FFFFFF' }, + }, + lineWidth: { type: 'STATIC', options: { size: 2 } }, + iconSize: { type: 'STATIC', options: { size: 8 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbol: { + options: { symbolizeAs: 'icon', symbolId: 'marker' }, + }, + }, + }, + id: 'uuid.v4()', + label: `apm-* | Server Point`, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + type: 'VECTOR', + query: { query: '', language: 'kuery' }, +}; + export const mockLineLayer = { sourceDescriptor: { type: 'ES_PEW_PEW', @@ -173,6 +281,66 @@ export const mockLineLayer = { query: { query: '', language: 'kuery' }, }; +export const mockClientServerLineLayer = { + sourceDescriptor: { + type: 'ES_PEW_PEW', + applyGlobalQuery: true, + id: 'uuid.v4()', + indexPatternId: '8c7323ac-97ad-4b53-ac0a-40f8f691a918', + sourceGeoField: 'client.geo.location', + destGeoField: 'server.geo.location', + metrics: [ + { type: 'sum', field: 'client.bytes', label: 'client.bytes' }, + { type: 'sum', field: 'server.bytes', label: 'server.bytes' }, + ], + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: '#1EA593' }, + }, + lineColor: { + type: 'STATIC', + options: { color: '#6092C0' }, + }, + lineWidth: { + type: 'DYNAMIC', + options: { + field: { + label: 'count', + name: 'doc_count', + origin: 'source', + }, + minSize: 1, + maxSize: 8, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, + }, + }, + iconSize: { type: 'STATIC', options: { size: 10 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbol: { + options: { symbolizeAs: 'circle', symbolId: 'airfield' }, + }, + }, + }, + id: 'uuid.v4()', + label: `apm-* | Line`, + minZoom: 0, + maxZoom: 24, + alpha: 0.5, + visible: true, + type: 'VECTOR', + query: { query: '', language: 'kuery' }, +}; + export const mockLayerList = [ { sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true }, @@ -209,3 +377,83 @@ export const mockLayerListDouble = [ mockDestinationLayer, mockSourceLayer, ]; + +export const mockLayerListMixed = [ + { + sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true }, + id: 'uuid.v4()', + label: null, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + style: null, + type: 'VECTOR_TILE', + }, + mockLineLayer, + mockDestinationLayer, + mockSourceLayer, + mockClientServerLineLayer, + mockServerLayer, + mockClientLayer, +]; + +export const mockAPMIndexPattern: IndexPatternSavedObject = { + id: 'apm-*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'apm-*', + }, +}; + +export const mockAPMRegexIndexPattern: IndexPatternSavedObject = { + id: 'apm-7.*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'apm-7.*', + }, +}; + +export const mockFilebeatIndexPattern: IndexPatternSavedObject = { + id: 'filebeat-*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'filebeat-*', + }, +}; + +export const mockAuditbeatIndexPattern: IndexPatternSavedObject = { + id: 'auditbeat-*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'auditbeat-*', + }, +}; + +export const mockAPMTransactionIndexPattern: IndexPatternSavedObject = { + id: 'apm-*-transaction*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'apm-*-transaction*', + }, +}; + +export const mockGlobIndexPattern: IndexPatternSavedObject = { + id: '*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: '*', + }, +}; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index 771e220a2a0b3..cbbb4f8c6249e 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -18,7 +18,7 @@ import { Loader } from '../loader'; import { displayErrorToast, useStateToaster } from '../toasters'; import { Embeddable } from './embeddable'; import { EmbeddableHeader } from './embeddable_header'; -import { createEmbeddable } from './embedded_map_helpers'; +import { createEmbeddable, findMatchingIndexPatterns } from './embedded_map_helpers'; import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; @@ -107,10 +107,12 @@ export const EmbeddedMapComponent = ({ useEffect(() => { let isSubscribed = true; async function setupEmbeddable() { - // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import - const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => - siemDefaultIndices.includes(ip.attributes.title) - ); + // Ensure at least one `siem:defaultIndex` kibana index pattern exists before creating embeddable + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns, + siemDefaultIndices, + }); + if (matchingIndexPatterns.length === 0 && isSubscribed) { setIsLoading(false); setIsIndexError(true); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx index a83e8377deeb6..0ffb13cd66028 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx @@ -4,9 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createEmbeddable } from './embedded_map_helpers'; +import { createEmbeddable, findMatchingIndexPatterns } from './embedded_map_helpers'; import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; import { createPortalNode } from 'react-reverse-portal'; +import { + mockAPMIndexPattern, + mockAPMRegexIndexPattern, + mockAPMTransactionIndexPattern, + mockAuditbeatIndexPattern, + mockFilebeatIndexPattern, + mockGlobIndexPattern, +} from './__mocks__/mock'; jest.mock('ui/new_platform'); @@ -51,4 +59,58 @@ describe('embedded_map_helpers', () => { expect(embeddable.reload).toHaveBeenCalledTimes(1); }); }); + + describe('findMatchingIndexPatterns', () => { + const siemDefaultIndices = [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ]; + + test('finds exact matching index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockFilebeatIndexPattern, mockAuditbeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockFilebeatIndexPattern, mockAuditbeatIndexPattern]); + }); + + test('finds glob-matched index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockAPMIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockAPMIndexPattern, mockFilebeatIndexPattern]); + }); + + test('does not find glob-matched index pattern containing regex', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockAPMRegexIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockFilebeatIndexPattern]); + }); + + test('finds exact glob-matched index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockAPMTransactionIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([ + mockAPMTransactionIndexPattern, + mockFilebeatIndexPattern, + ]); + }); + + test('finds glob-only index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockGlobIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockGlobIndexPattern, mockFilebeatIndexPattern]); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx index 838e74cc5624c..2d4714401f3b3 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx @@ -7,6 +7,7 @@ import uuid from 'uuid'; import React from 'react'; import { OutPortal, PortalNode } from 'react-reverse-portal'; +import minimatch from 'minimatch'; import { ViewMode } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { IndexPatternMapping, @@ -20,6 +21,7 @@ import { getLayerList } from './map_config'; import { MAP_SAVED_OBJECT_TYPE } from '../../../../maps/common/constants'; import * as i18n from './translations'; import { Query, esFilters } from '../../../../../../../src/plugins/data/public'; +import { IndexPatternSavedObject } from '../ml_popover/types'; /** * Creates MapEmbeddable with provided initial configuration @@ -108,3 +110,25 @@ export const createEmbeddable = async ( return embeddableObject; }; + +/** + * Returns kibanaIndexPatterns that wildcard match at least one of siemDefaultIndices + * + * @param kibanaIndexPatterns + * @param siemDefaultIndices + */ +export const findMatchingIndexPatterns = ({ + kibanaIndexPatterns, + siemDefaultIndices, +}: { + kibanaIndexPatterns: IndexPatternSavedObject[]; + siemDefaultIndices: string[]; +}): IndexPatternSavedObject[] => { + try { + return kibanaIndexPatterns.filter(kip => + siemDefaultIndices.some(sdi => minimatch(sdi, kip.attributes.title)) + ); + } catch { + return []; + } +}; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts index 4004db5f21795..1707293ff6fd8 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts @@ -4,13 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getDestinationLayer, getLayerList, getLineLayer, getSourceLayer } from './map_config'; +import { getDestinationLayer, getLayerList, getLineLayer, getSourceLayer, lmc } from './map_config'; import { + mockAPMIndexPatternIds, + mockClientLayer, + mockClientServerLineLayer, mockDestinationLayer, mockIndexPatternIds, mockLayerList, mockLayerListDouble, + mockLayerListMixed, mockLineLayer, + mockServerLayer, mockSourceLayer, } from './__mocks__/mock'; @@ -32,29 +37,70 @@ describe('map_config', () => { const layerList = getLayerList([...mockIndexPatternIds, ...mockIndexPatternIds]); expect(layerList).toStrictEqual(mockLayerListDouble); }); + + test('it returns the complete layerList for multiple indices with custom layer mapping', () => { + const layerList = getLayerList([...mockIndexPatternIds, ...mockAPMIndexPatternIds]); + expect(layerList).toStrictEqual(mockLayerListMixed); + }); }); describe('#getSourceLayer', () => { test('it returns a source layer', () => { - const layerList = getSourceLayer(mockIndexPatternIds[0].title, mockIndexPatternIds[0].id); + const layerList = getSourceLayer( + mockIndexPatternIds[0].title, + mockIndexPatternIds[0].id, + lmc.default.source + ); expect(layerList).toStrictEqual(mockSourceLayer); }); + + test('it returns a source layer for custom layer mapping', () => { + const layerList = getSourceLayer( + mockAPMIndexPatternIds[0].title, + mockAPMIndexPatternIds[0].id, + lmc[mockAPMIndexPatternIds[0].title].source + ); + expect(layerList).toStrictEqual(mockClientLayer); + }); }); describe('#getDestinationLayer', () => { test('it returns a destination layer', () => { const layerList = getDestinationLayer( mockIndexPatternIds[0].title, - mockIndexPatternIds[0].id + mockIndexPatternIds[0].id, + lmc.default.destination ); expect(layerList).toStrictEqual(mockDestinationLayer); }); + + test('it returns a destination layer for custom layer mapping', () => { + const layerList = getDestinationLayer( + mockAPMIndexPatternIds[0].title, + mockAPMIndexPatternIds[0].id, + lmc[mockAPMIndexPatternIds[0].title].destination + ); + expect(layerList).toStrictEqual(mockServerLayer); + }); }); describe('#getLineLayer', () => { test('it returns a line layer', () => { - const layerList = getLineLayer(mockIndexPatternIds[0].title, mockIndexPatternIds[0].id); + const layerList = getLineLayer( + mockIndexPatternIds[0].title, + mockIndexPatternIds[0].id, + lmc.default + ); expect(layerList).toStrictEqual(mockLineLayer); }); + + test('it returns a line layer for custom layer mapping', () => { + const layerList = getLineLayer( + mockAPMIndexPatternIds[0].title, + mockAPMIndexPatternIds[0].id, + lmc[mockAPMIndexPatternIds[0].title] + ); + expect(layerList).toStrictEqual(mockClientServerLineLayer); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts index f2d4505ffd1bf..f34376421e9b2 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts @@ -6,11 +6,16 @@ import uuid from 'uuid'; import { euiPaletteColorBlind } from '@elastic/eui'; -import { IndexPatternMapping } from './types'; +import { + IndexPatternMapping, + LayerMapping, + LayerMappingCollection, + LayerMappingDetails, +} from './types'; import * as i18n from './translations'; const euiVisColorPalette = euiPaletteColorBlind(); -// Update source/destination field mappings to modify what fields will be returned to map tooltip +// Update field mappings to modify what fields will be returned to map tooltip const sourceFieldMappings: Record = { 'host.name': i18n.HOST, 'source.ip': i18n.SOURCE_IP, @@ -25,16 +30,67 @@ const destinationFieldMappings: Record = { 'destination.geo.country_iso_code': i18n.LOCATION, 'destination.as.organization.name': i18n.ASN, }; +const clientFieldMappings: Record = { + 'host.name': i18n.HOST, + 'client.ip': i18n.CLIENT_IP, + 'client.domain': i18n.CLIENT_DOMAIN, + 'client.geo.country_iso_code': i18n.LOCATION, + 'client.as.organization.name': i18n.ASN, +}; +const serverFieldMappings: Record = { + 'host.name': i18n.HOST, + 'server.ip': i18n.SERVER_IP, + 'server.domain': i18n.SERVER_DOMAIN, + 'server.geo.country_iso_code': i18n.LOCATION, + 'server.as.organization.name': i18n.ASN, +}; // Mapping of field -> display name for use within map tooltip export const sourceDestinationFieldMappings: Record = { ...sourceFieldMappings, ...destinationFieldMappings, + ...clientFieldMappings, + ...serverFieldMappings, }; // Field names of LineLayer props returned from Maps API export const SUM_OF_SOURCE_BYTES = 'sum_of_source.bytes'; export const SUM_OF_DESTINATION_BYTES = 'sum_of_destination.bytes'; +export const SUM_OF_CLIENT_BYTES = 'sum_of_client.bytes'; +export const SUM_OF_SERVER_BYTES = 'sum_of_server.bytes'; + +// Mapping to fields for creating specific layers for a given index pattern +// e.g. The apm-* index pattern needs layers for client/server instead of source/destination +export const lmc: LayerMappingCollection = { + default: { + source: { + metricField: 'source.bytes', + geoField: 'source.geo.location', + tooltipProperties: Object.keys(sourceFieldMappings), + label: i18n.SOURCE_LAYER, + }, + destination: { + metricField: 'destination.bytes', + geoField: 'destination.geo.location', + tooltipProperties: Object.keys(destinationFieldMappings), + label: i18n.DESTINATION_LAYER, + }, + }, + 'apm-*': { + source: { + metricField: 'client.bytes', + geoField: 'client.geo.location', + tooltipProperties: Object.keys(clientFieldMappings), + label: i18n.CLIENT_LAYER, + }, + destination: { + metricField: 'server.bytes', + geoField: 'server.geo.location', + tooltipProperties: Object.keys(serverFieldMappings), + label: i18n.SERVER_LAYER, + }, + }, +}; /** * Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source, @@ -58,9 +114,9 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { ...indexPatternIds.reduce((acc: object[], { title, id }) => { return [ ...acc, - getLineLayer(title, id), - getDestinationLayer(title, id), - getSourceLayer(title, id), + getLineLayer(title, id, lmc[title] ?? lmc.default), + getDestinationLayer(title, id, lmc[title]?.destination ?? lmc.default.destination), + getSourceLayer(title, id, lmc[title]?.source ?? lmc.default.source), ]; }, []), ]; @@ -72,15 +128,20 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Source point" * @param indexPatternId used as layer's indexPattern to query for data + * @param layerDetails layer-specific field details */ -export const getSourceLayer = (indexPatternTitle: string, indexPatternId: string) => ({ +export const getSourceLayer = ( + indexPatternTitle: string, + indexPatternId: string, + layerDetails: LayerMappingDetails +) => ({ sourceDescriptor: { id: uuid.v4(), type: 'ES_SEARCH', applyGlobalQuery: true, - geoField: 'source.geo.location', + geoField: layerDetails.geoField, filterByMapBounds: false, - tooltipProperties: Object.keys(sourceFieldMappings), + tooltipProperties: layerDetails.tooltipProperties, useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, @@ -109,7 +170,7 @@ export const getSourceLayer = (indexPatternTitle: string, indexPatternId: string }, }, id: uuid.v4(), - label: `${indexPatternTitle} | ${i18n.SOURCE_LAYER}`, + label: `${indexPatternTitle} | ${layerDetails.label}`, minZoom: 0, maxZoom: 24, alpha: 1, @@ -125,15 +186,21 @@ export const getSourceLayer = (indexPatternTitle: string, indexPatternId: string * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Destination point" * @param indexPatternId used as layer's indexPattern to query for data + * @param layerDetails layer-specific field details + * */ -export const getDestinationLayer = (indexPatternTitle: string, indexPatternId: string) => ({ +export const getDestinationLayer = ( + indexPatternTitle: string, + indexPatternId: string, + layerDetails: LayerMappingDetails +) => ({ sourceDescriptor: { id: uuid.v4(), type: 'ES_SEARCH', applyGlobalQuery: true, - geoField: 'destination.geo.location', + geoField: layerDetails.geoField, filterByMapBounds: true, - tooltipProperties: Object.keys(destinationFieldMappings), + tooltipProperties: layerDetails.tooltipProperties, useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, @@ -162,7 +229,7 @@ export const getDestinationLayer = (indexPatternTitle: string, indexPatternId: s }, }, id: uuid.v4(), - label: `${indexPatternTitle} | ${i18n.DESTINATION_LAYER}`, + label: `${indexPatternTitle} | ${layerDetails.label}`, minZoom: 0, maxZoom: 24, alpha: 1, @@ -177,18 +244,31 @@ export const getDestinationLayer = (indexPatternTitle: string, indexPatternId: s * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Line" * @param indexPatternId used as layer's indexPattern to query for data + * @param layerDetails layer-specific field details */ -export const getLineLayer = (indexPatternTitle: string, indexPatternId: string) => ({ +export const getLineLayer = ( + indexPatternTitle: string, + indexPatternId: string, + layerDetails: LayerMapping +) => ({ sourceDescriptor: { type: 'ES_PEW_PEW', applyGlobalQuery: true, id: uuid.v4(), indexPatternId, - sourceGeoField: 'source.geo.location', - destGeoField: 'destination.geo.location', + sourceGeoField: layerDetails.source.geoField, + destGeoField: layerDetails.destination.geoField, metrics: [ - { type: 'sum', field: 'source.bytes', label: 'source.bytes' }, - { type: 'sum', field: 'destination.bytes', label: 'destination.bytes' }, + { + type: 'sum', + field: layerDetails.source.metricField, + label: layerDetails.source.metricField, + }, + { + type: 'sum', + field: layerDetails.destination.metricField, + label: layerDetails.destination.metricField, + }, ], }, style: { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap index 05f507ec3a775..bf62efe6a8263 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap @@ -49,3 +49,53 @@ exports[`LineToolTipContent renders correctly against snapshot 1`] = ` `; + +exports[`LineToolTipContent renders correctly against snapshot when rendering client & server 1`] = ` + + + + + + Client + + + + + + + + + + Server + + + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx index 824c717427763..a0e57a2e850c1 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx @@ -8,7 +8,12 @@ import { shallow } from 'enzyme'; import React from 'react'; import { LineToolTipContentComponent } from './line_tool_tip_content'; import { FeatureProperty } from '../types'; -import { SUM_OF_DESTINATION_BYTES, SUM_OF_SOURCE_BYTES } from '../map_config'; +import { + SUM_OF_CLIENT_BYTES, + SUM_OF_DESTINATION_BYTES, + SUM_OF_SERVER_BYTES, + SUM_OF_SOURCE_BYTES, +} from '../map_config'; describe('LineToolTipContent', () => { const mockFeatureProps: FeatureProperty[] = [ @@ -22,10 +27,31 @@ describe('LineToolTipContent', () => { }, ]; + const mockClientServerFeatureProps: FeatureProperty[] = [ + { + _propertyKey: SUM_OF_SERVER_BYTES, + _rawValue: 'testPropValue', + }, + { + _propertyKey: SUM_OF_CLIENT_BYTES, + _rawValue: 'testPropValue', + }, + ]; + test('renders correctly against snapshot', () => { const wrapper = shallow( ); expect(wrapper).toMatchSnapshot(); }); + + test('renders correctly against snapshot when rendering client & server', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx index 0c416868bfb03..eff4769944765 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx @@ -8,7 +8,12 @@ import React from 'react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { SourceDestinationArrows } from '../../source_destination/source_destination_arrows'; -import { SUM_OF_DESTINATION_BYTES, SUM_OF_SOURCE_BYTES } from '../map_config'; +import { + SUM_OF_CLIENT_BYTES, + SUM_OF_DESTINATION_BYTES, + SUM_OF_SERVER_BYTES, + SUM_OF_SOURCE_BYTES, +} from '../map_config'; import { FeatureProperty } from '../types'; import * as i18n from '../translations'; @@ -38,25 +43,29 @@ export const LineToolTipContentComponent = ({ {} ); + const isSrcDest = Object.keys(lineProps).includes(SUM_OF_SOURCE_BYTES); + return ( - {i18n.SOURCE} + {isSrcDest ? i18n.SOURCE : i18n.CLIENT} - {i18n.DESTINATION} + {isSrcDest ? i18n.DESTINATION : i18n.SERVER} diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts index 958619bee19d3..1e99a7219d427 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts @@ -41,6 +41,20 @@ export const DESTINATION_LAYER = i18n.translate( } ); +export const CLIENT_LAYER = i18n.translate( + 'xpack.siem.components.embeddables.embeddedMap.clientLayerLabel', + { + defaultMessage: 'Client Point', + } +); + +export const SERVER_LAYER = i18n.translate( + 'xpack.siem.components.embeddables.embeddedMap.serverLayerLabel', + { + defaultMessage: 'Server Point', + } +); + export const LINE_LAYER = i18n.translate( 'xpack.siem.components.embeddables.embeddedMap.lineLayerLabel', { @@ -118,6 +132,20 @@ export const DESTINATION_IP = i18n.translate( } ); +export const CLIENT_IP = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.clientIPTitle', + { + defaultMessage: 'Client IP', + } +); + +export const SERVER_IP = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.serverIPTitle', + { + defaultMessage: 'Server IP', + } +); + export const SOURCE_DOMAIN = i18n.translate( 'xpack.siem.components.embeddables.mapToolTip.pointContent.sourceDomainTitle', { @@ -132,6 +160,20 @@ export const DESTINATION_DOMAIN = i18n.translate( } ); +export const CLIENT_DOMAIN = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.clientDomainTitle', + { + defaultMessage: 'Client domain', + } +); + +export const SERVER_DOMAIN = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.serverDomainTitle', + { + defaultMessage: 'Server domain', + } +); + export const LOCATION = i18n.translate( 'xpack.siem.components.embeddables.mapToolTip.pointContent.locationTitle', { @@ -159,3 +201,17 @@ export const DESTINATION = i18n.translate( defaultMessage: 'Destination', } ); + +export const CLIENT = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.lineContent.clientLabel', + { + defaultMessage: 'Client', + } +); + +export const SERVER = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.lineContent.serverLabel', + { + defaultMessage: 'Server', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts index fdda9f949280a..6715a83e1b509 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts @@ -31,6 +31,22 @@ export interface IndexPatternMapping { id: string; } +export interface LayerMappingDetails { + metricField: string; + geoField: string; + tooltipProperties: string[]; + label: string; +} + +export interface LayerMapping { + source: LayerMappingDetails; + destination: LayerMappingDetails; +} + +export interface LayerMappingCollection { + [indexPatternTitle: string]: LayerMapping; +} + export type SetQuery = (params: { id: string; inspect: inputsModel.InspectQuery | null; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap index 4cf7cbb43cdc7..33ed6a8c87b5f 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap @@ -373,6 +373,7 @@ exports[`EventDetails rendering should match snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", @@ -1064,6 +1065,7 @@ In other use cases the message field can be used to concatenate different values "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index dfea99ffd7091..b8b03be4e4720 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -378,6 +378,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap index a0122093e7504..3608a81234677 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap @@ -370,6 +370,7 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index 6fc9145187a36..0a60c8facff9c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -365,6 +365,7 @@ exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap index b0431684050cf..9b59f69cad3a3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap @@ -370,6 +370,7 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*",