From 90f7d7dfe70268976fc57af0025fd473a102b46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 7 Sep 2020 18:57:16 +0200 Subject: [PATCH] [Security Solution] Refactor OverviewHost and OverviewNetwork to use Search Strategy (#76409) --- .../security_solution/hosts/all/index.ts | 1 - .../security_solution/hosts/details/index.ts | 23 ++ .../security_solution/hosts/index.ts | 4 +- .../security_solution/hosts/overview/index.ts | 83 ++++- .../security_solution/index.ts | 16 +- .../security_solution/network/index.ts | 2 + .../network/overview/index.ts | 62 ++++ .../cypress/fixtures/overview.json | 31 -- .../fixtures/overview_search_strategy.json | 33 ++ .../cypress/integration/overview.spec.ts | 1 + .../cypress/support/commands.js | 9 + .../cypress/support/index.d.ts | 1 + .../hosts/{overview => details}/_index.tsx | 42 +-- .../host_overview.gql_query.ts | 0 .../hosts/{overview => details}/index.tsx | 0 .../{overview => details}/translations.ts | 0 .../public/hosts/pages/details/index.tsx | 2 +- .../components/event_counts/index.test.tsx | 19 +- .../components/overview_host/index.test.tsx | 108 ++----- .../components/overview_host/index.tsx | 111 ++++--- .../overview_network/index.test.tsx | 107 ++----- .../components/overview_network/index.tsx | 109 +++---- .../containers/overview_host/index.tsx | 210 ++++++++----- .../containers/overview_host/translations.ts | 21 ++ .../containers/overview_network/index.tsx | 189 +++++++---- .../overview_network/translations.ts | 21 ++ .../hosts/{overview => details}/helpers.ts | 0 .../factory/hosts/details/index.ts | 38 +++ .../query.host_details.dsl.ts} | 6 +- .../security_solution/factory/hosts/index.ts | 6 +- .../factory/hosts/overview/index.ts | 58 +++- .../hosts/overview/query.overview_host.dsl.ts | 295 ++++++++++++++++++ .../factory/network/index.ts | 2 + .../factory/network/overview/index.ts | 63 ++++ .../overview/query.overview_network.dsl.ts | 105 +++++++ .../apis/security_solution/hosts.ts | 2 +- 36 files changed, 1279 insertions(+), 501 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts create mode 100644 x-pack/plugins/security_solution/cypress/fixtures/overview_search_strategy.json rename x-pack/plugins/security_solution/public/hosts/containers/hosts/{overview => details}/_index.tsx (79%) rename x-pack/plugins/security_solution/public/hosts/containers/hosts/{overview => details}/host_overview.gql_query.ts (100%) rename x-pack/plugins/security_solution/public/hosts/containers/hosts/{overview => details}/index.tsx (100%) rename x-pack/plugins/security_solution/public/hosts/containers/hosts/{overview => details}/translations.ts (100%) create mode 100644 x-pack/plugins/security_solution/public/overview/containers/overview_host/translations.ts create mode 100644 x-pack/plugins/security_solution/public/overview/containers/overview_network/translations.ts rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/{overview => details}/helpers.ts (100%) create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/{overview/query.host_overview.dsl.ts => details/query.host_details.dsl.ts} (86%) create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.dsl.ts diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts index 5ddcd8da30efb5..8191fa742ed9c2 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts @@ -12,7 +12,6 @@ import { RequestOptionsPaginated } from '../..'; export interface HostsEdges { node: HostItem; - cursor: CursorType; } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts new file mode 100644 index 00000000000000..2338df88abad98 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/details/index.ts @@ -0,0 +1,23 @@ +/* + * 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, TimerangeInput } from '../../../common'; +import { HostItem, HostsFields } from '../common'; +import { RequestOptionsPaginated } from '../..'; + +export interface HostDetailsStrategyResponse extends IEsSearchResponse { + hostDetails: HostItem; + inspect?: Maybe; +} + +export interface HostDetailsRequestOptions extends Partial> { + hostName: string; + skip?: boolean; + timerange: TimerangeInput; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index 297e17fd127b32..63a57c20a85932 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -7,14 +7,16 @@ export * from './all'; export * from './authentications'; export * from './common'; +export * from './details'; export * from './first_last_seen'; export * from './overview'; export * from './uncommon_processes'; export enum HostsQueries { authentications = 'authentications', + details = 'details', firstLastSeen = 'firstLastSeen', hosts = 'hosts', - hostOverview = 'hostOverview', + overview = 'overviewHost', uncommonProcesses = 'uncommonProcesses', } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts index 7d212a951905a1..569ed611bd35bc 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts @@ -5,18 +5,83 @@ */ import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; -import { Inspect, Maybe, TimerangeInput } from '../../../common'; -import { HostItem, HostsFields } from '../common'; -import { RequestOptionsPaginated } from '../..'; +import { Inspect, Maybe, SearchHit } from '../../../common'; +import { RequestBasicOptions } from '../..'; + +export type HostOverviewRequestOptions = RequestBasicOptions; export interface HostOverviewStrategyResponse extends IEsSearchResponse { - hostOverview: HostItem; inspect?: Maybe; + overviewHost: { + auditbeatAuditd?: Maybe; + auditbeatFIM?: Maybe; + auditbeatLogin?: Maybe; + auditbeatPackage?: Maybe; + auditbeatProcess?: Maybe; + auditbeatUser?: Maybe; + endgameDns?: Maybe; + endgameFile?: Maybe; + endgameImageLoad?: Maybe; + endgameNetwork?: Maybe; + endgameProcess?: Maybe; + endgameRegistry?: Maybe; + endgameSecurity?: Maybe; + filebeatSystemModule?: Maybe; + winlogbeatSecurity?: Maybe; + winlogbeatMWSysmonOperational?: Maybe; + }; } -export interface HostOverviewRequestOptions extends Partial> { - hostName: string; - skip?: boolean; - timerange: TimerangeInput; - inspect?: Maybe; +export interface OverviewHostHit extends SearchHit { + aggregations: { + auditd_count: { + doc_count: number; + }; + endgame_module: { + dns_event_count: { + doc_count: number; + }; + file_event_count: { + doc_count: number; + }; + image_load_event_count: { + doc_count: number; + }; + network_event_count: { + doc_count: number; + }; + process_event_count: { + doc_count: number; + }; + registry_event: { + doc_count: number; + }; + security_event_count: { + doc_count: number; + }; + }; + fim_count: { + doc_count: number; + }; + system_module: { + login_count: { + doc_count: number; + }; + package_count: { + doc_count: number; + }; + process_count: { + doc_count: number; + }; + user_count: { + doc_count: number; + }; + filebeat_count: { + doc_count: number; + }; + }; + winlog_count: { + doc_count: number; + }; + }; } 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 4d6bd87bb9d420..b7d905d22e8390 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 @@ -7,6 +7,8 @@ import { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; import { ESQuery } from '../../typed_json'; import { + HostDetailsStrategyResponse, + HostDetailsRequestOptions, HostOverviewStrategyResponse, HostAuthenticationsRequestOptions, HostAuthenticationsStrategyResponse, @@ -27,6 +29,8 @@ import { NetworkTlsRequestOptions, NetworkHttpStrategyResponse, NetworkHttpRequestOptions, + NetworkOverviewStrategyResponse, + NetworkOverviewRequestOptions, NetworkTopCountriesStrategyResponse, NetworkTopCountriesRequestOptions, NetworkTopNFlowStrategyResponse, @@ -73,7 +77,9 @@ export interface RequestOptionsPaginated extends RequestBasicOpt export type StrategyResponseType = T extends HostsQueries.hosts ? HostsStrategyResponse - : T extends HostsQueries.hostOverview + : T extends HostsQueries.details + ? HostDetailsStrategyResponse + : T extends HostsQueries.overview ? HostOverviewStrategyResponse : T extends HostsQueries.authentications ? HostAuthenticationsStrategyResponse @@ -85,6 +91,8 @@ export type StrategyResponseType = T extends HostsQ ? NetworkDnsStrategyResponse : T extends NetworkQueries.http ? NetworkHttpStrategyResponse + : T extends NetworkQueries.overview + ? NetworkOverviewStrategyResponse : T extends NetworkQueries.tls ? NetworkTlsStrategyResponse : T extends NetworkQueries.topCountries @@ -97,7 +105,9 @@ export type StrategyResponseType = T extends HostsQ export type StrategyRequestType = T extends HostsQueries.hosts ? HostsRequestOptions - : T extends HostsQueries.hostOverview + : T extends HostsQueries.details + ? HostDetailsRequestOptions + : T extends HostsQueries.overview ? HostOverviewRequestOptions : T extends HostsQueries.authentications ? HostAuthenticationsRequestOptions @@ -109,6 +119,8 @@ export type StrategyRequestType = T extends HostsQu ? NetworkDnsRequestOptions : T extends NetworkQueries.http ? NetworkHttpRequestOptions + : T extends NetworkQueries.overview + ? NetworkOverviewRequestOptions : T extends NetworkQueries.tls ? NetworkTlsRequestOptions : T extends NetworkQueries.topCountries 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 a0ef43eb3d8998..d61acbe62ffb03 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 @@ -7,6 +7,7 @@ export * from './common'; export * from './dns'; export * from './http'; +export * from './overview'; export * from './tls'; export * from './top_countries'; export * from './top_n_flow'; @@ -14,6 +15,7 @@ export * from './top_n_flow'; export enum NetworkQueries { dns = 'dns', http = 'http', + overview = 'overviewNetwork', tls = 'tls', topCountries = 'topCountries', topNFlow = 'topNFlow', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts new file mode 100644 index 00000000000000..b7c8b7448eaf7c --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/overview/index.ts @@ -0,0 +1,62 @@ +/* + * 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, SearchHit } from '../../../common'; +import { RequestBasicOptions } from '../..'; + +export type NetworkOverviewRequestOptions = RequestBasicOptions; + +export interface NetworkOverviewStrategyResponse extends IEsSearchResponse { + inspect?: Maybe; + overviewNetwork: { + auditbeatSocket?: Maybe; + filebeatCisco?: Maybe; + filebeatNetflow?: Maybe; + filebeatPanw?: Maybe; + filebeatSuricata?: Maybe; + filebeatZeek?: Maybe; + packetbeatDNS?: Maybe; + packetbeatFlow?: Maybe; + packetbeatTLS?: Maybe; + }; +} + +export interface OverviewNetworkHit extends SearchHit { + aggregations: { + unique_flow_count: { + doc_count: number; + }; + unique_dns_count: { + doc_count: number; + }; + unique_suricata_count: { + doc_count: number; + }; + unique_zeek_count: { + doc_count: number; + }; + unique_socket_count: { + doc_count: number; + }; + unique_filebeat_count: { + unique_netflow_count: { + doc_count: number; + }; + unique_panw_count: { + doc_count: number; + }; + unique_cisco_count: { + doc_count: number; + }; + }; + unique_packetbeat_count: { + unique_tls_count: { + doc_count: number; + }; + }; + }; +} diff --git a/x-pack/plugins/security_solution/cypress/fixtures/overview.json b/x-pack/plugins/security_solution/cypress/fixtures/overview.json index 69594b88b75154..c4aeda0c446e43 100644 --- a/x-pack/plugins/security_solution/cypress/fixtures/overview.json +++ b/x-pack/plugins/security_solution/cypress/fixtures/overview.json @@ -2,37 +2,6 @@ "data": { "source": { "id": "default", - "OverviewNetwork": { - "auditbeatSocket": 578502, - "filebeatCisco": 999, - "filebeatNetflow": 2544, - "filebeatPanw": 678, - "filebeatSuricata": 303699, - "filebeatZeek": 71129, - "packetbeatDNS": 1090, - "packetbeatFlow": 722153, - "packetbeatTLS": 340, - "__typename": "OverviewNetworkData" - }, - "OverviewHost": { - "auditbeatAuditd": 123, - "auditbeatFIM": 345, - "auditbeatLogin": 456, - "auditbeatPackage": 567, - "auditbeatProcess": 678, - "auditbeatUser": 789, - "endgameDns": 391, - "endgameFile": 392, - "endgameImageLoad": 393, - "endgameNetwork": 394, - "endgameProcess": 395, - "endgameRegistry": 396, - "endgameSecurity": 397, - "filebeatSystemModule": 890, - "winlogbeatSecurity": 70, - "winlogbeatMWSysmonOperational": 30, - "__typename": "OverviewHostData" - }, "status": { "indicesExist": true, "indexFields": [], diff --git a/x-pack/plugins/security_solution/cypress/fixtures/overview_search_strategy.json b/x-pack/plugins/security_solution/cypress/fixtures/overview_search_strategy.json new file mode 100644 index 00000000000000..d0c75170150916 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/fixtures/overview_search_strategy.json @@ -0,0 +1,33 @@ +{ + "overviewNetwork": { + "auditbeatSocket": 578502, + "filebeatCisco": 999, + "filebeatNetflow": 2544, + "filebeatPanw": 678, + "filebeatSuricata": 303699, + "filebeatZeek": 71129, + "packetbeatDNS": 1090, + "packetbeatFlow": 722153, + "packetbeatTLS": 340, + "__typename": "OverviewNetworkData" + }, + "overviewHost": { + "auditbeatAuditd": 123, + "auditbeatFIM": 345, + "auditbeatLogin": 456, + "auditbeatPackage": 567, + "auditbeatProcess": 678, + "auditbeatUser": 789, + "endgameDns": 391, + "endgameFile": 392, + "endgameImageLoad": 393, + "endgameNetwork": 394, + "endgameProcess": 395, + "endgameRegistry": 396, + "endgameSecurity": 397, + "filebeatSystemModule": 890, + "winlogbeatSecurity": 70, + "winlogbeatMWSysmonOperational": 30, + "__typename": "OverviewHostData" + } +} diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index b799d487acd086..14464333fcafe0 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -14,6 +14,7 @@ import { OVERVIEW_URL } from '../urls/navigation'; describe('Overview Page', () => { before(() => { cy.stubSecurityApi('overview'); + cy.stubSearchStrategyApi('overview_search_strategy'); loginAndWaitForPage(OVERVIEW_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/support/commands.js b/x-pack/plugins/security_solution/cypress/support/commands.js index f0dd7976011761..0e3c9562aedf05 100644 --- a/x-pack/plugins/security_solution/cypress/support/commands.js +++ b/x-pack/plugins/security_solution/cypress/support/commands.js @@ -39,6 +39,15 @@ Cypress.Commands.add('stubSecurityApi', function (dataFileName) { cy.route('POST', 'api/solutions/security/graphql', `@${dataFileName}JSON`); }); +Cypress.Commands.add('stubSearchStrategyApi', function (dataFileName) { + cy.on('window:before:load', (win) => { + win.fetch = null; + }); + cy.server(); + cy.fixture(dataFileName).as(`${dataFileName}JSON`); + cy.route('POST', 'internal/search/securitySolutionSearchStrategy', `@${dataFileName}JSON`); +}); + Cypress.Commands.add( 'attachFile', { diff --git a/x-pack/plugins/security_solution/cypress/support/index.d.ts b/x-pack/plugins/security_solution/cypress/support/index.d.ts index 906e526e2c4a04..f66aeff5d578d3 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.d.ts +++ b/x-pack/plugins/security_solution/cypress/support/index.d.ts @@ -7,6 +7,7 @@ declare namespace Cypress { interface Chainable { stubSecurityApi(dataFileName: string): Chainable; + stubSearchStrategyApi(dataFileName: string): Chainable; attachFile(fileName: string, fileType?: string): Chainable; } } diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx similarity index 79% rename from x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx rename to x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx index b28f479634d421..7b248d867bb765 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx @@ -16,8 +16,8 @@ import { useKibana } from '../../../../common/lib/kibana'; import { HostItem, HostsQueries, - HostOverviewRequestOptions, - HostOverviewStrategyResponse, + HostDetailsRequestOptions, + HostDetailsStrategyResponse, } from '../../../../../common/search_strategy/security_solution/hosts'; import * as i18n from './translations'; @@ -25,18 +25,18 @@ import { AbortError } from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; -const ID = 'hostOverviewQuery'; +const ID = 'hostDetailsQuery'; -export interface HostOverviewArgs { +export interface HostDetailsArgs { id: string; inspect: InspectResponse; - hostOverview: HostItem; + hostDetails: HostItem; refetch: inputsModel.Refetch; startDate: string; endDate: string; } -interface UseHostOverview { +interface UseHostDetails { id?: string; hostName: string; endDate: string; @@ -44,22 +44,22 @@ interface UseHostOverview { startDate: string; } -export const useHostOverview = ({ +export const useHostDetails = ({ endDate, hostName, skip = false, startDate, id = ID, -}: UseHostOverview): [boolean, HostOverviewArgs] => { +}: UseHostDetails): [boolean, HostDetailsArgs] => { 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 [hostOverviewRequest, setHostOverviewRequest] = useState({ + const [hostDetailsRequest, setHostDetailsRequest] = useState({ defaultIndex, hostName, - factoryQueryType: HostsQueries.hostOverview, + factoryQueryType: HostsQueries.details, timerange: { interval: '12h', from: startDate, @@ -67,9 +67,9 @@ export const useHostOverview = ({ }, }); - const [hostOverviewResponse, setHostOverviewResponse] = useState({ + const [hostDetailsResponse, setHostDetailsResponse] = useState({ endDate, - hostOverview: {}, + hostDetails: {}, id: ID, inspect: { dsl: [], @@ -79,15 +79,15 @@ export const useHostOverview = ({ startDate, }); - const hostOverviewSearch = useCallback( - (request: HostOverviewRequestOptions) => { + const hostDetailsSearch = useCallback( + (request: HostDetailsRequestOptions) => { let didCancel = false; const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); const searchSubscription$ = data.search - .search(request, { + .search(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) @@ -96,9 +96,9 @@ export const useHostOverview = ({ if (!response.isPartial && !response.isRunning) { if (!didCancel) { setLoading(false); - setHostOverviewResponse((prevResponse) => ({ + setHostDetailsResponse((prevResponse) => ({ ...prevResponse, - hostOverview: response.hostOverview, + hostDetails: response.hostDetails, inspect: getInspectResponse(response, prevResponse.inspect), refetch: refetch.current, })); @@ -135,7 +135,7 @@ export const useHostOverview = ({ ); useEffect(() => { - setHostOverviewRequest((prevRequest) => { + setHostDetailsRequest((prevRequest) => { const myRequest = { ...prevRequest, defaultIndex, @@ -154,8 +154,8 @@ export const useHostOverview = ({ }, [defaultIndex, endDate, hostName, startDate, skip]); useEffect(() => { - hostOverviewSearch(hostOverviewRequest); - }, [hostOverviewRequest, hostOverviewSearch]); + hostDetailsSearch(hostDetailsRequest); + }, [hostDetailsRequest, hostDetailsSearch]); - return [loading, hostOverviewResponse]; + return [loading, hostDetailsResponse]; }; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/host_overview.gql_query.ts b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/host_overview.gql_query.ts similarity index 100% rename from x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/host_overview.gql_query.ts rename to x-pack/plugins/security_solution/public/hosts/containers/hosts/details/host_overview.gql_query.ts diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/index.tsx rename to x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/translations.ts rename to x-pack/plugins/security_solution/public/hosts/containers/hosts/details/translations.ts diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 67f563e944f42f..49b63a5f76a143 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -25,7 +25,7 @@ import { HostOverview } from '../../../overview/components/host_overview'; import { manageQuery } from '../../../common/components/page/manage_query'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; -import { HostOverviewByNameQuery } from '../../containers/hosts/overview'; +import { HostOverviewByNameQuery } from '../../containers/hosts/details'; import { KpiHostDetailsQuery } from '../../containers/kpi_host_details'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useWithSource } from '../../../common/containers/source'; diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx index 8268a550257c9c..f2d6b503260825 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx @@ -22,29 +22,24 @@ describe('EventCounts', () => { test('it filters the `Host events` widget with a `host.name` `exists` filter', () => { const wrapper = mount( - - - + , + { wrappingComponent: TestProviders } ); expect( - (wrapper.find('[data-test-subj="overview-host-query"]').first().props() as OverviewHostProps) - .filterQuery + (wrapper.find('Memo(OverviewHostComponent)').first().props() as OverviewHostProps).filterQuery ).toContain('[{"bool":{"should":[{"exists":{"field":"host.name"}}]'); }); test('it filters the `Network events` widget with a `source.ip` or `destination.ip` `exists` filter', () => { const wrapper = mount( - - - + , + { wrappingComponent: TestProviders } ); expect( - (wrapper - .find('[data-test-subj="overview-network-query"]') - .first() - .props() as OverviewNetworkProps).filterQuery + (wrapper.find('Memo(OverviewNetworkComponent)').first().props() as OverviewNetworkProps) + .filterQuery ).toContain( '{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"exists":{"field":"source.ip"}}],"minimum_should_match":1}},{"bool":{"should":[{"exists":{"field":"destination.ip"}}],"minimum_should_match":1}}],"minimum_should_match":1}}]}}]' ); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx index 5ff78c9b29cf5a..b932add7afc2c7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx @@ -7,7 +7,6 @@ import { cloneDeep } from 'lodash/fp'; import { mount } from 'enzyme'; import React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; import '../../../common/mock/match_media'; import { @@ -21,11 +20,7 @@ import { import { OverviewHost } from '.'; import { createStore, State } from '../../../common/store'; -import { overviewHostQuery } from '../../containers/overview_host/index.gql_query'; -import { GetOverviewHostQuery } from '../../../graphql/types'; - -// we don't have the types for waitFor just yet, so using "as waitFor" until when we do -import { wait as waitFor } from '@testing-library/react'; +import { useHostOverview } from '../../containers/overview_host'; jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/link_to'); @@ -33,67 +28,30 @@ jest.mock('../../../common/components/link_to'); const startDate = '2020-01-20T20:49:57.080Z'; const endDate = '2020-01-21T20:49:57.080Z'; -interface MockedProvidedQuery { - request: { - query: GetOverviewHostQuery.Query; - fetchPolicy: string; - variables: GetOverviewHostQuery.Variables; - }; - result: { - data: { - source: unknown; - }; - }; -} - -const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [ - { - request: { - query: overviewHostQuery, - fetchPolicy: 'cache-and-network', - variables: { - sourceId: 'default', - timerange: { interval: '12h', from: startDate, to: endDate }, - filterQuery: undefined, - defaultIndex: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - inspect: false, - }, - }, - result: { - data: { - source: { - id: 'default', - OverviewHost: { - auditbeatAuditd: 1, - auditbeatFIM: 1, - auditbeatLogin: 1, - auditbeatPackage: 1, - auditbeatProcess: 1, - auditbeatUser: 1, - endgameDns: 1, - endgameFile: 1, - endgameImageLoad: 1, - endgameNetwork: 1, - endgameProcess: 1, - endgameRegistry: 1, - endgameSecurity: 1, - filebeatSystemModule: 1, - winlogbeatSecurity: 1, - winlogbeatMWSysmonOperational: 1, - }, - }, - }, - }, +const MOCKED_RESPONSE = { + overviewHost: { + auditbeatAuditd: 1, + auditbeatFIM: 1, + auditbeatLogin: 1, + auditbeatPackage: 1, + auditbeatProcess: 1, + auditbeatUser: 1, + endgameDns: 1, + endgameFile: 1, + endgameImageLoad: 1, + endgameNetwork: 1, + endgameProcess: 1, + endgameRegistry: 1, + endgameSecurity: 1, + filebeatSystemModule: 1, + winlogbeatSecurity: 1, + winlogbeatMWSysmonOperational: 1, }, -]; +}; + +jest.mock('../../containers/overview_host'); +const useHostOverviewMock = useHostOverview as jest.Mock; +useHostOverviewMock.mockReturnValue([false, MOCKED_RESPONSE]); describe('OverviewHost', () => { const state: State = mockGlobalState; @@ -131,8 +89,9 @@ describe('OverviewHost', () => { }); test('it renders an empty subtitle while loading', () => { + useHostOverviewMock.mockReturnValueOnce([true, { overviewHost: {} }]); const wrapper = mount( - + ); @@ -142,18 +101,13 @@ describe('OverviewHost', () => { test('it renders the expected event count in the subtitle after loading events', async () => { const wrapper = mount( - - - - + + ); - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('[data-test-subj="header-panel-subtitle"]').first().text()).toEqual( - 'Showing: 16 events' - ); - }); + expect(wrapper.find('[data-test-subj="header-panel-subtitle"]').first().text()).toEqual( + 'Showing: 16 events' + ); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx index 783e433dfba265..3f35d0abbaa856 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx @@ -12,7 +12,7 @@ import React, { useMemo, useCallback } from 'react'; import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants'; import { ESQuery } from '../../../../common/typed_json'; -import { ID as OverviewHostQueryId, OverviewHostQuery } from '../../containers/overview_host'; +import { ID as OverviewHostQueryId, useHostOverview } from '../../containers/overview_host'; import { HeaderSection } from '../../../common/components/header_section'; import { useUiSetting$, useKibana } from '../../../common/lib/kibana'; import { getHostsUrl, useFormatUrl } from '../../../common/components/link_to'; @@ -44,6 +44,12 @@ const OverviewHostComponent: React.FC = ({ const { navigateToApp } = useKibana().services.application; const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const [loading, { overviewHost, id, inspect, refetch }] = useHostOverview({ + endDate, + filterQuery, + startDate, + }); + const goToHost = useCallback( (ev) => { ev.preventDefault(); @@ -54,6 +60,16 @@ const OverviewHostComponent: React.FC = ({ [navigateToApp, urlSearch] ); + const hostEventsCount = useMemo( + () => getOverviewHostStats(overviewHost).reduce((total, stat) => total + stat.count, 0), + [overviewHost] + ); + + const formattedHostEventsCount = useMemo( + () => numeral(hostEventsCount).format(defaultNumberFormat), + [defaultNumberFormat, hostEventsCount] + ); + const hostPageButton = useMemo( () => ( @@ -65,71 +81,54 @@ const OverviewHostComponent: React.FC = ({ ), [goToHost, formatUrl] ); + return ( - + ) : ( + <>{''} + ) + } + title={ + + } > - {({ overviewHost, loading, id, inspect, refetch }) => { - const hostEventsCount = getOverviewHostStats(overviewHost).reduce( - (total, stat) => total + stat.count, - 0 - ); - const formattedHostEventsCount = numeral(hostEventsCount).format(defaultNumberFormat); + <> + + {hostPageButton} + + - return ( - <> - - ) : ( - <>{''} - ) - } - title={ - - } - > - <> - - {hostPageButton} - - - - - - ); - }} - + ); }; +OverviewHostComponent.displayName = 'OverviewHostComponent'; + export const OverviewHost = React.memo(OverviewHostComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx index 0bb887b38a4b10..f67287ea4b9e2d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx @@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash/fp'; import { mount } from 'enzyme'; import React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; + import '../../../common/mock/match_media'; import { apolloClientObservable, @@ -17,13 +17,9 @@ import { createSecuritySolutionStorageMock, kibanaObservable, } from '../../../common/mock'; - import { OverviewNetwork } from '.'; import { createStore, State } from '../../../common/store'; -import { overviewNetworkQuery } from '../../containers/overview_network/index.gql_query'; -import { GetOverviewHostQuery } from '../../../graphql/types'; -// we don't have the types for waitFor just yet, so using "as waitFor" until when we do -import { wait as waitFor } from '@testing-library/react'; +import { useNetworkOverview } from '../../containers/overview_network'; jest.mock('../../../common/components/link_to'); const mockNavigateToApp = jest.fn(); @@ -48,60 +44,23 @@ jest.mock('../../../common/lib/kibana', () => { const startDate = '2020-01-20T20:49:57.080Z'; const endDate = '2020-01-21T20:49:57.080Z'; -interface MockedProvidedQuery { - request: { - query: GetOverviewHostQuery.Query; - fetchPolicy: string; - variables: GetOverviewHostQuery.Variables; - }; - result: { - data: { - source: unknown; - }; - }; -} - -const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [ - { - request: { - query: overviewNetworkQuery, - fetchPolicy: 'cache-and-network', - variables: { - sourceId: 'default', - timerange: { interval: '12h', from: startDate, to: endDate }, - filterQuery: undefined, - defaultIndex: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - inspect: false, - }, - }, - result: { - data: { - source: { - id: 'default', - OverviewNetwork: { - auditbeatSocket: 1, - filebeatCisco: 1, - filebeatNetflow: 1, - filebeatPanw: 1, - filebeatSuricata: 1, - filebeatZeek: 1, - packetbeatDNS: 1, - packetbeatFlow: 1, - packetbeatTLS: 1, - }, - }, - }, - }, +const MOCKED_RESPONSE = { + overviewNetwork: { + auditbeatSocket: 1, + filebeatCisco: 1, + filebeatNetflow: 1, + filebeatPanw: 1, + filebeatSuricata: 1, + filebeatZeek: 1, + packetbeatDNS: 1, + packetbeatFlow: 1, + packetbeatTLS: 1, }, -]; +}; + +jest.mock('../../containers/overview_network'); +const useNetworkOverviewMock = useNetworkOverview as jest.Mock; +useNetworkOverviewMock.mockReturnValue([false, MOCKED_RESPONSE]); describe('OverviewNetwork', () => { const state: State = mockGlobalState; @@ -139,8 +98,9 @@ describe('OverviewNetwork', () => { }); test('it renders an empty subtitle while loading', () => { + useNetworkOverviewMock.mockReturnValueOnce([true, { overviewNetwork: {} }]); const wrapper = mount( - + ); @@ -150,27 +110,20 @@ describe('OverviewNetwork', () => { test('it renders the expected event count in the subtitle after loading events', async () => { const wrapper = mount( - - - - + + ); - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('[data-test-subj="header-panel-subtitle"]').first().text()).toEqual( - 'Showing: 9 events' - ); - }); + expect(wrapper.find('[data-test-subj="header-panel-subtitle"]').first().text()).toEqual( + 'Showing: 9 events' + ); }); it('it renders View Network', () => { const wrapper = mount( - - - - + + ); @@ -179,10 +132,8 @@ describe('OverviewNetwork', () => { it('when click on View Network we call navigateToApp to make sure to navigate to right page', () => { const wrapper = mount( - - - - + + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx index 8282eaeb63c28b..089bed3c678087 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx @@ -17,7 +17,7 @@ import { useUiSetting$, useKibana } from '../../../common/lib/kibana'; import { manageQuery } from '../../../common/components/page/manage_query'; import { ID as OverviewNetworkQueryId, - OverviewNetworkQuery, + useNetworkOverview, } from '../../containers/overview_network'; import { getOverviewNetworkStats, OverviewNetworkStats } from '../overview_network_stats'; import { getNetworkUrl, useFormatUrl } from '../../../common/components/link_to'; @@ -45,6 +45,12 @@ const OverviewNetworkComponent: React.FC = ({ const { navigateToApp } = useKibana().services.application; const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const [loading, { overviewNetwork, id, inspect, refetch }] = useNetworkOverview({ + endDate, + filterQuery, + startDate, + }); + const goToNetwork = useCallback( (ev) => { ev.preventDefault(); @@ -55,6 +61,15 @@ const OverviewNetworkComponent: React.FC = ({ [navigateToApp, urlSearch] ); + const networkEventsCount = useMemo( + () => getOverviewNetworkStats(overviewNetwork).reduce((total, stat) => total + stat.count, 0), + [overviewNetwork] + ); + const formattedNetworkEventsCount = useMemo( + () => numeral(networkEventsCount).format(defaultNumberFormat), + [defaultNumberFormat, networkEventsCount] + ); + const networkPageButton = useMemo( () => ( = ({ return ( - - - {({ overviewNetwork, loading, id, inspect, refetch }) => { - const networkEventsCount = getOverviewNetworkStats(overviewNetwork).reduce( - (total, stat) => total + stat.count, - 0 - ); - const formattedNetworkEventsCount = numeral(networkEventsCount).format( - defaultNumberFormat - ); - - return ( - <> - - ) : ( - <>{''} - ) - } - title={ - - } - > - {networkPageButton} - - - + <> + - - ); - }} - + ) : ( + <>{''} + ) + } + title={ + + } + > + {networkPageButton} + + + + diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx index a6977c3bbd49dc..e011e6c7b6b65e 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx @@ -4,95 +4,161 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr } from 'lodash/fp'; -import React, { useMemo } from 'react'; -import { Query } from 'react-apollo'; -import { connect, ConnectedProps } from 'react-redux'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { GetOverviewHostQuery, OverviewHostData } from '../../../graphql/types'; -import { useUiSetting } from '../../../common/lib/kibana'; -import { inputsModel, inputsSelectors } from '../../../common/store/inputs'; -import { State } from '../../../common/store'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { QueryTemplateProps } from '../../../common/containers/query_template'; - -import { overviewHostQuery } from './index.gql_query'; +import { + HostsQueries, + HostOverviewRequestOptions, + HostOverviewStrategyResponse, +} from '../../../../common/search_strategy/security_solution'; +import { useKibana } from '../../../common/lib/kibana'; +import { inputsModel } from '../../../common/store/inputs'; +import { createFilter } from '../../../common/containers/helpers'; +import { ESQuery } from '../../../../common/typed_json'; import { useManageSource } from '../../../common/containers/sourcerer'; import { SOURCERER_FEATURE_FLAG_ON } from '../../../common/containers/sourcerer/constants'; +import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../helpers'; +import { InspectResponse } from '../../../types'; +import * as i18n from './translations'; export const ID = 'overviewHostQuery'; -export interface OverviewHostArgs { +export interface HostOverviewArgs { id: string; - inspect: inputsModel.InspectQuery; - loading: boolean; - overviewHost: OverviewHostData; + inspect: InspectResponse; + isInspected: boolean; + overviewHost: HostOverviewStrategyResponse['overviewHost']; refetch: inputsModel.Refetch; } -export interface OverviewHostProps extends QueryTemplateProps { - children: (args: OverviewHostArgs) => React.ReactNode; - sourceId: string; +interface UseHostOverview { + filterQuery?: ESQuery | string; endDate: string; + skip?: boolean; startDate: string; } -const OverviewHostComponentQuery = React.memo( - ({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => { - const { activeSourceGroupId, getManageSourceGroupById } = useManageSource(); - const { indexPatterns } = useMemo(() => getManageSourceGroupById(activeSourceGroupId), [ - getManageSourceGroupById, - activeSourceGroupId, - ]); - const uiDefaultIndexPatterns = useUiSetting(DEFAULT_INDEX_KEY); - const defaultIndex = SOURCERER_FEATURE_FLAG_ON ? indexPatterns : uiDefaultIndexPatterns; - return ( - - query={overviewHostQuery} - fetchPolicy={getDefaultFetchPolicy()} - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate, - to: endDate, - }, - filterQuery: createFilter(filterQuery), - defaultIndex, - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const overviewHost = getOr({}, `source.OverviewHost`, data); - return children({ - id, - inspect: getOr(null, 'source.OverviewHost.inspect', data), - overviewHost, - loading, - refetch, - }); - }} - - ); - } -); +export const useHostOverview = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseHostOverview): [boolean, HostOverviewArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const { activeSourceGroupId, getManageSourceGroupById } = useManageSource(); + const { indexPatterns } = useMemo(() => getManageSourceGroupById(activeSourceGroupId), [ + getManageSourceGroupById, + activeSourceGroupId, + ]); + const uiDefaultIndexPatterns = uiSettings.get(DEFAULT_INDEX_KEY); + const defaultIndex = SOURCERER_FEATURE_FLAG_ON ? indexPatterns : uiDefaultIndexPatterns; -OverviewHostComponentQuery.displayName = 'OverviewHostComponentQuery'; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [overviewHostRequest, setHostRequest] = useState({ + defaultIndex, + factoryQueryType: HostsQueries.overview, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); -const makeMapStateToProps = () => { - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = ID }: OverviewHostProps) => { - const { isInspected } = getQuery(state, id); - return { - isInspected, - }; - }; - return mapStateToProps; -}; + const [overviewHostResponse, setHostOverviewResponse] = useState({ + overviewHost: {}, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); -const connector = connect(makeMapStateToProps); + const overviewHostSearch = useCallback( + (request: HostOverviewRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); -type PropsFromRedux = ConnectedProps; + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostOverviewResponse((prevResponse) => ({ + ...prevResponse, + overviewHost: response.overviewHost, + 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_HOST_OVERVIEW); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOST_OVERVIEW, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); -export const OverviewHostQuery = connector(OverviewHostComponentQuery); + useEffect(() => { + setHostRequest((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(() => { + overviewHostSearch(overviewHostRequest); + }, [overviewHostRequest, overviewHostSearch]); + + return [loading, overviewHostResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_host/translations.ts b/x-pack/plugins/security_solution/public/overview/containers/overview_host/translations.ts new file mode 100644 index 00000000000000..65894847a3e74c --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_host/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_HOST_OVERVIEW = i18n.translate( + 'xpack.securitySolution.allHost.errorSearchDescription', + { + defaultMessage: `An error has occurred on all hosts search`, + } +); + +export const FAIL_HOST_OVERVIEW = i18n.translate( + 'xpack.securitySolution.allHost.failSearchDescription', + { + defaultMessage: `Failed to run search on all hosts`, + } +); diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx index 38c035f6883b69..c61606e0c31dd4 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx @@ -4,85 +4,152 @@ * 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 { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { GetOverviewNetworkQuery, OverviewNetworkData } from '../../../graphql/types'; -import { useUiSetting } from '../../../common/lib/kibana'; -import { State } from '../../../common/store'; -import { inputsModel, inputsSelectors } from '../../../common/store/inputs'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { QueryTemplateProps } from '../../../common/containers/query_template'; - -import { overviewNetworkQuery } from './index.gql_query'; +import { + NetworkQueries, + NetworkOverviewRequestOptions, + NetworkOverviewStrategyResponse, +} from '../../../../common/search_strategy/security_solution'; +import { useKibana } from '../../../common/lib/kibana'; +import { inputsModel } from '../../../common/store/inputs'; +import { createFilter } from '../../../common/containers/helpers'; +import { ESQuery } from '../../../../common/typed_json'; +import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../helpers'; +import { InspectResponse } from '../../../types'; +import * as i18n from './translations'; export const ID = 'overviewNetworkQuery'; -export interface OverviewNetworkArgs { +export interface NetworkOverviewArgs { id: string; - inspect: inputsModel.InspectQuery; - overviewNetwork: OverviewNetworkData; - loading: boolean; + inspect: InspectResponse; + isInspected: boolean; + overviewNetwork: NetworkOverviewStrategyResponse['overviewNetwork']; refetch: inputsModel.Refetch; } -export interface OverviewNetworkProps extends QueryTemplateProps { - children: (args: OverviewNetworkArgs) => React.ReactNode; - sourceId: string; +interface UseNetworkOverview { + filterQuery?: ESQuery | string; endDate: string; + skip?: boolean; startDate: string; } -export const OverviewNetworkComponentQuery = React.memo( - ({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => ( - - query={overviewNetworkQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - variables={{ - sourceId, +export const useNetworkOverview = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseNetworkOverview): [boolean, NetworkOverviewArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [overviewNetworkRequest, setNetworkRequest] = useState({ + defaultIndex, + factoryQueryType: NetworkQueries.overview, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [overviewNetworkResponse, setNetworkOverviewResponse] = useState({ + overviewNetwork: {}, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const overviewNetworkSearch = useCallback( + (request: NetworkOverviewRequestOptions) => { + 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); + setNetworkOverviewResponse((prevResponse) => ({ + ...prevResponse, + overviewNetwork: response.overviewNetwork, + 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_OVERVIEW); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_NETWORK_OVERVIEW, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setNetworkRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), timerange: { interval: '12h', from: startDate, to: endDate, }, - filterQuery: createFilter(filterQuery), - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const overviewNetwork = getOr({}, `source.OverviewNetwork`, data); - return children({ - id, - inspect: getOr(null, 'source.OverviewNetwork.inspect', data), - overviewNetwork, - loading, - refetch, - }); - }} - - ) -); + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); -OverviewNetworkComponentQuery.displayName = 'OverviewNetworkComponentQuery'; + useEffect(() => { + overviewNetworkSearch(overviewNetworkRequest); + }, [overviewNetworkRequest, overviewNetworkSearch]); -const makeMapStateToProps = () => { - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = ID }: OverviewNetworkProps) => { - const { isInspected } = getQuery(state, id); - return { - isInspected, - }; - }; - return mapStateToProps; + return [loading, overviewNetworkResponse]; }; - -const connector = connect(makeMapStateToProps); - -type PropsFromRedux = ConnectedProps; - -export const OverviewNetworkQuery = connector(OverviewNetworkComponentQuery); diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/translations.ts b/x-pack/plugins/security_solution/public/overview/containers/overview_network/translations.ts new file mode 100644 index 00000000000000..ac6b638a4ba011 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/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_OVERVIEW = i18n.translate( + 'xpack.securitySolution.allHost.errorSearchDescription', + { + defaultMessage: `An error has occurred on all hosts search`, + } +); + +export const FAIL_NETWORK_OVERVIEW = i18n.translate( + 'xpack.securitySolution.allHost.failSearchDescription', + { + defaultMessage: `Failed to run search on all hosts`, + } +); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/helpers.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts new file mode 100644 index 00000000000000..616e4ed0bac387 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.ts @@ -0,0 +1,38 @@ +/* + * 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 { get } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + HostAggEsData, + HostAggEsItem, + HostDetailsStrategyResponse, + HostsQueries, + HostDetailsRequestOptions, +} from '../../../../../../common/search_strategy/security_solution/hosts'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; +import { buildHostDetailsQuery } from './query.host_details.dsl'; +import { formatHostItem } from './helpers'; + +export const hostDetails: SecuritySolutionFactory = { + buildDsl: (options: HostDetailsRequestOptions) => buildHostDetailsQuery(options), + parse: async ( + options: HostDetailsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; + const inspect = { + dsl: [inspectStringifyObject(buildHostDetailsQuery(options))], + response: [inspectStringifyObject(response)], + }; + const formattedHostItem = formatHostItem(aggregations); + + return { ...response, inspect, hostDetails: formattedHostItem }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.host_overview.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/query.host_details.dsl.ts similarity index 86% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.host_overview.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/query.host_details.dsl.ts index 85cc87414c38e8..ade6128f0b0528 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.host_overview.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/query.host_details.dsl.ts @@ -6,16 +6,16 @@ import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; import { cloudFieldsMap, hostFieldsMap } from '../../../../../../common/ecs/ecs_fields'; -import { HostOverviewRequestOptions } from '../../../../../../common/search_strategy/security_solution'; +import { HostDetailsRequestOptions } from '../../../../../../common/search_strategy/security_solution'; import { buildFieldsTermAggregation } from '../../../../../lib/hosts/helpers'; import { reduceFields } from '../../../../../utils/build_query/reduce_fields'; import { HOST_FIELDS } from './helpers'; -export const buildHostOverviewQuery = ({ +export const buildHostDetailsQuery = ({ hostName, defaultIndex, timerange: { from, to }, -}: HostOverviewRequestOptions): ISearchRequestParams => { +}: HostDetailsRequestOptions): ISearchRequestParams => { const esFields = reduceFields(HOST_FIELDS, { ...hostFieldsMap, ...cloudFieldsMap }); const filter = [ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 38d81c229ac5f8..85619cfec62ce7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -11,14 +11,16 @@ import { import { SecuritySolutionFactory } from '../types'; import { allHosts } from './all'; -import { overviewHost } from './overview'; +import { hostDetails } from './details'; +import { hostOverview } from './overview'; import { firstLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; export const hostsFactory: Record> = { + [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, - [HostsQueries.hostOverview]: overviewHost, + [HostsQueries.overview]: hostOverview, [HostsQueries.firstLastSeen]: firstLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts index 8bdda9ef895b23..7a28c983ec466d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts @@ -4,37 +4,63 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash/fp'; +import { get, getOr } from 'lodash/fp'; import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; import { - HostAggEsData, - HostAggEsItem, HostOverviewStrategyResponse, HostsQueries, HostOverviewRequestOptions, + OverviewHostHit, } from '../../../../../../common/search_strategy/security_solution/hosts'; - import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; -import { buildHostOverviewQuery } from './query.host_overview.dsl'; -import { formatHostItem } from './helpers'; +import { buildOverviewHostQuery } from './query.overview_host.dsl'; -export const overviewHost: SecuritySolutionFactory = { - buildDsl: (options: HostOverviewRequestOptions) => { - return buildHostOverviewQuery(options); - }, +export const hostOverview: SecuritySolutionFactory = { + buildDsl: (options: HostOverviewRequestOptions) => buildOverviewHostQuery(options), parse: async ( options: HostOverviewRequestOptions, - response: IEsSearchResponse + response: IEsSearchResponse ): Promise => { - const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; + const aggregations: OverviewHostHit = get('aggregations', response.rawResponse) || {}; const inspect = { - dsl: [inspectStringifyObject(buildHostOverviewQuery(options))], - response: [inspectStringifyObject(response)], + dsl: [inspectStringifyObject(buildOverviewHostQuery(options))], }; - const formattedHostItem = formatHostItem(aggregations); - return { ...response, inspect, hostOverview: formattedHostItem }; + return { + ...response, + inspect, + overviewHost: { + auditbeatAuditd: getOr(null, 'auditd_count.doc_count', aggregations), + auditbeatFIM: getOr(null, 'fim_count.doc_count', aggregations), + auditbeatLogin: getOr(null, 'system_module.login_count.doc_count', aggregations), + auditbeatPackage: getOr(null, 'system_module.package_count.doc_count', aggregations), + auditbeatProcess: getOr(null, 'system_module.process_count.doc_count', aggregations), + auditbeatUser: getOr(null, 'system_module.user_count.doc_count', aggregations), + endgameDns: getOr(null, 'endgame_module.dns_event_count.doc_count', aggregations), + endgameFile: getOr(null, 'endgame_module.file_event_count.doc_count', aggregations), + endgameImageLoad: getOr( + null, + 'endgame_module.image_load_event_count.doc_count', + aggregations + ), + endgameNetwork: getOr(null, 'endgame_module.network_event_count.doc_count', aggregations), + endgameProcess: getOr(null, 'endgame_module.process_event_count.doc_count', aggregations), + endgameRegistry: getOr(null, 'endgame_module.registry_event.doc_count', aggregations), + endgameSecurity: getOr(null, 'endgame_module.security_event_count.doc_count', aggregations), + filebeatSystemModule: getOr(null, 'system_module.filebeat_count.doc_count', aggregations), + winlogbeatSecurity: getOr( + null, + 'winlog_module.security_event_count.doc_count', + aggregations + ), + winlogbeatMWSysmonOperational: getOr( + null, + 'winlog_module.mwsysmon_operational_event_count.doc_count', + response + ), + }, + }; }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts new file mode 100644 index 00000000000000..cbb098331e112b --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.ts @@ -0,0 +1,295 @@ +/* + * 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 { createQueryFilterClauses } from '../../../../../utils/build_query'; +import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { HostOverviewRequestOptions } from '../../../../../../common/search_strategy/security_solution/hosts'; + +export const buildOverviewHostQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostOverviewRequestOptions): ISearchRequestParams => { + 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: { + auditd_count: { + filter: { + term: { + 'event.module': 'auditd', + }, + }, + }, + endgame_module: { + filter: { + bool: { + should: [ + { + term: { 'event.module': 'endpoint' }, + }, + { + term: { + 'event.module': 'endgame', + }, + }, + ], + }, + }, + aggs: { + dns_event_count: { + filter: { + bool: { + should: [ + { + bool: { + filter: [ + { term: { 'network.protocol': 'dns' } }, + { term: { 'event.category': 'network' } }, + ], + }, + }, + { + term: { + 'endgame.event_type_full': 'dns_event', + }, + }, + ], + }, + }, + }, + file_event_count: { + filter: { + bool: { + should: [ + { + term: { + 'event.category': 'file', + }, + }, + { + term: { + 'endgame.event_type_full': 'file_event', + }, + }, + ], + }, + }, + }, + image_load_event_count: { + filter: { + bool: { + should: [ + { + bool: { + should: [ + { + term: { + 'event.category': 'library', + }, + }, + { + term: { + 'event.category': 'driver', + }, + }, + ], + }, + }, + { + term: { + 'endgame.event_type_full': 'image_load_event', + }, + }, + ], + }, + }, + }, + network_event_count: { + filter: { + bool: { + should: [ + { + bool: { + filter: [ + { + bool: { + must_not: { + term: { 'network.protocol': 'dns' }, + }, + }, + }, + { + term: { 'event.category': 'network' }, + }, + ], + }, + }, + { + term: { + 'endgame.event_type_full': 'network_event', + }, + }, + ], + }, + }, + }, + process_event_count: { + filter: { + bool: { + should: [ + { + term: { 'event.category': 'process' }, + }, + { + term: { + 'endgame.event_type_full': 'process_event', + }, + }, + ], + }, + }, + }, + registry_event: { + filter: { + bool: { + should: [ + { + term: { 'event.category': 'registry' }, + }, + { + term: { + 'endgame.event_type_full': 'registry_event', + }, + }, + ], + }, + }, + }, + security_event_count: { + filter: { + bool: { + should: [ + { + bool: { + filter: [ + { term: { 'event.category': 'session' } }, + { term: { 'event.category': 'authentication' } }, + ], + }, + }, + { + term: { + 'endgame.event_type_full': 'security_event', + }, + }, + ], + }, + }, + }, + }, + }, + fim_count: { + filter: { + term: { + 'event.module': 'file_integrity', + }, + }, + }, + winlog_module: { + filter: { + term: { + 'agent.type': 'winlogbeat', + }, + }, + aggs: { + mwsysmon_operational_event_count: { + filter: { + term: { + 'winlog.channel': 'Microsoft-Windows-Sysmon/Operational', + }, + }, + }, + security_event_count: { + filter: { + term: { + 'winlog.channel': 'Security', + }, + }, + }, + }, + }, + system_module: { + filter: { + term: { + 'event.module': 'system', + }, + }, + aggs: { + login_count: { + filter: { + term: { + 'event.dataset': 'login', + }, + }, + }, + package_count: { + filter: { + term: { + 'event.dataset': 'package', + }, + }, + }, + process_count: { + filter: { + term: { + 'event.dataset': 'process', + }, + }, + }, + user_count: { + filter: { + term: { + 'event.dataset': 'user', + }, + }, + }, + filebeat_count: { + filter: { + term: { + 'agent.type': 'filebeat', + }, + }, + }, + }, + }, + }, + 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/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts index f2b4f851747973..3b927b85899994 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 @@ -12,6 +12,7 @@ import { import { SecuritySolutionFactory } from '../types'; import { networkDns } from './dns'; import { networkHttp } from './http'; +import { networkOverview } from './overview'; import { networkTls } from './tls'; import { networkTopCountries } from './top_countries'; import { networkTopNFlow } from './top_n_flow'; @@ -19,6 +20,7 @@ import { networkTopNFlow } from './top_n_flow'; export const networkFactory: Record> = { [NetworkQueries.dns]: networkDns, [NetworkQueries.http]: networkHttp, + [NetworkQueries.overview]: networkOverview, [NetworkQueries.tls]: networkTls, [NetworkQueries.topCountries]: networkTopCountries, [NetworkQueries.topNFlow]: networkTopNFlow, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts new file mode 100644 index 00000000000000..45cbbfe85bae86 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts @@ -0,0 +1,63 @@ +/* + * 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 { get, getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + NetworkQueries, + NetworkOverviewStrategyResponse, + NetworkOverviewRequestOptions, + OverviewNetworkHit, +} from '../../../../../../common/search_strategy/security_solution/network'; +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; +import { buildOverviewNetworkQuery } from './query.overview_network.dsl'; + +export const networkOverview: SecuritySolutionFactory = { + buildDsl: (options: NetworkOverviewRequestOptions) => buildOverviewNetworkQuery(options), + parse: async ( + options: NetworkOverviewRequestOptions, + response: IEsSearchResponse + ): Promise => { + const aggregations: OverviewNetworkHit = get('aggregations', response.rawResponse) || {}; + const inspect = { + dsl: [inspectStringifyObject(buildOverviewNetworkQuery(options))], + }; + + return { + ...response, + inspect, + overviewNetwork: { + auditbeatSocket: getOr(null, 'unique_socket_count.doc_count', aggregations), + filebeatCisco: getOr( + null, + 'unique_filebeat_count.unique_cisco_count.doc_count', + aggregations + ), + filebeatNetflow: getOr( + null, + 'unique_filebeat_count.unique_netflow_count.doc_count', + aggregations + ), + filebeatPanw: getOr( + null, + 'unique_filebeat_count.unique_panw_count.doc_count', + aggregations + ), + filebeatSuricata: getOr(null, 'unique_suricata_count.doc_count', aggregations), + filebeatZeek: getOr(null, 'unique_zeek_count.doc_count', aggregations), + packetbeatDNS: getOr(null, 'unique_dns_count.doc_count', aggregations), + packetbeatFlow: getOr(null, 'unique_flow_count.doc_count', aggregations), + packetbeatTLS: getOr( + null, + 'unique_packetbeat_count.unique_tls_count.doc_count', + aggregations + ), + }, + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.dsl.ts new file mode 100644 index 00000000000000..7f0482644c9a4b --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/query.overview_network.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 { createQueryFilterClauses } from '../../../../../utils/build_query'; +import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { NetworkOverviewRequestOptions } from '../../../../../../common/search_strategy/security_solution/network'; + +export const buildOverviewNetworkQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: NetworkOverviewRequestOptions): ISearchRequestParams => { + 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: { + unique_flow_count: { + filter: { + term: { type: 'flow' }, + }, + }, + unique_dns_count: { + filter: { + term: { type: 'dns' }, + }, + }, + unique_suricata_count: { + filter: { + term: { 'service.type': 'suricata' }, + }, + }, + unique_zeek_count: { + filter: { + term: { 'service.type': 'zeek' }, + }, + }, + unique_socket_count: { + filter: { + term: { 'event.dataset': 'socket' }, + }, + }, + unique_filebeat_count: { + filter: { + term: { 'agent.type': 'filebeat' }, + }, + aggs: { + unique_netflow_count: { + filter: { + term: { 'input.type': 'netflow' }, + }, + }, + unique_panw_count: { + filter: { + term: { 'event.module': 'panw' }, + }, + }, + unique_cisco_count: { + filter: { + term: { 'event.module': 'cisco' }, + }, + }, + }, + }, + unique_packetbeat_count: { + filter: { + term: { 'agent.type': 'packetbeat' }, + }, + aggs: { + unique_tls_count: { + filter: { + term: { 'network.protocol': 'tls' }, + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/test/api_integration/apis/security_solution/hosts.ts b/x-pack/test/api_integration/apis/security_solution/hosts.ts index 2904935719d2ca..621718013db7f2 100644 --- a/x-pack/test/api_integration/apis/security_solution/hosts.ts +++ b/x-pack/test/api_integration/apis/security_solution/hosts.ts @@ -13,7 +13,7 @@ import { GetHostsTableQuery, HostsFields, } from '../../../../plugins/security_solution/public/graphql/types'; -import { HostOverviewQuery } from '../../../../plugins/security_solution/public/hosts/containers/hosts/overview/host_overview.gql_query'; +import { HostOverviewQuery } from '../../../../plugins/security_solution/public/hosts/containers/hosts/details/host_overview.gql_query'; import { HostFirstLastSeenGqlQuery } from '../../../../plugins/security_solution/public/hosts/containers/hosts/first_last_seen/first_last_seen.gql_query'; import { HostsTableQuery } from '../../../../plugins/security_solution/public/hosts/containers/hosts/hosts_table.gql_query'; import { FtrProviderContext } from '../../ftr_provider_context';