From 895747ad38790fd41f32558ea8074240218c935a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 15 Sep 2021 19:17:33 +0200 Subject: [PATCH] [Uptime] Use async search api for certificates (#111731) --- .../public/hooks/use_es_search.ts | 4 +- x-pack/plugins/observability/public/index.ts | 1 + .../uptime/common/constants/rest_api.ts | 1 - .../common/requests/get_certs_request_body.ts | 183 ++++++++++++++++++ .../uptime/common/runtime_types/certs.ts | 8 +- .../plugins/uptime/common/utils/es_search.ts | 12 ++ .../certificates_list.test.tsx.snap | 105 ---------- .../components/certificates/cert_search.tsx | 15 +- .../certificates/certificate_title.tsx | 4 +- .../certificates/certificates_list.test.tsx | 59 +++++- .../certificates/certificates_list.tsx | 13 +- .../certificates/use_cert_search.ts | 60 ++++++ .../public/lib/__mocks__/uptime_store.mock.ts | 5 +- .../uptime/public/pages/certificates.tsx | 28 +-- .../uptime/public/state/api/certificates.ts | 14 -- .../public/state/certificates/certificates.ts | 31 +-- .../uptime/public/state/effects/index.ts | 2 - .../public/state/selectors/index.test.ts | 5 +- .../uptime/server/lib/alerts/tls.test.ts | 9 +- .../plugins/uptime/server/lib/alerts/tls.ts | 9 +- .../uptime/server/lib/alerts/tls_legacy.ts | 10 +- x-pack/plugins/uptime/server/lib/lib.ts | 8 +- .../server/lib/requests/get_certs.test.ts | 2 +- .../uptime/server/lib/requests/get_certs.ts | 177 +++-------------- .../server/lib/requests/get_ping_histogram.ts | 2 +- .../uptime/server/rest_api/certs/certs.ts | 54 ------ .../plugins/uptime/server/rest_api/index.ts | 2 - .../api_integration/apis/uptime/rest/certs.ts | 43 +++- 28 files changed, 432 insertions(+), 434 deletions(-) create mode 100644 x-pack/plugins/uptime/common/requests/get_certs_request_body.ts create mode 100644 x-pack/plugins/uptime/common/utils/es_search.ts delete mode 100644 x-pack/plugins/uptime/public/components/certificates/__snapshots__/certificates_list.test.tsx.snap create mode 100644 x-pack/plugins/uptime/public/components/certificates/use_cert_search.ts delete mode 100644 x-pack/plugins/uptime/public/state/api/certificates.ts delete mode 100644 x-pack/plugins/uptime/server/rest_api/certs/certs.ts diff --git a/x-pack/plugins/observability/public/hooks/use_es_search.ts b/x-pack/plugins/observability/public/hooks/use_es_search.ts index 27c4081a9977..70ffdbba22c5 100644 --- a/x-pack/plugins/observability/public/hooks/use_es_search.ts +++ b/x-pack/plugins/observability/public/hooks/use_es_search.ts @@ -12,7 +12,7 @@ import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { isCompleteResponse } from '../../../../../src/plugins/data/common'; import { useFetcher } from './use_fetcher'; -export const useEsSearch = ( +export const useEsSearch = ( params: TParams, fnDeps: any[] ) => { @@ -43,7 +43,7 @@ export const useEsSearch = ( const { rawResponse } = response as any; - return { data: rawResponse as ESSearchResponse, loading }; + return { data: rawResponse as ESSearchResponse, loading }; }; export function createEsParams(params: T): T { diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 380190aa7f22..710bed3adb89 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -60,6 +60,7 @@ export { export const LazyAlertsFlyout = lazy(() => import('./pages/alerts/alerts_flyout')); export { useFetcher, FETCH_STATUS } from './hooks/use_fetcher'; +export { useEsSearch, createEsParams } from './hooks/use_es_search'; export * from './typings'; diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts index b49ccc3222a0..655f9629b848 100644 --- a/x-pack/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/plugins/uptime/common/constants/rest_api.ts @@ -6,7 +6,6 @@ */ export enum API_URLS { - CERTS = '/api/uptime/certs', INDEX_PATTERN = `/api/uptime/index_pattern`, INDEX_STATUS = '/api/uptime/index_status', MONITOR_LIST = `/api/uptime/monitor/list`, diff --git a/x-pack/plugins/uptime/common/requests/get_certs_request_body.ts b/x-pack/plugins/uptime/common/requests/get_certs_request_body.ts new file mode 100644 index 000000000000..5a729c7e3b96 --- /dev/null +++ b/x-pack/plugins/uptime/common/requests/get_certs_request_body.ts @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { estypes } from '@elastic/elasticsearch'; +import { CertResult, GetCertsParams, Ping } from '../runtime_types'; +import { createEsQuery } from '../utils/es_search'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { CertificatesResults } from '../../server/lib/requests/get_certs'; +import { asMutableArray } from '../utils/as_mutable_array'; + +enum SortFields { + 'issuer' = 'tls.server.x509.issuer.common_name', + 'not_after' = 'tls.server.x509.not_after', + 'not_before' = 'tls.server.x509.not_before', + 'common_name' = 'tls.server.x509.subject.common_name', +} + +export const DEFAULT_SORT = 'not_after'; +export const DEFAULT_DIRECTION = 'asc'; +export const DEFAULT_SIZE = 20; +export const DEFAULT_FROM = 'now-5m'; +export const DEFAULT_TO = 'now'; + +export const getCertsRequestBody = ({ + pageIndex, + search, + notValidBefore, + notValidAfter, + size = DEFAULT_SIZE, + to = DEFAULT_TO, + from = DEFAULT_FROM, + sortBy = DEFAULT_SORT, + direction = DEFAULT_DIRECTION, +}: GetCertsParams) => { + const sort = SortFields[sortBy as keyof typeof SortFields]; + + const searchRequest = createEsQuery({ + body: { + from: pageIndex * size, + size, + sort: asMutableArray([ + { + [sort]: { + order: direction, + }, + }, + ]), + query: { + bool: { + ...(search + ? { + minimum_should_match: 1, + should: [ + { + multi_match: { + query: escape(search), + type: 'phrase_prefix' as const, + fields: [ + 'monitor.id.text', + 'monitor.name.text', + 'url.full.text', + 'tls.server.x509.subject.common_name.text', + 'tls.server.x509.issuer.common_name.text', + ], + }, + }, + ], + } + : {}), + filter: [ + { + exists: { + field: 'tls.server.hash.sha256', + }, + }, + { + range: { + 'monitor.timespan': { + gte: from, + lte: to, + }, + }, + }, + ...(notValidBefore + ? [ + { + range: { + 'tls.certificate_not_valid_before': { + lte: notValidBefore, + }, + }, + }, + ] + : []), + ...(notValidAfter + ? [ + { + range: { + 'tls.certificate_not_valid_after': { + lte: notValidAfter, + }, + }, + }, + ] + : []), + ] as estypes.QueryDslQueryContainer, + }, + }, + _source: [ + 'monitor.id', + 'monitor.name', + 'tls.server.x509.issuer.common_name', + 'tls.server.x509.subject.common_name', + 'tls.server.hash.sha1', + 'tls.server.hash.sha256', + 'tls.server.x509.not_after', + 'tls.server.x509.not_before', + ], + collapse: { + field: 'tls.server.hash.sha256', + inner_hits: { + _source: { + includes: ['monitor.id', 'monitor.name', 'url.full'], + }, + collapse: { + field: 'monitor.id', + }, + name: 'monitors', + sort: [{ 'monitor.id': 'asc' as const }], + }, + }, + aggs: { + total: { + cardinality: { + field: 'tls.server.hash.sha256', + }, + }, + }, + }, + }); + + return searchRequest.body; +}; + +export const processCertsResult = (result: CertificatesResults): CertResult => { + const certs = result.hits?.hits?.map((hit) => { + const ping = hit._source; + const server = ping.tls?.server!; + + const notAfter = server?.x509?.not_after; + const notBefore = server?.x509?.not_before; + const issuer = server?.x509?.issuer?.common_name; + const commonName = server?.x509?.subject?.common_name; + const sha1 = server?.hash?.sha1; + const sha256 = server?.hash?.sha256; + + const monitors = hit.inner_hits!.monitors.hits.hits.map((monitor) => { + const monitorPing = monitor._source as Ping; + + return { + name: monitorPing?.monitor.name, + id: monitorPing?.monitor.id, + url: monitorPing?.url?.full, + }; + }); + + return { + monitors, + issuer, + sha1, + sha256: sha256 as string, + not_after: notAfter, + not_before: notBefore, + common_name: commonName, + }; + }); + const total = result.aggregations?.total?.value ?? 0; + return { certs, total }; +}; diff --git a/x-pack/plugins/uptime/common/runtime_types/certs.ts b/x-pack/plugins/uptime/common/runtime_types/certs.ts index 3a5e88529250..c1a411effb90 100644 --- a/x-pack/plugins/uptime/common/runtime_types/certs.ts +++ b/x-pack/plugins/uptime/common/runtime_types/certs.ts @@ -9,10 +9,7 @@ import * as t from 'io-ts'; export const GetCertsParamsType = t.intersection([ t.type({ - index: t.number, - size: t.number, - sortBy: t.string, - direction: t.string, + pageIndex: t.number, }), t.partial({ search: t.string, @@ -20,6 +17,9 @@ export const GetCertsParamsType = t.intersection([ notValidAfter: t.string, from: t.string, to: t.string, + sortBy: t.string, + direction: t.string, + size: t.number, }), ]); diff --git a/x-pack/plugins/uptime/common/utils/es_search.ts b/x-pack/plugins/uptime/common/utils/es_search.ts new file mode 100644 index 000000000000..ba72d09a4e8e --- /dev/null +++ b/x-pack/plugins/uptime/common/utils/es_search.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { estypes } from '@elastic/elasticsearch'; + +export function createEsQuery(params: T): T { + return params; +} diff --git a/x-pack/plugins/uptime/public/components/certificates/__snapshots__/certificates_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/certificates/__snapshots__/certificates_list.test.tsx.snap deleted file mode 100644 index dc808ffcdd22..000000000000 --- a/x-pack/plugins/uptime/public/components/certificates/__snapshots__/certificates_list.test.tsx.snap +++ /dev/null @@ -1,105 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CertificateList shallow renders expected elements for valid props 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx b/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx index 08650edbaac2..391d38921fee 100644 --- a/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx +++ b/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import React, { ChangeEvent } from 'react'; +import React, { ChangeEvent, useState } from 'react'; import { EuiFieldSearch } from '@elastic/eui'; import styled from 'styled-components'; +import useDebounce from 'react-use/lib/useDebounce'; import * as labels from './translations'; const WrapFieldSearch = styled('div')` @@ -19,10 +20,20 @@ interface Props { } export const CertificateSearch: React.FC = ({ setSearch }) => { + const [debouncedValue, setDebouncedValue] = useState(''); + const onChange = (e: ChangeEvent) => { - setSearch(e.target.value); + setDebouncedValue(e.target.value); }; + useDebounce( + () => { + setSearch(debouncedValue); + }, + 350, + [debouncedValue] + ); + return ( { - const { data: certificates } = useSelector(certificatesSelector); + const total = useSelector(certificatesSelector); return ( {certificates?.total ?? 0}, + total: {total ?? 0}, }} /> ); diff --git a/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx b/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx index ec6a5d91a67c..ff54267b8b8b 100644 --- a/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx @@ -6,11 +6,11 @@ */ import React from 'react'; -import { shallowWithRouter } from '../../lib'; import { CertificateList, CertSort } from './certificates_list'; +import { render } from '../../lib/helper/rtl_helpers'; describe('CertificateList', () => { - it('shallow renders expected elements for valid props', () => { + it('render empty state', () => { const page = { index: 0, size: 10, @@ -20,8 +20,59 @@ describe('CertificateList', () => { direction: 'asc', }; + const { getByText } = render( + + ); + expect( - shallowWithRouter() - ).toMatchSnapshot(); + getByText('No Certificates found. Note: Certificates are only visible for Heartbeat 7.8+') + ).toBeInTheDocument(); + }); + + it('renders certificates list', () => { + const page = { + index: 0, + size: 10, + }; + const sort: CertSort = { + field: 'not_after', + direction: 'asc', + }; + + const { getByText } = render( + + ); + + expect(getByText('BadSSL Expired')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx b/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx index f01cdc5dfb9c..69b39538cb3e 100644 --- a/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx +++ b/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx @@ -7,13 +7,11 @@ import React from 'react'; import moment from 'moment'; -import { useSelector } from 'react-redux'; import { Direction, EuiBasicTable } from '@elastic/eui'; -import { certificatesSelector } from '../../state/certificates/certificates'; import { CertStatus } from './cert_status'; import { CertMonitors } from './cert_monitors'; import * as labels from './translations'; -import { Cert, CertMonitor } from '../../../common/runtime_types'; +import { Cert, CertMonitor, CertResult } from '../../../common/runtime_types'; import { FingerprintCol } from './fingerprint_col'; import { LOADING_CERTIFICATES, NO_CERTS_AVAILABLE } from './translations'; @@ -40,11 +38,10 @@ interface Props { page: Page; sort: CertSort; onChange: (page: Page, sort: CertSort) => void; + certificates: CertResult & { loading?: boolean }; } -export const CertificateList: React.FC = ({ page, sort, onChange }) => { - const { data: certificates, loading } = useSelector(certificatesSelector); - +export const CertificateList: React.FC = ({ page, certificates, sort, onChange }) => { const onTableChange = (newVal: Partial) => { onChange(newVal.page as Page, newVal.sort as CertSort); }; @@ -100,7 +97,7 @@ export const CertificateList: React.FC = ({ page, sort, onChange }) => { return ( = ({ page, sort, onChange }) => { }, }} noItemsMessage={ - loading ? ( + certificates.loading ? ( LOADING_CERTIFICATES ) : ( {NO_CERTS_AVAILABLE} diff --git a/x-pack/plugins/uptime/public/components/certificates/use_cert_search.ts b/x-pack/plugins/uptime/public/components/certificates/use_cert_search.ts new file mode 100644 index 000000000000..22531faff2da --- /dev/null +++ b/x-pack/plugins/uptime/public/components/certificates/use_cert_search.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useSelector } from 'react-redux'; +import { useContext } from 'react'; +import { useEsSearch, createEsParams } from '../../../../observability/public'; + +import { CertResult, GetCertsParams, Ping } from '../../../common/runtime_types'; + +import { selectDynamicSettings } from '../../state/selectors'; +import { + DEFAULT_DIRECTION, + DEFAULT_FROM, + DEFAULT_SIZE, + DEFAULT_SORT, + DEFAULT_TO, + getCertsRequestBody, + processCertsResult, +} from '../../../common/requests/get_certs_request_body'; +import { UptimeRefreshContext } from '../../contexts'; + +export const useCertSearch = ({ + pageIndex, + size = DEFAULT_SIZE, + search, + sortBy = DEFAULT_SORT, + direction = DEFAULT_DIRECTION, +}: GetCertsParams): CertResult & { loading?: boolean } => { + const settings = useSelector(selectDynamicSettings); + const { lastRefresh } = useContext(UptimeRefreshContext); + + const searchBody = getCertsRequestBody({ + pageIndex, + size, + search, + sortBy, + direction, + to: DEFAULT_TO, + from: DEFAULT_FROM, + }); + + const esParams = createEsParams({ + index: settings.settings?.heartbeatIndices, + body: searchBody, + }); + + const { data: result, loading } = useEsSearch(esParams, [ + settings.settings?.heartbeatIndices, + size, + pageIndex, + lastRefresh, + search, + ]); + + return result ? { ...processCertsResult(result), loading } : { certs: [], total: 0, loading }; +}; diff --git a/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts b/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts index c0b4c893d93d..b70aab338f05 100644 --- a/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts +++ b/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts @@ -95,10 +95,7 @@ export const mockState: AppState = { }, }, certificates: { - certs: { - data: null, - loading: false, - }, + total: 0, }, selectedFilters: null, alerts: { diff --git a/x-pack/plugins/uptime/public/pages/certificates.tsx b/x-pack/plugins/uptime/public/pages/certificates.tsx index 4b8617d59454..787238eb8ea6 100644 --- a/x-pack/plugins/uptime/public/pages/certificates.tsx +++ b/x-pack/plugins/uptime/public/pages/certificates.tsx @@ -7,13 +7,13 @@ import { useDispatch } from 'react-redux'; import { EuiSpacer } from '@elastic/eui'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTrackPageview } from '../../../observability/public'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; -import { UptimeRefreshContext } from '../contexts'; -import { getCertificatesAction } from '../state/certificates/certificates'; import { CertificateList, CertificateSearch, CertSort } from '../components/certificates'; +import { useCertSearch } from '../components/certificates/use_cert_search'; +import { setCertificatesTotalAction } from '../state/certificates/certificates'; const DEFAULT_PAGE_SIZE = 10; const LOCAL_STORAGE_KEY = 'xpack.uptime.certList.pageSize'; @@ -40,22 +40,21 @@ export const CertificatesPage: React.FC = () => { const dispatch = useDispatch(); - const { lastRefresh } = useContext(UptimeRefreshContext); - useEffect(() => { dispatch(getDynamicSettings()); }, [dispatch]); + const certificates = useCertSearch({ + search, + size: page.size, + pageIndex: page.index, + sortBy: sort.field, + direction: sort.direction, + }); + useEffect(() => { - dispatch( - getCertificatesAction.get({ - search, - ...page, - sortBy: sort.field, - direction: sort.direction, - }) - ); - }, [dispatch, page, search, sort.direction, sort.field, lastRefresh]); + dispatch(setCertificatesTotalAction({ total: certificates.total })); + }, [certificates.total, dispatch]); return ( <> @@ -70,6 +69,7 @@ export const CertificatesPage: React.FC = () => { localStorage.setItem(LOCAL_STORAGE_KEY, pageVal.size.toString()); }} sort={sort} + certificates={certificates} /> ); diff --git a/x-pack/plugins/uptime/public/state/api/certificates.ts b/x-pack/plugins/uptime/public/state/api/certificates.ts deleted file mode 100644 index 33fb584cfc81..000000000000 --- a/x-pack/plugins/uptime/public/state/api/certificates.ts +++ /dev/null @@ -1,14 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { API_URLS } from '../../../common/constants'; -import { apiService } from './utils'; -import { CertResultType, GetCertsParams } from '../../../common/runtime_types'; - -export const fetchCertificates = async (params: GetCertsParams) => { - return await apiService.get(API_URLS.CERTS, params, CertResultType); -}; diff --git a/x-pack/plugins/uptime/public/state/certificates/certificates.ts b/x-pack/plugins/uptime/public/state/certificates/certificates.ts index ca2d5e7a17a4..b5e5376c00df 100644 --- a/x-pack/plugins/uptime/public/state/certificates/certificates.ts +++ b/x-pack/plugins/uptime/public/state/certificates/certificates.ts @@ -5,40 +5,27 @@ * 2.0. */ -import { handleActions } from 'redux-actions'; -import { takeLatest } from 'redux-saga/effects'; -import { createAsyncAction } from '../actions/utils'; -import { asyncInitState, handleAsyncAction } from '../reducers/utils'; -import { CertResult, GetCertsParams } from '../../../common/runtime_types'; +import { Action, createAction, handleActions } from 'redux-actions'; import { AppState } from '../index'; -import { AsyncInitState } from '../reducers/types'; -import { fetchEffectFactory } from '../effects/fetch_effect'; -import { fetchCertificates } from '../api/certificates'; -export const getCertificatesAction = createAsyncAction( - 'GET_CERTIFICATES' -); +export const setCertificatesTotalAction = createAction('SET_CERTIFICATES_TOTAL'); export interface CertificatesState { - certs: AsyncInitState; + total: number; } const initialState = { - certs: asyncInitState(), + total: 0, }; export const certificatesReducer = handleActions( { - ...handleAsyncAction('certs', getCertificatesAction), + [String(setCertificatesTotalAction)]: (state, action: Action) => ({ + ...state, + total: action.payload.total, + }), }, initialState ); -export function* fetchCertificatesEffect() { - yield takeLatest( - getCertificatesAction.get, - fetchEffectFactory(fetchCertificates, getCertificatesAction.success, getCertificatesAction.fail) - ); -} - -export const certificatesSelector = ({ certificates }: AppState) => certificates.certs; +export const certificatesSelector = ({ certificates }: AppState) => certificates.total; diff --git a/x-pack/plugins/uptime/public/state/effects/index.ts b/x-pack/plugins/uptime/public/state/effects/index.ts index df02180b1c28..68acdb54ed7f 100644 --- a/x-pack/plugins/uptime/public/state/effects/index.ts +++ b/x-pack/plugins/uptime/public/state/effects/index.ts @@ -16,7 +16,6 @@ import { fetchPingsEffect, fetchPingHistogramEffect } from './ping'; import { fetchMonitorDurationEffect } from './monitor_duration'; import { fetchMLJobEffect } from './ml_anomaly'; import { fetchIndexStatusEffect } from './index_status'; -import { fetchCertificatesEffect } from '../certificates/certificates'; import { fetchAlertsEffect } from '../alerts/alerts'; import { fetchJourneyStepsEffect } from './journey'; import { fetchNetworkEventsEffect } from './network_events'; @@ -39,7 +38,6 @@ export function* rootEffect() { yield fork(fetchMLJobEffect); yield fork(fetchMonitorDurationEffect); yield fork(fetchIndexStatusEffect); - yield fork(fetchCertificatesEffect); yield fork(fetchAlertsEffect); yield fork(fetchJourneyStepsEffect); yield fork(fetchNetworkEventsEffect); diff --git a/x-pack/plugins/uptime/public/state/selectors/index.test.ts b/x-pack/plugins/uptime/public/state/selectors/index.test.ts index 520ebdac0c1e..89a201d1391c 100644 --- a/x-pack/plugins/uptime/public/state/selectors/index.test.ts +++ b/x-pack/plugins/uptime/public/state/selectors/index.test.ts @@ -93,10 +93,7 @@ describe('state selectors', () => { }, }, certificates: { - certs: { - data: null, - loading: false, - }, + total: 0, }, selectedFilters: null, alerts: { diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.test.ts index 36e9e9fb36ab..b069d368b2a3 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.test.ts @@ -6,11 +6,10 @@ */ import moment from 'moment'; import { ALERT_SEVERITY_WARNING, ALERT_SEVERITY } from '@kbn/rule-data-utils'; -import { tlsAlertFactory, getCertSummary, DEFAULT_SIZE } from './tls'; +import { tlsAlertFactory, getCertSummary } from './tls'; import { TLS } from '../../../common/constants/alerts'; import { CertResult, DynamicSettings } from '../../../common/runtime_types'; import { createRuleTypeMocks, bootstrapDependencies } from './test_utils'; -import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { savedObjectsAdapter, UMSavedObjectsAdapter } from '../saved_objects'; @@ -123,10 +122,8 @@ describe('tls alert', () => { }); expect(mockGetter).toBeCalledWith( expect.objectContaining({ - from: DEFAULT_FROM, - to: DEFAULT_TO, - index: 0, - size: DEFAULT_SIZE, + pageIndex: 0, + size: 1000, notValidAfter: `now+${DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold}d`, notValidBefore: `now-${DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold}d`, sortBy: 'common_name', diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 63636ddfe590..8e49c07f62c4 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -13,7 +13,6 @@ import { TLS } from '../../../common/constants/alerts'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { Cert, CertResult } from '../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; -import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs'; import { TlsTranslations } from '../../../common/translations'; import { ActionGroupIdsOf } from '../../../../alerting/common'; @@ -23,8 +22,6 @@ import { createUptimeESClient } from '../lib'; export type ActionGroupIds = ActionGroupIdsOf; -export const DEFAULT_SIZE = 20; - interface TlsAlertState { commonName: string; issuer: string; @@ -130,10 +127,8 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, const { certs, total }: CertResult = await libs.requests.getCerts({ uptimeEsClient, - from: DEFAULT_FROM, - to: DEFAULT_TO, - index: 0, - size: DEFAULT_SIZE, + pageIndex: 0, + size: 1000, notValidAfter: `now+${ dynamicSettings?.certExpirationThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts index 812925f22b24..fe49fc841648 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts @@ -13,7 +13,6 @@ import { TLS_LEGACY } from '../../../common/constants/alerts'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { Cert, CertResult } from '../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; -import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs'; import { ActionGroupIdsOf } from '../../../../alerting/common'; import { AlertInstanceContext } from '../../../../alerting/common'; @@ -21,13 +20,16 @@ import { AlertInstance } from '../../../../alerting/server'; import { savedObjectsAdapter } from '../saved_objects'; import { createUptimeESClient } from '../lib'; +import { + DEFAULT_FROM, + DEFAULT_SIZE, + DEFAULT_TO, +} from '../../../common/requests/get_certs_request_body'; export type ActionGroupIds = ActionGroupIdsOf; type TLSAlertInstance = AlertInstance, AlertInstanceContext, ActionGroupIds>; -const DEFAULT_SIZE = 20; - interface TlsAlertState { count: number; agingCount: number; @@ -125,7 +127,7 @@ export const tlsLegacyAlertFactory: UptimeAlertTypeFactory = (_s uptimeEsClient, from: DEFAULT_FROM, to: DEFAULT_TO, - index: 0, + pageIndex: 0, size: DEFAULT_SIZE, notValidAfter: `now+${ dynamicSettings?.certExpirationThreshold ?? diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts index cf0084131353..9b8ea6b98c8b 100644 --- a/x-pack/plugins/uptime/server/lib/lib.ts +++ b/x-pack/plugins/uptime/server/lib/lib.ts @@ -58,9 +58,9 @@ export function createUptimeESClient({ return { baseESClient: esClient, - async search( + async search( params: TParams - ): Promise<{ body: ESSearchResponse }> { + ): Promise<{ body: ESSearchResponse }> { let res: any; let esError: any; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( @@ -155,7 +155,3 @@ export function debugESCall({ } console.log(`\n`); } - -export function createEsQuery(params: T): T { - return params; -} diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts index 6e06dea0436d..a6b37215c141 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts @@ -94,7 +94,7 @@ describe('getCerts', () => { const result = await getCerts({ uptimeEsClient, - index: 1, + pageIndex: 1, from: 'now-2d', to: 'now+1h', search: 'my_common_name', diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 86a9825f8a48..e5e9b74fb3a5 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -5,170 +5,37 @@ * 2.0. */ +import { PromiseType } from 'utility-types'; import { UMElasticsearchQueryFn } from '../adapters'; import { CertResult, GetCertsParams, Ping } from '../../../common/runtime_types'; +import { + getCertsRequestBody, + processCertsResult, +} from '../../../common/requests/get_certs_request_body'; +import { UptimeESClient } from '../lib'; -enum SortFields { - 'issuer' = 'tls.server.x509.issuer.common_name', - 'not_after' = 'tls.server.x509.not_after', - 'not_before' = 'tls.server.x509.not_before', - 'common_name' = 'tls.server.x509.subject.common_name', -} +export const getCerts: UMElasticsearchQueryFn = async ( + requestParams +) => { + const result = await getCertsResults(requestParams); -export const getCerts: UMElasticsearchQueryFn = async ({ - uptimeEsClient, - index, - from, - to, - size, - search, - notValidBefore, - notValidAfter, - sortBy, - direction, -}) => { - const sort = SortFields[sortBy as keyof typeof SortFields]; + return processCertsResult(result); +}; - const searchBody = { - from: index * size, - size, - sort: [ - { - [sort]: { - order: direction as 'asc' | 'desc', - }, - }, - ], - query: { - bool: { - ...(search - ? { - minimum_should_match: 1, - should: [ - { - multi_match: { - query: escape(search), - type: 'phrase_prefix' as const, - fields: [ - 'monitor.id.text', - 'monitor.name.text', - 'url.full.text', - 'tls.server.x509.subject.common_name.text', - 'tls.server.x509.issuer.common_name.text', - ], - }, - }, - ], - } - : {}), - filter: [ - { - exists: { - field: 'tls.server.hash.sha256', - }, - }, - { - range: { - 'monitor.timespan': { - gte: from, - lte: to, - }, - }, - }, - ], - }, - }, - _source: [ - 'monitor.id', - 'monitor.name', - 'tls.server.x509.issuer.common_name', - 'tls.server.x509.subject.common_name', - 'tls.server.hash.sha1', - 'tls.server.hash.sha256', - 'tls.server.x509.not_after', - 'tls.server.x509.not_before', - ], - collapse: { - field: 'tls.server.hash.sha256', - inner_hits: { - _source: { - includes: ['monitor.id', 'monitor.name', 'url.full'], - }, - collapse: { - field: 'monitor.id', - }, - name: 'monitors', - sort: [{ 'monitor.id': 'asc' as const }], - }, - }, - aggs: { - total: { - cardinality: { - field: 'tls.server.hash.sha256', - }, - }, - }, - }; +export type CertificatesResults = PromiseType>; - if (notValidBefore || notValidAfter) { - const validityFilters: any = { - bool: { - should: [], - }, - }; - if (notValidBefore) { - validityFilters.bool.should.push({ - range: { - 'tls.certificate_not_valid_before': { - lte: notValidBefore, - }, - }, - }); - } - if (notValidAfter) { - validityFilters.bool.should.push({ - range: { - 'tls.certificate_not_valid_after': { - lte: notValidAfter, - }, - }, - }); - } +const getCertsResults = async ( + requestParams: GetCertsParams & { uptimeEsClient: UptimeESClient } +) => { + const { uptimeEsClient } = requestParams; - searchBody.query.bool.filter.push(validityFilters); - } + const searchBody = getCertsRequestBody(requestParams); - const { body: result } = await uptimeEsClient.search({ + const request = { body: searchBody }; + + const { body: result } = await uptimeEsClient.search({ body: searchBody, }); - const certs = (result?.hits?.hits ?? []).map((hit) => { - const ping = hit._source as Ping; - const server = ping.tls?.server!; - - const notAfter = server?.x509?.not_after; - const notBefore = server?.x509?.not_before; - const issuer = server?.x509?.issuer?.common_name; - const commonName = server?.x509?.subject?.common_name; - const sha1 = server?.hash?.sha1; - const sha256 = server?.hash?.sha256; - - const monitors = hit.inner_hits!.monitors.hits.hits.map((monitor: any) => ({ - name: monitor._source?.monitor.name, - id: monitor._source?.monitor.id, - url: monitor._source?.url?.full, - })); - - return { - monitors, - issuer, - sha1, - sha256: sha256 as string, - not_after: notAfter, - not_before: notBefore, - common_name: commonName, - }; - }); - const total = result?.aggregations?.total?.value ?? 0; - return { certs, total }; + return result; }; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts index 33615bb8516b..c5ac246fe935 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts @@ -10,7 +10,7 @@ import { GetPingHistogramParams, HistogramResult } from '../../../common/runtime import { QUERY } from '../../../common/constants'; import { getHistogramInterval } from '../helper/get_histogram_interval'; import { UMElasticsearchQueryFn } from '../adapters/framework'; -import { createEsQuery } from '../lib'; +import { createEsQuery } from '../../../common/utils/es_search'; export const getPingHistogram: UMElasticsearchQueryFn< GetPingHistogramParams, diff --git a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts deleted file mode 100644 index f974ee6bee76..000000000000 --- a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts +++ /dev/null @@ -1,54 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { API_URLS } from '../../../common/constants'; -import { UMServerLibs } from '../../lib/lib'; -import { UMRestApiRouteFactory } from '../types'; - -export const DEFAULT_FROM = 'now-5m'; -export const DEFAULT_TO = 'now'; - -const DEFAULT_SIZE = 25; -const DEFAULT_SORT = 'not_after'; -const DEFAULT_DIRECTION = 'asc'; - -export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ - method: 'GET', - path: API_URLS.CERTS, - validate: { - query: schema.object({ - from: schema.maybe(schema.string()), - to: schema.maybe(schema.string()), - search: schema.maybe(schema.string()), - index: schema.maybe(schema.number()), - size: schema.maybe(schema.number()), - sortBy: schema.maybe(schema.string()), - direction: schema.maybe(schema.string()), - }), - }, - handler: async ({ uptimeEsClient, request }): Promise => { - const index = request.query?.index ?? 0; - const size = request.query?.size ?? DEFAULT_SIZE; - const from = request.query?.from ?? DEFAULT_FROM; - const to = request.query?.to ?? DEFAULT_TO; - const sortBy = request.query?.sortBy ?? DEFAULT_SORT; - const direction = request.query?.direction ?? DEFAULT_DIRECTION; - const { search } = request.query; - - return await libs.requests.getCerts({ - uptimeEsClient, - index, - search, - size, - from, - to, - sortBy, - direction, - }); - }, -}); diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index 8ae878669ba3..0196a6d192e8 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { createGetCertsRoute } from './certs/certs'; import { createGetOverviewFilters } from './overview_filters'; import { createGetPingHistogramRoute, @@ -35,7 +34,6 @@ export { createRouteWithAuth } from './create_route_with_auth'; export { uptimeRouteWrapper } from './uptime_route_wrapper'; export const restApiRoutes: UMRestApiRouteFactory[] = [ - createGetCertsRoute, createGetOverviewFilters, createGetPingsRoute, createGetIndexPatternRoute, diff --git a/x-pack/test/api_integration/apis/uptime/rest/certs.ts b/x-pack/test/api_integration/apis/uptime/rest/certs.ts index c9c8efe3f3a1..b9bc38801d39 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/certs.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/certs.ts @@ -9,9 +9,12 @@ import expect from '@kbn/expect'; import moment from 'moment'; import { isRight } from 'fp-ts/lib/Either'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { API_URLS } from '../../../../../plugins/uptime/common/constants'; import { CertType } from '../../../../../plugins/uptime/common/runtime_types'; import { makeChecksWithStatus } from './helper/make_checks'; +import { + processCertsResult, + getCertsRequestBody, +} from '../../../../../plugins/uptime/common/requests/get_certs_request_body'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -21,8 +24,18 @@ export default function ({ getService }: FtrProviderContext) { describe('certs api', () => { describe('empty index', async () => { it('returns empty array for no data', async () => { - const apiResponse = await supertest.get(API_URLS.CERTS); - expect(JSON.stringify(apiResponse.body)).to.eql('{"certs":[],"total":0}'); + const apiResponse = await supertest + .post(`/internal/search/ese`) + .set('kbn-xsrf', 'true') + .send({ + params: { + index: 'heartbeat-*', + body: getCertsRequestBody({ pageIndex: 0, size: 10 }), + }, + }); + + const result = processCertsResult(apiResponse.body.rawResponse); + expect(JSON.stringify(result)).to.eql('{"certs":[],"total":0}'); }); }); @@ -67,19 +80,29 @@ export default function ({ getService }: FtrProviderContext) { }); it('retrieves expected cert data', async () => { - const apiResponse = await supertest.get(API_URLS.CERTS); - const { body } = apiResponse; + const { body } = await supertest + .post(`/internal/search/ese`) + .set('kbn-xsrf', 'true') + .send({ + params: { + index: 'heartbeat-*', + body: getCertsRequestBody({ pageIndex: 0, size: 10 }), + }, + }); - expect(body.certs).not.to.be(undefined); - expect(Array.isArray(body.certs)).to.be(true); - expect(body.certs).to.have.length(1); + const result = processCertsResult(body.rawResponse); - const decoded = CertType.decode(body.certs[0]); + expect(result.certs).not.to.be(undefined); + expect(Array.isArray(result.certs)).to.be(true); + expect(result.certs).to.have.length(1); + + const decoded = CertType.decode(result.certs[0]); expect(isRight(decoded)).to.be(true); - const cert = body.certs[0]; + const cert = result.certs[0]; expect(Array.isArray(cert.monitors)).to.be(true); expect(cert.monitors[0]).to.eql({ + name: undefined, id: monitorId, url: 'http://localhost:5678/pattern?r=200x5,500x1', });