From 56b9f9fa0ee586010d8d25997dd3c7f738796ee6 Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Mon, 28 Oct 2019 17:43:14 +0100 Subject: [PATCH] [SIEM] Add Authentications histogram (#48260) --- .../components/matrix_over_time/index.tsx | 6 +- .../hosts/authentications_over_time/index.tsx | 28 +++ .../authentications_over_time/translation.ts | 20 ++ .../hosts/authentications_over_time/utils.ts | 39 ++++ .../hosts/events_over_time/translation.ts | 7 - .../kpi_hosts/kpi_host_details_mapping.ts | 14 +- .../page/hosts/kpi_hosts/kpi_hosts_mapping.ts | 17 +- .../components/page/hosts/kpi_hosts/types.ts | 13 ++ .../authentications_over_time.gql_query.ts | 37 ++++ .../authentications_over_time/index.tsx | 113 ++++++++++ .../siem/public/graphql/introspection.json | 196 +++++++++++++----- .../plugins/siem/public/graphql/types.ts | 85 +++++++- .../siem/public/pages/hosts/details/index.tsx | 3 + .../authentications_query_tab_body.tsx | 103 +++++---- .../graphql/authentications/resolvers.ts | 15 +- .../graphql/authentications/schema.gql.ts | 11 + .../plugins/siem/server/graphql/types.ts | 137 ++++++++---- ....test.ts => elasticsearch_adapter.test.ts} | 0 .../authentications/elasticsearch_adapter.ts | 63 +++++- .../siem/server/lib/authentications/index.ts | 10 +- .../query.authentications_over_time.dsl.ts | 79 +++++++ .../siem/server/lib/authentications/types.ts | 26 ++- 22 files changed, 861 insertions(+), 161 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/translation.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/utils.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/types.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/authentications_over_time.gql_query.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx rename x-pack/legacy/plugins/siem/server/lib/authentications/{elastic_adapter.test.ts => elasticsearch_adapter.test.ts} (100%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx index 2898541a4a3d1..3523723574be6 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx @@ -31,8 +31,9 @@ export interface MatrixOverTimeBasicProps { } export interface MatrixOverTimeProps extends MatrixOverTimeBasicProps { + customChartData?: ChartSeriesData[]; title: string; - subtitle: string; + subtitle?: string; dataKey: string; } @@ -73,6 +74,7 @@ const getBarchartConfigs = (from: number, to: number, onBrushEnd: UpdateDateRang }); export const MatrixOverTimeHistogram = ({ + customChartData, id, loading, data, @@ -91,7 +93,7 @@ export const MatrixOverTimeHistogram = ({ const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE); const [loadingInitial, setLoadingInitial] = useState(false); - const barChartData: ChartSeriesData[] = [ + const barChartData: ChartSeriesData[] = customChartData || [ { key: dataKey, value: data, diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/index.tsx new file mode 100644 index 0000000000000..ad343933a268c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/index.tsx @@ -0,0 +1,28 @@ +/* + * 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 * as i18n from './translation'; +import { getCustomChartData } from './utils'; +import { MatrixOverTimeHistogram, MatrixOverTimeBasicProps } from '../../../matrix_over_time'; + +export const AuthenticationsOverTimeHistogram = (props: MatrixOverTimeBasicProps) => { + const dataKey = 'authenticationsOverTime'; + const { data, ...matrixOverTimeProps } = props; + + const customChartData = getCustomChartData(data); + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/translation.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/translation.ts new file mode 100644 index 0000000000000..c9a21bd348caa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/translation.ts @@ -0,0 +1,20 @@ +/* + * 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 AUTHENTICATIONS_COUNT = i18n.translate( + 'xpack.siem.authenticationsOverTime.authenticationCountTitle', + { + defaultMessage: 'Authentications count', + } +); + +export const UNIT = (totalCount: number) => + i18n.translate('xpack.siem.authenticationsOverTime.unit', { + values: { totalCount }, + defaultMessage: `{totalCount, plural, =1 {authentication} other {authentications}}`, + }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/utils.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/utils.ts new file mode 100644 index 0000000000000..3cc89eeff6540 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/utils.ts @@ -0,0 +1,39 @@ +/* + * 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 { groupBy, map, toPairs } from 'lodash/fp'; + +import { ChartSeriesData } from '../../../charts/common'; +import { MatrixOverTimeHistogramData } from '../../../../graphql/types'; +import { KpiHostsChartColors } from '../kpi_hosts/types'; + +const formatToChartDataItem = ([key, value]: [ + string, + MatrixOverTimeHistogramData[] +]): ChartSeriesData => ({ + key, + value, +}); + +const addCustomColors = (item: ChartSeriesData) => { + if (item.key === 'authentication_success') { + item.color = KpiHostsChartColors.authSuccess; + } + + if (item.key === 'authentication_failure') { + item.color = KpiHostsChartColors.authFailure; + } + + return item; +}; + +export const getCustomChartData = (data: MatrixOverTimeHistogramData[]): ChartSeriesData[] => { + const dataGroupedByEvent = groupBy('g', data); + const dataGroupedEntries = toPairs(dataGroupedByEvent); + const formattedChartData = map(formatToChartDataItem, dataGroupedEntries); + + return map(addCustomColors, formattedChartData); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts index 5f68a1a1cae7d..edc9f97193840 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts @@ -13,13 +13,6 @@ export const EVENT_COUNT_FREQUENCY_BY_ACTION = i18n.translate( } ); -export const LOADING_EVENTS_OVER_TIME = i18n.translate( - 'xpack.siem.eventsOverTime.loadingEventsOverTimeTitle', - { - defaultMessage: 'Loading events histogram', - } -); - export const SHOWING = i18n.translate('xpack.siem.eventsOverTime.showing', { defaultMessage: 'Showing', }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts index dbaf35c5b0636..59f8e55c46106 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts @@ -6,11 +6,7 @@ import * as i18n from './translations'; import { StatItems } from '../../../stat_items'; - -const euiColorVis0 = '#00B3A4'; -const euiColorVis2 = '#DB1374'; -const euiColorVis3 = '#490092'; -const euiColorVis9 = '#920000'; +import { KpiHostsChartColors } from './types'; export const kpiHostDetailsMapping: Readonly = [ { @@ -22,7 +18,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.SUCCESS_CHART_LABEL, description: i18n.SUCCESS_UNIT_LABEL, value: null, - color: euiColorVis0, + color: KpiHostsChartColors.authSuccess, icon: 'check', }, { @@ -30,7 +26,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.FAIL_CHART_LABEL, description: i18n.FAIL_UNIT_LABEL, value: null, - color: euiColorVis9, + color: KpiHostsChartColors.authFailure, icon: 'cross', }, ], @@ -48,7 +44,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.SOURCE_CHART_LABEL, description: i18n.SOURCE_UNIT_LABEL, value: null, - color: euiColorVis2, + color: KpiHostsChartColors.uniqueSourceIps, icon: 'visMapCoordinate', }, { @@ -56,7 +52,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.DESTINATION_CHART_LABEL, description: i18n.DESTINATION_UNIT_LABEL, value: null, - color: euiColorVis3, + color: KpiHostsChartColors.uniqueDestinationIps, icon: 'visMapCoordinate', }, ], diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts index ca91b49b27185..e2d6348d05840 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts @@ -6,12 +6,7 @@ import * as i18n from './translations'; import { StatItems } from '../../../stat_items'; - -const euiColorVis0 = '#00B3A4'; -const euiColorVis1 = '#3185FC'; -const euiColorVis2 = '#DB1374'; -const euiColorVis3 = '#490092'; -const euiColorVis9 = '#920000'; +import { KpiHostsChartColors } from './types'; export const kpiHostsMapping: Readonly = [ { @@ -21,7 +16,7 @@ export const kpiHostsMapping: Readonly = [ { key: 'hosts', value: null, - color: euiColorVis1, + color: KpiHostsChartColors.hosts, icon: 'storage', }, ], @@ -38,7 +33,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.SUCCESS_CHART_LABEL, description: i18n.SUCCESS_UNIT_LABEL, value: null, - color: euiColorVis0, + color: KpiHostsChartColors.authSuccess, icon: 'check', }, { @@ -46,7 +41,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.FAIL_CHART_LABEL, description: i18n.FAIL_UNIT_LABEL, value: null, - color: euiColorVis9, + color: KpiHostsChartColors.authFailure, icon: 'cross', }, ], @@ -64,7 +59,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.SOURCE_CHART_LABEL, description: i18n.SOURCE_UNIT_LABEL, value: null, - color: euiColorVis2, + color: KpiHostsChartColors.uniqueSourceIps, icon: 'visMapCoordinate', }, { @@ -72,7 +67,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.DESTINATION_CHART_LABEL, description: i18n.DESTINATION_UNIT_LABEL, value: null, - color: euiColorVis3, + color: KpiHostsChartColors.uniqueDestinationIps, icon: 'visMapCoordinate', }, ], diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/types.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/types.ts new file mode 100644 index 0000000000000..f2f50d72952ac --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/types.ts @@ -0,0 +1,13 @@ +/* + * 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 enum KpiHostsChartColors { + authSuccess = '#00B3A4', + authFailure = '#920000', + uniqueSourceIps = '#DB1374', + uniqueDestinationIps = '#490092', + hosts = '#3185FC', +} diff --git a/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/authentications_over_time.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/authentications_over_time.gql_query.ts new file mode 100644 index 0000000000000..dc05f4f8232d5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/authentications_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 AuthenticationsOverTimeGqlQuery = gql` + query GetAuthenticationsOverTimeQuery( + $sourceId: ID! + $timerange: TimerangeInput! + $defaultIndex: [String!]! + $filterQuery: String + $inspect: Boolean! + ) { + source(id: $sourceId) { + id + AuthenticationsOverTime( + timerange: $timerange + filterQuery: $filterQuery + defaultIndex: $defaultIndex + ) { + authenticationsOverTime { + x + y + g + } + totalCount + inspect @include(if: $inspect) { + dsl + response + } + } + } + } +`; diff --git a/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx new file mode 100644 index 0000000000000..1d6d96869b6a9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx @@ -0,0 +1,113 @@ +/* + * 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 { inputsModel, State, inputsSelectors, hostsModel } from '../../../store'; +import { createFilter, getDefaultFetchPolicy } from '../../helpers'; +import { QueryTemplate, QueryTemplateProps } from '../../query_template'; + +import { AuthenticationsOverTimeGqlQuery } from './authentications_over_time.gql_query'; +import { + GetAuthenticationsOverTimeQuery, + MatrixOverTimeHistogramData, +} from '../../../graphql/types'; + +const ID = 'authenticationsOverTimeQuery'; + +export interface AuthenticationsArgs { + endDate: number; + authenticationsOverTime: MatrixOverTimeHistogramData[]; + id: string; + inspect: inputsModel.InspectQuery; + loading: boolean; + refetch: inputsModel.Refetch; + startDate: number; + totalCount: number; +} + +export interface OwnProps extends QueryTemplateProps { + children?: (args: AuthenticationsArgs) => React.ReactNode; + type: hostsModel.HostsType; +} + +export interface AuthenticationsOverTimeComponentReduxProps { + isInspected: boolean; +} + +type AuthenticationsOverTimeProps = OwnProps & AuthenticationsOverTimeComponentReduxProps; + +class AuthenticationsOverTimeComponentQuery extends QueryTemplate< + AuthenticationsOverTimeProps, + GetAuthenticationsOverTimeQuery.Query, + GetAuthenticationsOverTimeQuery.Variables +> { + public render() { + const { + children, + filterQuery, + id = ID, + isInspected, + sourceId, + startDate, + endDate, + } = this.props; + return ( + + query={AuthenticationsOverTimeGqlQuery} + fetchPolicy={getDefaultFetchPolicy()} + notifyOnNetworkStatusChange + variables={{ + filterQuery: createFilter(filterQuery), + sourceId, + timerange: { + interval: '12h', + from: startDate!, + to: endDate!, + }, + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + inspect: isInspected, + }} + > + {({ data, loading, refetch }) => { + const source = getOr({}, `source.AuthenticationsOverTime`, data); + const authenticationsOverTime = getOr([], `authenticationsOverTime`, source); + const totalCount = getOr(-1, 'totalCount', source); + return children!({ + endDate: endDate!, + authenticationsOverTime, + id, + inspect: getOr(null, 'inspect', source), + loading, + refetch, + startDate: startDate!, + totalCount, + }); + }} + + ); + } +} + +const makeMapStateToProps = () => { + const getQuery = inputsSelectors.globalQueryByIdSelector(); + const mapStateToProps = (state: State, { type, id = ID }: OwnProps) => { + const { isInspected } = getQuery(state, id); + return { + isInspected, + }; + }; + return mapStateToProps; +}; + +export const AuthenticationsOverTimeQuery = connect(makeMapStateToProps)( + AuthenticationsOverTimeComponentQuery +); diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json index 0348b283ed318..488a6310db3f2 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json @@ -727,6 +727,53 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "AuthenticationsOverTime", + "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": "AuthenticationsOverTimeData", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "Timeline", "description": "", @@ -3117,6 +3164,108 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "AuthenticationsOverTimeData", + "description": "", + "fields": [ + { + "name": "inspect", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "authenticationsOverTime", + "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": "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", @@ -5836,53 +5985,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": "HostsSortField", diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts index 50fb6bd9e8a8a..dbb62d73e5994 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts @@ -401,6 +401,8 @@ export interface Source { /** Gets Authentication success and failures based on a timerange */ Authentications: AuthenticationsData; + AuthenticationsOverTime: AuthenticationsOverTimeData; + Timeline: TimelineData; TimelineDetails: TimelineDetailsData; @@ -634,6 +636,22 @@ export interface Inspect { response: string[]; } +export interface AuthenticationsOverTimeData { + inspect?: Maybe; + + authenticationsOverTime: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + +export interface MatrixOverTimeHistogramData { + x: number; + + y: number; + + g: string; +} + export interface TimelineData { edges: TimelineEdges[]; @@ -1218,14 +1236,6 @@ export interface EventsOverTimeData { totalCount: number; } -export interface MatrixOverTimeHistogramData { - x: number; - - y: number; - - g: string; -} - export interface HostsData { edges: HostsEdges[]; @@ -1978,6 +1988,13 @@ export interface AuthenticationsSourceArgs { defaultIndex: string[]; } +export interface AuthenticationsOverTimeSourceArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; +} export interface TimelineSourceArgs { pagination: PaginationInput; @@ -2241,6 +2258,58 @@ export interface DeleteTimelineMutationArgs { // Documents // ==================================================== +export namespace GetAuthenticationsOverTimeQuery { + 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; + + AuthenticationsOverTime: AuthenticationsOverTime; + }; + + export type AuthenticationsOverTime = { + __typename?: 'AuthenticationsOverTimeData'; + + authenticationsOverTime: _AuthenticationsOverTime[]; + + totalCount: number; + + inspect: Maybe; + }; + + export type _AuthenticationsOverTime = { + __typename?: 'MatrixOverTimeHistogramData'; + + x: number; + + y: number; + + g: string; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + export namespace GetAuthenticationsQuery { export type Variables = { sourceId: string; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx index aa81481bedbe7..30744b3e24c4b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx @@ -179,11 +179,14 @@ const HostDetailsComponent = React.memo( )} + + + {}, }: HostsComponentsQueryProps) => ( - - {({ - authentications, - totalCount, - loading, - pageInfo, - loadPage, - id, - inspect, - isInspected, - refetch, - }) => ( - - )} - + <> + + {({ authenticationsOverTime, loading, id, inspect, refetch, totalCount }) => ( + + )} + + + + {({ + authentications, + totalCount, + loading, + pageInfo, + loadPage, + id, + inspect, + isInspected, + refetch, + }) => ( + + )} + + ); AuthenticationsQueryTabBody.displayName = 'AuthenticationsQueryTabBody'; diff --git a/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts index b66ccd9a111b7..aaa66215e98f4 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts @@ -7,7 +7,7 @@ import { SourceResolvers } from '../../graphql/types'; import { Authentications } from '../../lib/authentications'; import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; -import { createOptionsPaginated } from '../../utils/build_query/create_options'; +import { createOptionsPaginated, createOptions } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; type QueryAuthenticationsResolver = ChildResolverOf< @@ -15,6 +15,11 @@ type QueryAuthenticationsResolver = ChildResolverOf< QuerySourceResolver >; +type QueryAuthenticationsOverTimeResolver = ChildResolverOf< + AppResolverOf, + QuerySourceResolver +>; + export interface AuthenticationsResolversDeps { authentications: Authentications; } @@ -24,6 +29,7 @@ export const createAuthenticationsResolvers = ( ): { Source: { Authentications: QueryAuthenticationsResolver; + AuthenticationsOverTime: QueryAuthenticationsOverTimeResolver; }; } => ({ Source: { @@ -31,5 +37,12 @@ export const createAuthenticationsResolvers = ( const options = createOptionsPaginated(source, args, info); return libs.authentications.getAuthentications(req, options); }, + async AuthenticationsOverTime(source, args, { req }, info) { + const options = { + ...createOptions(source, args, info), + defaultIndex: args.defaultIndex, + }; + return libs.authentications.getAuthenticationsOverTime(req, options); + }, }, }); diff --git a/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts index 20935ce9ed03f..5a65ef5d678ce 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts @@ -34,6 +34,12 @@ export const authenticationsSchema = gql` inspect: Inspect } + type AuthenticationsOverTimeData { + inspect: Inspect + authenticationsOverTime: [MatrixOverTimeHistogramData!]! + totalCount: Float! + } + extend type Source { "Gets Authentication success and failures based on a timerange" Authentications( @@ -42,5 +48,10 @@ export const authenticationsSchema = gql` filterQuery: String defaultIndex: [String!]! ): AuthenticationsData! + AuthenticationsOverTime( + timerange: TimerangeInput! + filterQuery: String + defaultIndex: [String!]! + ): AuthenticationsOverTimeData! } `; diff --git a/x-pack/legacy/plugins/siem/server/graphql/types.ts b/x-pack/legacy/plugins/siem/server/graphql/types.ts index 776efeebd6ddb..6de11e0652871 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/types.ts @@ -403,6 +403,8 @@ export interface Source { /** Gets Authentication success and failures based on a timerange */ Authentications: AuthenticationsData; + AuthenticationsOverTime: AuthenticationsOverTimeData; + Timeline: TimelineData; TimelineDetails: TimelineDetailsData; @@ -636,6 +638,22 @@ export interface Inspect { response: string[]; } +export interface AuthenticationsOverTimeData { + inspect?: Maybe; + + authenticationsOverTime: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + +export interface MatrixOverTimeHistogramData { + x: number; + + y: number; + + g: string; +} + export interface TimelineData { edges: TimelineEdges[]; @@ -1220,14 +1238,6 @@ export interface EventsOverTimeData { totalCount: number; } -export interface MatrixOverTimeHistogramData { - x: number; - - y: number; - - g: string; -} - export interface HostsData { edges: HostsEdges[]; @@ -1980,6 +1990,13 @@ export interface AuthenticationsSourceArgs { defaultIndex: string[]; } +export interface AuthenticationsOverTimeSourceArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; +} export interface TimelineSourceArgs { pagination: PaginationInput; @@ -2586,6 +2603,12 @@ export namespace SourceResolvers { /** Gets Authentication success and failures based on a timerange */ Authentications?: AuthenticationsResolver; + AuthenticationsOverTime?: AuthenticationsOverTimeResolver< + AuthenticationsOverTimeData, + TypeParent, + TContext + >; + Timeline?: TimelineResolver; TimelineDetails?: TimelineDetailsResolver; @@ -2661,6 +2684,19 @@ export namespace SourceResolvers { defaultIndex: string[]; } + export type AuthenticationsOverTimeResolver< + R = AuthenticationsOverTimeData, + Parent = Source, + TContext = SiemContext + > = Resolver; + export interface AuthenticationsOverTimeArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; + } + export type TimelineResolver< R = TimelineData, Parent = Source, @@ -3617,6 +3653,62 @@ export namespace InspectResolvers { >; } +export namespace AuthenticationsOverTimeDataResolvers { + export interface Resolvers { + inspect?: InspectResolver, TypeParent, TContext>; + + authenticationsOverTime?: AuthenticationsOverTimeResolver< + MatrixOverTimeHistogramData[], + TypeParent, + TContext + >; + + totalCount?: TotalCountResolver; + } + + export type InspectResolver< + R = Maybe, + Parent = AuthenticationsOverTimeData, + TContext = SiemContext + > = Resolver; + export type AuthenticationsOverTimeResolver< + R = MatrixOverTimeHistogramData[], + Parent = AuthenticationsOverTimeData, + TContext = SiemContext + > = Resolver; + export type TotalCountResolver< + R = number, + Parent = AuthenticationsOverTimeData, + TContext = SiemContext + > = 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; @@ -5567,32 +5659,6 @@ export namespace EventsOverTimeDataResolvers { > = 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 HostsDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -8110,6 +8176,8 @@ export type IResolvers = { CursorType?: CursorTypeResolvers.Resolvers; PageInfoPaginated?: PageInfoPaginatedResolvers.Resolvers; Inspect?: InspectResolvers.Resolvers; + AuthenticationsOverTimeData?: AuthenticationsOverTimeDataResolvers.Resolvers; + MatrixOverTimeHistogramData?: MatrixOverTimeHistogramDataResolvers.Resolvers; TimelineData?: TimelineDataResolvers.Resolvers; TimelineEdges?: TimelineEdgesResolvers.Resolvers; TimelineItem?: TimelineItemResolvers.Resolvers; @@ -8161,7 +8229,6 @@ export type IResolvers = { DetailItem?: DetailItemResolvers.Resolvers; LastEventTimeData?: LastEventTimeDataResolvers.Resolvers; EventsOverTimeData?: EventsOverTimeDataResolvers.Resolvers; - MatrixOverTimeHistogramData?: MatrixOverTimeHistogramDataResolvers.Resolvers; HostsData?: HostsDataResolvers.Resolvers; HostsEdges?: HostsEdgesResolvers.Resolvers; HostItem?: HostItemResolvers.Resolvers; diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/authentications/elastic_adapter.test.ts rename to x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts index 79f13ce4461e5..9a9e30bf01c04 100644 --- a/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts @@ -6,20 +6,50 @@ import { getOr } from 'lodash/fp'; -import { AuthenticationsData, AuthenticationsEdges } from '../../graphql/types'; +import { + AuthenticationsData, + AuthenticationsEdges, + AuthenticationsOverTimeData, + MatrixOverTimeHistogramData, +} from '../../graphql/types'; import { mergeFieldsWithHit, inspectStringifyObject } from '../../utils/build_query'; -import { FrameworkAdapter, FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { + FrameworkAdapter, + FrameworkRequest, + RequestOptionsPaginated, + RequestBasicOptions, +} from '../framework'; import { TermAggregation } from '../types'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; import { auditdFieldsMap, buildQuery } from './query.dsl'; +import { buildAuthenticationsOverTimeQuery } from './query.authentications_over_time.dsl'; import { AuthenticationBucket, AuthenticationData, AuthenticationHit, AuthenticationsAdapter, + AuthenticationsActionGroupData, } from './types'; +const getAuthenticationsOverTimeByAuthenticationResult = ( + data: AuthenticationsActionGroupData[] +): MatrixOverTimeHistogramData[] => { + let result: MatrixOverTimeHistogramData[] = []; + data.forEach(({ key: group, events }) => { + const eventsData = getOr([], 'buckets', events).map( + ({ key, doc_count }: { key: number; doc_count: number }) => ({ + x: key, + y: doc_count, + g: group, + }) + ); + result = [...result, ...eventsData]; + }); + + return result; +}; + export class ElasticsearchAuthenticationAdapter implements AuthenticationsAdapter { constructor(private readonly framework: FrameworkAdapter) {} @@ -79,6 +109,35 @@ export class ElasticsearchAuthenticationAdapter implements AuthenticationsAdapte }, }; } + + public async getAuthenticationsOverTime( + request: FrameworkRequest, + options: RequestBasicOptions + ): Promise { + const dsl = buildAuthenticationsOverTimeQuery(options); + const response = await this.framework.callWithRequest( + request, + 'search', + dsl + ); + const totalCount = getOr(0, 'hits.total.value', response); + const authenticationsOverTimeBucket = getOr( + [], + 'aggregations.eventActionGroup.buckets', + response + ); + const inspect = { + dsl: [inspectStringifyObject(dsl)], + response: [inspectStringifyObject(response)], + }; + return { + inspect, + authenticationsOverTime: getAuthenticationsOverTimeByAuthenticationResult( + authenticationsOverTimeBucket + ), + totalCount, + }; + } } export const formatAuthenticationData = ( diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts index c1b93818943db..b369c358e1619 100644 --- a/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts @@ -5,9 +5,10 @@ */ import { AuthenticationsData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated, RequestBasicOptions } from '../framework'; import { AuthenticationsAdapter } from './types'; +import { AuthenticationsOverTimeData } from '../../../public/graphql/types'; export class Authentications { constructor(private readonly adapter: AuthenticationsAdapter) {} @@ -18,4 +19,11 @@ export class Authentications { ): Promise { return this.adapter.getAuthentications(req, options); } + + public async getAuthenticationsOverTime( + req: FrameworkRequest, + options: RequestBasicOptions + ): Promise { + return this.adapter.getAuthenticationsOverTime(req, options); + } } 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 new file mode 100644 index 0000000000000..e2ff0013e063c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts @@ -0,0 +1,79 @@ +/* + * 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 buildAuthenticationsOverTimeQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, + sourceConfiguration: { + fields: { timestamp }, + }, +}: RequestBasicOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + [timestamp]: { + gte: from, + lte: to, + }, + }, + }, + ]; + + const getHistogramAggregation = () => { + const minIntervalSeconds = 10; + const interval = calculateTimeseriesInterval(from, to, minIntervalSeconds); + const histogramTimestampField = '@timestamp'; + const dateHistogram = { + date_histogram: { + field: histogramTimestampField, + fixed_interval: `${interval}s`, + }, + }; + const autoDateHistogram = { + auto_date_histogram: { + field: histogramTimestampField, + buckets: 36, + }, + }; + return { + eventActionGroup: { + terms: { + field: 'event.type', + include: ['authentication_success', 'authentication_failure'], + order: { + _count: 'desc', + }, + size: 2, + }, + aggs: { + events: interval ? dateHistogram : autoDateHistogram, + }, + }, + }; + }; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: getHistogramAggregation(), + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts index 2d2c7ba547c09..6e83a2bdba956 100644 --- a/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AuthenticationsData, LastSourceHost } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { + AuthenticationsData, + AuthenticationsOverTimeData, + LastSourceHost, +} from '../../graphql/types'; +import { FrameworkRequest, RequestOptionsPaginated, RequestBasicOptions } from '../framework'; import { Hit, SearchHit, TotalHit } from '../types'; export interface AuthenticationsAdapter { @@ -13,6 +17,10 @@ export interface AuthenticationsAdapter { req: FrameworkRequest, options: RequestOptionsPaginated ): Promise; + getAuthenticationsOverTime( + req: FrameworkRequest, + options: RequestBasicOptions + ): Promise; } type StringOrNumber = string | number; @@ -60,3 +68,17 @@ export interface AuthenticationData extends SearchHit { }; }; } + +interface AuthenticationsOverTimeHistogramData { + key_as_string: string; + key: number; + doc_count: number; +} + +export interface AuthenticationsActionGroupData { + key: number; + events: { + bucket: AuthenticationsOverTimeHistogramData[]; + }; + doc_count: number; +}