From 6a8ac63db0444b445aff040789eee36493c7fab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Tue, 15 Sep 2020 20:35:52 +0200 Subject: [PATCH] [Security Solution] Refactor Network KPI to use search strategy (#77410) (#77518) --- .../security_solution/index.ts | 37 ++- .../security_solution/network/index.ts | 1 + .../network/kpi/dns/index.ts | 16 ++ .../security_solution/network/kpi/index.ts | 32 +++ .../network/kpi/network_events/index.ts | 16 ++ .../network/kpi/tls_handshakes/index.ts | 16 ++ .../network/kpi/unique_flows/index.ts | 16 ++ .../network/kpi/unique_private_ips/index.ts | 26 ++ .../components/stat_items/index.test.tsx | 26 +- .../common/components/stat_items/index.tsx | 13 +- .../__snapshots__/index.test.tsx.snap | 75 +----- .../components/kpi_network/common/index.tsx | 69 +++++ .../components/kpi_network/dns/index.tsx | 59 ++++ .../kpi_network/dns/translations.ts | 11 + .../components/kpi_network/index.test.tsx | 38 +-- .../network/components/kpi_network/index.tsx | 252 +++++------------- .../network/components/kpi_network/mock.ts | 64 ++--- .../kpi_network/network_events/index.tsx | 64 +++++ .../network_events/translations.ts | 14 + .../kpi_network/tls_handshakes/index.tsx | 59 ++++ .../tls_handshakes/translations.ts | 14 + .../network/components/kpi_network/types.ts | 17 ++ .../kpi_network/unique_flows/index.tsx | 59 ++++ .../kpi_network/unique_flows/translations.ts | 14 + .../kpi_network/unique_private_ips/index.tsx | 78 ++++++ .../{ => unique_private_ips}/translations.ts | 25 -- .../containers/kpi_network/dns/index.tsx | 157 +++++++++++ .../kpi_network/dns/translations.ts | 21 ++ .../network/containers/kpi_network/index.tsx | 84 +----- .../kpi_network/network_events/index.tsx | 164 ++++++++++++ .../network_events/translations.ts | 21 ++ .../kpi_network/tls_handshakes/index.tsx | 164 ++++++++++++ .../tls_handshakes/translations.ts | 21 ++ .../kpi_network/unique_flows/index.tsx | 164 ++++++++++++ .../kpi_network/unique_flows/translations.ts | 21 ++ .../kpi_network/unique_private_ips/index.tsx | 175 ++++++++++++ .../unique_private_ips/translations.ts | 21 ++ .../public/network/pages/network.tsx | 31 +-- .../factory/network/index.test.ts | 20 +- .../factory/network/index.ts | 16 +- .../factory/network/kpi/common/index.ts | 38 +++ .../factory/network/kpi/dns/index.ts | 35 +++ .../kpi/dns/query.network_kpi_dns.dsl.ts | 72 +++++ .../factory/network/kpi/index.ts | 28 ++ .../network/kpi/network_events/index.ts | 35 +++ .../query.network_kpi_network_events.dsl.ts | 46 ++++ .../network/kpi/tls_handshakes/index.ts | 35 +++ .../query.network_kpi_tls_handshakes.dsl.ts | 72 +++++ .../factory/network/kpi/unique_flows/index.ts | 35 +++ .../query.network_kpi_unique_flows.dsl.ts | 53 ++++ .../network/kpi/unique_private_ips/index.ts | 61 +++++ ...uery.network_kpi_unique_private_ips.dsl.ts | 105 ++++++++ 52 files changed, 2332 insertions(+), 474 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/dns/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx rename x-pack/plugins/security_solution/public/network/components/kpi_network/{ => unique_private_ips}/translations.ts (65%) create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/translations.ts create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx create mode 100644 x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/translations.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/common/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 35fcc3b07fd05..95f3cd4fd7da7 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -39,6 +39,17 @@ import { NetworkTopNFlowRequestOptions, NetworkUsersStrategyResponse, NetworkUsersRequestOptions, + NetworkKpiQueries, + NetworkKpiDnsStrategyResponse, + NetworkKpiDnsRequestOptions, + NetworkKpiNetworkEventsStrategyResponse, + NetworkKpiNetworkEventsRequestOptions, + NetworkKpiTlsHandshakesStrategyResponse, + NetworkKpiTlsHandshakesRequestOptions, + NetworkKpiUniqueFlowsStrategyResponse, + NetworkKpiUniqueFlowsRequestOptions, + NetworkKpiUniquePrivateIpsStrategyResponse, + NetworkKpiUniquePrivateIpsRequestOptions, } from './network'; import { MatrixHistogramQuery, @@ -57,7 +68,11 @@ export * from './hosts'; export * from './matrix_histogram'; export * from './network'; -export type FactoryQueryTypes = HostsQueries | NetworkQueries | typeof MatrixHistogramQuery; +export type FactoryQueryTypes = + | HostsQueries + | NetworkQueries + | NetworkKpiQueries + | typeof MatrixHistogramQuery; export interface RequestBasicOptions extends IEsSearchRequest { timerange: TimerangeInput; @@ -107,6 +122,16 @@ export type StrategyResponseType = T extends HostsQ ? NetworkTopNFlowStrategyResponse : T extends NetworkQueries.users ? NetworkUsersStrategyResponse + : T extends NetworkKpiQueries.dns + ? NetworkKpiDnsStrategyResponse + : T extends NetworkKpiQueries.networkEvents + ? NetworkKpiNetworkEventsStrategyResponse + : T extends NetworkKpiQueries.tlsHandshakes + ? NetworkKpiTlsHandshakesStrategyResponse + : T extends NetworkKpiQueries.uniqueFlows + ? NetworkKpiUniqueFlowsStrategyResponse + : T extends NetworkKpiQueries.uniquePrivateIps + ? NetworkKpiUniquePrivateIpsStrategyResponse : T extends typeof MatrixHistogramQuery ? MatrixHistogramStrategyResponse : never; @@ -139,6 +164,16 @@ export type StrategyRequestType = T extends HostsQu ? NetworkTopNFlowRequestOptions : T extends NetworkQueries.users ? NetworkUsersRequestOptions + : T extends NetworkKpiQueries.dns + ? NetworkKpiDnsRequestOptions + : T extends NetworkKpiQueries.networkEvents + ? NetworkKpiNetworkEventsRequestOptions + : T extends NetworkKpiQueries.tlsHandshakes + ? NetworkKpiTlsHandshakesRequestOptions + : T extends NetworkKpiQueries.uniqueFlows + ? NetworkKpiUniqueFlowsRequestOptions + : T extends NetworkKpiQueries.uniquePrivateIps + ? NetworkKpiUniquePrivateIpsRequestOptions : T extends typeof MatrixHistogramQuery ? MatrixHistogramRequestOptions : never; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts index 4e73fe11ef430..19f90b2f57e7d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts @@ -8,6 +8,7 @@ export * from './common'; export * from './details'; export * from './dns'; export * from './http'; +export * from './kpi'; export * from './overview'; export * from './tls'; export * from './top_countries'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts new file mode 100644 index 0000000000000..7c192de080e60 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/dns/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; + +export type NetworkKpiDnsRequestOptions = RequestBasicOptions; + +export interface NetworkKpiDnsStrategyResponse extends IEsSearchResponse { + dnsQueries: number; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/index.ts new file mode 100644 index 0000000000000..e94ab030fbdb9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/index.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './dns'; +export * from './network_events'; +export * from './tls_handshakes'; +export * from './unique_flows'; +export * from './unique_private_ips'; + +import { NetworkKpiDnsStrategyResponse } from './dns'; +import { NetworkKpiNetworkEventsStrategyResponse } from './network_events'; +import { NetworkKpiTlsHandshakesStrategyResponse } from './tls_handshakes'; +import { NetworkKpiUniqueFlowsStrategyResponse } from './unique_flows'; +import { NetworkKpiUniquePrivateIpsStrategyResponse } from './unique_private_ips'; + +export enum NetworkKpiQueries { + dns = 'networkKpiDns', + networkEvents = 'networkKpiNetworkEvents', + tlsHandshakes = 'networkKpiTlsHandshakes', + uniqueFlows = 'networkKpiUniqueFlows', + uniquePrivateIps = 'networkKpiUniquePrivateIps', +} + +export type NetworkKpiStrategyResponse = + | Omit + | Omit + | Omit + | Omit + | Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts new file mode 100644 index 0000000000000..dcccf8e7f3ac9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/network_events/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; + +export type NetworkKpiNetworkEventsRequestOptions = RequestBasicOptions; + +export interface NetworkKpiNetworkEventsStrategyResponse extends IEsSearchResponse { + networkEvents: number; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts new file mode 100644 index 0000000000000..216c57fdb6c3c --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/tls_handshakes/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; + +export type NetworkKpiTlsHandshakesRequestOptions = RequestBasicOptions; + +export interface NetworkKpiTlsHandshakesStrategyResponse extends IEsSearchResponse { + tlsHandshakes: number; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts new file mode 100644 index 0000000000000..dc7d36b7cd4af --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_flows/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; + +export type NetworkKpiUniqueFlowsRequestOptions = RequestBasicOptions; + +export interface NetworkKpiUniqueFlowsStrategyResponse extends IEsSearchResponse { + uniqueFlowId: number; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts new file mode 100644 index 0000000000000..ccf4d397ffd8f --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/kpi/unique_private_ips/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; + +export interface NetworkKpiHistogramData { + x?: Maybe; + y?: Maybe; +} + +export type NetworkKpiUniquePrivateIpsRequestOptions = RequestBasicOptions; + +export interface NetworkKpiUniquePrivateIpsStrategyResponse extends IEsSearchResponse { + uniqueSourcePrivateIps: number; + uniqueSourcePrivateIpsHistogram: NetworkKpiHistogramData[] | null; + uniqueDestinationPrivateIps: number; + uniqueDestinationPrivateIpsHistogram: NetworkKpiHistogramData[] | null; + inspect?: Maybe; +} + +export type UniquePrivateAttributeQuery = 'source' | 'destination'; diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx index 8a78706e17a4c..664d8b2ff5598 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx @@ -23,7 +23,7 @@ import { import { BarChart } from '../charts/barchart'; import { AreaChart } from '../charts/areachart'; import { EuiHorizontalRule } from '@elastic/eui'; -import { fieldTitleChartMapping } from '../../../network/components/kpi_network'; +import { fieldsMapping as fieldTitleChartMapping } from '../../../network/components/kpi_network/unique_private_ips'; import { mockData, mockEnableChartsData, @@ -39,7 +39,8 @@ import { } from '../../mock'; import { State, createStore } from '../../store'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { KpiNetworkData, KpiHostsData } from '../../../graphql/types'; +import { KpiHostsData } from '../../../graphql/types'; +import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; const from = '2019-06-15T06:00:00.000Z'; const to = '2019-06-18T06:00:00.000Z'; @@ -74,7 +75,6 @@ describe('Stat Items Component', () => { fields={[{ key: 'hosts', value: null, color: '#6092C0', icon: 'cross' }]} from={from} id="statItems" - index={0} key="mock-keys" to={to} narrowDateRange={mockNarrowDateRange} @@ -94,7 +94,6 @@ describe('Stat Items Component', () => { fields={[{ key: 'hosts', value: null, color: '#6092C0', icon: 'cross' }]} from={from} id="statItems" - index={0} key="mock-keys" to={to} narrowDateRange={mockNarrowDateRange} @@ -176,7 +175,6 @@ describe('Stat Items Component', () => { ], from, id: 'statItems', - index: 0, key: 'mock-keys', to, narrowDateRange: mockNarrowDateRange, @@ -214,41 +212,37 @@ describe('Stat Items Component', () => { describe('addValueToFields', () => { const mockNetworkMappings = fieldTitleChartMapping[0]; - const mockKpiNetworkData = mockData.KpiNetwork; test('should update value from data', () => { - const result = addValueToFields(mockNetworkMappings.fields, mockKpiNetworkData); + const result = addValueToFields(mockNetworkMappings.fields, mockData); expect(result).toEqual(mockEnableChartsData.fields); }); }); describe('addValueToAreaChart', () => { const mockNetworkMappings = fieldTitleChartMapping[0]; - const mockKpiNetworkData = mockData.KpiNetwork; test('should add areaChart from data', () => { - const result = addValueToAreaChart(mockNetworkMappings.fields, mockKpiNetworkData); + const result = addValueToAreaChart(mockNetworkMappings.fields, mockData); expect(result).toEqual(mockEnableChartsData.areaChart); }); }); describe('addValueToBarChart', () => { const mockNetworkMappings = fieldTitleChartMapping[0]; - const mockKpiNetworkData = mockData.KpiNetwork; test('should add areaChart from data', () => { - const result = addValueToBarChart(mockNetworkMappings.fields, mockKpiNetworkData); + const result = addValueToBarChart(mockNetworkMappings.fields, mockData); expect(result).toEqual(mockEnableChartsData.barChart); }); }); describe('useKpiMatrixStatus', () => { const mockNetworkMappings = fieldTitleChartMapping; - const mockKpiNetworkData = mockData.KpiNetwork; const MockChildComponent = (mappedStatItemProps: StatItemsProps) => ; const MockHookWrapperComponent = ({ fieldsMapping, data, }: { fieldsMapping: Readonly; - data: KpiNetworkData | KpiHostsData; + data: NetworkKpiStrategyResponse | KpiHostsData; }) => { const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( fieldsMapping, @@ -271,7 +265,7 @@ describe('useKpiMatrixStatus', () => { test('it updates status correctly', () => { const wrapper = mount( <> - + ); @@ -281,7 +275,7 @@ describe('useKpiMatrixStatus', () => { test('it should not append areaChart if enableAreaChart is off', () => { const wrapper = mount( <> - + ); @@ -291,7 +285,7 @@ describe('useKpiMatrixStatus', () => { test('it should not append barChart if enableBarChart is off', () => { const wrapper = mount( <> - + ); diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx index 183f89d9320f3..13a93a784a2c9 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx @@ -18,7 +18,8 @@ import { get, getOr } from 'lodash/fp'; import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; -import { KpiHostsData, KpiNetworkData } from '../../../graphql/types'; +import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; +import { KpiHostsData } from '../../../graphql/types'; import { AreaChart } from '../charts/areachart'; import { BarChart } from '../charts/barchart'; import { ChartSeriesData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common'; @@ -58,7 +59,7 @@ export interface StatItems { enableBarChart?: boolean; fields: StatItem[]; grow?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | true | false | null; - index: number; + index?: number; key: string; statKey?: string; } @@ -112,12 +113,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener export const addValueToFields = ( fields: StatItem[], - data: KpiHostsData | KpiNetworkData + data: KpiHostsData | NetworkKpiStrategyResponse ): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) })); export const addValueToAreaChart = ( fields: StatItem[], - data: KpiHostsData | KpiNetworkData + data: KpiHostsData | NetworkKpiStrategyResponse ): ChartSeriesData[] => fields .filter((field) => get(`${field.key}Histogram`, data) != null) @@ -129,7 +130,7 @@ export const addValueToAreaChart = ( export const addValueToBarChart = ( fields: StatItem[], - data: KpiHostsData | KpiNetworkData + data: KpiHostsData | NetworkKpiStrategyResponse ): ChartSeriesData[] => { if (fields.length === 0) return []; return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => { @@ -158,7 +159,7 @@ export const addValueToBarChart = ( export const useKpiMatrixStatus = ( mappings: Readonly, - data: KpiHostsData | KpiNetworkData, + data: KpiHostsData | NetworkKpiStrategyResponse, id: string, from: string, to: string, diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap index 2f97e45b217f3..49562162e94a8 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap @@ -1,81 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`KpiNetwork Component rendering it renders loading icons 1`] = ` - -`; - exports[`KpiNetwork Component rendering it renders the default widget 1`] = ` `; diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx new file mode 100644 index 0000000000000..c4881b0f29a68 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiFlexItem, EuiLoadingSpinner, EuiFlexGroup } from '@elastic/eui'; +import styled from 'styled-components'; + +import { manageQuery } from '../../../../common/components/page/manage_query'; +import { NetworkKpiStrategyResponse } from '../../../../../common/search_strategy'; +import { + StatItemsComponent, + StatItemsProps, + useKpiMatrixStatus, + StatItems, +} from '../../../../common/components/stat_items'; +import { UpdateDateRange } from '../../../../common/components/charts/common'; + +const kpiWidgetHeight = 228; + +export const FlexGroup = styled(EuiFlexGroup)` + min-height: ${kpiWidgetHeight}px; +`; + +FlexGroup.displayName = 'FlexGroup'; + +export const KpiNetworkBaseComponent = React.memo<{ + fieldsMapping: Readonly; + data: NetworkKpiStrategyResponse; + loading?: boolean; + id: string; + from: string; + to: string; + narrowDateRange: UpdateDateRange; +}>(({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => { + const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( + fieldsMapping, + data, + id, + from, + to, + narrowDateRange + ); + + if (loading) { + return ( + + + + + + ); + } + + return ( + + {statItemsProps.map((mappedStatItemProps) => ( + + ))} + + ); +}); + +KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent'; + +export const KpiNetworkBaseComponentManage = manageQuery(KpiNetworkBaseComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx new file mode 100644 index 0000000000000..889f3dacc2d98 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useNetworkKpiDns } from '../../../containers/kpi_network/dns'; +import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiProps } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'dnsQueries', + fields: [ + { + key: 'dnsQueries', + value: null, + }, + ], + description: i18n.DNS_QUERIES, + }, +]; + +const NetworkKpiDnsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiDns({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const NetworkKpiDns = React.memo(NetworkKpiDnsComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/translations.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/translations.ts new file mode 100644 index 0000000000000..0a3986fea3d24 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/translations.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const DNS_QUERIES = i18n.translate('xpack.securitySolution.kpiNetwork.dnsQueries.title', { + defaultMessage: 'DNS queries', +}); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx index 18f766f37ade2..2432d32b7b5d5 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx @@ -18,13 +18,17 @@ import { import '../../../common/mock/match_media'; import { createStore, State } from '../../../common/store'; import { KpiNetworkComponent } from '.'; -import { mockData } from './mock'; describe('KpiNetwork Component', () => { const state: State = mockGlobalState; - const from = '2019-06-15T06:00:00.000Z'; - const to = '2019-06-18T06:00:00.000Z'; - const narrowDateRange = jest.fn(); + const props = { + from: '2019-06-15T06:00:00.000Z', + to: '2019-06-18T06:00:00.000Z', + narrowDateRange: jest.fn(), + filterQuery: '', + setQuery: jest.fn(), + skip: true, + }; const { storage } = createSecuritySolutionStorageMock(); let store = createStore( @@ -46,34 +50,10 @@ describe('KpiNetwork Component', () => { }); describe('rendering', () => { - test('it renders loading icons', () => { - const wrapper = shallow( - - - - ); - - expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); - }); - test('it renders the default widget', () => { const wrapper = shallow( - + ); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx index dd8979bc02a61..674e592940fa6 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx @@ -5,195 +5,77 @@ */ import React from 'react'; +import { EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; -import { - EuiFlexItem, - EuiLoadingSpinner, - EuiFlexGroup, - EuiSpacer, - euiPaletteColorBlind, -} from '@elastic/eui'; -import styled from 'styled-components'; -import { chunk as _chunk } from 'lodash/fp'; +import { NetworkKpiDns } from './dns'; +import { NetworkKpiNetworkEvents } from './network_events'; +import { NetworkKpiTlsHandshakes } from './tls_handshakes'; +import { NetworkKpiUniqueFlows } from './unique_flows'; +import { NetworkKpiUniquePrivateIps } from './unique_private_ips'; +import { NetworkKpiProps } from './types'; -import { - StatItemsComponent, - StatItemsProps, - useKpiMatrixStatus, - StatItems, -} from '../../../common/components/stat_items'; -import { KpiNetworkData } from '../../../graphql/types'; - -import * as i18n from './translations'; -import { UpdateDateRange } from '../../../common/components/charts/common'; - -const kipsPerRow = 2; -const kpiWidgetHeight = 228; - -const euiVisColorPalette = euiPaletteColorBlind(); -const euiColorVis1 = euiVisColorPalette[1]; -const euiColorVis2 = euiVisColorPalette[2]; -const euiColorVis3 = euiVisColorPalette[3]; - -interface KpiNetworkProps { - data: KpiNetworkData; - from: string; - id: string; - loading: boolean; - to: string; - narrowDateRange: UpdateDateRange; -} - -export const fieldTitleChartMapping: Readonly = [ - { - key: 'UniqueIps', - index: 2, - fields: [ - { - key: 'uniqueSourcePrivateIps', - value: null, - name: i18n.SOURCE_CHART_LABEL, - description: i18n.SOURCE_UNIT_LABEL, - color: euiColorVis2, - icon: 'visMapCoordinate', - }, - { - key: 'uniqueDestinationPrivateIps', - value: null, - name: i18n.DESTINATION_CHART_LABEL, - description: i18n.DESTINATION_UNIT_LABEL, - color: euiColorVis3, - icon: 'visMapCoordinate', - }, - ], - description: i18n.UNIQUE_PRIVATE_IPS, - enableAreaChart: true, - enableBarChart: true, - grow: 2, - }, -]; - -const fieldTitleMatrixMapping: Readonly = [ - { - key: 'networkEvents', - index: 0, - fields: [ - { - key: 'networkEvents', - value: null, - color: euiColorVis1, - }, - ], - description: i18n.NETWORK_EVENTS, - grow: 1, - }, - { - key: 'dnsQueries', - index: 1, - fields: [ - { - key: 'dnsQueries', - value: null, - }, - ], - description: i18n.DNS_QUERIES, - }, - { - key: 'uniqueFlowId', - index: 3, - fields: [ - { - key: 'uniqueFlowId', - value: null, - }, - ], - description: i18n.UNIQUE_FLOW_IDS, - }, - { - key: 'tlsHandshakes', - index: 4, - fields: [ - { - key: 'tlsHandshakes', - value: null, - }, - ], - description: i18n.TLS_HANDSHAKES, - }, -]; - -const FlexGroup = styled(EuiFlexGroup)` - min-height: ${kpiWidgetHeight}px; -`; - -FlexGroup.displayName = 'FlexGroup'; - -export const KpiNetworkBaseComponent = React.memo<{ - fieldsMapping: Readonly; - data: KpiNetworkData; - id: string; - from: string; - to: string; - narrowDateRange: UpdateDateRange; -}>(({ fieldsMapping, data, id, from, to, narrowDateRange }) => { - const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( - fieldsMapping, - data, - id, - from, - to, - narrowDateRange - ); - - return ( +export const KpiNetworkComponent = React.memo( + ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( - {statItemsProps.map((mappedStatItemProps, idx) => { - return ; - })} + + + + + + + + + + + + + + + + + + + + + + - ); -}); - -KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent'; - -export const KpiNetworkComponent = React.memo( - ({ data, from, id, loading, to, narrowDateRange }) => { - return loading ? ( - - - - - - ) : ( - - - {_chunk(kipsPerRow, fieldTitleMatrixMapping).map((mappingsPerLine, idx) => ( - - {idx % kipsPerRow === 1 && } - - - ))} - - - - - - ); - } + ) ); KpiNetworkComponent.displayName = 'KpiNetworkComponent'; diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/mock.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/mock.ts index bd820d4ed367d..431d09418e9ed 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/mock.ts +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/mock.ts @@ -4,45 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KpiNetworkData } from '../../../graphql/types'; +import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; import { StatItems } from '../../../common/components/stat_items'; export const mockNarrowDateRange = jest.fn(); -export const mockData: { KpiNetwork: KpiNetworkData } = { - KpiNetwork: { - networkEvents: 16, - uniqueFlowId: 10277307, - uniqueSourcePrivateIps: 383, - uniqueSourcePrivateIpsHistogram: [ - { - x: new Date('2019-02-09T16:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-02-09T19:00:00.000Z').valueOf(), - y: 0, - }, - ], - uniqueDestinationPrivateIps: 18, - uniqueDestinationPrivateIpsHistogram: [ - { - x: new Date('2019-02-09T16:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-02-09T19:00:00.000Z').valueOf(), - y: 0, - }, - ], - dnsQueries: 278, - tlsHandshakes: 10000, - }, +export const mockData: NetworkKpiStrategyResponse = { + networkEvents: 16, + uniqueFlowId: 10277307, + uniqueSourcePrivateIps: 383, + uniqueSourcePrivateIpsHistogram: [ + { + x: new Date('2019-02-09T16:00:00.000Z').valueOf(), + y: 8, + }, + { + x: new Date('2019-02-09T19:00:00.000Z').valueOf(), + y: 0, + }, + ], + uniqueDestinationPrivateIps: 18, + uniqueDestinationPrivateIpsHistogram: [ + { + x: new Date('2019-02-09T16:00:00.000Z').valueOf(), + y: 8, + }, + { + x: new Date('2019-02-09T19:00:00.000Z').valueOf(), + y: 0, + }, + ], + dnsQueries: 278, + tlsHandshakes: 10000, }; const mockMappingItems: StatItems = { key: 'UniqueIps', - index: 0, fields: [ { key: 'uniqueSourcePrivateIps', @@ -64,7 +61,6 @@ const mockMappingItems: StatItems = { description: 'Unique private IPs', enableAreaChart: true, enableBarChart: true, - grow: 2, }; export const mockNoChartMappings: Readonly = [ @@ -97,7 +93,6 @@ export const mockDisableChartsInitialData = { description: 'Unique private IPs', enableAreaChart: false, enableBarChart: false, - grow: 2, areaChart: undefined, barChart: undefined, }; @@ -124,7 +119,6 @@ export const mockEnableChartsInitialData = { description: 'Unique private IPs', enableAreaChart: true, enableBarChart: true, - grow: 2, areaChart: [], barChart: [ { @@ -221,9 +215,7 @@ export const mockEnableChartsData = { }, ], from: '2019-06-15T06:00:00.000Z', - grow: 2, id: 'statItem', - index: 2, statKey: 'UniqueIps', to: '2019-06-18T06:00:00.000Z', narrowDateRange: mockNarrowDateRange, diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx new file mode 100644 index 0000000000000..3ee2acf1a115c --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { euiPaletteColorBlind } from '@elastic/eui'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events'; +import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiProps } from '../types'; +import * as i18n from './translations'; + +const euiVisColorPalette = euiPaletteColorBlind(); +const euiColorVis1 = euiVisColorPalette[1]; + +export const fieldsMapping: Readonly = [ + { + key: 'networkEvents', + fields: [ + { + key: 'networkEvents', + value: null, + color: euiColorVis1, + }, + ], + description: i18n.NETWORK_EVENTS, + }, +]; + +const NetworkKpiNetworkEventsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiNetworkEvents({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const NetworkKpiNetworkEvents = React.memo(NetworkKpiNetworkEventsComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/translations.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/translations.ts new file mode 100644 index 0000000000000..dc54f96a123ea --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const NETWORK_EVENTS = i18n.translate( + 'xpack.securitySolution.kpiNetwork.networkEvents.title', + { + defaultMessage: 'Network events', + } +); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx new file mode 100644 index 0000000000000..1a29465e2ad80 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useNetworkKpiTlsHandshakes } from '../../../containers/kpi_network/tls_handshakes'; +import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiProps } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'tlsHandshakes', + fields: [ + { + key: 'tlsHandshakes', + value: null, + }, + ], + description: i18n.TLS_HANDSHAKES, + }, +]; + +const NetworkKpiTlsHandshakesComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiTlsHandshakes({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const NetworkKpiTlsHandshakes = React.memo(NetworkKpiTlsHandshakesComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/translations.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/translations.ts new file mode 100644 index 0000000000000..f392687cf86a7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const TLS_HANDSHAKES = i18n.translate( + 'xpack.securitySolution.kpiNetwork.tlsHandshakes.title', + { + defaultMessage: 'TLS handshakes', + } +); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts new file mode 100644 index 0000000000000..d3a0ac5a6c5dd --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UpdateDateRange } from '../../../common/components/charts/common'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; + +export interface NetworkKpiProps { + filterQuery: string; + from: string; + to: string; + narrowDateRange: UpdateDateRange; + setQuery: GlobalTimeArgs['setQuery']; + skip: boolean; +} diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx new file mode 100644 index 0000000000000..489062851becc --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useNetworkKpiUniqueFlows } from '../../../containers/kpi_network/unique_flows'; +import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiProps } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'uniqueFlowId', + fields: [ + { + key: 'uniqueFlowId', + value: null, + }, + ], + description: i18n.UNIQUE_FLOW_IDS, + }, +]; + +const NetworkKpiUniqueFlowsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniqueFlows({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const NetworkKpiUniqueFlows = React.memo(NetworkKpiUniqueFlowsComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/translations.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/translations.ts new file mode 100644 index 0000000000000..a0bbb07b9856f --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/translations.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const UNIQUE_FLOW_IDS = i18n.translate( + 'xpack.securitySolution.kpiNetwork.uniqueFlowIds.title', + { + defaultMessage: 'Unique flow IDs', + } +); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx new file mode 100644 index 0000000000000..db57e96331650 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { euiPaletteColorBlind } from '@elastic/eui'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useNetworkKpiUniquePrivateIps } from '../../../containers/kpi_network/unique_private_ips'; +import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiProps } from '../types'; +import * as i18n from './translations'; + +const euiVisColorPalette = euiPaletteColorBlind(); +const euiColorVis2 = euiVisColorPalette[2]; +const euiColorVis3 = euiVisColorPalette[3]; + +export const fieldsMapping: Readonly = [ + { + key: 'UniqueIps', + fields: [ + { + key: 'uniqueSourcePrivateIps', + value: null, + name: i18n.SOURCE_CHART_LABEL, + description: i18n.SOURCE_UNIT_LABEL, + color: euiColorVis2, + icon: 'visMapCoordinate', + }, + { + key: 'uniqueDestinationPrivateIps', + value: null, + name: i18n.DESTINATION_CHART_LABEL, + description: i18n.DESTINATION_UNIT_LABEL, + color: euiColorVis3, + icon: 'visMapCoordinate', + }, + ], + description: i18n.UNIQUE_PRIVATE_IPS, + enableAreaChart: true, + enableBarChart: true, + }, +]; + +const NetworkKpiUniquePrivateIpsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniquePrivateIps({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const NetworkKpiUniquePrivateIps = React.memo(NetworkKpiUniquePrivateIpsComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/translations.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/translations.ts similarity index 65% rename from x-pack/plugins/security_solution/public/network/components/kpi_network/translations.ts rename to x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/translations.ts index 292077c1578d3..6b1fb94972bff 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/translations.ts +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/translations.ts @@ -6,31 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const NETWORK_EVENTS = i18n.translate( - 'xpack.securitySolution.kpiNetwork.networkEvents.title', - { - defaultMessage: 'Network events', - } -); - -export const UNIQUE_FLOW_IDS = i18n.translate( - 'xpack.securitySolution.kpiNetwork.uniqueFlowIds.title', - { - defaultMessage: 'Unique flow IDs', - } -); - -export const DNS_QUERIES = i18n.translate('xpack.securitySolution.kpiNetwork.dnsQueries.title', { - defaultMessage: 'DNS queries', -}); - -export const TLS_HANDSHAKES = i18n.translate( - 'xpack.securitySolution.kpiNetwork.tlsHandshakes.title', - { - defaultMessage: 'TLS handshakes', - } -); - export const UNIQUE_PRIVATE_IPS = i18n.translate( 'xpack.securitySolution.kpiNetwork.uniquePrivateIps.title', { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx new file mode 100644 index 0000000000000..295cbff76f6aa --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + NetworkKpiQueries, + NetworkKpiDnsRequestOptions, + NetworkKpiDnsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'networkKpiDnsQuery'; + +export interface NetworkKpiDnsArgs { + dnsQueries: number; + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseNetworkKpiDns { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useNetworkKpiDns = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseNetworkKpiDns): [boolean, NetworkKpiDnsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [networkKpiDnsRequest, setNetworkKpiDnsRequest] = useState({ + defaultIndex, + factoryQueryType: NetworkKpiQueries.dns, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [networkKpiDnsResponse, setNetworkKpiDnsResponse] = useState({ + dnsQueries: 0, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const networkKpiDnsSearch = useCallback( + (request: NetworkKpiDnsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkKpiDnsResponse((prevResponse) => ({ + ...prevResponse, + dnsQueries: response.dnsQueries, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_DNS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_NETWORK_KPI_DNS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setNetworkKpiDnsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + networkKpiDnsSearch(networkKpiDnsRequest); + }, [networkKpiDnsRequest, networkKpiDnsSearch]); + + return [loading, networkKpiDnsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/translations.ts b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/translations.ts new file mode 100644 index 0000000000000..c55fca673c6bf --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_NETWORK_KPI_DNS = i18n.translate( + 'xpack.securitySolution.networkKpiDns.errorSearchDescription', + { + defaultMessage: `An error has occurred on network kpi dns search`, + } +); + +export const FAIL_NETWORK_KPI_DNS = i18n.translate( + 'xpack.securitySolution.networkKpiDns.failSearchDescription', + { + defaultMessage: `Failed to run search on network kpi dns`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/index.tsx index edba8b4c2e65c..490b866012a48 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/index.tsx @@ -4,82 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr } from 'lodash/fp'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { connect, ConnectedProps } from 'react-redux'; - -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { GetKpiNetworkQuery, KpiNetworkData } from '../../../graphql/types'; -import { inputsModel, inputsSelectors, State } from '../../../common/store'; -import { useUiSetting } from '../../../common/lib/kibana'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { QueryTemplateProps } from '../../../common/containers/query_template'; - -import { kpiNetworkQuery } from './index.gql_query'; - -const ID = 'kpiNetworkQuery'; - -export interface KpiNetworkArgs { - id: string; - inspect: inputsModel.InspectQuery; - kpiNetwork: KpiNetworkData; - loading: boolean; - refetch: inputsModel.Refetch; -} - -export interface KpiNetworkProps extends QueryTemplateProps { - children: (args: KpiNetworkArgs) => React.ReactNode; -} - -const KpiNetworkComponentQuery = React.memo( - ({ id = ID, children, filterQuery, isInspected, skip, sourceId, startDate, endDate }) => ( - - query={kpiNetworkQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - skip={skip} - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate!, - to: endDate!, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const kpiNetwork = getOr({}, `source.KpiNetwork`, data); - return children({ - id, - inspect: getOr(null, 'source.KpiNetwork.inspect', data), - kpiNetwork, - loading, - refetch, - }); - }} - - ) -); - -KpiNetworkComponentQuery.displayName = 'KpiNetworkComponentQuery'; - -const makeMapStateToProps = () => { - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = ID }: KpiNetworkProps) => { - const { isInspected } = getQuery(state, id); - return { - isInspected, - }; - }; - return mapStateToProps; -}; - -const connector = connect(makeMapStateToProps); - -type PropsFromRedux = ConnectedProps; - -export const KpiNetworkQuery = connector(KpiNetworkComponentQuery); +export * from './dns'; +export * from './network_events'; +export * from './tls_handshakes'; +export * from './unique_flows'; +export * from './unique_private_ips'; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx new file mode 100644 index 0000000000000..8ab94432746f4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + NetworkKpiQueries, + NetworkKpiNetworkEventsRequestOptions, + NetworkKpiNetworkEventsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'networkKpiNetworkEventsQuery'; + +export interface NetworkKpiNetworkEventsArgs { + networkEvents: number; + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseNetworkKpiNetworkEvents { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useNetworkKpiNetworkEvents = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseNetworkKpiNetworkEvents): [boolean, NetworkKpiNetworkEventsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [networkKpiNetworkEventsRequest, setNetworkKpiNetworkEventsRequest] = useState< + NetworkKpiNetworkEventsRequestOptions + >({ + defaultIndex, + factoryQueryType: NetworkKpiQueries.networkEvents, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [networkKpiNetworkEventsResponse, setNetworkKpiNetworkEventsResponse] = useState< + NetworkKpiNetworkEventsArgs + >({ + networkEvents: 0, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const networkKpiNetworkEventsSearch = useCallback( + (request: NetworkKpiNetworkEventsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search( + request, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkKpiNetworkEventsResponse((prevResponse) => ({ + ...prevResponse, + networkEvents: response.networkEvents, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_NETWORK_EVENTS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_NETWORK_KPI_NETWORK_EVENTS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setNetworkKpiNetworkEventsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + networkKpiNetworkEventsSearch(networkKpiNetworkEventsRequest); + }, [networkKpiNetworkEventsRequest, networkKpiNetworkEventsSearch]); + + return [loading, networkKpiNetworkEventsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/translations.ts b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/translations.ts new file mode 100644 index 0000000000000..9951eb23ff4f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_NETWORK_KPI_NETWORK_EVENTS = i18n.translate( + 'xpack.securitySolution.networkKpiNetworkEvents.errorSearchDescription', + { + defaultMessage: `An error has occurred on network kpi network events search`, + } +); + +export const FAIL_NETWORK_KPI_NETWORK_EVENTS = i18n.translate( + 'xpack.securitySolution.networkKpiNetworkEvents.failSearchDescription', + { + defaultMessage: `Failed to run search on network kpi network events`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx new file mode 100644 index 0000000000000..f7630352fc3c4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + NetworkKpiQueries, + NetworkKpiTlsHandshakesRequestOptions, + NetworkKpiTlsHandshakesStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'networkKpiTlsHandshakesQuery'; + +export interface NetworkKpiTlsHandshakesArgs { + tlsHandshakes: number; + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseNetworkKpiTlsHandshakes { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useNetworkKpiTlsHandshakes = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseNetworkKpiTlsHandshakes): [boolean, NetworkKpiTlsHandshakesArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [networkKpiTlsHandshakesRequest, setNetworkKpiTlsHandshakesRequest] = useState< + NetworkKpiTlsHandshakesRequestOptions + >({ + defaultIndex, + factoryQueryType: NetworkKpiQueries.tlsHandshakes, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [networkKpiTlsHandshakesResponse, setNetworkKpiTlsHandshakesResponse] = useState< + NetworkKpiTlsHandshakesArgs + >({ + tlsHandshakes: 0, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const networkKpiTlsHandshakesSearch = useCallback( + (request: NetworkKpiTlsHandshakesRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search( + request, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkKpiTlsHandshakesResponse((prevResponse) => ({ + ...prevResponse, + tlsHandshakes: response.tlsHandshakes, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_TLS_HANDSHAKES); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_NETWORK_KPI_TLS_HANDSHAKES, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setNetworkKpiTlsHandshakesRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + networkKpiTlsHandshakesSearch(networkKpiTlsHandshakesRequest); + }, [networkKpiTlsHandshakesRequest, networkKpiTlsHandshakesSearch]); + + return [loading, networkKpiTlsHandshakesResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/translations.ts b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/translations.ts new file mode 100644 index 0000000000000..bca2b6610b533 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_NETWORK_KPI_TLS_HANDSHAKES = i18n.translate( + 'xpack.securitySolution.networkKpiTlsHandshakes.errorSearchDescription', + { + defaultMessage: `An error has occurred on network kpi tls handshakes search`, + } +); + +export const FAIL_NETWORK_KPI_TLS_HANDSHAKES = i18n.translate( + 'xpack.securitySolution.networkKpiTlsHandshakes.failSearchDescription', + { + defaultMessage: `Failed to run search on network kpi tls handshakes`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx new file mode 100644 index 0000000000000..5f1bd782b9abd --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + NetworkKpiQueries, + NetworkKpiUniqueFlowsRequestOptions, + NetworkKpiUniqueFlowsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'networkKpiUniqueFlowsQuery'; + +export interface NetworkKpiUniqueFlowsArgs { + uniqueFlowId: number; + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseNetworkKpiUniqueFlows { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useNetworkKpiUniqueFlows = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseNetworkKpiUniqueFlows): [boolean, NetworkKpiUniqueFlowsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [networkKpiUniqueFlowsRequest, setNetworkKpiUniqueFlowsRequest] = useState< + NetworkKpiUniqueFlowsRequestOptions + >({ + defaultIndex, + factoryQueryType: NetworkKpiQueries.uniqueFlows, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [networkKpiUniqueFlowsResponse, setNetworkKpiUniqueFlowsResponse] = useState< + NetworkKpiUniqueFlowsArgs + >({ + uniqueFlowId: 0, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const networkKpiUniqueFlowsSearch = useCallback( + (request: NetworkKpiUniqueFlowsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search( + request, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkKpiUniqueFlowsResponse((prevResponse) => ({ + ...prevResponse, + uniqueFlowId: response.uniqueFlowId, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_FLOWS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_NETWORK_KPI_UNIQUE_FLOWS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setNetworkKpiUniqueFlowsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + networkKpiUniqueFlowsSearch(networkKpiUniqueFlowsRequest); + }, [networkKpiUniqueFlowsRequest, networkKpiUniqueFlowsSearch]); + + return [loading, networkKpiUniqueFlowsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/translations.ts b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/translations.ts new file mode 100644 index 0000000000000..7760a629208e3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_NETWORK_KPI_UNIQUE_FLOWS = i18n.translate( + 'xpack.securitySolution.networkKpiUniqueFlows.errorSearchDescription', + { + defaultMessage: `An error has occurred on network kpi unique flows search`, + } +); + +export const FAIL_NETWORK_KPI_UNIQUE_FLOWS = i18n.translate( + 'xpack.securitySolution.networkKpiUniqueFlows.failSearchDescription', + { + defaultMessage: `Failed to run search on network kpi unique flows`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx new file mode 100644 index 0000000000000..f32f43d811137 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + NetworkKpiHistogramData, + NetworkKpiQueries, + NetworkKpiUniquePrivateIpsRequestOptions, + NetworkKpiUniquePrivateIpsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'networkKpiUniquePrivateIpsQuery'; + +export interface NetworkKpiUniquePrivateIpsArgs { + uniqueDestinationPrivateIps: number; + uniqueDestinationPrivateIpsHistogram: NetworkKpiHistogramData[] | null; + uniqueSourcePrivateIps: number; + uniqueSourcePrivateIpsHistogram: NetworkKpiHistogramData[] | null; + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseNetworkKpiUniquePrivateIps { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useNetworkKpiUniquePrivateIps = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseNetworkKpiUniquePrivateIps): [boolean, NetworkKpiUniquePrivateIpsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [networkKpiUniquePrivateIpsRequest, setNetworkKpiUniquePrivateIpsRequest] = useState< + NetworkKpiUniquePrivateIpsRequestOptions + >({ + defaultIndex, + factoryQueryType: NetworkKpiQueries.uniquePrivateIps, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [networkKpiUniquePrivateIpsResponse, setNetworkKpiUniquePrivateIpsResponse] = useState< + NetworkKpiUniquePrivateIpsArgs + >({ + uniqueDestinationPrivateIps: 0, + uniqueDestinationPrivateIpsHistogram: null, + uniqueSourcePrivateIps: 0, + uniqueSourcePrivateIpsHistogram: null, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const networkKpiUniquePrivateIpsSearch = useCallback( + (request: NetworkKpiUniquePrivateIpsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search< + NetworkKpiUniquePrivateIpsRequestOptions, + NetworkKpiUniquePrivateIpsStrategyResponse + >(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkKpiUniquePrivateIpsResponse((prevResponse) => ({ + ...prevResponse, + uniqueDestinationPrivateIps: response.uniqueDestinationPrivateIps, + uniqueDestinationPrivateIpsHistogram: + response.uniqueDestinationPrivateIpsHistogram, + uniqueSourcePrivateIps: response.uniqueSourcePrivateIps, + uniqueSourcePrivateIpsHistogram: response.uniqueSourcePrivateIpsHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_PRIVATE_IPS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_NETWORK_KPI_UNIQUE_PRIVATE_IPS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setNetworkKpiUniquePrivateIpsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + networkKpiUniquePrivateIpsSearch(networkKpiUniquePrivateIpsRequest); + }, [networkKpiUniquePrivateIpsRequest, networkKpiUniquePrivateIpsSearch]); + + return [loading, networkKpiUniquePrivateIpsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/translations.ts b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/translations.ts new file mode 100644 index 0000000000000..f37fd5c02e5a9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_NETWORK_KPI_UNIQUE_PRIVATE_IPS = i18n.translate( + 'xpack.securitySolution.networkKpiUniquePrivateIps.errorSearchDescription', + { + defaultMessage: `An error has occurred on network kpi unique private ips search`, + } +); + +export const FAIL_NETWORK_KPI_UNIQUE_PRIVATE_IPS = i18n.translate( + 'xpack.securitySolution.networkKpiUniquePrivateIps.failSearchDescription', + { + defaultMessage: `Failed to run search on network kpi unique private ips`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index 848dc94acff61..e04350fd38df5 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -18,11 +18,10 @@ import { FiltersGlobal } from '../../common/components/filters_global'; import { HeaderPage } from '../../common/components/header_page'; import { LastEventTime } from '../../common/components/last_event_time'; import { SiemNavigation } from '../../common/components/navigation'; -import { manageQuery } from '../../common/components/page/manage_query'; -import { KpiNetworkComponent } from '..//components/kpi_network'; + +import { KpiNetworkComponent } from '../components/kpi_network'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { KpiNetworkQuery } from '../../network/containers/kpi_network'; import { useFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; @@ -46,7 +45,6 @@ import { TimelineId } from '../../../common/types/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../timelines/store/timeline/model'; -const KpiNetworkComponentManage = manageQuery(KpiNetworkComponent); const sourceId = 'default'; const NetworkComponent = React.memo( @@ -127,27 +125,14 @@ const NetworkComponent = React.memo( - - {({ kpiNetwork, loading, id, inspect, refetch }) => ( - - )} - + from={from} + to={to} + narrowDateRange={narrowDateRange} + /> {capabilitiesFetched && !isInitializing ? ( diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.test.ts index f901d9f3dab5d..aca76b8529d58 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.test.ts @@ -3,7 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { NetworkQueries } from '../../../../../common/search_strategy/security_solution'; +import { + NetworkQueries, + NetworkKpiQueries, +} from '../../../../../common/search_strategy/security_solution'; import { networkFactory } from '.'; import { networkDetails } from './details'; @@ -14,6 +17,11 @@ import { networkTls } from './tls'; import { networkTopCountries } from './top_countries'; import { networkTopNFlow } from './top_n_flow'; import { networkUsers } from './users'; +import { networkKpiDns } from './kpi/dns'; +import { networkKpiNetworkEvents } from './kpi/network_events'; +import { networkKpiTlsHandshakes } from './kpi/tls_handshakes'; +import { networkKpiUniqueFlows } from './kpi/unique_flows'; +import { networkKpiUniquePrivateIps } from './kpi/unique_private_ips'; jest.mock('./details'); jest.mock('./dns'); @@ -23,6 +31,11 @@ jest.mock('./tls'); jest.mock('./top_countries'); jest.mock('./top_n_flow'); jest.mock('./users'); +jest.mock('./kpi/dns'); +jest.mock('./kpi/network_events'); +jest.mock('./kpi/tls_handshakes'); +jest.mock('./kpi/unique_flows'); +jest.mock('./kpi/unique_private_ips'); describe('networkFactory', () => { test('should include correct apis', () => { @@ -35,6 +48,11 @@ describe('networkFactory', () => { [NetworkQueries.topCountries]: networkTopCountries, [NetworkQueries.topNFlow]: networkTopNFlow, [NetworkQueries.users]: networkUsers, + [NetworkKpiQueries.dns]: networkKpiDns, + [NetworkKpiQueries.networkEvents]: networkKpiNetworkEvents, + [NetworkKpiQueries.tlsHandshakes]: networkKpiTlsHandshakes, + [NetworkKpiQueries.uniqueFlows]: networkKpiUniqueFlows, + [NetworkKpiQueries.uniquePrivateIps]: networkKpiUniquePrivateIps, }; expect(networkFactory).toEqual(expectedNetworkFactory); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts index bf5321d32b58f..1eaa59846aebf 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts @@ -7,8 +7,14 @@ import { FactoryQueryTypes, NetworkQueries, + NetworkKpiQueries, } from '../../../../../common/search_strategy/security_solution'; +import { networkKpiDns } from './kpi/dns'; +import { networkKpiNetworkEvents } from './kpi/network_events'; +import { networkKpiTlsHandshakes } from './kpi/tls_handshakes'; +import { networkKpiUniqueFlows } from './kpi/unique_flows'; +import { networkKpiUniquePrivateIps } from './kpi/unique_private_ips'; import { SecuritySolutionFactory } from '../types'; import { networkDetails } from './details'; import { networkDns } from './dns'; @@ -19,7 +25,10 @@ import { networkTopCountries } from './top_countries'; import { networkTopNFlow } from './top_n_flow'; import { networkUsers } from './users'; -export const networkFactory: Record> = { +export const networkFactory: Record< + NetworkQueries | NetworkKpiQueries, + SecuritySolutionFactory +> = { [NetworkQueries.details]: networkDetails, [NetworkQueries.dns]: networkDns, [NetworkQueries.http]: networkHttp, @@ -28,4 +37,9 @@ export const networkFactory: Record [ + { + bool: { + should: [ + { + exists: { + field: 'source.ip', + }, + }, + { + exists: { + field: 'destination.ip', + }, + }, + ], + minimum_should_match: 1, + }, + }, +]; + +export const formatHistogramData = ( + data: Array<{ key: number; count: { value: number } }> +): NetworkKpiHistogramData[] | null => + data && data.length > 0 + ? data.map(({ key, count }) => ({ + x: key, + y: getOr(null, 'value', count), + })) + : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts new file mode 100644 index 0000000000000..42713fb063188 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + NetworkKpiQueries, + NetworkKpiDnsStrategyResponse, + NetworkKpiDnsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/network'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildDnsQuery } from './query.network_kpi_dns.dsl'; + +export const networkKpiDns: SecuritySolutionFactory = { + buildDsl: (options: NetworkKpiDnsRequestOptions) => buildDnsQuery(options), + parse: async ( + options: NetworkKpiDnsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildDnsQuery(options))], + }; + + return { + ...response, + inspect, + dnsQueries: getOr(null, 'hits.total.value', response.rawResponse), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts new file mode 100644 index 0000000000000..bb8443bf39178 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/dns/query.network_kpi_dns.dsl.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { NetworkKpiDnsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +const getDnsQueryFilter = () => [ + { + bool: { + should: [ + { + exists: { + field: 'dns.question.name', + }, + }, + { + term: { + 'suricata.eve.dns.type': { + value: 'query', + }, + }, + }, + { + exists: { + field: 'zeek.dns.query', + }, + }, + ], + minimum_should_match: 1, + }, + }, +]; + +export const buildDnsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: NetworkKpiDnsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + ...getDnsQueryFilter(), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/index.ts new file mode 100644 index 0000000000000..9cc58271ac18a --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + FactoryQueryTypes, + NetworkKpiQueries, +} from '../../../../../../common/search_strategy/security_solution'; + +import { SecuritySolutionFactory } from '../../types'; +import { networkKpiDns } from './dns'; +import { networkKpiNetworkEvents } from './network_events'; +import { networkKpiTlsHandshakes } from './tls_handshakes'; +import { networkKpiUniqueFlows } from './unique_flows'; +import { networkKpiUniquePrivateIps } from './unique_private_ips'; + +export const networkKpiFactory: Record< + NetworkKpiQueries, + SecuritySolutionFactory +> = { + [NetworkKpiQueries.dns]: networkKpiDns, + [NetworkKpiQueries.networkEvents]: networkKpiNetworkEvents, + [NetworkKpiQueries.tlsHandshakes]: networkKpiTlsHandshakes, + [NetworkKpiQueries.uniqueFlows]: networkKpiUniqueFlows, + [NetworkKpiQueries.uniquePrivateIps]: networkKpiUniquePrivateIps, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts new file mode 100644 index 0000000000000..913b1d566a4ea --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + NetworkKpiQueries, + NetworkKpiNetworkEventsStrategyResponse, + NetworkKpiNetworkEventsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/network'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildNetworkEventsQuery } from './query.network_kpi_network_events.dsl'; + +export const networkKpiNetworkEvents: SecuritySolutionFactory = { + buildDsl: (options: NetworkKpiNetworkEventsRequestOptions) => buildNetworkEventsQuery(options), + parse: async ( + options: NetworkKpiNetworkEventsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildNetworkEventsQuery(options))], + }; + + return { + ...response, + inspect, + networkEvents: getOr(null, 'hits.total.value', response.rawResponse), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts new file mode 100644 index 0000000000000..2749e3fa64518 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/query.network_kpi_network_events.dsl.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { NetworkKpiNetworkEventsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; +import { getIpFilter } from '../common'; + +export const buildNetworkEventsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: NetworkKpiNetworkEventsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + ...getIpFilter(), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts new file mode 100644 index 0000000000000..41df66cf28b40 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + NetworkKpiQueries, + NetworkKpiTlsHandshakesStrategyResponse, + NetworkKpiTlsHandshakesRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/network'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildTlsHandshakeQuery } from './query.network_kpi_tls_handshakes.dsl'; + +export const networkKpiTlsHandshakes: SecuritySolutionFactory = { + buildDsl: (options: NetworkKpiTlsHandshakesRequestOptions) => buildTlsHandshakeQuery(options), + parse: async ( + options: NetworkKpiTlsHandshakesRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildTlsHandshakeQuery(options))], + }; + + return { + ...response, + inspect, + tlsHandshakes: getOr(null, 'hits.total.value', response.rawResponse), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts new file mode 100644 index 0000000000000..2b48061c64122 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/query.network_kpi_tls_handshakes.dsl.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { NetworkKpiTlsHandshakesRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; +import { getIpFilter } from '../common'; + +const getTlsHandshakesQueryFilter = () => [ + { + bool: { + should: [ + { + exists: { + field: 'tls.version', + }, + }, + { + exists: { + field: 'suricata.eve.tls.version', + }, + }, + { + exists: { + field: 'zeek.ssl.version', + }, + }, + ], + minimum_should_match: 1, + }, + }, +]; + +export const buildTlsHandshakeQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: NetworkKpiTlsHandshakesRequestOptions) => { + const filter = [ + ...getIpFilter(), + ...createQueryFilterClauses(filterQuery), + ...getTlsHandshakesQueryFilter(), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts new file mode 100644 index 0000000000000..3f2ace27b8bdb --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + NetworkKpiQueries, + NetworkKpiUniqueFlowsStrategyResponse, + NetworkKpiUniqueFlowsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/network'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildUniqueFlowsQuery } from './query.network_kpi_unique_flows.dsl'; + +export const networkKpiUniqueFlows: SecuritySolutionFactory = { + buildDsl: (options: NetworkKpiUniqueFlowsRequestOptions) => buildUniqueFlowsQuery(options), + parse: async ( + options: NetworkKpiUniqueFlowsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildUniqueFlowsQuery(options))], + }; + + return { + ...response, + inspect, + uniqueFlowId: getOr(null, 'aggregations.unique_flow_id.value', response.rawResponse), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts new file mode 100644 index 0000000000000..e6340db661e8f --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_flows/query.network_kpi_unique_flows.dsl.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { NetworkKpiUniqueFlowsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/network'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; +import { getIpFilter } from '../common'; + +export const buildUniqueFlowsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: NetworkKpiUniqueFlowsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + ...getIpFilter(), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: { + unique_flow_id: { + cardinality: { + field: 'network.community_id', + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts new file mode 100644 index 0000000000000..dbb2dd539641a --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + NetworkKpiQueries, + NetworkKpiUniquePrivateIpsStrategyResponse, + NetworkKpiUniquePrivateIpsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/network'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { formatHistogramData } from '../common'; +import { buildUniquePrivateIpsQuery } from './query.network_kpi_unique_private_ips.dsl'; + +export const networkKpiUniquePrivateIps: SecuritySolutionFactory = { + buildDsl: (options: NetworkKpiUniquePrivateIpsRequestOptions) => + buildUniquePrivateIpsQuery(options), + parse: async ( + options: NetworkKpiUniquePrivateIpsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildUniquePrivateIpsQuery(options))], + }; + + const uniqueSourcePrivateIpsHistogram = getOr( + null, + 'aggregations.source.histogram.buckets', + response.rawResponse + ); + const uniqueDestinationPrivateIpsHistogram = getOr( + null, + 'aggregations.destination.histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + uniqueSourcePrivateIps: getOr( + null, + 'aggregations.source.unique_private_ips.value', + response.rawResponse + ), + uniqueDestinationPrivateIps: getOr( + null, + 'aggregations.destination.unique_private_ips.value', + response.rawResponse + ), + uniqueSourcePrivateIpsHistogram: formatHistogramData(uniqueSourcePrivateIpsHistogram), + uniqueDestinationPrivateIpsHistogram: formatHistogramData( + uniqueDestinationPrivateIpsHistogram + ), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts new file mode 100644 index 0000000000000..f1b2b06203cad --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/query.network_kpi_unique_private_ips.dsl.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + NetworkKpiUniquePrivateIpsRequestOptions, + UniquePrivateAttributeQuery, +} from '../../../../../../../common/search_strategy/security_solution/network'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +const getUniquePrivateIpsFilter = (attrQuery: UniquePrivateAttributeQuery) => ({ + bool: { + should: [ + { + term: { + [`${attrQuery}.ip`]: '10.0.0.0/8', + }, + }, + { + term: { + [`${attrQuery}.ip`]: '192.168.0.0/16', + }, + }, + { + term: { + [`${attrQuery}.ip`]: '172.16.0.0/12', + }, + }, + { + term: { + [`${attrQuery}.ip`]: 'fd00::/8', + }, + }, + ], + minimum_should_match: 1, + }, +}); + +const getAggs = (attrQuery: 'source' | 'destination') => ({ + [attrQuery]: { + filter: getUniquePrivateIpsFilter(attrQuery), + aggs: { + unique_private_ips: { + cardinality: { + field: `${attrQuery}.ip`, + }, + }, + histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: `${attrQuery}.ip`, + }, + }, + }, + }, + }, + }, +}); + +export const buildUniquePrivateIpsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: NetworkKpiUniquePrivateIpsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + aggregations: { + ...getAggs('source'), + ...getAggs('destination'), + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +};