Adding Private ips to network page (#32527)

* add query for unique private ip

display unique private ip

update mock data

update unit test

rename file

add unique private ip type

add loading icon

fix unit test

rename variables

update integration test

build types

* update loading icon

* update snapshot

* fix type

* abstract card generator

* card itme refactor

* create managed kpiNetwork component
This commit is contained in:
Angela Chuang 2019-03-21 11:00:31 -07:00 committed by GitHub
parent e390ed5ddc
commit 8fa9c32116
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 464 additions and 131 deletions

View file

@ -1,13 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NetworkTopNFlow Table Component rendering it renders the default Authentication table 1`] = `
exports[`NetworkTopNFlow Table Component rendering it renders loading icons 1`] = `
<pure(Component)
data={
Object {
"activeAgents": 60015,
"networkEvents": 16,
"uniqueFlowId": 10277307,
"uniquePrivateIps": 383,
}
}
loading={true}
/>
`;
exports[`NetworkTopNFlow Table Component rendering it renders the default widget 1`] = `
<pure(Component)
data={
Object {
"activeAgents": 60015,
"networkEvents": 16,
"uniqueFlowId": 10277307,
"uniquePrivateIps": 383,
}
}
loading={false}
/>
`;

View file

@ -25,10 +25,20 @@ describe('NetworkTopNFlow Table Component', () => {
});
describe('rendering', () => {
test('it renders the default Authentication table', () => {
test('it renders loading icons', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<KpiNetworkComponent data={mockData.KpiNetwork} />
<KpiNetworkComponent data={mockData.KpiNetwork} loading={true} />
</ReduxStoreProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it renders the default widget', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<KpiNetworkComponent data={mockData.KpiNetwork} loading={false} />
</ReduxStoreProvider>
);

View file

@ -10,8 +10,9 @@ import {
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { EuiLoadingSpinner } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { has } from 'lodash/fp';
import { get } from 'lodash/fp';
import React from 'react';
import { pure } from 'recompose';
@ -22,37 +23,47 @@ import * as i18n from './translations';
interface KpiNetworkProps {
data: KpiNetworkData;
loading: boolean;
}
const kpiNetworkCards = (data: KpiNetworkData) => [
{
title:
has('networkEvents', data) && data.networkEvents !== null
? numeral(data.networkEvents).format('0,0')
: getEmptyTagValue(),
description: i18n.NETWORK_EVENTS,
},
{
title:
has('uniqueFlowId', data) && data.uniqueFlowId !== null
? numeral(data.uniqueFlowId).format('0,0')
: getEmptyTagValue(),
description: i18n.UNIQUE_ID,
},
{
title:
has('activeAgents', data) && data.activeAgents !== null
? numeral(data.activeAgents).format('0,0')
: getEmptyTagValue(),
description: i18n.ACTIVE_AGENTS,
},
];
export const KpiNetworkComponent = pure<KpiNetworkProps>(({ data }) => (
interface CardItemProps {
isLoading: boolean;
i18nKey: string;
data: KpiNetworkData;
property: string;
}
const fieldTitleMapping = (isLoading: boolean, title: number | null | undefined) => {
return isLoading ? (
<EuiLoadingSpinner size="m" />
) : title != null ? (
numeral(title).format('0,0')
) : (
getEmptyTagValue()
);
};
const CardItem = pure<CardItemProps>(({ isLoading, i18nKey, data, property }) => {
const matrixTitle: number | null | undefined = get(property, data);
const matrixDescription: string = get(i18nKey, i18n);
return (
<EuiFlexItem key={matrixDescription}>
<EuiCard title={fieldTitleMapping(isLoading, matrixTitle)} description={matrixDescription} />
</EuiFlexItem>
);
});
export const KpiNetworkComponent = pure<KpiNetworkProps>(({ data, loading }) => (
<EuiFlexGroup>
{kpiNetworkCards(data).map(item => (
<EuiFlexItem key={item.description}>
<EuiCard title={item.title} description={item.description} />
</EuiFlexItem>
))}
<CardItem isLoading={loading} i18nKey="NETWORK_EVENTS" data={data} property="networkEvents" />
<CardItem isLoading={loading} i18nKey="UNIQUE_ID" data={data} property="uniqueFlowId" />
<CardItem isLoading={loading} i18nKey="ACTIVE_AGENTS" data={data} property="activeAgents" />
<CardItem
isLoading={loading}
i18nKey="UNIQUE_PRIVATE_IP"
data={data}
property="uniquePrivateIps"
/>
</EuiFlexGroup>
));

View file

@ -11,5 +11,6 @@ export const mockData: { KpiNetwork: KpiNetworkData } = {
networkEvents: 16,
uniqueFlowId: 10277307,
activeAgents: 60015,
uniquePrivateIps: 383,
},
};

View file

@ -17,3 +17,14 @@ export const UNIQUE_ID = i18n.translate('xpack.secops.kpiNetwork.source.uniquiId
export const ACTIVE_AGENTS = i18n.translate('xpack.secops.kpiNetwork.source.activeAgentsTitle', {
defaultMessage: 'Active Agents',
});
export const UNIQUE_PRIVATE_IP = i18n.translate(
'xpack.secops.kpiNetwork.source.uniquePrivateIpsTitle',
{
defaultMessage: 'Unique Private IP',
}
);
export const LOADING = i18n.translate('xpack.secops.kpiNetwork.source.loadingDescription', {
defaultMessage: 'Loading',
});

View file

@ -14,6 +14,7 @@ export const kpiNetworkQuery = gql`
networkEvents
uniqueFlowId
activeAgents
uniquePrivateIps
}
}
}

View file

@ -10,6 +10,7 @@ import { Query } from 'react-apollo';
import { pure } from 'recompose';
import { GetKpiNetworkQuery, KpiNetworkData } from '../../graphql/types';
import { inputsModel } from '../../store';
import { createFilter } from '../helpers';
import { QueryTemplateProps } from '../query_template';
@ -18,6 +19,8 @@ import { kpiNetworkQuery } from './index.gql_query';
export interface KpiNetworkArgs {
id: string;
kpiNetwork: KpiNetworkData;
loading: boolean;
refetch: inputsModel.Refetch;
}
export interface KpiNetworkProps extends QueryTemplateProps {
@ -40,11 +43,13 @@ export const KpiNetworkQuery = pure<KpiNetworkProps>(
filterQuery: createFilter(filterQuery),
}}
>
{({ data }) => {
{({ data, loading, refetch }) => {
const kpiNetwork = getOr({}, `source.KpiNetwork`, data);
return children({
id,
kpiNetwork,
loading,
refetch,
});
}}
</Query>

View file

@ -3091,6 +3091,14 @@
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "uniquePrivateIps",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@ -4211,11 +4219,7 @@
"name": "networkEvents",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "Float", "ofType": null }
},
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
@ -4223,11 +4227,7 @@
"name": "uniqueFlowId",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "Float", "ofType": null }
},
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},

View file

@ -715,11 +715,13 @@ export interface SayMyName {
}
export interface KpiNetworkData {
networkEvents: number;
networkEvents?: number | null;
uniqueFlowId: number;
uniqueFlowId?: number | null;
activeAgents?: number | null;
uniquePrivateIps?: number | null;
}
// ====================================================
@ -1449,11 +1451,13 @@ export namespace GetKpiNetworkQuery {
export type KpiNetwork = {
__typename?: 'KpiNetworkData';
networkEvents: number;
networkEvents?: number | null;
uniqueFlowId: number;
uniqueFlowId?: number | null;
activeAgents?: number | null;
uniquePrivateIps?: number | null;
};
}

View file

@ -31,13 +31,12 @@ const basePath = chrome.getBasePath();
const NetworkTopNFlowTableManage = manageQuery(NetworkTopNFlowTable);
const NetworkDnsTableManage = manageQuery(NetworkDnsTable);
const KpiNetworkComponentManage = manageQuery(KpiNetworkComponent);
interface NetworkComponentReduxProps {
filterQuery: string;
}
type NetworkComponentProps = NetworkComponentReduxProps;
const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
<WithSource sourceId="default" indexTypes={[IndexType.FILEBEAT, IndexType.PACKETBEAT]}>
{({ filebeatIndicesExist, indexPattern }) =>
@ -56,7 +55,15 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
sourceId="default"
startDate={from}
>
{({ kpiNetwork }) => <KpiNetworkComponent data={kpiNetwork} />}
{({ kpiNetwork, loading, id, refetch }) => (
<KpiNetworkComponentManage
id={id}
setQuery={setQuery}
refetch={refetch}
data={kpiNetwork}
loading={loading}
/>
)}
</KpiNetworkQuery>
<EuiSpacer size="m" />
<NetworkTopNFlowQuery

View file

@ -15,6 +15,7 @@ export const mockKpiNetworkData: { KpiNetwork: KpiNetworkData } = {
networkEvents: 0,
uniqueFlowId: 0,
activeAgents: 0,
uniquePrivateIps: 0,
},
};

View file

@ -8,9 +8,10 @@ import gql from 'graphql-tag';
export const kpiNetworkSchema = gql`
type KpiNetworkData {
networkEvents: Float!
uniqueFlowId: Float!
networkEvents: Float
uniqueFlowId: Float
activeAgents: Float
uniquePrivateIps: Float
}
extend type Source {

View file

@ -744,11 +744,13 @@ export interface SayMyName {
}
export interface KpiNetworkData {
networkEvents: number;
networkEvents?: number | null;
uniqueFlowId: number;
uniqueFlowId?: number | null;
activeAgents?: number | null;
uniquePrivateIps?: number | null;
}
// ====================================================
@ -3311,20 +3313,22 @@ export namespace SayMyNameResolvers {
export namespace KpiNetworkDataResolvers {
export interface Resolvers<Context = SecOpsContext, TypeParent = KpiNetworkData> {
networkEvents?: NetworkEventsResolver<number, TypeParent, Context>;
networkEvents?: NetworkEventsResolver<number | null, TypeParent, Context>;
uniqueFlowId?: UniqueFlowIdResolver<number, TypeParent, Context>;
uniqueFlowId?: UniqueFlowIdResolver<number | null, TypeParent, Context>;
activeAgents?: ActiveAgentsResolver<number | null, TypeParent, Context>;
uniquePrivateIps?: UniquePrivateIpsResolver<number | null, TypeParent, Context>;
}
export type NetworkEventsResolver<
R = number,
R = number | null,
Parent = KpiNetworkData,
Context = SecOpsContext
> = Resolver<R, Parent, Context>;
export type UniqueFlowIdResolver<
R = number,
R = number | null,
Parent = KpiNetworkData,
Context = SecOpsContext
> = Resolver<R, Parent, Context>;
@ -3333,4 +3337,9 @@ export namespace KpiNetworkDataResolvers {
Parent = KpiNetworkData,
Context = SecOpsContext
> = Resolver<R, Parent, Context>;
export type UniquePrivateIpsResolver<
R = number | null,
Parent = KpiNetworkData,
Context = SecOpsContext
> = Resolver<R, Parent, Context>;
}

View file

@ -4,67 +4,104 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { cloneDeep } from 'lodash/fp';
import { KpiNetworkData } from '../../graphql/types';
import { FrameworkAdapter, FrameworkRequest } from '../framework';
import { ElasticsearchKpiNetworkAdapter } from './elasticsearch_adapter';
import { mockOptions, mockRequest, mockResponse, mockResult } from './mock';
import { mockMsearchOptions, mockOptions, mockRequest, mockResponse, mockResult } from './mock';
import * as generalQueryDsl from './query_general.dsl';
import * as uniquePrvateIpQueryDsl from './query_unique_private_ips.dsl';
describe('Network Kpi elasticsearch_adapter', () => {
describe('Happy Path - get Data', () => {
const mockCallWithRequest = jest.fn();
mockCallWithRequest.mockResolvedValue(mockResponse);
const mockFramework: FrameworkAdapter = {
version: 'mock',
callWithRequest: mockCallWithRequest,
exposeStaticDir: jest.fn(),
registerGraphQLEndpoint: jest.fn(),
getIndexPatternsService: jest.fn(),
};
jest.mock('../framework', () => ({
callWithRequest: mockCallWithRequest,
}));
const mockCallWithRequest = jest.fn();
const mockFramework: FrameworkAdapter = {
version: 'mock',
callWithRequest: mockCallWithRequest,
exposeStaticDir: jest.fn(),
registerGraphQLEndpoint: jest.fn(),
getIndexPatternsService: jest.fn(),
};
let mockBuildQuery: jest.SpyInstance;
let mockBuildUniquePrvateIpsQuery: jest.SpyInstance;
let EsKpiNetwork: ElasticsearchKpiNetworkAdapter;
let data: KpiNetworkData;
test('getKpiNetwork', async () => {
const EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
const data: KpiNetworkData = await EsKpiNetwork.getKpiNetwork(
mockRequest as FrameworkRequest,
mockOptions
);
describe('getKpiNetwork - call stack', () => {
beforeAll(async () => {
mockCallWithRequest.mockResolvedValue(mockResponse);
jest.mock('../framework', () => ({
callWithRequest: mockCallWithRequest,
}));
mockBuildQuery = jest.spyOn(generalQueryDsl, 'buildGeneralQuery').mockReturnValue([]);
mockBuildUniquePrvateIpsQuery = jest
.spyOn(uniquePrvateIpQueryDsl, 'buildUniquePrvateIpQuery')
.mockReturnValue([]);
EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
data = await EsKpiNetwork.getKpiNetwork(mockRequest as FrameworkRequest, mockOptions);
});
afterAll(() => {
mockCallWithRequest.mockReset();
mockBuildQuery.mockRestore();
mockBuildUniquePrvateIpsQuery.mockRestore();
});
test('should build general query with correct option', () => {
expect(mockBuildQuery).toHaveBeenCalledWith(mockOptions);
});
test('should build query for unique private ip (source) with correct option', () => {
expect(mockBuildUniquePrvateIpsQuery).toHaveBeenCalledWith('source', mockOptions);
});
test('should build query for unique private ip (destination) with correct option', () => {
expect(mockBuildUniquePrvateIpsQuery).toHaveBeenCalledWith('destination', mockOptions);
});
test('should send msearch request', () => {
expect(mockCallWithRequest).toHaveBeenCalledWith(mockRequest, 'msearch', mockMsearchOptions);
});
});
describe('Happy Path - get Data', () => {
beforeAll(async () => {
mockCallWithRequest.mockResolvedValue(mockResponse);
jest.mock('../framework', () => ({
callWithRequest: mockCallWithRequest,
}));
EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
data = await EsKpiNetwork.getKpiNetwork(mockRequest as FrameworkRequest, mockOptions);
});
afterAll(() => {
mockCallWithRequest.mockReset();
});
test('getKpiNetwork - response with data', () => {
expect(data).toEqual(mockResult);
});
});
describe('Unhappy Path - No data', () => {
const mockNoDataResponse = cloneDeep(mockResponse);
mockNoDataResponse.hits.total.value = 0;
mockNoDataResponse.aggregations.unique_flow_id.value = 0;
mockNoDataResponse.aggregations.active_agents.value = 0;
const mockCallWithRequest = jest.fn();
mockCallWithRequest.mockResolvedValue(mockNoDataResponse);
const mockFramework: FrameworkAdapter = {
version: 'mock',
callWithRequest: mockCallWithRequest,
exposeStaticDir: jest.fn(),
registerGraphQLEndpoint: jest.fn(),
getIndexPatternsService: jest.fn(),
};
jest.mock('../framework', () => ({
callWithRequest: mockCallWithRequest,
}));
beforeAll(async () => {
mockCallWithRequest.mockResolvedValue(null);
jest.mock('../framework', () => ({
callWithRequest: mockCallWithRequest,
}));
EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
data = await EsKpiNetwork.getKpiNetwork(mockRequest as FrameworkRequest, mockOptions);
});
test('getKpiNetwork', async () => {
const EsKpiNetwork = new ElasticsearchKpiNetworkAdapter(mockFramework);
const data: KpiNetworkData = await EsKpiNetwork.getKpiNetwork(
mockRequest as FrameworkRequest,
mockOptions
);
afterAll(() => {
mockCallWithRequest.mockReset();
});
test('getKpiNetwork - response without data', async () => {
expect(data).toEqual({
networkEvents: 0,
uniqueFlowId: 0,
activeAgents: 0,
networkEvents: null,
uniqueFlowId: null,
activeAgents: null,
uniquePrivateIps: null,
});
});
});

View file

@ -4,14 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { getOr } from 'lodash/fp';
import { getOr, isNil } from 'lodash/fp';
import { KpiNetworkData } from '../../graphql/types';
import { FrameworkAdapter, FrameworkRequest, RequestBasicOptions } from '../framework';
import {
DatabaseMultiResponse,
FrameworkAdapter,
FrameworkRequest,
RequestBasicOptions,
} from '../framework';
import { TermAggregation } from '../types';
import { buildQuery } from './query.dsl';
import { KpiNetworkAdapter, KpiNetworkHit } from './types';
import { buildGeneralQuery } from './query_general.dsl';
import { buildUniquePrvateIpQuery } from './query_unique_private_ips.dsl';
import { KpiNetworkAdapter, KpiNetworkESMSearchBody, KpiNetworkHit } from './types';
export class ElasticsearchKpiNetworkAdapter implements KpiNetworkAdapter {
constructor(private readonly framework: FrameworkAdapter) {}
@ -20,16 +26,55 @@ export class ElasticsearchKpiNetworkAdapter implements KpiNetworkAdapter {
request: FrameworkRequest,
options: RequestBasicOptions
): Promise<KpiNetworkData> {
const generalQuery: KpiNetworkESMSearchBody[] = buildGeneralQuery(options);
const uniqueSourcePrivateIpsQuery: KpiNetworkESMSearchBody[] = buildUniquePrvateIpQuery(
'source',
options
);
const uniqueDestinationPrivateIpsQuery: KpiNetworkESMSearchBody[] = buildUniquePrvateIpQuery(
'destination',
options
);
const response = await this.framework.callWithRequest<KpiNetworkHit, TermAggregation>(
request,
'search',
buildQuery(options)
'msearch',
{
body: [
...generalQuery,
...uniqueSourcePrivateIpsQuery,
...uniqueDestinationPrivateIpsQuery,
],
}
);
return {
networkEvents: getOr(null, 'hits.total.value', response),
uniqueFlowId: getOr(null, 'aggregations.unique_flow_id.value', response),
activeAgents: getOr(null, 'aggregations.active_agents.value', response),
networkEvents: getOr(null, 'responses.0.hits.total.value', response),
uniqueFlowId: getOr(null, 'responses.0.aggregations.unique_flow_id.value', response),
activeAgents: getOr(null, 'responses.0.aggregations.active_agents.value', response),
uniquePrivateIps: this.combineUniquePrivateIp(response),
};
}
private combineUniquePrivateIp(
response: DatabaseMultiResponse<KpiNetworkHit, TermAggregation>
): number | null {
const uniqueSourcePrivateIp = getOr(
null,
'responses.1.aggregations.unique_private_ips.value',
response
);
const uniqueDestinationPrivateIp = getOr(
null,
'responses.2.aggregations.unique_private_ips.value',
response
);
if (!isNil(uniqueSourcePrivateIp) && !isNil(uniqueDestinationPrivateIp)) {
return uniqueSourcePrivateIp + uniqueDestinationPrivateIp;
} else if (isNil(uniqueSourcePrivateIp) && !isNil(uniqueDestinationPrivateIp)) {
return uniqueDestinationPrivateIp;
} else if (!isNil(uniqueSourcePrivateIp) && isNil(uniqueDestinationPrivateIp)) {
return uniqueSourcePrivateIp;
}
return null;
}
}

View file

@ -24,6 +24,10 @@ export const mockOptions: RequestBasicOptions = {
filterQuery: {},
};
export const mockMsearchOptions = {
body: [],
};
export const mockRequest = {
params: {},
payload: {
@ -34,21 +38,35 @@ export const mockRequest = {
filterQuery: '',
},
query:
'query GetKpiNetworkQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String) {\n source(id: $sourceId) {\n id\n KpiNetwork(timerange: $timerange, filterQuery: $filterQuery) {\n networkEvents\n uniqueFlowId\n activeAgents\n }\n }\n }',
'query GetKpiNetworkQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String) {\n source(id: $sourceId) {\n id\n KpiNetwork(timerange: $timerange, filterQuery: $filterQuery) {\n networkEvents\n uniqueFlowId\n activeAgents\n uniquePrivateIps\n }\n }\n }',
},
query: {},
};
export const mockResponse = {
took: 89,
timed_out: false,
_shards: { total: 18, successful: 18, skipped: 0, failed: 0 },
hits: { total: { value: 950867, relation: 'eq' }, max_score: null, hits: [] },
aggregations: { unique_flow_id: { value: 50243 }, active_agents: { value: 15 } },
responses: [
{
took: 258,
timed_out: false,
_shards: { total: 26, successful: 26, skipped: 0, failed: 0 },
hits: { total: { value: 950867, relation: 'eq' }, max_score: null, hits: [] },
aggregations: { unique_flow_id: { value: 50243 }, active_agents: { value: 15 } },
status: 200,
},
{
took: 323,
timed_out: false,
_shards: { total: 26, successful: 26, skipped: 0, failed: 0 },
hits: { total: { value: 406839, relation: 'eq' }, max_score: null, hits: [] },
aggregations: { unique_private_ips: { value: 383 } },
status: 200,
},
],
};
export const mockResult = {
networkEvents: 950867,
uniqueFlowId: 50243,
activeAgents: 15,
uniquePrivateIps: 383,
};

View file

@ -6,7 +6,9 @@
import { createQueryFilterClauses } from '../../utils/build_query';
import { RequestBasicOptions } from '../framework';
const getKpiNetworkFilter = () => [
import { KpiNetworkESMSearchBody } from './types';
const getGeneralQueryFilter = () => [
{
bool: {
filter: [
@ -39,7 +41,7 @@ const getKpiNetworkFilter = () => [
},
];
export const buildQuery = ({
export const buildGeneralQuery = ({
filterQuery,
timerange: { from, to },
sourceConfiguration: {
@ -47,10 +49,10 @@ export const buildQuery = ({
logAlias,
packetbeatAlias,
},
}: RequestBasicOptions) => {
}: RequestBasicOptions): KpiNetworkESMSearchBody[] => {
const filter = [
...createQueryFilterClauses(filterQuery),
...getKpiNetworkFilter(),
...getGeneralQueryFilter(),
{
range: {
[timestamp]: {
@ -61,11 +63,13 @@ export const buildQuery = ({
},
];
const dslQuery = {
allowNoIndices: true,
index: [logAlias, packetbeatAlias],
ignoreUnavailable: true,
body: {
const dslQuery = [
{
index: [logAlias, packetbeatAlias],
allowNoIndices: true,
ignoreUnavailable: true,
},
{
aggregations: {
unique_flow_id: {
cardinality: {
@ -86,7 +90,7 @@ export const buildQuery = ({
size: 0,
track_total_hits: true,
},
};
];
return dslQuery;
};

View file

@ -0,0 +1,133 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createQueryFilterClauses } from '../../utils/build_query';
import { RequestBasicOptions } from '../framework';
import { KpiNetworkESMSearchBody, UniquePrivateAttributeQuery } from './types';
const getUniquePrivateIpsFilter = (attrQuery: UniquePrivateAttributeQuery) => [
{
bool: {
should: [
{
bool: {
should: [
{
match: {
[`${attrQuery}.ip`]: '10.0.0.0/8',
},
},
],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
bool: {
should: [
{
match: {
[`${attrQuery}.ip`]: '192.168.0.0/16',
},
},
],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
bool: {
should: [
{
match: {
[`${attrQuery}.ip`]: '172.16.0.0/12',
},
},
],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
match_phrase: {
[`${attrQuery}.ip`]: 'fd00::/8',
},
},
],
minimum_should_match: 1,
},
},
],
minimum_should_match: 1,
},
},
],
minimum_should_match: 1,
},
},
],
minimum_should_match: 1,
},
},
];
export const buildUniquePrvateIpQuery = (
attrQuery: UniquePrivateAttributeQuery,
{
filterQuery,
timerange: { from, to },
sourceConfiguration: {
fields: { timestamp },
logAlias,
packetbeatAlias,
},
}: RequestBasicOptions
): KpiNetworkESMSearchBody[] => {
const filter = [
...createQueryFilterClauses(filterQuery),
...getUniquePrivateIpsFilter(attrQuery),
{
range: {
[timestamp]: {
gte: from,
lte: to,
},
},
},
];
const dslQuery = [
{
allowNoIndices: true,
index: [logAlias, packetbeatAlias],
ignoreUnavailable: true,
},
{
aggregations: {
unique_private_ips: {
cardinality: {
field: `${attrQuery}.ip`,
},
},
},
query: {
bool: {
filter,
},
},
size: 0,
track_total_hits: true,
},
];
return dslQuery;
};

View file

@ -21,3 +21,20 @@ export interface KpiNetworkHit extends SearchHit {
};
};
}
export interface KpiNetworkHeader {
index: string[] | string;
allowNoIndices?: boolean;
ignoreUnavailable?: boolean;
}
export interface KpiNetworkBody {
query?: object;
aggregations?: object;
size?: number;
track_total_hits?: boolean;
}
export type KpiNetworkESMSearchBody = KpiNetworkBody | KpiNetworkHeader;
export type UniquePrivateAttributeQuery = 'source' | 'destination';

View file

@ -38,6 +38,7 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
expect(kpiNetwork!.networkEvents).to.be(6157);
expect(kpiNetwork!.uniqueFlowId).to.be(712);
expect(kpiNetwork!.activeAgents).to.equal(1);
expect(kpiNetwork!.uniquePrivateIps).to.equal(17);
});
});
});
@ -67,6 +68,7 @@ const kpiNetworkTests: KbnTestProvider = ({ getService }) => {
expect(kpiNetwork!.networkEvents).to.be(6157);
expect(kpiNetwork!.uniqueFlowId).to.be(712);
expect(kpiNetwork!.activeAgents).to.equal(1);
expect(kpiNetwork!.uniquePrivateIps).to.equal(17);
});
});
});