From ef63dc8c613c979a20a64c28b871d8c272231dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 31 Aug 2020 21:46:38 +0200 Subject: [PATCH] [Security Solution] Refactor Network TLS to use Search Strategy (#76241) --- .../security_solution/hosts/all/index.ts | 14 +- .../security_solution/hosts/common/index.ts | 5 + .../hosts/first_last_seen/index.ts | 4 +- .../security_solution/hosts/overview/index.ts | 4 +- .../security_solution/index.ts | 21 +- .../security_solution/network/index.ts | 11 + .../security_solution/network/tls/index.ts | 64 ++++ .../public/network/containers/tls/index.tsx | 289 ++++++++++-------- .../network/containers/tls/translations.ts | 21 ++ .../pages/ip_details/tls_query_table.tsx | 62 ++-- .../pages/navigation/tls_query_tab_body.tsx | 68 +++-- .../factory/hosts/all/query.all_hosts.dsl.ts | 10 +- .../security_solution/factory/index.ts | 2 + .../factory/network/index.ts | 15 + .../factory/network/tls/helpers.ts | 35 +++ .../factory/network/tls/index.ts | 59 ++++ .../network/tls/query.tls_network.dsl.ts | 108 +++++++ 17 files changed, 579 insertions(+), 213 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts create mode 100644 x-pack/plugins/security_solution/public/network/containers/tls/translations.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/helpers.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_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 cd8a58d03d30c0..91a53066b4f4b1 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 @@ -6,15 +6,8 @@ import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; -import { HostItem } from '../common'; -import { - CursorType, - Inspect, - Maybe, - PageInfoPaginated, - RequestOptionsPaginated, - SortField, -} from '../..'; +import { HostItem, HostsFields } from '../common'; +import { CursorType, Inspect, Maybe, PageInfoPaginated, RequestOptionsPaginated } from '../..'; export interface HostsEdges { node: HostItem; @@ -29,7 +22,6 @@ export interface HostsStrategyResponse extends IEsSearchResponse { inspect?: Maybe; } -export interface HostsRequestOptions extends RequestOptionsPaginated { - sort: SortField; +export interface HostsRequestOptions extends RequestOptionsPaginated { defaultIndex: string[]; } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts index fb4fd762a86bf3..d15da4bf07ae74 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -14,6 +14,11 @@ export enum HostPolicyResponseActionStatus { warning = 'warning', } +export enum HostsFields { + lastSeen = 'lastSeen', + hostName = 'hostName', +} + export interface EndpointFields { endpointPolicy?: Maybe; sensorVersion?: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts index 93a5c0582fb968..cbabe9dd111153 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts @@ -6,8 +6,10 @@ import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; import { Inspect, Maybe, RequestOptionsPaginated } from '../..'; +import { HostsFields } from '../common'; -export interface HostFirstLastSeenRequestOptions extends Partial { +export interface HostFirstLastSeenRequestOptions + extends Partial> { hostName: string; } export interface HostFirstLastSeenStrategyResponse extends IEsSearchResponse { 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 bc797e7a03fb87..8d54481f56dbde 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 @@ -6,7 +6,7 @@ import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; -import { HostItem } from '../common'; +import { HostItem, HostsFields } from '../common'; import { Inspect, Maybe, RequestOptionsPaginated, TimerangeInput } from '../..'; export interface HostOverviewStrategyResponse extends IEsSearchResponse { @@ -14,7 +14,7 @@ export interface HostOverviewStrategyResponse extends IEsSearchResponse { inspect?: Maybe; } -export interface HostOverviewRequestOptions extends Partial { +export interface HostOverviewRequestOptions extends Partial> { hostName: string; skip?: boolean; timerange: TimerangeInput; 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 073b96e400f5c4..175784bc5ade57 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 @@ -15,10 +15,13 @@ import { HostsRequestOptions, HostsStrategyResponse, } from './hosts'; +import { NetworkQueries, NetworkTlsStrategyResponse, NetworkTlsRequestOptions } from './network'; + export * from './hosts'; +export * from './network'; export type Maybe = T | null; -export type FactoryQueryTypes = HostsQueries; +export type FactoryQueryTypes = HostsQueries | NetworkQueries; export type SearchHit = IEsSearchResponse['rawResponse']['hits']['hits'][0]; @@ -48,8 +51,8 @@ export enum Direction { desc = 'desc', } -export interface SortField { - field: 'lastSeen' | 'hostName'; +export interface SortField { + field: Field; direction: Direction; } @@ -95,14 +98,14 @@ export interface RequestBasicOptions extends IEsSearchRequest { factoryQueryType?: FactoryQueryTypes; } -export interface RequestOptions extends RequestBasicOptions { +export interface RequestOptions extends RequestBasicOptions { pagination: PaginationInput; - sortField?: SortField; + sort: SortField; } -export interface RequestOptionsPaginated extends RequestBasicOptions { +export interface RequestOptionsPaginated extends RequestBasicOptions { pagination: PaginationInputPaginated; - sortField?: SortField; + sort: SortField; } export type StrategyResponseType = T extends HostsQueries.hosts @@ -111,6 +114,8 @@ export type StrategyResponseType = T extends HostsQ ? HostOverviewStrategyResponse : T extends HostsQueries.firstLastSeen ? HostFirstLastSeenStrategyResponse + : T extends NetworkQueries.tls + ? NetworkTlsStrategyResponse : never; export type StrategyRequestType = T extends HostsQueries.hosts @@ -119,4 +124,6 @@ export type StrategyRequestType = T extends HostsQu ? HostOverviewRequestOptions : T extends HostsQueries.firstLastSeen ? HostFirstLastSeenRequestOptions + : T extends NetworkQueries.tls + ? NetworkTlsRequestOptions : 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 new file mode 100644 index 00000000000000..680a3697ef0bd5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.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. + */ + +export * from './tls'; + +export enum NetworkQueries { + tls = 'tls', +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts new file mode 100644 index 00000000000000..c9e593bb7a7d2d --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts @@ -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 { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; +import { CursorType, Inspect, Maybe, PageInfoPaginated, RequestOptionsPaginated } from '../..'; + +export interface TlsBuckets { + key: string; + timestamp?: { + value: number; + value_as_string: string; + }; + subjects: { + buckets: Readonly>; + }; + ja3: { + buckets: Readonly>; + }; + issuers: { + buckets: Readonly>; + }; + not_after: { + buckets: Readonly>; + }; +} + +export interface TlsNode { + _id?: Maybe; + timestamp?: Maybe; + notAfter?: Maybe; + subjects?: Maybe; + ja3?: Maybe; + issuers?: Maybe; +} + +export enum FlowTargetSourceDest { + destination = 'destination', + source = 'source', +} + +export enum TlsFields { + _id = '_id', +} + +export interface TlsEdges { + node: TlsNode; + cursor: CursorType; +} + +export interface NetworkTlsRequestOptions extends RequestOptionsPaginated { + ip: string; + flowTarget: FlowTargetSourceDest; + defaultIndex: string[]; +} + +export interface NetworkTlsStrategyResponse extends IEsSearchResponse { + edges: TlsEdges[]; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx index 17506f9a01cb92..77c6446fbb3d05 100644 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx @@ -4,38 +4,33 @@ * 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 } from 'react-redux'; -import { compose } from 'redux'; +import { noop } from 'lodash/fp'; +import { useState, useEffect, useCallback, useRef } from 'react'; +import { shallowEqual, useSelector } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; +import { ESTermQuery } from '../../../../common/typed_json'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { - PageInfoPaginated, - TlsEdges, - TlsSortField, - GetTlsQuery, - FlowTargetSourceDest, -} from '../../../graphql/types'; -import { inputsModel, State, inputsSelectors } from '../../../common/store'; -import { withKibana, WithKibanaProps } from '../../../common/lib/kibana'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; +import { inputsModel, State } from '../../../common/store'; +import { useKibana } from '../../../common/lib/kibana'; +import { createFilter } from '../../../common/containers/helpers'; +import { TlsEdges, PageInfoPaginated, FlowTargetSourceDest } from '../../../graphql/types'; import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers'; -import { - QueryTemplatePaginated, - QueryTemplatePaginatedProps, -} from '../../../common/containers/query_template_paginated'; import { networkModel, networkSelectors } from '../../store'; -import { tlsQuery } from './index.gql_query'; +import { + NetworkQueries, + NetworkTlsRequestOptions, + NetworkTlsStrategyResponse, +} from '../../../../common/search_strategy/security_solution/network'; +import { AbortError } from '../../../../../../../src/plugins/data/common'; +import * as i18n from './translations'; -const ID = 'tlsQuery'; +const ID = 'networkTlsQuery'; -export interface TlsArgs { +export interface NetworkTlsArgs { id: string; inspect: inputsModel.InspectQuery; isInspected: boolean; - loading: boolean; loadPage: (newActivePage: number) => void; pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; @@ -43,121 +38,159 @@ export interface TlsArgs { totalCount: number; } -export interface OwnProps extends QueryTemplatePaginatedProps { - children: (args: TlsArgs) => React.ReactNode; +interface UseNetworkTls { flowTarget: FlowTargetSourceDest; ip: string; type: networkModel.NetworkType; + filterQuery?: ESTermQuery | string; + endDate: string; + startDate: string; + skip: boolean; + id?: string; } -export interface TlsComponentReduxProps { - activePage: number; - isInspected: boolean; - limit: number; - sort: TlsSortField; -} +export const useNetworkTls = ({ + endDate, + filterQuery, + flowTarget, + id = ID, + ip, + skip, + startDate, + type, +}: UseNetworkTls): [boolean, NetworkTlsArgs] => { + const getTlsSelector = networkSelectors.tlsSelector(); + const { activePage, limit, sort } = useSelector( + (state: State) => getTlsSelector(state, type, flowTarget), + shallowEqual + ); + 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 [networkTlsRequest, setHostRequest] = useState({ + defaultIndex, + factoryQueryType: NetworkQueries.tls, + filterQuery: createFilter(filterQuery), + flowTarget, + ip, + pagination: generateTablePaginationOptions(activePage, limit), + sort, + timerange: { + interval: '12h', + from: startDate ? startDate : '', + to: endDate ? endDate : new Date(Date.now()).toISOString(), + }, + }); + + const wrappedLoadMore = useCallback( + (newActivePage: number) => { + setHostRequest((prevRequest) => ({ + ...prevRequest, + pagination: generateTablePaginationOptions(newActivePage, limit), + })); + }, + [limit] + ); -type TlsProps = OwnProps & TlsComponentReduxProps & WithKibanaProps; + const [networkTlsResponse, setNetworkTlsResponse] = useState({ + tls: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + loadPage: wrappedLoadMore, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + refetch: refetch.current, + totalCount: -1, + }); -class TlsComponentQuery extends QueryTemplatePaginated< - TlsProps, - GetTlsQuery.Query, - GetTlsQuery.Variables -> { - public render() { - const { - activePage, - children, - endDate, - filterQuery, - flowTarget, - id = ID, - ip, - isInspected, - kibana, - limit, - skip, - sourceId, - startDate, - sort, - } = this.props; - const variables: GetTlsQuery.Variables = { - defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), - filterQuery: createFilter(filterQuery), - flowTarget, - inspect: isInspected, - ip, - pagination: generateTablePaginationOptions(activePage, limit), - sort, - sourceId, - timerange: { - interval: '12h', - from: startDate ? startDate : '', - to: endDate ? endDate : new Date(Date.now()).toISOString(), - }, - }; - return ( - - query={tlsQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - skip={skip} - variables={variables} - > - {({ data, loading, fetchMore, networkStatus, refetch }) => { - const tls = getOr([], 'source.Tls.edges', data); - this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newActivePage: number) => ({ - variables: { - pagination: generateTablePaginationOptions(newActivePage, limit), + const networkTlsSearch = useCallback( + (request: NetworkTlsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + signal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkTlsResponse((prevResponse) => ({ + ...prevResponse, + tls: response.edges, + inspect: response.inspect ?? prevResponse.inspect, + pageInfo: response.pageInfo, + refetch: refetch.current, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_TLS); + searchSubscription$.unsubscribe(); + } }, - updateQuery: (prev, { fetchMoreResult }) => { - if (!fetchMoreResult) { - return prev; + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_NETWORK_TLS, text: msg.message }); } - return { - ...fetchMoreResult, - source: { - ...fetchMoreResult.source, - Tls: { - ...fetchMoreResult.source.Tls, - edges: [...fetchMoreResult.source.Tls.edges], - }, - }, - }; }, - })); - const isLoading = this.isItAValidLoading(loading, variables, networkStatus); - return children({ - id, - inspect: getOr(null, 'source.Tls.inspect', data), - isInspected, - loading: isLoading, - loadPage: this.wrappedLoadMore, - pageInfo: getOr({}, 'source.Tls.pageInfo', data), - refetch: this.memoizedRefetchQuery(variables, limit, refetch), - tls, - totalCount: getOr(-1, 'source.Tls.totalCount', data), }); - }} - - ); - } -} + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); -const makeMapStateToProps = () => { - const getTlsSelector = networkSelectors.tlsSelector(); - const getQuery = inputsSelectors.globalQueryByIdSelector(); - return (state: State, { flowTarget, id = ID, type }: OwnProps) => { - const { isInspected } = getQuery(state, id); - return { - ...getTlsSelector(state, type, flowTarget), - isInspected, - }; - }; -}; + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + sort, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip]); + + useEffect(() => { + networkTlsSearch(networkTlsRequest); + }, [networkTlsRequest, networkTlsSearch]); -export const TlsQuery = compose>( - connect(makeMapStateToProps), - withKibana -)(TlsComponentQuery); + return [loading, networkTlsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/translations.ts b/x-pack/plugins/security_solution/public/network/containers/tls/translations.ts new file mode 100644 index 00000000000000..aafa3ff0a98b0a --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/tls/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_TLS = i18n.translate( + 'xpack.securitySolution.networkTls.errorSearchDescription', + { + defaultMessage: `An error has occurred on network tls search`, + } +); + +export const FAIL_NETWORK_TLS = i18n.translate( + 'xpack.securitySolution.networkTls.failSearchDescription', + { + defaultMessage: `Failed to run search on network tls`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx index f0c3628af78d85..5184fccecf07a5 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx @@ -8,7 +8,7 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { manageQuery } from '../../../common/components/page/manage_query'; import { TlsTable } from '../../components/tls_table'; -import { TlsQuery } from '../../containers/tls'; +import { useNetworkTls } from '../../containers/tls'; import { TlsQueryTableComponentProps } from './types'; const TlsTableManage = manageQuery(TlsTable); @@ -22,34 +22,36 @@ export const TlsQueryTable = ({ skip, startDate, type, -}: TlsQueryTableComponentProps) => ( - - {({ id, inspect, isInspected, tls, totalCount, pageInfo, loading, loadPage, refetch }) => ( - - )} - -); +}: TlsQueryTableComponentProps) => { + const [ + loading, + { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }, + ] = useNetworkTls({ + endDate, + filterQuery, + flowTarget, + ip, + skip, + startDate, + type, + }); + + return ( + + ); +}; TlsQueryTable.displayName = 'TlsQueryTable'; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx index 00da5496e54405..279891cc181e3d 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx @@ -6,13 +6,13 @@ import React from 'react'; import { getOr } from 'lodash/fp'; import { manageQuery } from '../../../common/components/page/manage_query'; -import { TlsQuery } from '../../../network/containers/tls'; +import { useNetworkTls } from '../../../network/containers/tls'; import { TlsTable } from '../../components/tls_table'; import { TlsQueryTabBodyProps } from './types'; const TlsTableManage = manageQuery(TlsTable); -export const TlsQueryTabBody = ({ +const TlsQueryTabBodyComponent: React.FC = ({ endDate, filterQuery, flowTarget, @@ -21,32 +21,38 @@ export const TlsQueryTabBody = ({ skip, startDate, type, -}: TlsQueryTabBodyProps) => ( - - {({ id, inspect, isInspected, tls, totalCount, pageInfo, loading, loadPage, refetch }) => ( - - )} - -); +}) => { + const [ + loading, + { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }, + ] = useNetworkTls({ + endDate, + filterQuery, + flowTarget, + ip, + skip, + startDate, + type, + }); + + return ( + + ); +}; + +TlsQueryTabBodyComponent.displayName = 'TlsQueryTabBodyComponent'; + +export const TlsQueryTabBody = React.memo(TlsQueryTabBodyComponent); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts index a9101f54ada55c..ea1b896452c4e8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts @@ -10,8 +10,10 @@ import { Direction, HostsRequestOptions, SortField, + HostsFields, } from '../../../../../../common/search_strategy/security_solution'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; +import { assertUnreachable } from '../../../../../../common/utility_types'; export const buildHostsQuery = ({ defaultIndex, @@ -77,11 +79,13 @@ export const buildHostsQuery = ({ type QueryOrder = { lastSeen: Direction } | { _key: Direction }; -const getQueryOrder = (sort: SortField): QueryOrder => { +const getQueryOrder = (sort: SortField): QueryOrder => { switch (sort.field) { - case 'lastSeen': + case HostsFields.lastSeen: return { lastSeen: sort.direction }; - case 'hostName': + case HostsFields.hostName: return { _key: sort.direction }; + default: + return assertUnreachable(sort.field as never); } }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts index 53433dfc208cbc..a50c9e40048562 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts @@ -7,6 +7,7 @@ import { FactoryQueryTypes } from '../../../../common/search_strategy/security_solution'; import { hostsFactory } from './hosts'; +import { networkFactory } from './network'; import { SecuritySolutionFactory } from './types'; export const securitySolutionFactory: Record< @@ -14,4 +15,5 @@ export const securitySolutionFactory: Record< SecuritySolutionFactory > = { ...hostsFactory, + ...networkFactory, }; 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 new file mode 100644 index 00000000000000..2c21d9741d648f --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts @@ -0,0 +1,15 @@ +/* + * 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 } from '../../../../../common/search_strategy/security_solution'; +import { NetworkQueries } from '../../../../../common/search_strategy/security_solution/network'; + +import { SecuritySolutionFactory } from '../types'; +import { networkTls } from './tls'; + +export const networkFactory: Record> = { + [NetworkQueries.tls]: networkTls, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/helpers.ts new file mode 100644 index 00000000000000..59359fd35a34eb --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/helpers.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 { + TlsBuckets, + TlsEdges, +} from '../../../../../../common/search_strategy/security_solution/network'; + +export const getTlsEdges = (response: IEsSearchResponse): TlsEdges[] => + formatTlsEdges(getOr([], 'aggregations.sha1.buckets', response.rawResponse)); + +export const formatTlsEdges = (buckets: TlsBuckets[]): TlsEdges[] => + buckets.map((bucket: TlsBuckets) => { + const edge: TlsEdges = { + node: { + _id: bucket.key, + subjects: bucket.subjects.buckets.map(({ key }) => key), + ja3: bucket.ja3.buckets.map(({ key }) => key), + issuers: bucket.issuers.buckets.map(({ key }) => key), + // eslint-disable-next-line @typescript-eslint/naming-convention + notAfter: bucket.not_after.buckets.map(({ key_as_string }) => key_as_string), + }, + cursor: { + value: bucket.key, + tiebreaker: null, + }, + }; + return edge; + }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts new file mode 100644 index 00000000000000..32836c0ef6869e --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts @@ -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 { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; + +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; +import { + NetworkTlsStrategyResponse, + NetworkQueries, + NetworkTlsRequestOptions, + TlsEdges, +} from '../../../../../../common/search_strategy/security_solution/network'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; + +import { getTlsEdges } from './helpers'; +import { buildTlsQuery } from './query.tls_network.dsl'; + +export const networkTls: SecuritySolutionFactory = { + buildDsl: (options: NetworkTlsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildTlsQuery(options); + }, + parse: async ( + options: NetworkTlsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; + const totalCount = getOr(0, 'aggregations.count.value', response.rawResponse); + const tlsEdges: TlsEdges[] = getTlsEdges(response); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = tlsEdges.splice(cursorStart, querySize - cursorStart); + const inspect = { + dsl: [inspectStringifyObject(buildTlsQuery(options))], + response: [inspectStringifyObject(response)], + }; + const showMorePagesIndicator = totalCount > fakeTotalCount; + + return { + ...response, + edges, + inspect, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, + totalCount, + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts new file mode 100644 index 00000000000000..eb4e25c29e3a13 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts @@ -0,0 +1,108 @@ +/* + * 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 { assertUnreachable } from '../../../../../../common/utility_types'; +import { createQueryFilterClauses } from '../../../../../utils/build_query'; + +import { + NetworkTlsRequestOptions, + SortField, + Direction, + TlsFields, +} from '../../../../../../common/search_strategy/security_solution'; + +const getAggs = (querySize: number, sort: SortField) => ({ + count: { + cardinality: { + field: 'tls.server.hash.sha1', + }, + }, + sha1: { + terms: { + field: 'tls.server.hash.sha1', + size: querySize, + order: { + ...getQueryOrder(sort), + }, + }, + aggs: { + issuers: { + terms: { + field: 'tls.server.issuer', + }, + }, + subjects: { + terms: { + field: 'tls.server.subject', + }, + }, + not_after: { + terms: { + field: 'tls.server.not_after', + }, + }, + ja3: { + terms: { + field: 'tls.server.ja3s', + }, + }, + }, + }, +}); + +export const buildTlsQuery = ({ + ip, + sort, + filterQuery, + flowTarget, + pagination: { querySize }, + defaultIndex, + timerange: { from, to }, +}: NetworkTlsRequestOptions) => { + const defaultFilter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { gte: from, lte: to, format: 'strict_date_optional_time' }, + }, + }, + ]; + + const filter = ip ? [...defaultFilter, { term: { [`${flowTarget}.ip`]: ip } }] : defaultFilter; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + aggs: { + ...getAggs(querySize, sort), + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; + +interface QueryOrder { + _key: Direction; +} + +const getQueryOrder = (sort: SortField): QueryOrder => { + switch (sort.field) { + case TlsFields._id: + return { _key: sort.direction }; + default: + return assertUnreachable(sort.field); + } +};