diff --git a/package.json b/package.json index 04415b481d5dd..a1873134e3cb8 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,8 @@ "**/typescript": "3.7.2", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", - "**/image-diff/gm/debug": "^2.6.9" + "**/image-diff/gm/debug": "^2.6.9", + "**/deepmerge": "^4.2.2" }, "workspaces": { "packages": [ @@ -155,6 +156,7 @@ "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", "d3-cloud": "1.2.5", + "deepmerge": "^4.2.2", "del": "^5.1.0", "elasticsearch": "^16.5.0", "elasticsearch-browser": "^16.5.0", diff --git a/x-pack/legacy/plugins/siem/public/components/anomalies_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/anomalies_over_time/index.tsx new file mode 100644 index 0000000000000..2337f2cd7512a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/anomalies_over_time/index.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { MatrixHistogramBasicProps } from '../matrix_histogram/types'; +import { MatrixOverTimeHistogramData } from '../../graphql/types'; +import { MatrixHistogram } from '../matrix_histogram'; +import * as i18n from './translation'; + +export const AnomaliesOverTimeHistogram = ( + props: MatrixHistogramBasicProps +) => { + const dataKey = 'anomaliesOverTime'; + const { totalCount } = props; + const subtitle = `${i18n.SHOWING}: ${totalCount.toLocaleString()} ${i18n.UNIT(totalCount)}`; + const { ...matrixOverTimeProps } = props; + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/anomalies_over_time/translation.ts b/x-pack/legacy/plugins/siem/public/components/anomalies_over_time/translation.ts new file mode 100644 index 0000000000000..f28a7176fd09d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/anomalies_over_time/translation.ts @@ -0,0 +1,24 @@ +/* + * 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 ANOMALIES_COUNT_FREQUENCY_BY_ACTION = i18n.translate( + 'xpack.siem.anomaliesOverTime.anomaliesCountFrequencyByJobTile', + { + defaultMessage: 'Anomalies count by job', + } +); + +export const SHOWING = i18n.translate('xpack.siem.anomaliesOverTime.showing', { + defaultMessage: 'Showing', +}); + +export const UNIT = (totalCount: number) => + i18n.translate('xpack.siem.anomaliesOverTime.unit', { + values: { totalCount }, + defaultMessage: `{totalCount, plural, =1 {anomaly} other {anomalies}}`, + }); diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/anomalies_over_time.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/anomalies_over_time.gql_query.ts new file mode 100644 index 0000000000000..498cdaec131e8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/anomalies_over_time.gql_query.ts @@ -0,0 +1,37 @@ +/* + * 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 AnomaliesOverTimeGqlQuery = gql` + query GetAnomaliesOverTimeQuery( + $sourceId: ID! + $timerange: TimerangeInput! + $defaultIndex: [String!]! + $filterQuery: String + $inspect: Boolean! + ) { + source(id: $sourceId) { + id + AnomaliesOverTime( + timerange: $timerange + filterQuery: $filterQuery + defaultIndex: $defaultIndex + ) { + anomaliesOverTime { + x + y + g + } + totalCount + inspect @include(if: $inspect) { + dsl + response + } + } + } + } +`; diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/index.tsx new file mode 100644 index 0000000000000..0d1ffba1ecd82 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/index.tsx @@ -0,0 +1,86 @@ +/* + * 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 { State, inputsSelectors } from '../../../store'; +import { getDefaultFetchPolicy } from '../../helpers'; +import { QueryTemplate } from '../../query_template'; + +import { AnomaliesOverTimeGqlQuery } from './anomalies_over_time.gql_query'; +import { GetAnomaliesOverTimeQuery } from '../../../graphql/types'; +import { AnomaliesOverTimeProps, OwnProps } from './types'; + +const ID = 'anomaliesOverTimeQuery'; + +class AnomaliesOverTimeComponentQuery extends QueryTemplate< + AnomaliesOverTimeProps, + GetAnomaliesOverTimeQuery.Query, + GetAnomaliesOverTimeQuery.Variables +> { + public render() { + const { + children, + endDate, + filterQuery, + id = ID, + isInspected, + sourceId, + startDate, + } = this.props; + + return ( + + query={AnomaliesOverTimeGqlQuery} + fetchPolicy={getDefaultFetchPolicy()} + notifyOnNetworkStatusChange + variables={{ + filterQuery, + sourceId, + timerange: { + interval: 'day', + from: startDate!, + to: endDate!, + }, + defaultIndex: ['.ml-anomalies-*'], + inspect: isInspected, + }} + > + {({ data, loading, refetch }) => { + const source = getOr({}, `source.AnomaliesOverTime`, data); + const anomaliesOverTime = getOr([], `anomaliesOverTime`, source); + const totalCount = getOr(-1, 'totalCount', source); + return children!({ + endDate: endDate!, + anomaliesOverTime, + id, + inspect: getOr(null, 'inspect', source), + loading, + refetch, + startDate: startDate!, + totalCount, + }); + }} + + ); + } +} + +const makeMapStateToProps = () => { + const getQuery = inputsSelectors.globalQueryByIdSelector(); + const mapStateToProps = (state: State, { id = ID }: OwnProps) => { + const { isInspected } = getQuery(state, id); + return { + isInspected, + }; + }; + return mapStateToProps; +}; + +export const AnomaliesOverTimeQuery = connect(makeMapStateToProps)(AnomaliesOverTimeComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/types.ts b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/types.ts new file mode 100644 index 0000000000000..e6ece4a46e44f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_over_time/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { QueryTemplateProps } from '../../query_template'; +import { inputsModel, hostsModel, networkModel } from '../../../store'; +import { MatrixOverTimeHistogramData } from '../../../graphql/types'; + +export interface AnomaliesArgs { + endDate: number; + anomaliesOverTime: MatrixOverTimeHistogramData[]; + id: string; + inspect: inputsModel.InspectQuery; + loading: boolean; + refetch: inputsModel.Refetch; + startDate: number; + totalCount: number; +} + +export interface OwnProps extends Omit { + filterQuery?: string; + children?: (args: AnomaliesArgs) => React.ReactNode; + type: hostsModel.HostsType | networkModel.NetworkType; +} + +export interface AnomaliesOverTimeComponentReduxProps { + isInspected: boolean; +} + +export type AnomaliesOverTimeProps = OwnProps & AnomaliesOverTimeComponentReduxProps; diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx new file mode 100644 index 0000000000000..917f4dbcc211b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { AnomaliesQueryTabBodyProps } from './types'; +import { manageQuery } from '../../../components/page/manage_query'; +import { AnomaliesOverTimeHistogram } from '../../../components/anomalies_over_time'; +import { AnomaliesOverTimeQuery } from '../anomalies_over_time'; +import { getAnomaliesFilterQuery } from './utils'; +import { useSiemJobs } from '../../../components/ml_popover/hooks/use_siem_jobs'; +import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting'; +import { DEFAULT_ANOMALY_SCORE } from '../../../../common/constants'; + +const AnomaliesOverTimeManage = manageQuery(AnomaliesOverTimeHistogram); + +export const AnomaliesQueryTabBody = ({ + endDate, + skip, + startDate, + type, + narrowDateRange, + filterQuery, + anomaliesFilterQuery, + setQuery, + hideHistogramIfEmpty, + updateDateRange = () => {}, + AnomaliesTableComponent, + flowTarget, + ip, +}: AnomaliesQueryTabBodyProps) => { + const [siemJobsLoading, siemJobs] = useSiemJobs(true); + const [anomalyScore] = useKibanaUiSetting(DEFAULT_ANOMALY_SCORE); + + const mergedFilterQuery = getAnomaliesFilterQuery( + filterQuery, + anomaliesFilterQuery, + siemJobs, + anomalyScore, + flowTarget, + ip + ); + + return ( + <> + + {({ anomaliesOverTime, loading, id, inspect, refetch, totalCount }) => { + if (hideHistogramIfEmpty && !anomaliesOverTime.length) { + return
; + } + + return ( + <> + + + + ); + }} + + + + ); +}; + +AnomaliesQueryTabBody.displayName = 'AnomaliesQueryTabBody'; diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/types.ts b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/types.ts new file mode 100644 index 0000000000000..0aef02ddd929a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/types.ts @@ -0,0 +1,34 @@ +/* + * 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 { ESTermQuery } from '../../../../common/typed_json'; +import { NarrowDateRange } from '../../../components/ml/types'; +import { UpdateDateRange } from '../../../components/charts/common'; +import { SetQuery } from '../../../pages/hosts/navigation/types'; +import { FlowTarget } from '../../../graphql/types'; +import { HostsType } from '../../../store/hosts/model'; +import { NetworkType } from '../../../store/network/model'; +import { AnomaliesHostTable } from '../../../components/ml/tables/anomalies_host_table'; +import { AnomaliesNetworkTable } from '../../../components/ml/tables/anomalies_network_table'; + +interface QueryTabBodyProps { + type: HostsType | NetworkType; + filterQuery?: string | ESTermQuery; +} + +export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & { + startDate: number; + endDate: number; + skip: boolean; + setQuery: SetQuery; + narrowDateRange: NarrowDateRange; + updateDateRange?: UpdateDateRange; + anomaliesFilterQuery?: object; + hideHistogramIfEmpty?: boolean; + ip?: string; + flowTarget?: FlowTarget; + AnomaliesTableComponent: typeof AnomaliesHostTable | typeof AnomaliesNetworkTable; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/utils.ts b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/utils.ts new file mode 100644 index 0000000000000..9609619916ab1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/utils.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 deepmerge from 'deepmerge'; +import { createFilter } from '../../helpers'; +import { ESTermQuery } from '../../../../common/typed_json'; +import { SiemJob } from '../../../components/ml_popover/types'; +import { FlowTarget } from '../../../graphql/types'; + +export const getAnomaliesFilterQuery = ( + filterQuery: string | ESTermQuery | undefined, + anomaliesFilterQuery: object = {}, + siemJobs: SiemJob[] = [], + anomalyScore: number, + flowTarget?: FlowTarget, + ip?: string +): string => { + const siemJobIds = siemJobs + .filter(job => job.isInstalled) + .map(job => job.id) + .map(jobId => ({ + match_phrase: { + job_id: jobId, + }, + })); + + const filterQueryString = createFilter(filterQuery); + const filterQueryObject = filterQueryString ? JSON.parse(filterQueryString) : {}; + const mergedFilterQuery = deepmerge.all([ + filterQueryObject, + anomaliesFilterQuery, + { + bool: { + filter: [ + { + bool: { + should: siemJobIds, + minimum_should_match: 1, + }, + }, + { + match_phrase: { + result_type: 'record', + }, + }, + flowTarget && + ip && { + match_phrase: { + [`${flowTarget}.ip`]: ip, + }, + }, + { + range: { + record_score: { + gte: anomalyScore, + }, + }, + }, + ], + }, + }, + ]); + + return JSON.stringify(mergedFilterQuery); +}; diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json index a93168c835293..7c173a9a90626 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json @@ -666,6 +666,53 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "AnomaliesOverTime", + "description": "", + "args": [ + { + "name": "timerange", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } + }, + "defaultValue": null + }, + { + "name": "filterQuery", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "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": "AnomaliesOverTimeData", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "Authentications", "description": "Gets Authentication success and failures based on a timerange", @@ -2491,6 +2538,159 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "AnomaliesOverTimeData", + "description": "", + "fields": [ + { + "name": "inspect", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "anomaliesOverTime", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "MatrixOverTimeHistogramData", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Inspect", + "description": "", + "fields": [ + { + "name": "dsl", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "response", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "MatrixOverTimeHistogramData", + "description": "", + "fields": [ + { + "name": "x", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "y", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "g", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "PaginationInputPaginated", @@ -3200,57 +3400,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "Inspect", - "description": "", - "fields": [ - { - "name": "dsl", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "response", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "AuthenticationsOverTimeData", @@ -3306,53 +3455,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "MatrixOverTimeHistogramData", - "description": "", - "fields": [ - { - "name": "x", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "y", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "g", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "PaginationInput", diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts index ad05e42bcd859..1464b55648035 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts @@ -456,6 +456,8 @@ export interface Source { configuration: SourceConfiguration; /** The status of the source */ status: SourceStatus; + + AnomaliesOverTime: AnomaliesOverTimeData; /** Gets Authentication success and failures based on a timerange */ Authentications: AuthenticationsData; @@ -556,6 +558,28 @@ export interface IndexField { format?: Maybe; } +export interface AnomaliesOverTimeData { + inspect?: Maybe; + + anomaliesOverTime: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + +export interface Inspect { + dsl: string[]; + + response: string[]; +} + +export interface MatrixOverTimeHistogramData { + x: number; + + y: number; + + g: string; +} + export interface AuthenticationsData { edges: AuthenticationsEdges[]; @@ -690,12 +714,6 @@ export interface PageInfoPaginated { showMorePagesIndicator: boolean; } -export interface Inspect { - dsl: string[]; - - response: string[]; -} - export interface AuthenticationsOverTimeData { inspect?: Maybe; @@ -704,14 +722,6 @@ export interface AuthenticationsOverTimeData { totalCount: number; } -export interface MatrixOverTimeHistogramData { - x: number; - - y: number; - - g: string; -} - export interface TimelineData { edges: TimelineEdges[]; @@ -2127,6 +2137,13 @@ export interface GetAllTimelineQueryArgs { onlyUserFavorite?: Maybe; } +export interface AnomaliesOverTimeSourceArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; +} export interface AuthenticationsSourceArgs { timerange: TimerangeInput; @@ -2421,6 +2438,58 @@ export interface DeleteTimelineMutationArgs { // Documents // ==================================================== +export namespace GetAnomaliesOverTimeQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + defaultIndex: string[]; + filterQuery?: Maybe; + inspect: boolean; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + AnomaliesOverTime: AnomaliesOverTime; + }; + + export type AnomaliesOverTime = { + __typename?: 'AnomaliesOverTimeData'; + + anomaliesOverTime: _AnomaliesOverTime[]; + + totalCount: number; + + inspect: Maybe; + }; + + export type _AnomaliesOverTime = { + __typename?: 'MatrixOverTimeHistogramData'; + + x: number; + + y: number; + + g: string; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + export namespace GetAuthenticationsOverTimeQuery { export type Variables = { sourceId: string; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx index 48b6d34d0b28b..1252c7031e8a5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx @@ -10,6 +10,8 @@ import { Route, Switch } from 'react-router-dom'; import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime'; import { Anomaly } from '../../../components/ml/types'; import { HostsTableType } from '../../../store/hosts/model'; +import { AnomaliesQueryTabBody } from '../../../containers/anomalies/anomalies_query_tab_body'; +import { AnomaliesHostTable } from '../../../components/ml/tables/anomalies_host_table'; import { HostDetailsTabsProps } from './types'; import { type } from './utils'; @@ -18,7 +20,6 @@ import { HostsQueryTabBody, AuthenticationsQueryTabBody, UncommonProcessQueryTabBody, - AnomaliesQueryTabBody, EventsQueryTabBody, } from '../navigation'; @@ -84,7 +85,9 @@ const HostDetailsTabs = React.memo( /> } + render={() => ( + + )} /> ( /> } + render={() => ( + + )} /> ( - -); - -AnomaliesQueryTabBody.displayName = 'AnomaliesQueryTabBody'; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/index.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/index.ts index 8a8f23208363d..f20138f520620 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/index.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './anomalies_query_tab_body'; export * from './authentications_query_tab_body'; export * from './events_query_tab_body'; export * from './hosts_query_tab_body'; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts index d567038a05bd8..98d931dd7e275 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts @@ -25,6 +25,18 @@ type KeyHostsNavTab = KeyHostsNavTabWithoutMlPermission | KeyHostsNavTabWithMlPe export type HostsNavTab = Record; +export type SetQuery = ({ + id, + inspect, + loading, + refetch, +}: { + id: string; + inspect: InspectQuery | null; + loading: boolean; + refetch: Refetch; +}) => void; + interface QueryTabBodyProps { type: hostsModel.HostsType; startDate: number; @@ -32,30 +44,13 @@ interface QueryTabBodyProps { filterQuery?: string | ESTermQuery; } -export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & { - skip: boolean; - narrowDateRange: NarrowDateRange; - hostName?: string; -}; - export type HostsComponentsQueryProps = QueryTabBodyProps & { deleteQuery?: ({ id }: { id: string }) => void; indexPattern: StaticIndexPattern; skip: boolean; - setQuery: ({ - id, - inspect, - loading, - refetch, - }: { - id: string; - inspect: InspectQuery | null; - loading: boolean; - refetch: Refetch; - }) => void; + setQuery: SetQuery; updateDateRange?: UpdateDateRange; narrowDateRange?: NarrowDateRange; }; export type CommonChildren = (args: HostsComponentsQueryProps) => JSX.Element; -export type AnomaliesChildren = (args: AnomaliesQueryTabBodyProps) => JSX.Element; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx index 96111f0479938..477f435b84b20 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx @@ -39,6 +39,7 @@ import { NetworkTopNFlowQueryTable } from './network_top_n_flow_query_table'; import { TlsQueryTable } from './tls_query_table'; import { IPDetailsComponentProps } from './types'; import { UsersQueryTable } from './users_query_table'; +import { AnomaliesQueryTabBody } from '../../../containers/anomalies/anomalies_query_tab_body'; import { esQuery } from '../../../../../../../../src/plugins/data/public'; export { getBreadcrumbs } from './utils'; @@ -58,6 +59,7 @@ export const IPDetailsComponent = React.memo( setQuery, to, }) => { + const type = networkModel.NetworkType.details; const narrowDateRange = useCallback( (score, interval) => { const fromTo = scoreIntervalToDateTime(score, interval); @@ -108,7 +110,7 @@ export const IPDetailsComponent = React.memo( skip={isInitializing} sourceId="default" filterQuery={filterQuery} - type={networkModel.NetworkType.details} + type={type} ip={ip} > {({ id, inspect, ipOverviewData, loading, refetch }) => ( @@ -127,7 +129,7 @@ export const IPDetailsComponent = React.memo( anomaliesData={anomaliesData} loading={loading} isLoadingAnomaliesData={isLoadingAnomaliesData} - type={networkModel.NetworkType.details} + type={type} flowTarget={flowTarget} refetch={refetch} setQuery={setQuery} @@ -158,7 +160,7 @@ export const IPDetailsComponent = React.memo( ip={ip} skip={isInitializing} startDate={from} - type={networkModel.NetworkType.details} + type={type} setQuery={setQuery} indexPattern={indexPattern} /> @@ -172,7 +174,7 @@ export const IPDetailsComponent = React.memo( ip={ip} skip={isInitializing} startDate={from} - type={networkModel.NetworkType.details} + type={type} setQuery={setQuery} indexPattern={indexPattern} /> @@ -190,7 +192,7 @@ export const IPDetailsComponent = React.memo( ip={ip} skip={isInitializing} startDate={from} - type={networkModel.NetworkType.details} + type={type} setQuery={setQuery} indexPattern={indexPattern} /> @@ -204,7 +206,7 @@ export const IPDetailsComponent = React.memo( ip={ip} skip={isInitializing} startDate={from} - type={networkModel.NetworkType.details} + type={type} setQuery={setQuery} indexPattern={indexPattern} /> @@ -220,7 +222,7 @@ export const IPDetailsComponent = React.memo( ip={ip} skip={isInitializing} startDate={from} - type={networkModel.NetworkType.details} + type={type} setQuery={setQuery} /> @@ -232,7 +234,7 @@ export const IPDetailsComponent = React.memo( ip={ip} skip={isInitializing} startDate={from} - type={networkModel.NetworkType.details} + type={type} setQuery={setQuery} /> @@ -246,19 +248,23 @@ export const IPDetailsComponent = React.memo( setQuery={setQuery} skip={isInitializing} startDate={from} - type={networkModel.NetworkType.details} + type={type} /> - diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/anomalies_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/anomalies_query_tab_body.tsx deleted file mode 100644 index daf9cd2dd1d12..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/anomalies_query_tab_body.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { AnomaliesQueryTabBodyProps } from './types'; -import { AnomaliesNetworkTable } from '../../../components/ml/tables/anomalies_network_table'; - -export const AnomaliesQueryTabBody = ({ - to, - isInitializing, - from, - type, - narrowDateRange, -}: AnomaliesQueryTabBodyProps) => ( - -); - -AnomaliesQueryTabBody.displayName = 'AnomaliesQueryTabBody'; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/countries_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/countries_query_tab_body.tsx index 0fe370c144049..6ddd3bbec3a32 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/countries_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/countries_query_tab_body.tsx @@ -17,21 +17,21 @@ import { IPsQueryTabBodyProps as CountriesQueryTabBodyProps } from './types'; const NetworkTopCountriesTableManage = manageQuery(NetworkTopCountriesTable); export const CountriesQueryTabBody = ({ - to, + endDate, filterQuery, - isInitializing, - from, + skip, + startDate, setQuery, indexPattern, flowTarget, }: CountriesQueryTabBodyProps) => ( {({ diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/dns_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/dns_query_tab_body.tsx index 34ff35bd145a2..da3c2fcfbc67b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/dns_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/dns_query_tab_body.tsx @@ -12,28 +12,28 @@ import { NetworkDnsTable } from '../../../components/page/network/network_dns_ta import { NetworkDnsQuery, NetworkDnsHistogramQuery } from '../../../containers/network_dns'; import { manageQuery } from '../../../components/page/manage_query'; -import { DnsQueryTabBodyProps } from './types'; +import { NetworkComponentQueryProps } from './types'; import { NetworkDnsHistogram } from '../../../components/page/network/dns_histogram'; const NetworkDnsTableManage = manageQuery(NetworkDnsTable); const NetworkDnsHistogramManage = manageQuery(NetworkDnsHistogram); export const DnsQueryTabBody = ({ - to, + endDate, filterQuery, - isInitializing, - from, + skip, + startDate, setQuery, type, updateDateRange = () => {}, -}: DnsQueryTabBodyProps) => ( +}: NetworkComponentQueryProps) => ( <> {({ totalCount, loading, id, inspect, refetch, histogram }) => ( @@ -41,8 +41,8 @@ export const DnsQueryTabBody = ({ id={id} loading={loading} data={histogram} - endDate={to} - startDate={from} + endDate={endDate} + startDate={startDate} inspect={inspect} refetch={refetch} setQuery={setQuery} @@ -53,11 +53,11 @@ export const DnsQueryTabBody = ({ {({ diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/http_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/http_query_tab_body.tsx index a20a212623fb8..639a14d354ced 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/http_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/http_query_tab_body.tsx @@ -17,18 +17,18 @@ import { HttpQueryTabBodyProps } from './types'; const NetworkHttpTableManage = manageQuery(NetworkHttpTable); export const HttpQueryTabBody = ({ - to, + endDate, filterQuery, - isInitializing, - from, + skip, + startDate, setQuery, }: HttpQueryTabBodyProps) => ( {({ diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/index.ts b/x-pack/legacy/plugins/siem/public/pages/network/navigation/index.ts index 9e8b4c6215031..44b78cb3077ff 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/index.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './anomalies_query_tab_body'; export * from './network_routes'; export * from './network_routes_loading'; export * from './nav_tabs'; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx index 08ba75443b333..95aaa90fe7865 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx @@ -17,21 +17,21 @@ import { IPsQueryTabBodyProps } from './types'; const NetworkTopNFlowTableManage = manageQuery(NetworkTopNFlowTable); export const IPsQueryTabBody = ({ - to, + endDate, filterQuery, - isInitializing, - from, + skip, + startDate, setQuery, indexPattern, flowTarget, }: IPsQueryTabBodyProps) => ( {({ 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 0f373be94b45b..681e1f8e1e34d 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 @@ -14,7 +14,8 @@ import { scoreIntervalToDateTime } from '../../../components/ml/score/score_inte import { IPsQueryTabBody } from './ips_query_tab_body'; import { CountriesQueryTabBody } from './countries_query_tab_body'; import { HttpQueryTabBody } from './http_query_tab_body'; -import { AnomaliesQueryTabBody } from './anomalies_query_tab_body'; +import { AnomaliesQueryTabBody } from '../../../containers/anomalies/anomalies_query_tab_body'; +import { AnomaliesNetworkTable } from '../../../components/ml/tables/anomalies_network_table'; import { DnsQueryTabBody } from './dns_query_tab_body'; import { ConditionalFlexGroup } from './conditional_flex_group'; import { NetworkRoutesProps, NetworkRouteType } from './types'; @@ -50,24 +51,44 @@ export const NetworkRoutes = ({ [from, to] ); - const tabProps = { - networkPagePath, + const networkAnomaliesFilterQuery = { + bool: { + should: [ + { + exists: { + field: 'source.ip', + }, + }, + { + exists: { + field: 'destination.ip', + }, + }, + ], + minimum_should_match: 1, + }, + }; + + const commonProps = { + startDate: from, + endDate: to, + skip: isInitializing, type, - to, + narrowDateRange, + setQuery, filterQuery, - isInitializing, - from, + }; + + const tabProps = { + ...commonProps, indexPattern, - setQuery, updateDateRange, }; const anomaliesProps = { - from, - to, - isInitializing, - type, - narrowDateRange, + ...commonProps, + anomaliesFilterQuery: networkAnomaliesFilterQuery, + AnomaliesTableComponent: AnomaliesNetworkTable, }; return ( @@ -115,7 +136,12 @@ export const NetworkRoutes = ({ /> } + render={() => ( + + )} /> ); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/tls_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/tls_query_tab_body.tsx index 1f93e293be865..0adfec203e0a6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/tls_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/tls_query_tab_body.tsx @@ -13,23 +13,23 @@ import { TlsQueryTabBodyProps } from './types'; const TlsTableManage = manageQuery(TlsTable); export const TlsQueryTabBody = ({ - to, + endDate, filterQuery, flowTarget, ip = '', setQuery, - isInitializing, - from, + skip, + startDate, type, }: TlsQueryTabBodyProps) => ( {({ id, inspect, isInspected, tls, totalCount, pageInfo, loading, loadPage, refetch }) => ( 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 5f5f0a026d375..bc63e26f71eba 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 @@ -10,41 +10,37 @@ import { NavTab } from '../../../components/navigation/types'; import { FlowTargetSourceDest } from '../../../graphql/types'; import { networkModel } from '../../../store'; import { ESTermQuery } from '../../../../common/typed_json'; -import { NarrowDateRange } from '../../../components/ml/types'; import { GlobalTimeArgs } from '../../../containers/global_time'; import { SetAbsoluteRangeDatePicker } from '../types'; import { UpdateDateRange } from '../../../components/charts/common'; +import { NarrowDateRange } from '../../../components/ml/types'; -interface QueryTabBodyProps { +interface QueryTabBodyProps extends Pick { + skip: boolean; type: networkModel.NetworkType; + startDate: number; + endDate: number; filterQuery?: string | ESTermQuery; updateDateRange?: UpdateDateRange; narrowDateRange?: NarrowDateRange; } -export type DnsQueryTabBodyProps = QueryTabBodyProps & GlobalTimeArgs; +export type NetworkComponentQueryProps = QueryTabBodyProps; -export type IPsQueryTabBodyProps = QueryTabBodyProps & - GlobalTimeArgs & { - indexPattern: StaticIndexPattern; - flowTarget: FlowTargetSourceDest; - }; +export type IPsQueryTabBodyProps = QueryTabBodyProps & { + indexPattern: StaticIndexPattern; + flowTarget: FlowTargetSourceDest; +}; -export type TlsQueryTabBodyProps = QueryTabBodyProps & - GlobalTimeArgs & { - flowTarget: FlowTargetSourceDest; - ip?: string; - }; +export type TlsQueryTabBodyProps = QueryTabBodyProps & { + flowTarget: FlowTargetSourceDest; + ip?: string; +}; -export type HttpQueryTabBodyProps = QueryTabBodyProps & - GlobalTimeArgs & { - ip?: string; - }; -export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & - Pick & { - narrowDateRange: NarrowDateRange; - }; +export type HttpQueryTabBodyProps = QueryTabBodyProps & { + ip?: string; +}; export type NetworkRoutesProps = GlobalTimeArgs & { networkPagePath: string; diff --git a/x-pack/legacy/plugins/siem/server/graphql/anomalies/index.ts b/x-pack/legacy/plugins/siem/server/graphql/anomalies/index.ts new file mode 100644 index 0000000000000..4bfd6be173105 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/graphql/anomalies/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { createAnomaliesResolvers } from './resolvers'; +export { anomaliesSchema } from './schema.gql'; diff --git a/x-pack/legacy/plugins/siem/server/graphql/anomalies/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/anomalies/resolvers.ts new file mode 100644 index 0000000000000..47e227a8c0f84 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/graphql/anomalies/resolvers.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Anomalies } from '../../lib/anomalies'; +import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; +import { createOptions } from '../../utils/build_query/create_options'; +import { QuerySourceResolver } from '../sources/resolvers'; +import { SourceResolvers } from '../types'; + +export interface AnomaliesResolversDeps { + anomalies: Anomalies; +} + +type QueryAnomaliesOverTimeResolver = ChildResolverOf< + AppResolverOf, + QuerySourceResolver +>; + +export const createAnomaliesResolvers = ( + libs: AnomaliesResolversDeps +): { + Source: { + AnomaliesOverTime: QueryAnomaliesOverTimeResolver; + }; +} => ({ + Source: { + async AnomaliesOverTime(source, args, { req }, info) { + const options = { + ...createOptions(source, args, info), + defaultIndex: args.defaultIndex, + }; + return libs.anomalies.getAnomaliesOverTime(req, options); + }, + }, +}); diff --git a/x-pack/legacy/plugins/siem/server/graphql/anomalies/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/anomalies/schema.gql.ts new file mode 100644 index 0000000000000..1dad0aafd55b0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/graphql/anomalies/schema.gql.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import gql from 'graphql-tag'; + +export const anomaliesSchema = gql` + type AnomaliesOverTimeData { + inspect: Inspect + anomaliesOverTime: [MatrixOverTimeHistogramData!]! + totalCount: Float! + } + + extend type Source { + AnomaliesOverTime( + timerange: TimerangeInput! + filterQuery: String + defaultIndex: [String!]! + ): AnomaliesOverTimeData! + } +`; diff --git a/x-pack/legacy/plugins/siem/server/graphql/index.ts b/x-pack/legacy/plugins/siem/server/graphql/index.ts index 110a390c19531..901d27295479a 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/index.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/index.ts @@ -7,6 +7,7 @@ import { rootSchema } from '../../common/graphql/root'; import { sharedSchema } from '../../common/graphql/shared'; +import { anomaliesSchema } from './anomalies'; import { authenticationsSchema } from './authentications'; import { ecsSchema } from './ecs'; import { eventsSchema } from './events'; @@ -29,6 +30,7 @@ import { tlsSchema } from './tls'; import { uncommonProcessesSchema } from './uncommon_processes'; import { whoAmISchema } from './who_am_i'; export const schemas = [ + anomaliesSchema, authenticationsSchema, ecsSchema, eventsSchema, diff --git a/x-pack/legacy/plugins/siem/server/graphql/types.ts b/x-pack/legacy/plugins/siem/server/graphql/types.ts index 44cfc81339527..fda79ad543bf6 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/types.ts @@ -458,6 +458,8 @@ export interface Source { configuration: SourceConfiguration; /** The status of the source */ status: SourceStatus; + + AnomaliesOverTime: AnomaliesOverTimeData; /** Gets Authentication success and failures based on a timerange */ Authentications: AuthenticationsData; @@ -558,6 +560,28 @@ export interface IndexField { format?: Maybe; } +export interface AnomaliesOverTimeData { + inspect?: Maybe; + + anomaliesOverTime: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + +export interface Inspect { + dsl: string[]; + + response: string[]; +} + +export interface MatrixOverTimeHistogramData { + x: number; + + y: number; + + g: string; +} + export interface AuthenticationsData { edges: AuthenticationsEdges[]; @@ -692,12 +716,6 @@ export interface PageInfoPaginated { showMorePagesIndicator: boolean; } -export interface Inspect { - dsl: string[]; - - response: string[]; -} - export interface AuthenticationsOverTimeData { inspect?: Maybe; @@ -706,14 +724,6 @@ export interface AuthenticationsOverTimeData { totalCount: number; } -export interface MatrixOverTimeHistogramData { - x: number; - - y: number; - - g: string; -} - export interface TimelineData { edges: TimelineEdges[]; @@ -2129,6 +2139,13 @@ export interface GetAllTimelineQueryArgs { onlyUserFavorite?: Maybe; } +export interface AnomaliesOverTimeSourceArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; +} export interface AuthenticationsSourceArgs { timerange: TimerangeInput; @@ -2763,6 +2780,8 @@ export namespace SourceResolvers { configuration?: ConfigurationResolver; /** The status of the source */ status?: StatusResolver; + + AnomaliesOverTime?: AnomaliesOverTimeResolver; /** Gets Authentication success and failures based on a timerange */ Authentications?: AuthenticationsResolver; @@ -2834,6 +2853,19 @@ export namespace SourceResolvers { Parent, TContext >; + export type AnomaliesOverTimeResolver< + R = AnomaliesOverTimeData, + Parent = Source, + TContext = SiemContext + > = Resolver; + export interface AnomaliesOverTimeArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; + } + export type AuthenticationsResolver< R = AuthenticationsData, Parent = Source, @@ -3375,6 +3407,81 @@ export namespace IndexFieldResolvers { > = Resolver; } +export namespace AnomaliesOverTimeDataResolvers { + export interface Resolvers { + inspect?: InspectResolver, TypeParent, TContext>; + + anomaliesOverTime?: AnomaliesOverTimeResolver< + MatrixOverTimeHistogramData[], + TypeParent, + TContext + >; + + totalCount?: TotalCountResolver; + } + + export type InspectResolver< + R = Maybe, + Parent = AnomaliesOverTimeData, + TContext = SiemContext + > = Resolver; + export type AnomaliesOverTimeResolver< + R = MatrixOverTimeHistogramData[], + Parent = AnomaliesOverTimeData, + TContext = SiemContext + > = Resolver; + export type TotalCountResolver< + R = number, + Parent = AnomaliesOverTimeData, + TContext = SiemContext + > = Resolver; +} + +export namespace InspectResolvers { + export interface Resolvers { + dsl?: DslResolver; + + response?: ResponseResolver; + } + + export type DslResolver = Resolver< + R, + Parent, + TContext + >; + export type ResponseResolver = Resolver< + R, + Parent, + TContext + >; +} + +export namespace MatrixOverTimeHistogramDataResolvers { + export interface Resolvers { + x?: XResolver; + + y?: YResolver; + + g?: GResolver; + } + + export type XResolver< + R = number, + Parent = MatrixOverTimeHistogramData, + TContext = SiemContext + > = Resolver; + export type YResolver< + R = number, + Parent = MatrixOverTimeHistogramData, + TContext = SiemContext + > = Resolver; + export type GResolver< + R = string, + Parent = MatrixOverTimeHistogramData, + TContext = SiemContext + > = Resolver; +} + export namespace AuthenticationsDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -3820,25 +3927,6 @@ export namespace PageInfoPaginatedResolvers { > = Resolver; } -export namespace InspectResolvers { - export interface Resolvers { - dsl?: DslResolver; - - response?: ResponseResolver; - } - - export type DslResolver = Resolver< - R, - Parent, - TContext - >; - export type ResponseResolver = Resolver< - R, - Parent, - TContext - >; -} - export namespace AuthenticationsOverTimeDataResolvers { export interface Resolvers { inspect?: InspectResolver, TypeParent, TContext>; @@ -3869,32 +3957,6 @@ export namespace AuthenticationsOverTimeDataResolvers { > = Resolver; } -export namespace MatrixOverTimeHistogramDataResolvers { - export interface Resolvers { - x?: XResolver; - - y?: YResolver; - - g?: GResolver; - } - - export type XResolver< - R = number, - Parent = MatrixOverTimeHistogramData, - TContext = SiemContext - > = Resolver; - export type YResolver< - R = number, - Parent = MatrixOverTimeHistogramData, - TContext = SiemContext - > = Resolver; - export type GResolver< - R = string, - Parent = MatrixOverTimeHistogramData, - TContext = SiemContext - > = Resolver; -} - export namespace TimelineDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -8645,6 +8707,9 @@ export type IResolvers = { SourceFields?: SourceFieldsResolvers.Resolvers; SourceStatus?: SourceStatusResolvers.Resolvers; IndexField?: IndexFieldResolvers.Resolvers; + AnomaliesOverTimeData?: AnomaliesOverTimeDataResolvers.Resolvers; + Inspect?: InspectResolvers.Resolvers; + MatrixOverTimeHistogramData?: MatrixOverTimeHistogramDataResolvers.Resolvers; AuthenticationsData?: AuthenticationsDataResolvers.Resolvers; AuthenticationsEdges?: AuthenticationsEdgesResolvers.Resolvers; AuthenticationItem?: AuthenticationItemResolvers.Resolvers; @@ -8657,9 +8722,7 @@ export type IResolvers = { OsEcsFields?: OsEcsFieldsResolvers.Resolvers; CursorType?: CursorTypeResolvers.Resolvers; PageInfoPaginated?: PageInfoPaginatedResolvers.Resolvers; - Inspect?: InspectResolvers.Resolvers; AuthenticationsOverTimeData?: AuthenticationsOverTimeDataResolvers.Resolvers; - MatrixOverTimeHistogramData?: MatrixOverTimeHistogramDataResolvers.Resolvers; TimelineData?: TimelineDataResolvers.Resolvers; TimelineEdges?: TimelineEdgesResolvers.Resolvers; TimelineItem?: TimelineItemResolvers.Resolvers; diff --git a/x-pack/legacy/plugins/siem/server/init_server.ts b/x-pack/legacy/plugins/siem/server/init_server.ts index b040b773c1e53..08c481164d539 100644 --- a/x-pack/legacy/plugins/siem/server/init_server.ts +++ b/x-pack/legacy/plugins/siem/server/init_server.ts @@ -6,6 +6,7 @@ import { IResolvers, makeExecutableSchema } from 'graphql-tools'; import { schemas } from './graphql'; +import { createAnomaliesResolvers } from './graphql/anomalies'; import { createAuthenticationsResolvers } from './graphql/authentications'; import { createScalarToStringArrayValueResolvers } from './graphql/ecs'; import { createEsValueResolvers, createEventsResolvers } from './graphql/events'; @@ -32,6 +33,7 @@ import { createTlsResolvers } from './graphql/tls'; export const initServer = (libs: AppBackendLibs) => { const schema = makeExecutableSchema({ resolvers: [ + createAnomaliesResolvers(libs) as IResolvers, createAuthenticationsResolvers(libs) as IResolvers, createEsValueResolvers() as IResolvers, createEventsResolvers(libs) as IResolvers, diff --git a/x-pack/legacy/plugins/siem/server/lib/anomalies/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/anomalies/elasticsearch_adapter.ts new file mode 100644 index 0000000000000..f4b7aff4854e5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/anomalies/elasticsearch_adapter.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { AnomaliesOverTimeData } from '../../graphql/types'; +import { inspectStringifyObject } from '../../utils/build_query'; +import { FrameworkAdapter, FrameworkRequest, RequestBasicOptions } from '../framework'; +import { TermAggregation } from '../types'; + +import { AnomalyHit, AnomaliesAdapter, AnomaliesActionGroupData } from './types'; +import { buildAnomaliesOverTimeQuery } from './query.anomalies_over_time.dsl'; +import { MatrixOverTimeHistogramData } from '../../../public/graphql/types'; + +export class ElasticsearchAnomaliesAdapter implements AnomaliesAdapter { + constructor(private readonly framework: FrameworkAdapter) {} + + public async getAnomaliesOverTime( + request: FrameworkRequest, + options: RequestBasicOptions + ): Promise { + const dsl = buildAnomaliesOverTimeQuery(options); + + const response = await this.framework.callWithRequest( + request, + 'search', + dsl + ); + + const totalCount = getOr(0, 'hits.total.value', response); + const anomaliesOverTimeBucket = getOr([], 'aggregations.anomalyActionGroup.buckets', response); + + const inspect = { + dsl: [inspectStringifyObject(dsl)], + response: [inspectStringifyObject(response)], + }; + return { + inspect, + anomaliesOverTime: getAnomaliesOverTimeByJobId(anomaliesOverTimeBucket), + totalCount, + }; + } +} + +const getAnomaliesOverTimeByJobId = ( + data: AnomaliesActionGroupData[] +): MatrixOverTimeHistogramData[] => { + let result: MatrixOverTimeHistogramData[] = []; + data.forEach(({ key: group, anomalies }) => { + const anomaliesData = getOr([], 'buckets', anomalies).map( + ({ key, doc_count }: { key: number; doc_count: number }) => ({ + x: key, + y: doc_count, + g: group, + }) + ); + result = [...result, ...anomaliesData]; + }); + + return result; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/anomalies/index.ts b/x-pack/legacy/plugins/siem/server/lib/anomalies/index.ts new file mode 100644 index 0000000000000..7beeea4ad9e4e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/anomalies/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FrameworkRequest, RequestBasicOptions } from '../framework'; +export * from './elasticsearch_adapter'; +import { AnomaliesAdapter } from './types'; +import { AnomaliesOverTimeData } from '../../../public/graphql/types'; + +export class Anomalies { + constructor(private readonly adapter: AnomaliesAdapter) {} + + public async getAnomaliesOverTime( + req: FrameworkRequest, + options: RequestBasicOptions + ): Promise { + return this.adapter.getAnomaliesOverTime(req, options); + } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/anomalies/query.anomalies_over_time.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/anomalies/query.anomalies_over_time.dsl.ts new file mode 100644 index 0000000000000..34a6a6a8f601f --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/anomalies/query.anomalies_over_time.dsl.ts @@ -0,0 +1,75 @@ +/* + * 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, calculateTimeseriesInterval } from '../../utils/build_query'; +import { RequestBasicOptions } from '../framework'; + +export const buildAnomaliesOverTimeQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: RequestBasicOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + timestamp: { + gte: from, + lte: to, + }, + }, + }, + ]; + + const getHistogramAggregation = () => { + const interval = calculateTimeseriesInterval(from, to); + const histogramTimestampField = 'timestamp'; + const dateHistogram = { + date_histogram: { + field: histogramTimestampField, + fixed_interval: `${interval}s`, + }, + }; + const autoDateHistogram = { + auto_date_histogram: { + field: histogramTimestampField, + buckets: 36, + }, + }; + return { + anomalyActionGroup: { + terms: { + field: 'job_id', + order: { + _count: 'desc', + }, + size: 10, + }, + aggs: { + anomalies: interval ? dateHistogram : autoDateHistogram, + }, + }, + }; + }; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggs: getHistogramAggregation(), + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/anomalies/types.ts b/x-pack/legacy/plugins/siem/server/lib/anomalies/types.ts new file mode 100644 index 0000000000000..1e13ad88f8af3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/anomalies/types.ts @@ -0,0 +1,42 @@ +/* + * 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 { AnomaliesOverTimeData } from '../../graphql/types'; +import { FrameworkRequest, RequestBasicOptions } from '../framework'; +import { SearchHit } from '../types'; + +export interface AnomaliesAdapter { + getAnomaliesOverTime( + req: FrameworkRequest, + options: RequestBasicOptions + ): Promise; +} + +export interface AnomalySource { + [field: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any +} + +export interface AnomalyHit extends SearchHit { + sort: string[]; + _source: AnomalySource; + aggregations: { + [agg: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any + }; +} + +interface AnomaliesOverTimeHistogramData { + key_as_string: string; + key: number; + doc_count: number; +} + +export interface AnomaliesActionGroupData { + key: number; + anomalies: { + bucket: AnomaliesOverTimeHistogramData[]; + }; + doc_count: number; +} diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts index e2ff0013e063c..a6b788cb70657 100644 --- a/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts @@ -27,8 +27,7 @@ export const buildAuthenticationsOverTimeQuery = ({ ]; const getHistogramAggregation = () => { - const minIntervalSeconds = 10; - const interval = calculateTimeseriesInterval(from, to, minIntervalSeconds); + const interval = calculateTimeseriesInterval(from, to); const histogramTimestampField = '@timestamp'; const dateHistogram = { date_histogram: { diff --git a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts index c09db5bce5cc2..6e0c5e98206e4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts @@ -6,6 +6,8 @@ import { EnvironmentMode } from 'src/core/server'; import { ServerFacade } from '../../types'; +import { Anomalies } from '../anomalies'; +import { ElasticsearchAnomaliesAdapter } from '../anomalies/elasticsearch_adapter'; import { Authentications } from '../authentications'; import { ElasticsearchAuthenticationAdapter } from '../authentications/elasticsearch_adapter'; import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter'; @@ -43,6 +45,7 @@ export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackend const pinnedEvent = new PinnedEvent({ savedObjects: framework.getSavedObjectsService() }); const domainLibs: AppDomainLibs = { + anomalies: new Anomalies(new ElasticsearchAnomaliesAdapter(framework)), authentications: new Authentications(new ElasticsearchAuthenticationAdapter(framework)), events: new Events(new ElasticsearchEventsAdapter(framework)), fields: new IndexFields(new ElasticsearchIndexFieldAdapter(framework)), diff --git a/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts index e655485638e16..98bd6944c1b51 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts @@ -27,8 +27,7 @@ export const buildEventsOverTimeQuery = ({ ]; const getHistogramAggregation = () => { - const minIntervalSeconds = 10; - const interval = calculateTimeseriesInterval(from, to, minIntervalSeconds); + const interval = calculateTimeseriesInterval(from, to); const histogramTimestampField = '@timestamp'; const dateHistogram = { date_histogram: { diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index a5429ebf76517..13d040b969545 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Anomalies } from './anomalies'; import { Authentications } from './authentications'; import { ConfigurationAdapter } from './configuration'; import { Events } from './events'; @@ -27,6 +28,7 @@ import { SignalAlertParamsRest } from './detection_engine/alerts/types'; export * from './hosts'; export interface AppDomainLibs { + anomalies: Anomalies; authentications: Authentications; events: Events; fields: IndexFields; diff --git a/x-pack/legacy/plugins/siem/server/utils/build_query/calculate_timeseries_interval.ts b/x-pack/legacy/plugins/siem/server/utils/build_query/calculate_timeseries_interval.ts index 3eaaa6c30a4fa..752c686b243ac 100644 --- a/x-pack/legacy/plugins/siem/server/utils/build_query/calculate_timeseries_interval.ts +++ b/x-pack/legacy/plugins/siem/server/utils/build_query/calculate_timeseries_interval.ts @@ -91,8 +91,7 @@ export const calculateAuto = { export const calculateTimeseriesInterval = ( lowerBoundInMsSinceEpoch: number, - upperBoundInMsSinceEpoch: number, - minIntervalSeconds: number + upperBoundInMsSinceEpoch: number ) => { const duration = moment.duration(upperBoundInMsSinceEpoch - lowerBoundInMsSinceEpoch, 'ms'); diff --git a/yarn.lock b/yarn.lock index 68b9a74829281..64d33426d7aa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9719,15 +9719,10 @@ deep-object-diff@^1.1.0: resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== -deepmerge@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.2.0.tgz#58ef463a57c08d376547f8869fdc5bcee957f44e" - integrity sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow== - -deepmerge@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.0.0.tgz#3e3110ca29205f120d7cb064960a39c3d2087c09" - integrity sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww== +deepmerge@3.2.0, deepmerge@^4.0.0, deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== default-compare@^1.0.0: version "1.0.0"