From de1f5d02ea713a3c462369ba6b7bfec7e24b2ae9 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 15 Oct 2019 20:50:56 -0700 Subject: [PATCH] [SIEM] - Top countries by source/dest tables (#48179) --- .../public/components/page/network/index.tsx | 2 + .../__snapshots__/index.test.tsx.snap | 291 ++++++++++++ .../network_top_countries_table/columns.tsx | 176 ++++++++ .../index.test.tsx | 150 ++++++ .../network_top_countries_table/index.tsx | 206 +++++++++ .../network_top_countries_table/mock.ts | 56 +++ .../translations.ts | 67 +++ .../network_top_n_flow_table/columns.tsx | 10 +- .../network_top_n_flow_table/index.tsx | 24 +- .../components/paginated_table/index.tsx | 6 + .../source_destination/country_flag.tsx | 33 +- .../network_top_countries/index.gql_query.ts | 68 +++ .../network_top_countries/index.tsx | 156 +++++++ .../network_top_n_flow/index.gql_query.ts | 2 +- .../containers/network_top_n_flow/index.tsx | 4 +- .../siem/public/graphql/introspection.json | 426 ++++++++++++++++-- .../plugins/siem/public/graphql/types.ts | 223 ++++++++- .../plugins/siem/public/mock/global_state.ts | 30 +- .../siem/public/pages/network/ip_details.tsx | 91 ++++ .../navigation/countries_query_tab_body.tsx | 68 +++ .../pages/network/navigation/nav_tabs.tsx | 7 + .../network/navigation/network_routes.tsx | 15 + .../public/pages/network/navigation/types.ts | 5 +- .../public/pages/network/navigation/utils.ts | 3 +- .../siem/public/pages/network/translations.ts | 13 +- .../siem/public/store/network/actions.ts | 17 +- .../siem/public/store/network/helpers.test.ts | 78 +++- .../siem/public/store/network/helpers.ts | 16 + .../siem/public/store/network/model.ts | 40 +- .../siem/public/store/network/reducer.ts | 122 ++++- .../siem/public/store/network/selectors.ts | 26 +- .../siem/server/graphql/network/resolvers.ts | 15 + .../siem/server/graphql/network/schema.gql.ts | 58 ++- .../plugins/siem/server/graphql/types.ts | 392 +++++++++++++--- .../lib/network/elasticsearch_adapter.ts | 93 +++- .../plugins/siem/server/lib/network/index.ts | 18 +- .../plugins/siem/server/lib/network/mock.ts | 6 +- .../lib/network/query_top_countries.dsl.ts | 149 ++++++ .../lib/network/query_top_n_flow.dsl.ts | 18 +- .../plugins/siem/server/lib/network/types.ts | 20 +- .../apis/siem/network_top_n_flow.ts | 10 +- 41 files changed, 2993 insertions(+), 217 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/columns.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/mock.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.gql_query.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/network/navigation/countries_query_tab_body.tsx create mode 100644 x-pack/legacy/plugins/siem/server/lib/network/query_top_countries.dsl.ts diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/index.tsx index 9758477a4ee2a..40460f81fa83c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/index.tsx @@ -6,4 +6,6 @@ export { IpOverview } from './ip_overview'; export { KpiNetworkComponent } from './kpi_network'; +export { NetworkDnsTable } from './network_dns_table'; +export { NetworkTopCountriesTable } from './network_top_countries_table'; export { NetworkTopNFlowTable } from './network_top_n_flow_table'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..1127528c776b7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/__snapshots__/index.test.tsx.snap @@ -0,0 +1,291 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NetworkTopCountries Table Component rendering it renders the IP Details NetworkTopCountries table 1`] = ` + +`; + +exports[`NetworkTopCountries Table Component rendering it renders the default NetworkTopCountries table 1`] = ` + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/columns.tsx new file mode 100644 index 0000000000000..290891def3da8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/columns.tsx @@ -0,0 +1,176 @@ +/* + * 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 numeral from '@elastic/numeral'; +import React from 'react'; +import { StaticIndexPattern } from 'ui/index_patterns'; + +import { CountryFlagAndName } from '../../../source_destination/country_flag'; +import { + FlowTargetSourceDest, + NetworkTopCountriesEdges, + TopNetworkTablesEcsField, +} from '../../../../graphql/types'; +import { networkModel } from '../../../../store'; +import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; +import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; +import { getEmptyTagValue } from '../../../empty_value'; +import { Columns } from '../../../paginated_table'; +import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider'; +import { Provider } from '../../../timeline/data_providers/provider'; +import * as i18n from './translations'; +import { PreferenceFormattedBytes } from '../../../formatted_bytes'; + +export type NetworkTopCountriesColumns = [ + Columns, + Columns, + Columns, + Columns, + Columns, + Columns +]; + +export type NetworkTopCountriesColumnsIpDetails = [ + Columns, + Columns, + Columns, + Columns, + Columns +]; + +export const getNetworkTopCountriesColumns = ( + indexPattern: StaticIndexPattern, + flowTarget: FlowTargetSourceDest, + type: networkModel.NetworkType, + tableId: string +): NetworkTopCountriesColumns => [ + { + name: i18n.COUNTRY, + render: ({ node }) => { + const geo = get(`${flowTarget}.country`, node); + const geoAttr = `${flowTarget}.geo.country_iso_code`; + const id = escapeDataProviderId(`${tableId}-table-${flowTarget}-country-${geo}`); + if (geo != null) { + return ( + + snapshot.isDragging ? ( + + + + ) : ( + <> + + + ) + } + /> + ); + } else { + return getEmptyTagValue(); + } + }, + width: '20%', + }, + { + align: 'right', + field: 'node.network.bytes_in', + name: i18n.BYTES_IN, + sortable: true, + render: bytes => { + if (bytes != null) { + return ; + } else { + return getEmptyTagValue(); + } + }, + }, + { + align: 'right', + field: 'node.network.bytes_out', + name: i18n.BYTES_OUT, + sortable: true, + render: bytes => { + if (bytes != null) { + return ; + } else { + return getEmptyTagValue(); + } + }, + }, + { + align: 'right', + field: `node.${flowTarget}.flows`, + name: i18n.FLOWS, + sortable: true, + render: flows => { + if (flows != null) { + return numeral(flows).format('0,000'); + } else { + return getEmptyTagValue(); + } + }, + }, + { + align: 'right', + field: `node.${flowTarget}.${flowTarget}_ips`, + name: flowTarget === FlowTargetSourceDest.source ? i18n.SOURCE_IPS : i18n.DESTINATION_IPS, + sortable: true, + render: ips => { + if (ips != null) { + return numeral(ips).format('0,000'); + } else { + return getEmptyTagValue(); + } + }, + }, + { + align: 'right', + field: `node.${flowTarget}.${getOppositeField(flowTarget)}_ips`, + name: flowTarget === FlowTargetSourceDest.source ? i18n.SOURCE_IPS : i18n.DESTINATION_IPS, + sortable: true, + render: ips => { + if (ips != null) { + return numeral(ips).format('0,000'); + } else { + return getEmptyTagValue(); + } + }, + }, +]; + +export const getCountriesColumnsCurated = ( + indexPattern: StaticIndexPattern, + flowTarget: FlowTargetSourceDest, + type: networkModel.NetworkType, + tableId: string +): NetworkTopCountriesColumns | NetworkTopCountriesColumnsIpDetails => { + const columns = getNetworkTopCountriesColumns(indexPattern, flowTarget, type, tableId); + + // Columns to exclude from host details pages + if (type === networkModel.NetworkType.details) { + columns.pop(); + return columns; + } + + return columns; +}; + +const getOppositeField = (flowTarget: FlowTargetSourceDest): FlowTargetSourceDest => + flowTarget === FlowTargetSourceDest.source + ? FlowTargetSourceDest.destination + : FlowTargetSourceDest.source; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx new file mode 100644 index 0000000000000..4fa1bceac9888 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx @@ -0,0 +1,150 @@ +/* + * 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 { mount, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import { getOr } from 'lodash/fp'; +import * as React from 'react'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { Provider as ReduxStoreProvider } from 'react-redux'; + +import { FlowTargetSourceDest } from '../../../../graphql/types'; +import { + apolloClientObservable, + mockGlobalState, + mockIndexPattern, + TestProviders, +} from '../../../../mock'; +import { createStore, networkModel, State } from '../../../../store'; + +import { NetworkTopCountriesTable } from '.'; +import { mockData } from './mock'; +jest.mock('../../../../lib/settings/use_kibana_ui_setting'); +describe('NetworkTopCountries Table Component', () => { + const loadPage = jest.fn(); + const state: State = mockGlobalState; + + let store = createStore(state, apolloClientObservable); + + beforeEach(() => { + store = createStore(state, apolloClientObservable); + }); + + describe('rendering', () => { + test('it renders the default NetworkTopCountries table', () => { + const wrapper = shallow( + + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + test('it renders the IP Details NetworkTopCountries table', () => { + const wrapper = shallow( + + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + }); + + describe('Sorting on Table', () => { + test('when you click on the column header, you should show the sorting icon', () => { + const wrapper = mount( + + + + + + ); + expect(store.getState().network.page.queries.topCountriesSource.topCountriesSort).toEqual({ + direction: 'desc', + field: 'bytes_out', + }); + + wrapper + .find('.euiTable thead tr th button') + .at(1) + .simulate('click'); + + wrapper.update(); + + expect(store.getState().network.page.queries.topCountriesSource.topCountriesSort).toEqual({ + direction: 'asc', + field: 'bytes_out', + }); + expect( + wrapper + .find('.euiTable thead tr th button') + .first() + .text() + ).toEqual('Bytes inClick to sort in ascending order'); + expect( + wrapper + .find('.euiTable thead tr th button') + .at(1) + .text() + ).toEqual('Bytes outClick to sort in descending order'); + expect( + wrapper + .find('.euiTable thead tr th button') + .at(1) + .find('svg') + ).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx new file mode 100644 index 0000000000000..3972bb80b760b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx @@ -0,0 +1,206 @@ +/* + * 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 { isEqual, last } from 'lodash/fp'; +import React from 'react'; +import { connect } from 'react-redux'; +import { ActionCreator } from 'typescript-fsa'; +import { StaticIndexPattern } from 'ui/index_patterns'; + +import { networkActions } from '../../../../store/actions'; +import { + Direction, + FlowTargetSourceDest, + NetworkTopCountriesEdges, + NetworkTopTablesFields, + NetworkTopTablesSortField, +} from '../../../../graphql/types'; +import { networkModel, networkSelectors, State } from '../../../../store'; +import { Criteria, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; + +import { getCountriesColumnsCurated } from './columns'; +import * as i18n from './translations'; + +interface OwnProps { + data: NetworkTopCountriesEdges[]; + fakeTotalCount: number; + flowTargeted: FlowTargetSourceDest; + id: string; + indexPattern: StaticIndexPattern; + ip?: string; + isInspect: boolean; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; + totalCount: number; + type: networkModel.NetworkType; +} + +interface NetworkTopCountriesTableReduxProps { + activePage: number; + limit: number; + topCountriesSort: NetworkTopTablesSortField; +} + +interface NetworkTopCountriesTableDispatchProps { + updateIpDetailsTableActivePage: ActionCreator<{ + activePage: number; + tableType: networkModel.IpDetailsTableType; + }>; + updateNetworkPageTableActivePage: ActionCreator<{ + activePage: number; + tableType: networkModel.NetworkTableType; + }>; + updateTopCountriesLimit: ActionCreator<{ + limit: number; + networkType: networkModel.NetworkType; + tableType: networkModel.TopCountriesTableType; + }>; + updateTopCountriesSort: ActionCreator<{ + topCountriesSort: NetworkTopTablesSortField; + networkType: networkModel.NetworkType; + tableType: networkModel.TopCountriesTableType; + }>; +} + +type NetworkTopCountriesTableProps = OwnProps & + NetworkTopCountriesTableReduxProps & + NetworkTopCountriesTableDispatchProps; + +const rowItems: ItemsPerRow[] = [ + { + text: i18n.ROWS_5, + numberOfRow: 5, + }, + { + text: i18n.ROWS_10, + numberOfRow: 10, + }, +]; + +export const NetworkTopCountriesTableId = 'networkTopCountries-top-talkers'; + +const NetworkTopCountriesTableComponent = React.memo( + ({ + activePage, + data, + fakeTotalCount, + flowTargeted, + id, + indexPattern, + isInspect, + limit, + loading, + loadPage, + showMorePagesIndicator, + topCountriesSort, + totalCount, + type, + updateIpDetailsTableActivePage, + updateTopCountriesLimit, + updateTopCountriesSort, + updateNetworkPageTableActivePage, + }) => { + const onChange = (criteria: Criteria, tableType: networkModel.TopCountriesTableType) => { + if (criteria.sort != null) { + const splitField = criteria.sort.field.split('.'); + const field = last(splitField); + const newSortDirection = + field !== topCountriesSort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click + const newTopCountriesSort: NetworkTopTablesSortField = { + field: field as NetworkTopTablesFields, + direction: newSortDirection, + }; + if (!isEqual(newTopCountriesSort, topCountriesSort)) { + updateTopCountriesSort({ + topCountriesSort: newTopCountriesSort, + networkType: type, + tableType, + }); + } + } + }; + + let tableType: networkModel.TopCountriesTableType; + const headerTitle: string = + flowTargeted === FlowTargetSourceDest.source + ? i18n.SOURCE_COUNTRIES + : i18n.DESTINATION_COUNTRIES; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let updateTableActivePage: any; + if (type === networkModel.NetworkType.page) { + tableType = + flowTargeted === FlowTargetSourceDest.source + ? networkModel.NetworkTableType.topCountriesSource + : networkModel.NetworkTableType.topCountriesDestination; + updateTableActivePage = updateNetworkPageTableActivePage; + } else { + tableType = + flowTargeted === FlowTargetSourceDest.source + ? networkModel.IpDetailsTableType.topCountriesSource + : networkModel.IpDetailsTableType.topCountriesDestination; + updateTableActivePage = updateIpDetailsTableActivePage; + } + + const field = + topCountriesSort.field === NetworkTopTablesFields.bytes_out || + topCountriesSort.field === NetworkTopTablesFields.bytes_in + ? `node.network.${topCountriesSort.field}` + : `node.${flowTargeted}.${topCountriesSort.field}`; + + return ( + loadPage(newActivePage)} + onChange={criteria => onChange(criteria, tableType)} + pageOfItems={data} + showMorePagesIndicator={showMorePagesIndicator} + sorting={{ field, direction: topCountriesSort.direction }} + totalCount={fakeTotalCount} + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + tableType, + }) + } + updateLimitPagination={newLimit => + updateTopCountriesLimit({ limit: newLimit, networkType: type, tableType }) + } + /> + ); + } +); + +NetworkTopCountriesTableComponent.displayName = 'NetworkTopCountriesTableComponent'; + +const mapStateToProps = (state: State, ownProps: OwnProps) => + networkSelectors.topCountriesSelector(ownProps.flowTargeted, ownProps.type); + +export const NetworkTopCountriesTable = connect( + mapStateToProps, + { + updateTopCountriesLimit: networkActions.updateTopCountriesLimit, + updateTopCountriesSort: networkActions.updateTopCountriesSort, + updateNetworkPageTableActivePage: networkActions.updateNetworkPageTableActivePage, + updateIpDetailsTableActivePage: networkActions.updateIpDetailsTableActivePage, + } +)(NetworkTopCountriesTableComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/mock.ts new file mode 100644 index 0000000000000..42b933c7fba6d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/mock.ts @@ -0,0 +1,56 @@ +/* + * 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 { NetworkTopCountriesData } from '../../../../graphql/types'; + +export const mockData: { NetworkTopCountries: NetworkTopCountriesData } = { + NetworkTopCountries: { + totalCount: 524, + edges: [ + { + node: { + source: { + country: 'DE', + destination_ips: 12, + flows: 12345, + source_ips: 55, + }, + destination: null, + network: { + bytes_in: 3826633497, + bytes_out: 1083495734, + }, + }, + cursor: { + value: '8.8.8.8', + }, + }, + { + node: { + source: { + flows: 12345, + destination_ips: 12, + source_ips: 55, + country: 'US', + }, + destination: null, + network: { + bytes_in: 3826633497, + bytes_out: 1083495734, + }, + }, + cursor: { + value: '9.9.9.9', + }, + }, + ], + pageInfo: { + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, + }, + }, +}; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/translations.ts new file mode 100644 index 0000000000000..2fad2687daf1e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/translations.ts @@ -0,0 +1,67 @@ +/* + * 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 UNIT = (totalCount: number) => + i18n.translate('xpack.siem.networkTopCountriesTable.heading.unit', { + values: { totalCount }, + defaultMessage: `{totalCount, plural, =1 {Country} other {Countries}}`, + }); + +export const COUNTRY = i18n.translate('xpack.siem.networkTopCountriesTable.column.countryTitle', { + defaultMessage: 'Country', +}); + +export const BYTES_IN = i18n.translate('xpack.siem.networkTopCountriesTable.column.bytesInTitle', { + defaultMessage: 'Bytes in', +}); + +export const BYTES_OUT = i18n.translate( + 'xpack.siem.networkTopCountriesTable.column.bytesOutTitle', + { + defaultMessage: 'Bytes out', + } +); + +export const FLOWS = i18n.translate('xpack.siem.networkTopCountriesTable.column.flows', { + defaultMessage: 'Flows', +}); + +export const DESTINATION_COUNTRIES = i18n.translate( + 'xpack.siem.networkTopCountriesTable.heading.destinationCountries', + { + defaultMessage: 'Top Destination Countries', + } +); + +export const SOURCE_COUNTRIES = i18n.translate( + 'xpack.siem.networkTopCountriesTable.heading.sourceCountries', + { + defaultMessage: 'Top Source Countries', + } +); + +export const DESTINATION_IPS = i18n.translate( + 'xpack.siem.networkTopCountriesTable.column.destinationIps', + { + defaultMessage: 'Destination IPs', + } +); + +export const SOURCE_IPS = i18n.translate('xpack.siem.networkTopCountriesTable.column.sourceIps', { + defaultMessage: 'Source IPs', +}); + +export const ROWS_5 = i18n.translate('xpack.siem.networkTopCountriesTable.rows', { + values: { numRows: 5 }, + defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', +}); + +export const ROWS_10 = i18n.translate('xpack.siem.networkTopCountriesTable.rows', { + values: { numRows: 10 }, + defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', +}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx index bdb57a3323d4e..abb4d9ac29f1b 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx @@ -14,7 +14,7 @@ import { AutonomousSystemItem, FlowTargetSourceDest, NetworkTopNFlowEdges, - TopNFlowNetworkEcsField, + TopNetworkTablesEcsField, } from '../../../../graphql/types'; import { networkModel } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; @@ -32,8 +32,8 @@ export type NetworkTopNFlowColumns = [ Columns, Columns, Columns, - Columns, - Columns, + Columns, + Columns, Columns, Columns ]; @@ -42,8 +42,8 @@ export type NetworkTopNFlowColumnsIpDetails = [ Columns, Columns, Columns, - Columns, - Columns, + Columns, + Columns, Columns ]; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx index 507f8b75b842a..9e6e3cf844119 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx @@ -3,11 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexItem } from '@elastic/eui'; import { isEqual, last } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; -import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; import { StaticIndexPattern } from 'ui/index_patterns'; @@ -16,8 +14,8 @@ import { Direction, FlowTargetSourceDest, NetworkTopNFlowEdges, - NetworkTopNFlowFields, - NetworkTopNFlowSortField, + NetworkTopTablesFields, + NetworkTopTablesSortField, } from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; import { Criteria, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; @@ -42,7 +40,7 @@ interface OwnProps { interface NetworkTopNFlowTableReduxProps { activePage: number; limit: number; - topNFlowSort: NetworkTopNFlowSortField; + topNFlowSort: NetworkTopTablesSortField; } interface NetworkTopNFlowTableDispatchProps { @@ -60,7 +58,7 @@ interface NetworkTopNFlowTableDispatchProps { tableType: networkModel.TopNTableType; }>; updateTopNFlowSort: ActionCreator<{ - topNFlowSort: NetworkTopNFlowSortField; + topNFlowSort: NetworkTopTablesSortField; networkType: networkModel.NetworkType; tableType: networkModel.TopNTableType; }>; @@ -110,8 +108,8 @@ const NetworkTopNFlowTableComponent = React.memo( const field = last(splitField); const newSortDirection = field !== topNFlowSort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click - const newTopNFlowSort: NetworkTopNFlowSortField = { - field: field as NetworkTopNFlowFields, + const newTopNFlowSort: NetworkTopTablesSortField = { + field: field as NetworkTopTablesFields, direction: newSortDirection, }; if (!isEqual(newTopNFlowSort, topNFlowSort)) { @@ -145,8 +143,8 @@ const NetworkTopNFlowTableComponent = React.memo( } const field = - topNFlowSort.field === NetworkTopNFlowFields.bytes_out || - topNFlowSort.field === NetworkTopNFlowFields.bytes_in + topNFlowSort.field === NetworkTopTablesFields.bytes_out || + topNFlowSort.field === NetworkTopTablesFields.bytes_in ? `node.network.${topNFlowSort.field}` : `node.${flowTargeted}.${topNFlowSort.field}`; @@ -197,9 +195,3 @@ export const NetworkTopNFlowTable = connect( updateIpDetailsTableActivePage: networkActions.updateIpDetailsTableActivePage, } )(NetworkTopNFlowTableComponent); - -const SelectTypeItem = styled(EuiFlexItem)` - min-width: 180px; -`; - -SelectTypeItem.displayName = 'SelectTypeItem'; diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx index 1529648b7133e..4de64c6b32aa9 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx @@ -27,6 +27,10 @@ import { NetworkTopNFlowColumns, NetworkTopNFlowColumnsIpDetails, } from '../page/network/network_top_n_flow_table/columns'; +import { + NetworkTopCountriesColumns, + NetworkTopCountriesColumnsIpDetails, +} from '../page/network/network_top_countries_table/columns'; import { TlsColumns } from '../page/network/tls_table/columns'; import { UncommonProcessTableColumns } from '../page/hosts/uncommon_process_table'; import { UsersColumns } from '../page/network/users_table/columns'; @@ -68,6 +72,8 @@ declare type BasicTableColumns = | HostsTableColumns | HostsTableColumnsTest | NetworkDnsColumns + | NetworkTopCountriesColumns + | NetworkTopCountriesColumnsIpDetails | NetworkTopNFlowColumns | NetworkTopNFlowColumnsIpDetails | TlsColumns diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/country_flag.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/country_flag.tsx index 98639e0d38514..4de678f7f636e 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/country_flag.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/country_flag.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useEffect } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import { isEmpty } from 'lodash/fp'; import { EuiToolTip } from '@elastic/eui'; import countries from 'i18n-iso-countries'; @@ -22,7 +22,7 @@ export const getFlag = (countryCode: string): string | null => .replace(/./g, c => String.fromCharCode(55356, 56741 + c.charCodeAt(0))) : null; -/** Renders an emjoi flag for the specified country code */ +/** Renders an emoji flag for the specified country code */ export const CountryFlag = memo<{ countryCode: string; displayCountryNameOnHover?: boolean; @@ -47,3 +47,32 @@ export const CountryFlag = memo<{ }); CountryFlag.displayName = 'CountryFlag'; + +/** Renders an emjoi flag with country name for the specified country code */ +export const CountryFlagAndName = memo<{ + countryCode: string; + displayCountryNameOnHover?: boolean; +}>(({ countryCode, displayCountryNameOnHover = false }) => { + const [localesLoaded, setLocalesLoaded] = useState(false); + useEffect(() => { + if (isEmpty(countries.getNames('en'))) { + countries.registerLocale(countryJson); + } + setLocalesLoaded(true); + }, []); + + const flag = getFlag(countryCode); + + if (flag !== null && localesLoaded) { + return displayCountryNameOnHover ? ( + + {flag} + + ) : ( + {`${flag} ${countries.getName(countryCode, 'en')}`} + ); + } + return null; +}); + +CountryFlagAndName.displayName = 'CountryFlagAndName'; diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.gql_query.ts new file mode 100644 index 0000000000000..5850246ceecec --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.gql_query.ts @@ -0,0 +1,68 @@ +/* + * 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 gql from 'graphql-tag'; + +export const networkTopCountriesQuery = gql` + query GetNetworkTopCountriesQuery( + $sourceId: ID! + $ip: String + $filterQuery: String + $pagination: PaginationInputPaginated! + $sort: NetworkTopTablesSortField! + $flowTarget: FlowTargetSourceDest! + $timerange: TimerangeInput! + $defaultIndex: [String!]! + $inspect: Boolean! + ) { + source(id: $sourceId) { + id + NetworkTopCountries( + filterQuery: $filterQuery + flowTarget: $flowTarget + ip: $ip + pagination: $pagination + sort: $sort + timerange: $timerange + defaultIndex: $defaultIndex + ) { + totalCount + edges { + node { + source { + country + destination_ips + flows + source_ips + } + destination { + country + destination_ips + flows + source_ips + } + network { + bytes_in + bytes_out + } + } + cursor { + value + } + } + pageInfo { + activePage + fakeTotalCount + showMorePagesIndicator + } + inspect @include(if: $inspect) { + dsl + response + } + } + } + } +`; diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx new file mode 100644 index 0000000000000..8e3804ab77478 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx @@ -0,0 +1,156 @@ +/* + * 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 React from 'react'; +import { Query } from 'react-apollo'; +import { connect } from 'react-redux'; + +import chrome from 'ui/chrome'; +import { DEFAULT_INDEX_KEY } from '../../../common/constants'; +import { + FlowTargetSourceDest, + GetNetworkTopCountriesQuery, + NetworkTopCountriesEdges, + NetworkTopTablesSortField, + PageInfoPaginated, +} from '../../graphql/types'; +import { inputsModel, inputsSelectors, networkModel, networkSelectors, State } from '../../store'; +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; +import { createFilter, getDefaultFetchPolicy } from '../helpers'; +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; +import { networkTopCountriesQuery } from './index.gql_query'; + +const ID = 'networkTopCountriesQuery'; + +export interface NetworkTopCountriesArgs { + id: string; + ip?: string; + inspect: inputsModel.InspectQuery; + isInspected: boolean; + loading: boolean; + loadPage: (newActivePage: number) => void; + networkTopCountries: NetworkTopCountriesEdges[]; + pageInfo: PageInfoPaginated; + refetch: inputsModel.Refetch; + totalCount: number; +} + +export interface OwnProps extends QueryTemplatePaginatedProps { + children: (args: NetworkTopCountriesArgs) => React.ReactNode; + flowTarget: FlowTargetSourceDest; + ip?: string; + type: networkModel.NetworkType; +} + +export interface NetworkTopCountriesComponentReduxProps { + activePage: number; + isInspected: boolean; + limit: number; + topCountriesSort: NetworkTopTablesSortField; +} + +type NetworkTopCountriesProps = OwnProps & NetworkTopCountriesComponentReduxProps; + +class NetworkTopCountriesComponentQuery extends QueryTemplatePaginated< + NetworkTopCountriesProps, + GetNetworkTopCountriesQuery.Query, + GetNetworkTopCountriesQuery.Variables +> { + public render() { + const { + activePage, + children, + endDate, + flowTarget, + filterQuery, + id = `${ID}-${flowTarget}`, + ip, + isInspected, + limit, + skip, + sourceId, + startDate, + topCountriesSort, + } = this.props; + const variables: GetNetworkTopCountriesQuery.Variables = { + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + filterQuery: createFilter(filterQuery), + flowTarget, + inspect: isInspected, + ip, + pagination: generateTablePaginationOptions(activePage, limit), + sort: topCountriesSort, + sourceId, + timerange: { + interval: '12h', + from: startDate!, + to: endDate!, + }, + }; + return ( + + fetchPolicy={getDefaultFetchPolicy()} + notifyOnNetworkStatusChange + query={networkTopCountriesQuery} + skip={skip} + variables={variables} + > + {({ data, loading, fetchMore, networkStatus, refetch }) => { + const networkTopCountries = getOr([], `source.NetworkTopCountries.edges`, data); + this.setFetchMore(fetchMore); + this.setFetchMoreOptions((newActivePage: number) => ({ + variables: { + pagination: generateTablePaginationOptions(newActivePage, limit), + }, + updateQuery: (prev, { fetchMoreResult }) => { + if (!fetchMoreResult) { + return prev; + } + return { + ...fetchMoreResult, + source: { + ...fetchMoreResult.source, + NetworkTopCountries: { + ...fetchMoreResult.source.NetworkTopCountries, + edges: [...fetchMoreResult.source.NetworkTopCountries.edges], + }, + }, + }; + }, + })); + const isLoading = this.isItAValidLoading(loading, variables, networkStatus); + return children({ + id, + inspect: getOr(null, 'source.NetworkTopCountries.inspect', data), + isInspected, + loading: isLoading, + loadPage: this.wrappedLoadMore, + networkTopCountries, + pageInfo: getOr({}, 'source.NetworkTopCountries.pageInfo', data), + refetch: this.memoizedRefetchQuery(variables, limit, refetch), + totalCount: getOr(-1, 'source.NetworkTopCountries.totalCount', data), + }); + }} + + ); + } +} + +const mapStateToProps = ( + state: State, + { flowTarget, id = `${ID}-${flowTarget}`, type }: OwnProps +) => { + const getNetworkTopCountriesSelector = networkSelectors.topCountriesSelector(flowTarget, type); + const getQuery = inputsSelectors.globalQueryByIdSelector(); + const { isInspected } = getQuery(state, id); + return { + ...getNetworkTopCountriesSelector(state), + isInspected, + }; +}; + +export const NetworkTopCountriesQuery = connect(mapStateToProps)(NetworkTopCountriesComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts index 81a94bc94652b..a73f9ff9256ff 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts @@ -12,7 +12,7 @@ export const networkTopNFlowQuery = gql` $ip: String $filterQuery: String $pagination: PaginationInputPaginated! - $sort: NetworkTopNFlowSortField! + $sort: NetworkTopTablesSortField! $flowTarget: FlowTargetSourceDest! $timerange: TimerangeInput! $defaultIndex: [String!]! diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx index eba9c5640fb58..32629853480a0 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx @@ -15,7 +15,7 @@ import { FlowTargetSourceDest, GetNetworkTopNFlowQuery, NetworkTopNFlowEdges, - NetworkTopNFlowSortField, + NetworkTopTablesSortField, PageInfoPaginated, } from '../../graphql/types'; import { inputsModel, inputsSelectors, networkModel, networkSelectors, State } from '../../store'; @@ -50,7 +50,7 @@ export interface NetworkTopNFlowComponentReduxProps { activePage: number; isInspected: boolean; limit: number; - topNFlowSort: NetworkTopNFlowSortField; + topNFlowSort: NetworkTopTablesSortField; } type NetworkTopNFlowProps = OwnProps & NetworkTopNFlowComponentReduxProps; diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json index b8bdf27bed7fa..c012af5fd667f 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json @@ -1542,9 +1542,106 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "NetworkTopCountries", + "description": "", + "args": [ + { + "name": "id", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "filterQuery", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "ip", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "flowTarget", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null } + }, + "defaultValue": null + }, + { + "name": "pagination", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "sort", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "NetworkTopTablesSortField", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "timerange", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } + }, + "defaultValue": null + }, + { + "name": "defaultIndex", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "NetworkTopCountriesData", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "NetworkTopNFlow", - "description": "Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified", + "description": "", "args": [ { "name": "id", @@ -1596,7 +1693,7 @@ "name": null, "ofType": { "kind": "INPUT_OBJECT", - "name": "NetworkTopNFlowSortField", + "name": "NetworkTopTablesSortField", "ofType": null } }, @@ -7204,7 +7301,7 @@ }, { "kind": "INPUT_OBJECT", - "name": "NetworkTopNFlowSortField", + "name": "NetworkTopTablesSortField", "description": "", "fields": null, "inputFields": [ @@ -7214,7 +7311,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "ENUM", "name": "NetworkTopNFlowFields", "ofType": null } + "ofType": { "kind": "ENUM", "name": "NetworkTopTablesFields", "ofType": null } }, "defaultValue": null }, @@ -7235,7 +7332,7 @@ }, { "kind": "ENUM", - "name": "NetworkTopNFlowFields", + "name": "NetworkTopTablesFields", "description": "", "fields": null, "inputFields": null, @@ -7271,7 +7368,7 @@ }, { "kind": "OBJECT", - "name": "NetworkTopNFlowData", + "name": "NetworkTopCountriesData", "description": "", "fields": [ { @@ -7287,7 +7384,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopNFlowEdges", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "NetworkTopCountriesEdges", "ofType": null } } } }, @@ -7334,7 +7431,7 @@ }, { "kind": "OBJECT", - "name": "NetworkTopNFlowEdges", + "name": "NetworkTopCountriesEdges", "description": "", "fields": [ { @@ -7344,7 +7441,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopNFlowItem", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "NetworkTopCountriesItem", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -7369,7 +7466,7 @@ }, { "kind": "OBJECT", - "name": "NetworkTopNFlowItem", + "name": "NetworkTopCountriesItem", "description": "", "fields": [ { @@ -7384,7 +7481,7 @@ "name": "source", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "TopNFlowItemSource", "ofType": null }, + "type": { "kind": "OBJECT", "name": "TopCountriesItemSource", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, @@ -7392,7 +7489,7 @@ "name": "destination", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "TopNFlowItemDestination", "ofType": null }, + "type": { "kind": "OBJECT", "name": "TopCountriesItemDestination", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, @@ -7400,7 +7497,7 @@ "name": "network", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "TopNFlowNetworkEcsField", "ofType": null }, + "type": { "kind": "OBJECT", "name": "TopNetworkTablesEcsField", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -7412,38 +7509,30 @@ }, { "kind": "OBJECT", - "name": "TopNFlowItemSource", + "name": "TopCountriesItemSource", "description": "", "fields": [ { - "name": "autonomous_system", + "name": "country", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "domain", + "name": "destination_ips", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ip", + "name": "flows", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, @@ -7456,12 +7545,58 @@ "deprecationReason": null }, { - "name": "flows", + "name": "source_ips", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "GeoItem", + "description": "", + "fields": [ + { + "name": "geo", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "flowTarget", + "description": "", + "args": [], + "type": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TopCountriesItemDestination", + "description": "", + "fields": [ + { + "name": "country", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { "name": "destination_ips", @@ -7470,6 +7605,30 @@ "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "flows", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "location", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "source_ips", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -7479,19 +7638,19 @@ }, { "kind": "OBJECT", - "name": "AutonomousSystemItem", + "name": "TopNetworkTablesEcsField", "description": "", "fields": [ { - "name": "name", + "name": "bytes_in", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "number", + "name": "bytes_out", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7506,22 +7665,58 @@ }, { "kind": "OBJECT", - "name": "GeoItem", + "name": "NetworkTopNFlowData", "description": "", "fields": [ { - "name": "geo", + "name": "edges", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "NetworkTopNFlowEdges", "ofType": null } + } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "flowTarget", + "name": "totalCount", "description": "", "args": [], - "type": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inspect", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -7533,7 +7728,85 @@ }, { "kind": "OBJECT", - "name": "TopNFlowItemDestination", + "name": "NetworkTopNFlowEdges", + "description": "", + "fields": [ + { + "name": "node", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "NetworkTopNFlowItem", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "NetworkTopNFlowItem", + "description": "", + "fields": [ + { + "name": "_id", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "source", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "TopNFlowItemSource", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "destination", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "TopNFlowItemDestination", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "network", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "TopNetworkTablesEcsField", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TopNFlowItemSource", "description": "", "fields": [ { @@ -7585,7 +7858,7 @@ "deprecationReason": null }, { - "name": "source_ips", + "name": "destination_ips", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7600,19 +7873,86 @@ }, { "kind": "OBJECT", - "name": "TopNFlowNetworkEcsField", + "name": "AutonomousSystemItem", "description": "", "fields": [ { - "name": "bytes_in", + "name": "name", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "number", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TopNFlowItemDestination", + "description": "", + "fields": [ + { + "name": "autonomous_system", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "bytes_out", + "name": "domain", + "description": "", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ip", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "location", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "flows", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "source_ips", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts index 7514259caa2a7..b8ed6f2ad47fe 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts @@ -79,8 +79,8 @@ export interface UsersSortField { direction: Direction; } -export interface NetworkTopNFlowSortField { - field: NetworkTopNFlowFields; +export interface NetworkTopTablesSortField { + field: NetworkTopTablesFields; direction: Direction; } @@ -260,7 +260,7 @@ export enum FlowTargetSourceDest { source = 'source', } -export enum NetworkTopNFlowFields { +export enum NetworkTopTablesFields { bytes_in = 'bytes_in', bytes_out = 'bytes_out', flows = 'flows', @@ -426,7 +426,9 @@ export interface Source { KpiHosts: KpiHostsData; KpiHostDetails: KpiHostDetailsData; - /** Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified */ + + NetworkTopCountries: NetworkTopCountriesData; + NetworkTopNFlow: NetworkTopNFlowData; NetworkDns: NetworkDnsData; @@ -1456,6 +1458,68 @@ export interface KpiHostDetailsData { inspect?: Maybe; } +export interface NetworkTopCountriesData { + edges: NetworkTopCountriesEdges[]; + + totalCount: number; + + pageInfo: PageInfoPaginated; + + inspect?: Maybe; +} + +export interface NetworkTopCountriesEdges { + node: NetworkTopCountriesItem; + + cursor: CursorType; +} + +export interface NetworkTopCountriesItem { + _id?: Maybe; + + source?: Maybe; + + destination?: Maybe; + + network?: Maybe; +} + +export interface TopCountriesItemSource { + country?: Maybe; + + destination_ips?: Maybe; + + flows?: Maybe; + + location?: Maybe; + + source_ips?: Maybe; +} + +export interface GeoItem { + geo?: Maybe; + + flowTarget?: Maybe; +} + +export interface TopCountriesItemDestination { + country?: Maybe; + + destination_ips?: Maybe; + + flows?: Maybe; + + location?: Maybe; + + source_ips?: Maybe; +} + +export interface TopNetworkTablesEcsField { + bytes_in?: Maybe; + + bytes_out?: Maybe; +} + export interface NetworkTopNFlowData { edges: NetworkTopNFlowEdges[]; @@ -1479,7 +1543,7 @@ export interface NetworkTopNFlowItem { destination?: Maybe; - network?: Maybe; + network?: Maybe; } export interface TopNFlowItemSource { @@ -1502,12 +1566,6 @@ export interface AutonomousSystemItem { number?: Maybe; } -export interface GeoItem { - geo?: Maybe; - - flowTarget?: Maybe; -} - export interface TopNFlowItemDestination { autonomous_system?: Maybe; @@ -1522,12 +1580,6 @@ export interface TopNFlowItemDestination { source_ips?: Maybe; } -export interface TopNFlowNetworkEcsField { - bytes_in?: Maybe; - - bytes_out?: Maybe; -} - export interface NetworkDnsData { edges: NetworkDnsEdges[]; @@ -2061,6 +2113,23 @@ export interface KpiHostDetailsSourceArgs { defaultIndex: string[]; } +export interface NetworkTopCountriesSourceArgs { + id?: Maybe; + + filterQuery?: Maybe; + + ip?: Maybe; + + flowTarget: FlowTargetSourceDest; + + pagination: PaginationInputPaginated; + + sort: NetworkTopTablesSortField; + + timerange: TimerangeInput; + + defaultIndex: string[]; +} export interface NetworkTopNFlowSourceArgs { id?: Maybe; @@ -2072,7 +2141,7 @@ export interface NetworkTopNFlowSourceArgs { pagination: PaginationInputPaginated; - sort: NetworkTopNFlowSortField; + sort: NetworkTopTablesSortField; timerange: TimerangeInput; @@ -3071,13 +3140,127 @@ export namespace GetNetworkDnsQuery { }; } +export namespace GetNetworkTopCountriesQuery { + export type Variables = { + sourceId: string; + ip?: Maybe; + filterQuery?: Maybe; + pagination: PaginationInputPaginated; + sort: NetworkTopTablesSortField; + flowTarget: FlowTargetSourceDest; + timerange: TimerangeInput; + defaultIndex: string[]; + inspect: boolean; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + NetworkTopCountries: NetworkTopCountries; + }; + + export type NetworkTopCountries = { + __typename?: 'NetworkTopCountriesData'; + + totalCount: number; + + edges: Edges[]; + + pageInfo: PageInfo; + + inspect: Maybe; + }; + + export type Edges = { + __typename?: 'NetworkTopCountriesEdges'; + + node: Node; + + cursor: Cursor; + }; + + export type Node = { + __typename?: 'NetworkTopCountriesItem'; + + source: Maybe<_Source>; + + destination: Maybe; + + network: Maybe; + }; + + export type _Source = { + __typename?: 'TopCountriesItemSource'; + + country: Maybe; + + destination_ips: Maybe; + + flows: Maybe; + + source_ips: Maybe; + }; + + export type Destination = { + __typename?: 'TopCountriesItemDestination'; + + country: Maybe; + + destination_ips: Maybe; + + flows: Maybe; + + source_ips: Maybe; + }; + + export type Network = { + __typename?: 'TopNetworkTablesEcsField'; + + bytes_in: Maybe; + + bytes_out: Maybe; + }; + + export type Cursor = { + __typename?: 'CursorType'; + + value: Maybe; + }; + + export type PageInfo = { + __typename?: 'PageInfoPaginated'; + + activePage: number; + + fakeTotalCount: number; + + showMorePagesIndicator: boolean; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + export namespace GetNetworkTopNFlowQuery { export type Variables = { sourceId: string; ip?: Maybe; filterQuery?: Maybe; pagination: PaginationInputPaginated; - sort: NetworkTopNFlowSortField; + sort: NetworkTopTablesSortField; flowTarget: FlowTargetSourceDest; timerange: TimerangeInput; defaultIndex: string[]; @@ -3225,7 +3408,7 @@ export namespace GetNetworkTopNFlowQuery { }; export type Network = { - __typename?: 'TopNFlowNetworkEcsField'; + __typename?: 'TopNetworkTablesEcsField'; bytes_in: Maybe; diff --git a/x-pack/legacy/plugins/siem/public/mock/global_state.ts b/x-pack/legacy/plugins/siem/public/mock/global_state.ts index 78315e0d3ddfb..ac6dfbf08ca95 100644 --- a/x-pack/legacy/plugins/siem/public/mock/global_state.ts +++ b/x-pack/legacy/plugins/siem/public/mock/global_state.ts @@ -10,7 +10,7 @@ import { FlowTarget, HostsFields, NetworkDnsFields, - NetworkTopNFlowFields, + NetworkTopTablesFields, TlsFields, UsersFields, } from '../graphql/types'; @@ -65,15 +65,25 @@ export const mockGlobalState: State = { network: { page: { queries: { + [networkModel.NetworkTableType.topCountriesDestination]: { + activePage: 0, + limit: 10, + topCountriesSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, + }, + [networkModel.NetworkTableType.topCountriesSource]: { + activePage: 0, + limit: 10, + topCountriesSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, + }, [networkModel.NetworkTableType.topNFlowSource]: { activePage: 0, limit: 10, - topNFlowSort: { field: NetworkTopNFlowFields.bytes_out, direction: Direction.desc }, + topNFlowSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, }, [networkModel.NetworkTableType.topNFlowDestination]: { activePage: 0, limit: 10, - topNFlowSort: { field: NetworkTopNFlowFields.bytes_out, direction: Direction.desc }, + topNFlowSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, }, [networkModel.NetworkTableType.dns]: { activePage: 0, @@ -86,15 +96,25 @@ export const mockGlobalState: State = { details: { flowTarget: FlowTarget.source, queries: { + [networkModel.IpDetailsTableType.topCountriesDestination]: { + activePage: 0, + limit: 10, + topCountriesSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, + }, + [networkModel.IpDetailsTableType.topCountriesSource]: { + activePage: 0, + limit: 10, + topCountriesSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, + }, [networkModel.IpDetailsTableType.topNFlowSource]: { activePage: 0, limit: 10, - topNFlowSort: { field: NetworkTopNFlowFields.bytes_out, direction: Direction.desc }, + topNFlowSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, }, [networkModel.IpDetailsTableType.topNFlowDestination]: { activePage: 0, limit: 10, - topNFlowSort: { field: NetworkTopNFlowFields.bytes_out, direction: Direction.desc }, + topNFlowSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, }, [networkModel.IpDetailsTableType.tls]: { activePage: 0, diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx index 77364fa3a17b3..27d88e8475a65 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx @@ -40,6 +40,8 @@ import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '. import { setIpDetailsTablesActivePageToZero as dispatchIpDetailsTablesActivePageToZero } from '../../store/network/actions'; import { SpyRoute } from '../../utils/route/spy_routes'; import { NetworkEmptyPage } from './network_empty_page'; +import { NetworkTopCountriesQuery } from '../../containers/network_top_countries'; +import { NetworkTopCountriesTable } from '../../components/page/network/network_top_countries_table'; import * as i18n from './translations'; import { IPDetailsComponentProps } from './types'; @@ -47,6 +49,7 @@ const TlsTableManage = manageQuery(TlsTable); const UsersTableManage = manageQuery(UsersTable); const IpOverviewManage = manageQuery(IpOverview); const NetworkTopNFlowTableManage = manageQuery(NetworkTopNFlowTable); +const NetworkTopCountriesTableManage = manageQuery(NetworkTopCountriesTable); export const IPDetailsComponent = React.memo( ({ @@ -222,6 +225,94 @@ export const IPDetailsComponent = React.memo( + + + + {({ + id, + inspect, + isInspected, + loading, + loadPage, + networkTopCountries, + pageInfo, + refetch, + totalCount, + }) => ( + + )} + + + + + + {({ + id, + inspect, + isInspected, + loading, + loadPage, + networkTopCountries, + pageInfo, + refetch, + totalCount, + }) => ( + + )} + + + + + + ( + + {({ + id, + inspect, + isInspected, + loading, + loadPage, + networkTopCountries, + pageInfo, + refetch, + totalCount, + }) => ( + + )} + +); + +CountriesQueryTabBody.displayName = 'CountriesQueryTabBody'; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/nav_tabs.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/nav_tabs.tsx index c77eafe759752..d3a3feecc0d9d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/nav_tabs.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/nav_tabs.tsx @@ -19,6 +19,13 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab => disabled: false, urlKey: 'network', }, + [NetworkRouteType.countries]: { + id: NetworkRouteType.countries, + name: i18n.NAVIGATION_COUNTRIES_TITLE, + href: getTabsOnNetworkUrl(NetworkRouteType.countries), + disabled: false, + urlKey: 'network', + }, [NetworkRouteType.dns]: { id: NetworkRouteType.dns, name: i18n.NAVIGATION_DNS_TITLE, diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx index 0ed652804cc8e..cddd2d7aafed8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx @@ -12,6 +12,7 @@ import { FlowTargetSourceDest } from '../../../graphql/types'; import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime'; import { IPsQueryTabBody } from './ips_query_tab_body'; +import { CountriesQueryTabBody } from './countries_query_tab_body'; import { AnomaliesQueryTabBody } from './anomalies_query_tab_body'; import { DnsQueryTabBody } from './dns_query_tab_body'; import { ConditionalFlexGroup } from './conditional_flex_group'; @@ -79,6 +80,20 @@ export const NetworkRoutes = ({ )} /> + ( + + + + + + + + + + )} + /> } diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/types.ts b/x-pack/legacy/plugins/siem/public/pages/network/navigation/types.ts index bcdc0ef7aa790..3d36193b7ea12 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/types.ts @@ -41,7 +41,9 @@ export type NetworkRoutesProps = GlobalTimeArgs & { setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker; }; -export type KeyNetworkNavTabWithoutMlPermission = NetworkRouteType.ips & NetworkRouteType.dns; +export type KeyNetworkNavTabWithoutMlPermission = NetworkRouteType.countries & + NetworkRouteType.dns & + NetworkRouteType.ips; type KeyNetworkNavTabWithMlPermission = KeyNetworkNavTabWithoutMlPermission & NetworkRouteType.anomalies; @@ -52,6 +54,7 @@ export type NetworkNavTab = Record; export enum NetworkRouteType { ips = 'ips', + countries = 'countries', dns = 'dns', anomalies = 'anomalies', } diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/utils.ts b/x-pack/legacy/plugins/siem/public/pages/network/navigation/utils.ts index 9203c9d376db2..d6ad28757aee1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/utils.ts @@ -12,13 +12,14 @@ export const getNetworkRoutePath: GetNetworkRoutePath = ( hasMlUserPermission ) => { if (capabilitiesFetched && !hasMlUserPermission) { - return `${pagePath}/:tabName(${NetworkRouteType.ips}|${NetworkRouteType.dns})`; + return `${pagePath}/:tabName(${NetworkRouteType.ips}|${NetworkRouteType.dns}|${NetworkRouteType.countries})`; } return ( `${pagePath}/:tabName(` + `${NetworkRouteType.ips}|` + `${NetworkRouteType.dns}|` + + `${NetworkRouteType.countries}|` + `${NetworkRouteType.anomalies})` ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/translations.ts b/x-pack/legacy/plugins/siem/public/pages/network/translations.ts index ad60e3df86b9a..14a74f5666f0e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/translations.ts @@ -27,16 +27,23 @@ export const EMPTY_ACTION_SECONDARY = i18n.translate('xpack.siem.network.emptyAc defaultMessage: 'Go to documentation', }); -export const NAVIGATION_IPS_TITLE = i18n.translate('xpack.siem.netowork.navigation.ipsTitle', { +export const NAVIGATION_IPS_TITLE = i18n.translate('xpack.siem.network.navigation.ipsTitle', { defaultMessage: 'IPs', }); -export const NAVIGATION_DNS_TITLE = i18n.translate('xpack.siem.netowork.navigation.dnsTitle', { +export const NAVIGATION_COUNTRIES_TITLE = i18n.translate( + 'xpack.siem.network.navigation.countriesTitle', + { + defaultMessage: 'Top Countries', + } +); + +export const NAVIGATION_DNS_TITLE = i18n.translate('xpack.siem.network.navigation.dnsTitle', { defaultMessage: 'DNS', }); export const NAVIGATION_ANOMALIES_TITLE = i18n.translate( - 'xpack.siem.netowork.navigation.anomaliesTitle', + 'xpack.siem.network.navigation.anomaliesTitle', { defaultMessage: 'Anomalies', } diff --git a/x-pack/legacy/plugins/siem/public/store/network/actions.ts b/x-pack/legacy/plugins/siem/public/store/network/actions.ts index 17d0dd2c5695c..3688e257344fa 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/actions.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/actions.ts @@ -9,7 +9,7 @@ import actionCreatorFactory from 'typescript-fsa'; import { FlowTarget, NetworkDnsSortField, - NetworkTopNFlowSortField, + NetworkTopTablesSortField, TlsSortField, UsersSortField, } from '../../graphql/types'; @@ -57,12 +57,23 @@ export const updateTopNFlowLimit = actionCreator<{ }>('UPDATE_TOP_N_FLOW_LIMIT'); export const updateTopNFlowSort = actionCreator<{ - topNFlowSort: NetworkTopNFlowSortField; + topNFlowSort: NetworkTopTablesSortField; networkType: networkModel.NetworkType; tableType: networkModel.TopNTableType; }>('UPDATE_TOP_N_FLOW_SORT'); -// IP Details Actions +export const updateTopCountriesLimit = actionCreator<{ + limit: number; + networkType: networkModel.NetworkType; + tableType: networkModel.TopCountriesTableType; +}>('UPDATE_TOP_COUNTRIES_LIMIT'); + +export const updateTopCountriesSort = actionCreator<{ + topCountriesSort: NetworkTopTablesSortField; + networkType: networkModel.NetworkType; + tableType: networkModel.TopCountriesTableType; +}>('UPDATE_TOP_COUNTRIES_SORT'); + export const updateIpDetailsFlowTarget = actionCreator<{ flowTarget: FlowTarget; }>('UPDATE_IP_DETAILS_TARGET'); diff --git a/x-pack/legacy/plugins/siem/public/store/network/helpers.test.ts b/x-pack/legacy/plugins/siem/public/store/network/helpers.test.ts index f76939c5d3e3d..31db0cea80dc9 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/helpers.test.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/helpers.test.ts @@ -6,7 +6,7 @@ import { Direction, - NetworkTopNFlowFields, + NetworkTopTablesFields, NetworkDnsFields, TlsFields, UsersFields, @@ -19,11 +19,27 @@ import { setNetworkQueriesActivePageToZero } from './helpers'; export const mockNetworkState: NetworkModel = { page: { queries: { + [NetworkTableType.topCountriesSource]: { + activePage: 7, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, + [NetworkTableType.topCountriesDestination]: { + activePage: 3, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, [NetworkTableType.topNFlowSource]: { activePage: 7, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -31,7 +47,7 @@ export const mockNetworkState: NetworkModel = { activePage: 3, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -48,11 +64,27 @@ export const mockNetworkState: NetworkModel = { }, details: { queries: { + [IpDetailsTableType.topCountriesSource]: { + activePage: 7, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, + [IpDetailsTableType.topCountriesDestination]: { + activePage: 3, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, [IpDetailsTableType.topNFlowSource]: { activePage: 7, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -60,7 +92,7 @@ export const mockNetworkState: NetworkModel = { activePage: 3, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -87,7 +119,7 @@ export const mockNetworkState: NetworkModel = { describe('Network redux store', () => { describe('#setNetworkQueriesActivePageToZero', () => { - test('set activePage to zero for all queries in hosts page ', () => { + test('set activePage to zero for all queries in network page', () => { expect(setNetworkQueriesActivePageToZero(mockNetworkState, NetworkType.page)).toEqual({ [NetworkTableType.topNFlowSource]: { activePage: 0, @@ -105,10 +137,26 @@ describe('Network redux store', () => { dnsSortField: { field: 'uniqueDomains', direction: 'desc' }, isPtrIncluded: false, }, + [NetworkTableType.topCountriesDestination]: { + activePage: 0, + limit: 10, + topCountriesSort: { + direction: 'desc', + field: 'bytes_out', + }, + }, + [NetworkTableType.topCountriesSource]: { + activePage: 0, + limit: 10, + topCountriesSort: { + direction: 'desc', + field: 'bytes_out', + }, + }, }); }); - test('set activePage to zero for all queries in host details ', () => { + test('set activePage to zero for all queries in ip details ', () => { expect(setNetworkQueriesActivePageToZero(mockNetworkState, NetworkType.details)).toEqual({ [IpDetailsTableType.topNFlowSource]: { activePage: 0, @@ -120,6 +168,22 @@ describe('Network redux store', () => { limit: 10, topNFlowSort: { field: 'bytes_out', direction: 'desc' }, }, + [IpDetailsTableType.topCountriesDestination]: { + activePage: 0, + limit: 10, + topCountriesSort: { + direction: 'desc', + field: 'bytes_out', + }, + }, + [IpDetailsTableType.topCountriesSource]: { + activePage: 0, + limit: 10, + topCountriesSort: { + direction: 'desc', + field: 'bytes_out', + }, + }, [IpDetailsTableType.tls]: { activePage: 0, limit: 10, diff --git a/x-pack/legacy/plugins/siem/public/store/network/helpers.ts b/x-pack/legacy/plugins/siem/public/store/network/helpers.ts index b9876457625fb..60f4d6d1b4106 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/helpers.ts @@ -16,6 +16,14 @@ import { DEFAULT_TABLE_ACTIVE_PAGE } from '../constants'; export const setNetworkPageQueriesActivePageToZero = (state: NetworkModel): NetworkQueries => ({ ...state.page.queries, + [NetworkTableType.topCountriesSource]: { + ...state.page.queries[NetworkTableType.topCountriesSource], + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + }, + [NetworkTableType.topCountriesDestination]: { + ...state.page.queries[NetworkTableType.topCountriesDestination], + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + }, [NetworkTableType.topNFlowSource]: { ...state.page.queries[NetworkTableType.topNFlowSource], activePage: DEFAULT_TABLE_ACTIVE_PAGE, @@ -34,6 +42,14 @@ export const setNetworkDetailsQueriesActivePageToZero = ( state: NetworkModel ): IpOverviewQueries => ({ ...state.details.queries, + [IpDetailsTableType.topCountriesSource]: { + ...state.details.queries[IpDetailsTableType.topCountriesSource], + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + }, + [IpDetailsTableType.topCountriesDestination]: { + ...state.details.queries[IpDetailsTableType.topCountriesDestination], + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + }, [IpDetailsTableType.topNFlowSource]: { ...state.details.queries[IpDetailsTableType.topNFlowSource], activePage: DEFAULT_TABLE_ACTIVE_PAGE, diff --git a/x-pack/legacy/plugins/siem/public/store/network/model.ts b/x-pack/legacy/plugins/siem/public/store/network/model.ts index deaca981b1e0d..294177bd426b2 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/model.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/model.ts @@ -7,7 +7,7 @@ import { FlowTarget, NetworkDnsSortField, - NetworkTopNFlowSortField, + NetworkTopTablesSortField, TlsSortField, UsersSortField, } from '../../graphql/types'; @@ -19,20 +19,30 @@ export enum NetworkType { export enum NetworkTableType { dns = 'dns', - topNFlowSource = 'topNFlowSource', + topCountriesDestination = 'topCountriesDestination', + topCountriesSource = 'topCountriesSource', topNFlowDestination = 'topNFlowDestination', + topNFlowSource = 'topNFlowSource', } export type TopNTableType = - | NetworkTableType.topNFlowDestination - | NetworkTableType.topNFlowSource | IpDetailsTableType.topNFlowDestination - | IpDetailsTableType.topNFlowSource; + | IpDetailsTableType.topNFlowSource + | NetworkTableType.topNFlowDestination + | NetworkTableType.topNFlowSource; + +export type TopCountriesTableType = + | IpDetailsTableType.topCountriesDestination + | IpDetailsTableType.topCountriesSource + | NetworkTableType.topCountriesDestination + | NetworkTableType.topCountriesSource; export enum IpDetailsTableType { - topNFlowSource = 'topNFlowSourceIp', - topNFlowDestination = 'topNFlowDestinationIp', tls = 'tls', + topCountriesDestination = 'topCountriesDestinationIp', + topCountriesSource = 'topCountriesSourceIp', + topNFlowDestination = 'topNFlowDestinationIp', + topNFlowSource = 'topNFlowSourceIp', users = 'users', } @@ -43,7 +53,11 @@ export interface BasicQueryPaginated { // Network Page Models export interface TopNFlowQuery extends BasicQueryPaginated { - topNFlowSort: NetworkTopNFlowSortField; + topNFlowSort: NetworkTopTablesSortField; +} + +export interface TopCountriesQuery extends BasicQueryPaginated { + topCountriesSort: NetworkTopTablesSortField; } export interface DnsQuery extends BasicQueryPaginated { @@ -53,8 +67,10 @@ export interface DnsQuery extends BasicQueryPaginated { export interface NetworkQueries { [NetworkTableType.dns]: DnsQuery; - [NetworkTableType.topNFlowSource]: TopNFlowQuery; + [NetworkTableType.topCountriesDestination]: TopCountriesQuery; + [NetworkTableType.topCountriesSource]: TopCountriesQuery; [NetworkTableType.topNFlowDestination]: TopNFlowQuery; + [NetworkTableType.topNFlowSource]: TopNFlowQuery; } export interface NetworkPageModel { @@ -72,9 +88,11 @@ export interface UsersQuery extends BasicQueryPaginated { } export interface IpOverviewQueries { - [IpDetailsTableType.topNFlowSource]: TopNFlowQuery; - [IpDetailsTableType.topNFlowDestination]: TopNFlowQuery; [IpDetailsTableType.tls]: TlsQuery; + [IpDetailsTableType.topCountriesDestination]: TopCountriesQuery; + [IpDetailsTableType.topCountriesSource]: TopCountriesQuery; + [IpDetailsTableType.topNFlowDestination]: TopNFlowQuery; + [IpDetailsTableType.topNFlowSource]: TopNFlowQuery; [IpDetailsTableType.users]: UsersQuery; } diff --git a/x-pack/legacy/plugins/siem/public/store/network/reducer.ts b/x-pack/legacy/plugins/siem/public/store/network/reducer.ts index 74281bc2a4a5a..6a7c014707cbb 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/reducer.ts @@ -10,7 +10,7 @@ import { Direction, FlowTarget, NetworkDnsFields, - NetworkTopNFlowFields, + NetworkTopTablesFields, TlsFields, UsersFields, } from '../../graphql/types'; @@ -24,6 +24,8 @@ import { updateIpDetailsTableActivePage, updateIsPtrIncluded, updateNetworkPageTableActivePage, + updateTopCountriesLimit, + updateTopCountriesSort, updateTlsLimit, updateTlsSort, updateTopNFlowLimit, @@ -46,7 +48,7 @@ export const initialNetworkState: NetworkState = { activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -54,7 +56,7 @@ export const initialNetworkState: NetworkState = { activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -67,15 +69,47 @@ export const initialNetworkState: NetworkState = { }, isPtrIncluded: false, }, + [NetworkTableType.topCountriesSource]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, + [NetworkTableType.topCountriesDestination]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, }, }, details: { queries: { + [IpDetailsTableType.topCountriesSource]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, + [IpDetailsTableType.topCountriesDestination]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + topCountriesSort: { + field: NetworkTopTablesFields.bytes_out, + direction: Direction.desc, + }, + }, [IpDetailsTableType.topNFlowSource]: { activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -83,7 +117,7 @@ export const initialNetworkState: NetworkState = { activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { - field: NetworkTopNFlowFields.bytes_out, + field: NetworkTopTablesFields.bytes_out, direction: Direction.desc, }, }, @@ -263,6 +297,84 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) } return state; }) + .case(updateTopCountriesLimit, (state, { limit, networkType, tableType }) => { + if ( + networkType === NetworkType.page && + (tableType === NetworkTableType.topCountriesSource || + tableType === NetworkTableType.topCountriesDestination) + ) { + return { + ...state, + [networkType]: { + ...state[networkType], + queries: { + ...state[networkType].queries, + [tableType]: { + ...state[networkType].queries[tableType], + limit, + }, + }, + }, + }; + } else if ( + tableType === IpDetailsTableType.topCountriesDestination || + tableType === IpDetailsTableType.topCountriesSource + ) { + return { + ...state, + [NetworkType.details]: { + ...state[NetworkType.details], + queries: { + ...state[NetworkType.details].queries, + [tableType]: { + ...state[NetworkType.details].queries[tableType], + limit, + }, + }, + }, + }; + } + return state; + }) + .case(updateTopCountriesSort, (state, { topCountriesSort, networkType, tableType }) => { + if ( + networkType === NetworkType.page && + (tableType === NetworkTableType.topCountriesSource || + tableType === NetworkTableType.topCountriesDestination) + ) { + return { + ...state, + [networkType]: { + ...state[networkType], + queries: { + ...state[networkType].queries, + [tableType]: { + ...state[networkType].queries[tableType], + topCountriesSort, + }, + }, + }, + }; + } else if ( + tableType === IpDetailsTableType.topCountriesDestination || + tableType === IpDetailsTableType.topCountriesSource + ) { + return { + ...state, + [NetworkType.details]: { + ...state[NetworkType.details], + queries: { + ...state[NetworkType.details].queries, + [tableType]: { + ...state[NetworkType.details].queries[tableType], + topCountriesSort, + }, + }, + }, + }; + } + return state; + }) .case(updateIpDetailsFlowTarget, (state, { flowTarget }) => ({ ...state, [NetworkType.details]: { diff --git a/x-pack/legacy/plugins/siem/public/store/network/selectors.ts b/x-pack/legacy/plugins/siem/public/store/network/selectors.ts index 9987d4d4044b3..8f86cad3da2e2 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/selectors.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/selectors.ts @@ -23,8 +23,10 @@ export const dnsSelector = () => ); export enum NetworkTableType { dns = 'dns', - topNFlowSource = 'topNFlowSource', + topCountriesDestination = 'topCountriesDestination', + topCountriesSource = 'topCountriesSource', topNFlowDestination = 'topNFlowDestination', + topNFlowSource = 'topNFlowSource', } export const topNFlowSelector = (flowTarget: FlowTargetSourceDest, networkType: NetworkType) => { if (networkType === NetworkType.page) { @@ -45,6 +47,28 @@ export const topNFlowSelector = (flowTarget: FlowTargetSourceDest, networkType: ); }; +export const topCountriesSelector = ( + flowTarget: FlowTargetSourceDest, + networkType: NetworkType +) => { + if (networkType === NetworkType.page) { + return createSelector( + selectNetworkPage, + network => + flowTarget === FlowTargetSourceDest.source + ? network.queries[NetworkTableType.topCountriesSource] + : network.queries[NetworkTableType.topCountriesDestination] + ); + } + return createSelector( + selectNetworkDetails, + network => + flowTarget === FlowTargetSourceDest.source + ? network.queries[IpDetailsTableType.topCountriesSource] + : network.queries[IpDetailsTableType.topCountriesDestination] + ); +}; + // IP Details Selectors export const ipDetailsFlowTargetSelector = () => createSelector( diff --git a/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts index 23a8d4694ccae..7ce88ba4880be 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts @@ -10,6 +10,11 @@ import { Network } from '../../lib/network'; import { createOptionsPaginated } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; +type QueryNetworkTopCountriesResolver = ChildResolverOf< + AppResolverOf, + QuerySourceResolver +>; + type QueryNetworkTopNFlowResolver = ChildResolverOf< AppResolverOf, QuerySourceResolver @@ -28,11 +33,21 @@ export const createNetworkResolvers = ( libs: NetworkResolversDeps ): { Source: { + NetworkTopCountries: QueryNetworkTopCountriesResolver; NetworkTopNFlow: QueryNetworkTopNFlowResolver; NetworkDns: QueryDnsResolver; }; } => ({ Source: { + async NetworkTopCountries(source, args, { req }, info) { + const options = { + ...createOptionsPaginated(source, args, info), + flowTarget: args.flowTarget, + networkTopCountriesSort: args.sort, + ip: args.ip, + }; + return libs.network.getNetworkTopCountries(req, options); + }, async NetworkTopNFlow(source, args, { req }, info) { const options = { ...createOptionsPaginated(source, args, info), diff --git a/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts index acd19b6efc0ed..36b57ec9368ab 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts @@ -18,7 +18,7 @@ export const networkSchema = gql` unknown } - type TopNFlowNetworkEcsField { + type TopNetworkTablesEcsField { bytes_in: Float bytes_out: Float } @@ -33,6 +33,41 @@ export const networkSchema = gql` number: Float } + type TopCountriesItemSource { + country: String + destination_ips: Float + flows: Float + location: GeoItem + source_ips: Float + } + + type TopCountriesItemDestination { + country: String + destination_ips: Float + flows: Float + location: GeoItem + source_ips: Float + } + + type NetworkTopCountriesItem { + _id: String + source: TopCountriesItemSource + destination: TopCountriesItemDestination + network: TopNetworkTablesEcsField + } + + type NetworkTopCountriesEdges { + node: NetworkTopCountriesItem! + cursor: CursorType! + } + + type NetworkTopCountriesData { + edges: [NetworkTopCountriesEdges!]! + totalCount: Float! + pageInfo: PageInfoPaginated! + inspect: Inspect + } + type TopNFlowItemSource { autonomous_system: AutonomousSystemItem domain: [String!] @@ -51,7 +86,7 @@ export const networkSchema = gql` source_ips: Float } - enum NetworkTopNFlowFields { + enum NetworkTopTablesFields { bytes_in bytes_out flows @@ -59,8 +94,8 @@ export const networkSchema = gql` source_ips } - input NetworkTopNFlowSortField { - field: NetworkTopNFlowFields! + input NetworkTopTablesSortField { + field: NetworkTopTablesFields! direction: Direction! } @@ -68,7 +103,7 @@ export const networkSchema = gql` _id: String source: TopNFlowItemSource destination: TopNFlowItemDestination - network: TopNFlowNetworkEcsField + network: TopNetworkTablesEcsField } type NetworkTopNFlowEdges { @@ -118,14 +153,23 @@ export const networkSchema = gql` } extend type Source { - "Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified" + NetworkTopCountries( + id: String + filterQuery: String + ip: String + flowTarget: FlowTargetSourceDest! + pagination: PaginationInputPaginated! + sort: NetworkTopTablesSortField! + timerange: TimerangeInput! + defaultIndex: [String!]! + ): NetworkTopCountriesData! NetworkTopNFlow( id: String filterQuery: String ip: String flowTarget: FlowTargetSourceDest! pagination: PaginationInputPaginated! - sort: NetworkTopNFlowSortField! + sort: NetworkTopTablesSortField! timerange: TimerangeInput! defaultIndex: [String!]! ): NetworkTopNFlowData! diff --git a/x-pack/legacy/plugins/siem/server/graphql/types.ts b/x-pack/legacy/plugins/siem/server/graphql/types.ts index 8505d3efc4341..bce1f0139c49e 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/types.ts @@ -81,8 +81,8 @@ export interface UsersSortField { direction: Direction; } -export interface NetworkTopNFlowSortField { - field: NetworkTopNFlowFields; +export interface NetworkTopTablesSortField { + field: NetworkTopTablesFields; direction: Direction; } @@ -262,7 +262,7 @@ export enum FlowTargetSourceDest { source = 'source', } -export enum NetworkTopNFlowFields { +export enum NetworkTopTablesFields { bytes_in = 'bytes_in', bytes_out = 'bytes_out', flows = 'flows', @@ -428,7 +428,9 @@ export interface Source { KpiHosts: KpiHostsData; KpiHostDetails: KpiHostDetailsData; - /** Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified */ + + NetworkTopCountries: NetworkTopCountriesData; + NetworkTopNFlow: NetworkTopNFlowData; NetworkDns: NetworkDnsData; @@ -1458,6 +1460,68 @@ export interface KpiHostDetailsData { inspect?: Maybe; } +export interface NetworkTopCountriesData { + edges: NetworkTopCountriesEdges[]; + + totalCount: number; + + pageInfo: PageInfoPaginated; + + inspect?: Maybe; +} + +export interface NetworkTopCountriesEdges { + node: NetworkTopCountriesItem; + + cursor: CursorType; +} + +export interface NetworkTopCountriesItem { + _id?: Maybe; + + source?: Maybe; + + destination?: Maybe; + + network?: Maybe; +} + +export interface TopCountriesItemSource { + country?: Maybe; + + destination_ips?: Maybe; + + flows?: Maybe; + + location?: Maybe; + + source_ips?: Maybe; +} + +export interface GeoItem { + geo?: Maybe; + + flowTarget?: Maybe; +} + +export interface TopCountriesItemDestination { + country?: Maybe; + + destination_ips?: Maybe; + + flows?: Maybe; + + location?: Maybe; + + source_ips?: Maybe; +} + +export interface TopNetworkTablesEcsField { + bytes_in?: Maybe; + + bytes_out?: Maybe; +} + export interface NetworkTopNFlowData { edges: NetworkTopNFlowEdges[]; @@ -1481,7 +1545,7 @@ export interface NetworkTopNFlowItem { destination?: Maybe; - network?: Maybe; + network?: Maybe; } export interface TopNFlowItemSource { @@ -1504,12 +1568,6 @@ export interface AutonomousSystemItem { number?: Maybe; } -export interface GeoItem { - geo?: Maybe; - - flowTarget?: Maybe; -} - export interface TopNFlowItemDestination { autonomous_system?: Maybe; @@ -1524,12 +1582,6 @@ export interface TopNFlowItemDestination { source_ips?: Maybe; } -export interface TopNFlowNetworkEcsField { - bytes_in?: Maybe; - - bytes_out?: Maybe; -} - export interface NetworkDnsData { edges: NetworkDnsEdges[]; @@ -2063,6 +2115,23 @@ export interface KpiHostDetailsSourceArgs { defaultIndex: string[]; } +export interface NetworkTopCountriesSourceArgs { + id?: Maybe; + + filterQuery?: Maybe; + + ip?: Maybe; + + flowTarget: FlowTargetSourceDest; + + pagination: PaginationInputPaginated; + + sort: NetworkTopTablesSortField; + + timerange: TimerangeInput; + + defaultIndex: string[]; +} export interface NetworkTopNFlowSourceArgs { id?: Maybe; @@ -2074,7 +2143,7 @@ export interface NetworkTopNFlowSourceArgs { pagination: PaginationInputPaginated; - sort: NetworkTopNFlowSortField; + sort: NetworkTopTablesSortField; timerange: TimerangeInput; @@ -2542,7 +2611,13 @@ export namespace SourceResolvers { KpiHosts?: KpiHostsResolver; KpiHostDetails?: KpiHostDetailsResolver; - /** Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified */ + + NetworkTopCountries?: NetworkTopCountriesResolver< + NetworkTopCountriesData, + TypeParent, + TContext + >; + NetworkTopNFlow?: NetworkTopNFlowResolver; NetworkDns?: NetworkDnsResolver; @@ -2802,6 +2877,29 @@ export namespace SourceResolvers { defaultIndex: string[]; } + export type NetworkTopCountriesResolver< + R = NetworkTopCountriesData, + Parent = Source, + TContext = SiemContext + > = Resolver; + export interface NetworkTopCountriesArgs { + id?: Maybe; + + filterQuery?: Maybe; + + ip?: Maybe; + + flowTarget: FlowTargetSourceDest; + + pagination: PaginationInputPaginated; + + sort: NetworkTopTablesSortField; + + timerange: TimerangeInput; + + defaultIndex: string[]; + } + export type NetworkTopNFlowResolver< R = NetworkTopNFlowData, Parent = Source, @@ -2818,7 +2916,7 @@ export namespace SourceResolvers { pagination: PaginationInputPaginated; - sort: NetworkTopNFlowSortField; + sort: NetworkTopTablesSortField; timerange: TimerangeInput; @@ -6305,6 +6403,209 @@ export namespace KpiHostDetailsDataResolvers { > = Resolver; } +export namespace NetworkTopCountriesDataResolvers { + export interface Resolvers { + edges?: EdgesResolver; + + totalCount?: TotalCountResolver; + + pageInfo?: PageInfoResolver; + + inspect?: InspectResolver, TypeParent, TContext>; + } + + export type EdgesResolver< + R = NetworkTopCountriesEdges[], + Parent = NetworkTopCountriesData, + TContext = SiemContext + > = Resolver; + export type TotalCountResolver< + R = number, + Parent = NetworkTopCountriesData, + TContext = SiemContext + > = Resolver; + export type PageInfoResolver< + R = PageInfoPaginated, + Parent = NetworkTopCountriesData, + TContext = SiemContext + > = Resolver; + export type InspectResolver< + R = Maybe, + Parent = NetworkTopCountriesData, + TContext = SiemContext + > = Resolver; +} + +export namespace NetworkTopCountriesEdgesResolvers { + export interface Resolvers { + node?: NodeResolver; + + cursor?: CursorResolver; + } + + export type NodeResolver< + R = NetworkTopCountriesItem, + Parent = NetworkTopCountriesEdges, + TContext = SiemContext + > = Resolver; + export type CursorResolver< + R = CursorType, + Parent = NetworkTopCountriesEdges, + TContext = SiemContext + > = Resolver; +} + +export namespace NetworkTopCountriesItemResolvers { + export interface Resolvers { + _id?: _IdResolver, TypeParent, TContext>; + + source?: SourceResolver, TypeParent, TContext>; + + destination?: DestinationResolver, TypeParent, TContext>; + + network?: NetworkResolver, TypeParent, TContext>; + } + + export type _IdResolver< + R = Maybe, + Parent = NetworkTopCountriesItem, + TContext = SiemContext + > = Resolver; + export type SourceResolver< + R = Maybe, + Parent = NetworkTopCountriesItem, + TContext = SiemContext + > = Resolver; + export type DestinationResolver< + R = Maybe, + Parent = NetworkTopCountriesItem, + TContext = SiemContext + > = Resolver; + export type NetworkResolver< + R = Maybe, + Parent = NetworkTopCountriesItem, + TContext = SiemContext + > = Resolver; +} + +export namespace TopCountriesItemSourceResolvers { + export interface Resolvers { + country?: CountryResolver, TypeParent, TContext>; + + destination_ips?: DestinationIpsResolver, TypeParent, TContext>; + + flows?: FlowsResolver, TypeParent, TContext>; + + location?: LocationResolver, TypeParent, TContext>; + + source_ips?: SourceIpsResolver, TypeParent, TContext>; + } + + export type CountryResolver< + R = Maybe, + Parent = TopCountriesItemSource, + TContext = SiemContext + > = Resolver; + export type DestinationIpsResolver< + R = Maybe, + Parent = TopCountriesItemSource, + TContext = SiemContext + > = Resolver; + export type FlowsResolver< + R = Maybe, + Parent = TopCountriesItemSource, + TContext = SiemContext + > = Resolver; + export type LocationResolver< + R = Maybe, + Parent = TopCountriesItemSource, + TContext = SiemContext + > = Resolver; + export type SourceIpsResolver< + R = Maybe, + Parent = TopCountriesItemSource, + TContext = SiemContext + > = Resolver; +} + +export namespace GeoItemResolvers { + export interface Resolvers { + geo?: GeoResolver, TypeParent, TContext>; + + flowTarget?: FlowTargetResolver, TypeParent, TContext>; + } + + export type GeoResolver< + R = Maybe, + Parent = GeoItem, + TContext = SiemContext + > = Resolver; + export type FlowTargetResolver< + R = Maybe, + Parent = GeoItem, + TContext = SiemContext + > = Resolver; +} + +export namespace TopCountriesItemDestinationResolvers { + export interface Resolvers { + country?: CountryResolver, TypeParent, TContext>; + + destination_ips?: DestinationIpsResolver, TypeParent, TContext>; + + flows?: FlowsResolver, TypeParent, TContext>; + + location?: LocationResolver, TypeParent, TContext>; + + source_ips?: SourceIpsResolver, TypeParent, TContext>; + } + + export type CountryResolver< + R = Maybe, + Parent = TopCountriesItemDestination, + TContext = SiemContext + > = Resolver; + export type DestinationIpsResolver< + R = Maybe, + Parent = TopCountriesItemDestination, + TContext = SiemContext + > = Resolver; + export type FlowsResolver< + R = Maybe, + Parent = TopCountriesItemDestination, + TContext = SiemContext + > = Resolver; + export type LocationResolver< + R = Maybe, + Parent = TopCountriesItemDestination, + TContext = SiemContext + > = Resolver; + export type SourceIpsResolver< + R = Maybe, + Parent = TopCountriesItemDestination, + TContext = SiemContext + > = Resolver; +} + +export namespace TopNetworkTablesEcsFieldResolvers { + export interface Resolvers { + bytes_in?: BytesInResolver, TypeParent, TContext>; + + bytes_out?: BytesOutResolver, TypeParent, TContext>; + } + + export type BytesInResolver< + R = Maybe, + Parent = TopNetworkTablesEcsField, + TContext = SiemContext + > = Resolver; + export type BytesOutResolver< + R = Maybe, + Parent = TopNetworkTablesEcsField, + TContext = SiemContext + > = Resolver; +} + export namespace NetworkTopNFlowDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -6365,7 +6666,7 @@ export namespace NetworkTopNFlowItemResolvers { destination?: DestinationResolver, TypeParent, TContext>; - network?: NetworkResolver, TypeParent, TContext>; + network?: NetworkResolver, TypeParent, TContext>; } export type _IdResolver< @@ -6384,7 +6685,7 @@ export namespace NetworkTopNFlowItemResolvers { TContext = SiemContext > = Resolver; export type NetworkResolver< - R = Maybe, + R = Maybe, Parent = NetworkTopNFlowItem, TContext = SiemContext > = Resolver; @@ -6456,25 +6757,6 @@ export namespace AutonomousSystemItemResolvers { > = Resolver; } -export namespace GeoItemResolvers { - export interface Resolvers { - geo?: GeoResolver, TypeParent, TContext>; - - flowTarget?: FlowTargetResolver, TypeParent, TContext>; - } - - export type GeoResolver< - R = Maybe, - Parent = GeoItem, - TContext = SiemContext - > = Resolver; - export type FlowTargetResolver< - R = Maybe, - Parent = GeoItem, - TContext = SiemContext - > = Resolver; -} - export namespace TopNFlowItemDestinationResolvers { export interface Resolvers { autonomous_system?: AutonomousSystemResolver, TypeParent, TContext>; @@ -6522,25 +6804,6 @@ export namespace TopNFlowItemDestinationResolvers { > = Resolver; } -export namespace TopNFlowNetworkEcsFieldResolvers { - export interface Resolvers { - bytes_in?: BytesInResolver, TypeParent, TContext>; - - bytes_out?: BytesOutResolver, TypeParent, TContext>; - } - - export type BytesInResolver< - R = Maybe, - Parent = TopNFlowNetworkEcsField, - TContext = SiemContext - > = Resolver; - export type BytesOutResolver< - R = Maybe, - Parent = TopNFlowNetworkEcsField, - TContext = SiemContext - > = Resolver; -} - export namespace NetworkDnsDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -7922,14 +8185,19 @@ export type IResolvers = { KpiHostsData?: KpiHostsDataResolvers.Resolvers; KpiHostHistogramData?: KpiHostHistogramDataResolvers.Resolvers; KpiHostDetailsData?: KpiHostDetailsDataResolvers.Resolvers; + NetworkTopCountriesData?: NetworkTopCountriesDataResolvers.Resolvers; + NetworkTopCountriesEdges?: NetworkTopCountriesEdgesResolvers.Resolvers; + NetworkTopCountriesItem?: NetworkTopCountriesItemResolvers.Resolvers; + TopCountriesItemSource?: TopCountriesItemSourceResolvers.Resolvers; + GeoItem?: GeoItemResolvers.Resolvers; + TopCountriesItemDestination?: TopCountriesItemDestinationResolvers.Resolvers; + TopNetworkTablesEcsField?: TopNetworkTablesEcsFieldResolvers.Resolvers; NetworkTopNFlowData?: NetworkTopNFlowDataResolvers.Resolvers; NetworkTopNFlowEdges?: NetworkTopNFlowEdgesResolvers.Resolvers; NetworkTopNFlowItem?: NetworkTopNFlowItemResolvers.Resolvers; TopNFlowItemSource?: TopNFlowItemSourceResolvers.Resolvers; AutonomousSystemItem?: AutonomousSystemItemResolvers.Resolvers; - GeoItem?: GeoItemResolvers.Resolvers; TopNFlowItemDestination?: TopNFlowItemDestinationResolvers.Resolvers; - TopNFlowNetworkEcsField?: TopNFlowNetworkEcsFieldResolvers.Resolvers; NetworkDnsData?: NetworkDnsDataResolvers.Resolvers; NetworkDnsEdges?: NetworkDnsEdgesResolvers.Resolvers; NetworkDnsItem?: NetworkDnsItemResolvers.Resolvers; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts index 0b787a08cec17..5a871a3f9c9b4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts @@ -12,6 +12,8 @@ import { GeoItem, NetworkDnsData, NetworkDnsEdges, + NetworkTopCountriesData, + NetworkTopCountriesEdges, NetworkTopNFlowData, NetworkTopNFlowEdges, } from '../../graphql/types'; @@ -20,14 +22,63 @@ import { DatabaseSearchResponse, FrameworkAdapter, FrameworkRequest } from '../f import { TermAggregation } from '../types'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; -import { NetworkDnsRequestOptions, NetworkTopNFlowRequestOptions } from './index'; +import { + NetworkDnsRequestOptions, + NetworkTopCountriesRequestOptions, + NetworkTopNFlowRequestOptions, +} from './index'; import { buildDnsQuery } from './query_dns.dsl'; import { buildTopNFlowQuery, getOppositeField } from './query_top_n_flow.dsl'; -import { NetworkAdapter, NetworkDnsBuckets, NetworkTopNFlowBuckets } from './types'; +import { buildTopCountriesQuery } from './query_top_countries.dsl'; +import { + NetworkAdapter, + NetworkDnsBuckets, + NetworkTopCountriesBuckets, + NetworkTopNFlowBuckets, +} from './types'; export class ElasticsearchNetworkAdapter implements NetworkAdapter { constructor(private readonly framework: FrameworkAdapter) {} + public async getNetworkTopCountries( + request: FrameworkRequest, + options: NetworkTopCountriesRequestOptions + ): Promise { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + const dsl = buildTopCountriesQuery(options); + const response = await this.framework.callWithRequest( + request, + 'search', + dsl + ); + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; + const totalCount = getOr(0, 'aggregations.top_countries_count.value', response); + const networkTopCountriesEdges: NetworkTopCountriesEdges[] = getTopCountriesEdges( + response, + options + ); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = networkTopCountriesEdges.splice(cursorStart, querySize - cursorStart); + const inspect = { + dsl: [inspectStringifyObject(dsl)], + response: [inspectStringifyObject(response)], + }; + const showMorePagesIndicator = totalCount > fakeTotalCount; + + return { + edges, + inspect, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, + totalCount, + }; + } + public async getNetworkTopNFlow( request: FrameworkRequest, options: NetworkTopNFlowRequestOptions @@ -112,6 +163,16 @@ const getTopNFlowEdges = ( ); }; +const getTopCountriesEdges = ( + response: DatabaseSearchResponse, + options: NetworkTopCountriesRequestOptions +): NetworkTopCountriesEdges[] => { + return formatTopCountriesEdges( + getOr([], `aggregations.${options.flowTarget}.buckets`, response), + options.flowTarget + ); +}; + const getFlowTargetFromString = (flowAsString: string) => flowAsString === 'source' ? FlowTargetSourceDest.source : FlowTargetSourceDest.destination; @@ -181,6 +242,34 @@ const formatTopNFlowEdges = ( }, })); +const formatTopCountriesEdges = ( + buckets: NetworkTopCountriesBuckets[], + flowTarget: FlowTargetSourceDest +): NetworkTopCountriesEdges[] => + buckets.map((bucket: NetworkTopCountriesBuckets) => ({ + node: { + _id: bucket.key, + [flowTarget]: { + country: bucket.key, + flows: getOr(0, 'flows.value', bucket), + [`${getOppositeField(flowTarget)}_ips`]: getOr( + 0, + `${getOppositeField(flowTarget)}_ips.value`, + bucket + ), + [`${flowTarget}_ips`]: getOr(0, `${flowTarget}_ips.value`, bucket), + }, + network: { + bytes_in: getOr(0, 'bytes_in.value', bucket), + bytes_out: getOr(0, 'bytes_out.value', bucket), + }, + }, + cursor: { + value: bucket.key, + tiebreaker: null, + }, + })); + const formatDnsEdges = (buckets: NetworkDnsBuckets[]): NetworkDnsEdges[] => buckets.map((bucket: NetworkDnsBuckets) => ({ node: { diff --git a/x-pack/legacy/plugins/siem/server/lib/network/index.ts b/x-pack/legacy/plugins/siem/server/lib/network/index.ts index c183122af998f..e391dd922ce31 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/index.ts @@ -8,8 +8,9 @@ import { FlowTargetSourceDest, Maybe, NetworkDnsSortField, + NetworkTopCountriesData, NetworkTopNFlowData, - NetworkTopNFlowSortField, + NetworkTopTablesSortField, } from '../../graphql/types'; import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; export * from './elasticsearch_adapter'; @@ -18,7 +19,13 @@ import { NetworkAdapter } from './types'; export * from './types'; export interface NetworkTopNFlowRequestOptions extends RequestOptionsPaginated { - networkTopNFlowSort: NetworkTopNFlowSortField; + networkTopNFlowSort: NetworkTopTablesSortField; + flowTarget: FlowTargetSourceDest; + ip?: Maybe; +} + +export interface NetworkTopCountriesRequestOptions extends RequestOptionsPaginated { + networkTopCountriesSort: NetworkTopTablesSortField; flowTarget: FlowTargetSourceDest; ip?: Maybe; } @@ -31,6 +38,13 @@ export interface NetworkDnsRequestOptions extends RequestOptionsPaginated { export class Network { constructor(private readonly adapter: NetworkAdapter) {} + public async getNetworkTopCountries( + req: FrameworkRequest, + options: NetworkTopCountriesRequestOptions + ): Promise { + return this.adapter.getNetworkTopCountries(req, options); + } + public async getNetworkTopNFlow( req: FrameworkRequest, options: NetworkTopNFlowRequestOptions diff --git a/x-pack/legacy/plugins/siem/server/lib/network/mock.ts b/x-pack/legacy/plugins/siem/server/lib/network/mock.ts index b0df45ab60a2c..21b00bf188d20 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/mock.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/mock.ts @@ -5,7 +5,7 @@ */ import { defaultIndexPattern } from '../../../default_index_pattern'; -import { Direction, FlowTargetSourceDest, NetworkTopNFlowFields } from '../../graphql/types'; +import { Direction, FlowTargetSourceDest, NetworkTopTablesFields } from '../../graphql/types'; import { NetworkTopNFlowRequestOptions } from '.'; @@ -54,7 +54,7 @@ export const mockOptions: NetworkTopNFlowRequestOptions = { 'pageInfo.__typename', '__typename', ], - networkTopNFlowSort: { field: NetworkTopNFlowFields.bytes_out, direction: Direction.desc }, + networkTopNFlowSort: { field: NetworkTopTablesFields.bytes_out, direction: Direction.desc }, flowTarget: FlowTargetSourceDest.source, }; @@ -80,7 +80,7 @@ export const mockRequest = { $ip: String $filterQuery: String $pagination: PaginationInputPaginated! - $sort: NetworkTopNFlowSortField! + $sort: NetworkTopTablesSortField! $flowTarget: FlowTargetSourceDest! $timerange: TimerangeInput! $defaultIndex: [String!]! diff --git a/x-pack/legacy/plugins/siem/server/lib/network/query_top_countries.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/network/query_top_countries.dsl.ts new file mode 100644 index 0000000000000..40bee7eee8155 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/network/query_top_countries.dsl.ts @@ -0,0 +1,149 @@ +/* + * 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 { + Direction, + FlowTargetSourceDest, + NetworkTopTablesSortField, + NetworkTopTablesFields, +} from '../../graphql/types'; +import { assertUnreachable, createQueryFilterClauses } from '../../utils/build_query'; + +import { NetworkTopCountriesRequestOptions } from './index'; + +const getCountAgg = (flowTarget: FlowTargetSourceDest) => ({ + top_countries_count: { + cardinality: { + field: `${flowTarget}.geo.country_iso_code`, + }, + }, +}); + +export const buildTopCountriesQuery = ({ + defaultIndex, + filterQuery, + flowTarget, + networkTopCountriesSort, + pagination: { querySize }, + sourceConfiguration: { + fields: { timestamp }, + }, + timerange: { from, to }, + ip, +}: NetworkTopCountriesRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { range: { [timestamp]: { gte: from, lte: to } } }, + ]; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + aggregations: { + ...getCountAgg(flowTarget), + ...getFlowTargetAggs(networkTopCountriesSort, flowTarget, querySize), + }, + query: { + bool: ip + ? { + filter, + should: [ + { + term: { + [`${getOppositeField(flowTarget)}.ip`]: ip, + }, + }, + ], + minimum_should_match: 1, + } + : { + filter, + }, + }, + }, + size: 0, + track_total_hits: false, + }; + return dslQuery; +}; + +const getFlowTargetAggs = ( + networkTopCountriesSortField: NetworkTopTablesSortField, + flowTarget: FlowTargetSourceDest, + querySize: number +) => ({ + [flowTarget]: { + terms: { + field: `${flowTarget}.geo.country_iso_code`, + size: querySize, + order: { + ...getQueryOrder(networkTopCountriesSortField), + }, + }, + aggs: { + bytes_in: { + sum: { + field: `${getOppositeField(flowTarget)}.bytes`, + }, + }, + bytes_out: { + sum: { + field: `${flowTarget}.bytes`, + }, + }, + flows: { + cardinality: { + field: 'network.community_id', + }, + }, + source_ips: { + cardinality: { + field: 'source.ip', + }, + }, + destination_ips: { + cardinality: { + field: 'destination.ip', + }, + }, + }, + }, +}); + +export const getOppositeField = (flowTarget: FlowTargetSourceDest): FlowTargetSourceDest => { + switch (flowTarget) { + case FlowTargetSourceDest.source: + return FlowTargetSourceDest.destination; + case FlowTargetSourceDest.destination: + return FlowTargetSourceDest.source; + } + assertUnreachable(flowTarget); +}; + +type QueryOrder = + | { bytes_in: Direction } + | { bytes_out: Direction } + | { flows: Direction } + | { destination_ips: Direction } + | { source_ips: Direction }; + +const getQueryOrder = (networkTopCountriesSortField: NetworkTopTablesSortField): QueryOrder => { + switch (networkTopCountriesSortField.field) { + case NetworkTopTablesFields.bytes_in: + return { bytes_in: networkTopCountriesSortField.direction }; + case NetworkTopTablesFields.bytes_out: + return { bytes_out: networkTopCountriesSortField.direction }; + case NetworkTopTablesFields.flows: + return { flows: networkTopCountriesSortField.direction }; + case NetworkTopTablesFields.destination_ips: + return { destination_ips: networkTopCountriesSortField.direction }; + case NetworkTopTablesFields.source_ips: + return { source_ips: networkTopCountriesSortField.direction }; + } + assertUnreachable(networkTopCountriesSortField.field); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts index 5fcd5bf5d7187..47bbabf5505ca 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts @@ -7,8 +7,8 @@ import { Direction, FlowTargetSourceDest, - NetworkTopNFlowSortField, - NetworkTopNFlowFields, + NetworkTopTablesSortField, + NetworkTopTablesFields, } from '../../graphql/types'; import { assertUnreachable, createQueryFilterClauses } from '../../utils/build_query'; @@ -73,7 +73,7 @@ export const buildTopNFlowQuery = ({ }; const getFlowTargetAggs = ( - networkTopNFlowSortField: NetworkTopNFlowSortField, + networkTopNFlowSortField: NetworkTopTablesSortField, flowTarget: FlowTargetSourceDest, querySize: number ) => ({ @@ -172,17 +172,17 @@ type QueryOrder = | { destination_ips: Direction } | { source_ips: Direction }; -const getQueryOrder = (networkTopNFlowSortField: NetworkTopNFlowSortField): QueryOrder => { +const getQueryOrder = (networkTopNFlowSortField: NetworkTopTablesSortField): QueryOrder => { switch (networkTopNFlowSortField.field) { - case NetworkTopNFlowFields.bytes_in: + case NetworkTopTablesFields.bytes_in: return { bytes_in: networkTopNFlowSortField.direction }; - case NetworkTopNFlowFields.bytes_out: + case NetworkTopTablesFields.bytes_out: return { bytes_out: networkTopNFlowSortField.direction }; - case NetworkTopNFlowFields.flows: + case NetworkTopTablesFields.flows: return { flows: networkTopNFlowSortField.direction }; - case NetworkTopNFlowFields.destination_ips: + case NetworkTopTablesFields.destination_ips: return { destination_ips: networkTopNFlowSortField.direction }; - case NetworkTopNFlowFields.source_ips: + case NetworkTopTablesFields.source_ips: return { source_ips: networkTopNFlowSortField.direction }; } assertUnreachable(networkTopNFlowSortField.field); diff --git a/x-pack/legacy/plugins/siem/server/lib/network/types.ts b/x-pack/legacy/plugins/siem/server/lib/network/types.ts index c90c5e0ea24ea..e2e443fb50259 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/types.ts @@ -4,11 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { NetworkDnsData, NetworkTopNFlowData } from '../../graphql/types'; +import { NetworkTopCountriesData, NetworkDnsData, NetworkTopNFlowData } from '../../graphql/types'; import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; import { TotalValue } from '../types'; export interface NetworkAdapter { + getNetworkTopCountries( + req: FrameworkRequest, + options: RequestOptionsPaginated + ): Promise; getNetworkTopNFlow( req: FrameworkRequest, options: RequestOptionsPaginated @@ -75,6 +79,20 @@ export interface NetworkTopNFlowBuckets { source_ips?: number; } +export interface NetworkTopCountriesBuckets { + country: string; + key: string; + bytes_in: { + value: number; + }; + bytes_out: { + value: number; + }; + flows: number; + destination_ips: number; + source_ips: number; +} + export interface NetworkDnsBuckets { key: string; doc_count: number; diff --git a/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts b/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts index efa0dc9c72d9c..ee4344bb0f1ee 100644 --- a/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts +++ b/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts @@ -10,7 +10,7 @@ import { Direction, FlowTargetSourceDest, GetNetworkTopNFlowQuery, - NetworkTopNFlowFields, + NetworkTopTablesFields, } from '../../../../legacy/plugins/siem/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -39,7 +39,7 @@ export default function({ getService }: FtrProviderContext) { from: FROM, }, flowTarget: FlowTargetSourceDest.source, - sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.desc }, + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, pagination: { activePage: 0, cursorStart: 0, @@ -76,7 +76,7 @@ export default function({ getService }: FtrProviderContext) { from: FROM, }, flowTarget: FlowTargetSourceDest.source, - sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.asc }, + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.asc }, pagination: { activePage: 0, cursorStart: 0, @@ -112,7 +112,7 @@ export default function({ getService }: FtrProviderContext) { to: TO, from: FROM, }, - sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.desc }, + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, flowTarget: FlowTargetSourceDest.destination, pagination: { activePage: 0, @@ -146,7 +146,7 @@ export default function({ getService }: FtrProviderContext) { to: TO, from: FROM, }, - sort: { field: NetworkTopNFlowFields.bytes_in, direction: Direction.desc }, + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, flowTarget: FlowTargetSourceDest.source, pagination: { activePage: 1,