[Uptime] Use async search api for certificates (#111731)
This commit is contained in:
parent
ce2aac3763
commit
895747ad38
|
@ -12,7 +12,7 @@ import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||||
import { isCompleteResponse } from '../../../../../src/plugins/data/common';
|
import { isCompleteResponse } from '../../../../../src/plugins/data/common';
|
||||||
import { useFetcher } from './use_fetcher';
|
import { useFetcher } from './use_fetcher';
|
||||||
|
|
||||||
export const useEsSearch = <TParams extends estypes.SearchRequest>(
|
export const useEsSearch = <DocumentSource extends unknown, TParams extends estypes.SearchRequest>(
|
||||||
params: TParams,
|
params: TParams,
|
||||||
fnDeps: any[]
|
fnDeps: any[]
|
||||||
) => {
|
) => {
|
||||||
|
@ -43,7 +43,7 @@ export const useEsSearch = <TParams extends estypes.SearchRequest>(
|
||||||
|
|
||||||
const { rawResponse } = response as any;
|
const { rawResponse } = response as any;
|
||||||
|
|
||||||
return { data: rawResponse as ESSearchResponse<unknown, TParams>, loading };
|
return { data: rawResponse as ESSearchResponse<DocumentSource, TParams>, loading };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createEsParams<T extends estypes.SearchRequest>(params: T): T {
|
export function createEsParams<T extends estypes.SearchRequest>(params: T): T {
|
||||||
|
|
|
@ -60,6 +60,7 @@ export {
|
||||||
|
|
||||||
export const LazyAlertsFlyout = lazy(() => import('./pages/alerts/alerts_flyout'));
|
export const LazyAlertsFlyout = lazy(() => import('./pages/alerts/alerts_flyout'));
|
||||||
export { useFetcher, FETCH_STATUS } from './hooks/use_fetcher';
|
export { useFetcher, FETCH_STATUS } from './hooks/use_fetcher';
|
||||||
|
export { useEsSearch, createEsParams } from './hooks/use_es_search';
|
||||||
|
|
||||||
export * from './typings';
|
export * from './typings';
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export enum API_URLS {
|
export enum API_URLS {
|
||||||
CERTS = '/api/uptime/certs',
|
|
||||||
INDEX_PATTERN = `/api/uptime/index_pattern`,
|
INDEX_PATTERN = `/api/uptime/index_pattern`,
|
||||||
INDEX_STATUS = '/api/uptime/index_status',
|
INDEX_STATUS = '/api/uptime/index_status',
|
||||||
MONITOR_LIST = `/api/uptime/monitor/list`,
|
MONITOR_LIST = `/api/uptime/monitor/list`,
|
||||||
|
|
183
x-pack/plugins/uptime/common/requests/get_certs_request_body.ts
Normal file
183
x-pack/plugins/uptime/common/requests/get_certs_request_body.ts
Normal file
|
@ -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 };
|
||||||
|
};
|
|
@ -9,10 +9,7 @@ import * as t from 'io-ts';
|
||||||
|
|
||||||
export const GetCertsParamsType = t.intersection([
|
export const GetCertsParamsType = t.intersection([
|
||||||
t.type({
|
t.type({
|
||||||
index: t.number,
|
pageIndex: t.number,
|
||||||
size: t.number,
|
|
||||||
sortBy: t.string,
|
|
||||||
direction: t.string,
|
|
||||||
}),
|
}),
|
||||||
t.partial({
|
t.partial({
|
||||||
search: t.string,
|
search: t.string,
|
||||||
|
@ -20,6 +17,9 @@ export const GetCertsParamsType = t.intersection([
|
||||||
notValidAfter: t.string,
|
notValidAfter: t.string,
|
||||||
from: t.string,
|
from: t.string,
|
||||||
to: t.string,
|
to: t.string,
|
||||||
|
sortBy: t.string,
|
||||||
|
direction: t.string,
|
||||||
|
size: t.number,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
12
x-pack/plugins/uptime/common/utils/es_search.ts
Normal file
12
x-pack/plugins/uptime/common/utils/es_search.ts
Normal file
|
@ -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<T extends estypes.SearchRequest>(params: T): T {
|
||||||
|
return params;
|
||||||
|
}
|
|
@ -1,105 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`CertificateList shallow renders expected elements for valid props 1`] = `
|
|
||||||
<ContextProvider
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"history": Object {
|
|
||||||
"action": "POP",
|
|
||||||
"block": [Function],
|
|
||||||
"canGo": [Function],
|
|
||||||
"createHref": [Function],
|
|
||||||
"entries": Array [
|
|
||||||
Object {
|
|
||||||
"hash": "",
|
|
||||||
"key": "TestKeyForTesting",
|
|
||||||
"pathname": "/",
|
|
||||||
"search": "",
|
|
||||||
"state": undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"go": [Function],
|
|
||||||
"goBack": [Function],
|
|
||||||
"goForward": [Function],
|
|
||||||
"index": 0,
|
|
||||||
"length": 1,
|
|
||||||
"listen": [Function],
|
|
||||||
"location": Object {
|
|
||||||
"hash": "",
|
|
||||||
"key": "TestKeyForTesting",
|
|
||||||
"pathname": "/",
|
|
||||||
"search": "",
|
|
||||||
"state": undefined,
|
|
||||||
},
|
|
||||||
"push": [Function],
|
|
||||||
"replace": [Function],
|
|
||||||
},
|
|
||||||
"location": Object {
|
|
||||||
"hash": "",
|
|
||||||
"key": "TestKeyForTesting",
|
|
||||||
"pathname": "/",
|
|
||||||
"search": "",
|
|
||||||
"state": undefined,
|
|
||||||
},
|
|
||||||
"match": Object {
|
|
||||||
"isExact": true,
|
|
||||||
"params": Object {},
|
|
||||||
"path": "/",
|
|
||||||
"url": "/",
|
|
||||||
},
|
|
||||||
"staticContext": undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ContextProvider
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"action": "POP",
|
|
||||||
"block": [Function],
|
|
||||||
"canGo": [Function],
|
|
||||||
"createHref": [Function],
|
|
||||||
"entries": Array [
|
|
||||||
Object {
|
|
||||||
"hash": "",
|
|
||||||
"key": "TestKeyForTesting",
|
|
||||||
"pathname": "/",
|
|
||||||
"search": "",
|
|
||||||
"state": undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"go": [Function],
|
|
||||||
"goBack": [Function],
|
|
||||||
"goForward": [Function],
|
|
||||||
"index": 0,
|
|
||||||
"length": 1,
|
|
||||||
"listen": [Function],
|
|
||||||
"location": Object {
|
|
||||||
"hash": "",
|
|
||||||
"key": "TestKeyForTesting",
|
|
||||||
"pathname": "/",
|
|
||||||
"search": "",
|
|
||||||
"state": undefined,
|
|
||||||
},
|
|
||||||
"push": [Function],
|
|
||||||
"replace": [Function],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CertificateList
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
page={
|
|
||||||
Object {
|
|
||||||
"index": 0,
|
|
||||||
"size": 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort={
|
|
||||||
Object {
|
|
||||||
"direction": "asc",
|
|
||||||
"field": "not_after",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ContextProvider>
|
|
||||||
</ContextProvider>
|
|
||||||
`;
|
|
|
@ -5,9 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ChangeEvent } from 'react';
|
import React, { ChangeEvent, useState } from 'react';
|
||||||
import { EuiFieldSearch } from '@elastic/eui';
|
import { EuiFieldSearch } from '@elastic/eui';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import useDebounce from 'react-use/lib/useDebounce';
|
||||||
import * as labels from './translations';
|
import * as labels from './translations';
|
||||||
|
|
||||||
const WrapFieldSearch = styled('div')`
|
const WrapFieldSearch = styled('div')`
|
||||||
|
@ -19,10 +20,20 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CertificateSearch: React.FC<Props> = ({ setSearch }) => {
|
export const CertificateSearch: React.FC<Props> = ({ setSearch }) => {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState('');
|
||||||
|
|
||||||
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value);
|
setDebouncedValue(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useDebounce(
|
||||||
|
() => {
|
||||||
|
setSearch(debouncedValue);
|
||||||
|
},
|
||||||
|
350,
|
||||||
|
[debouncedValue]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapFieldSearch>
|
<WrapFieldSearch>
|
||||||
<EuiFieldSearch
|
<EuiFieldSearch
|
||||||
|
|
|
@ -11,14 +11,14 @@ import { useSelector } from 'react-redux';
|
||||||
import { certificatesSelector } from '../../state/certificates/certificates';
|
import { certificatesSelector } from '../../state/certificates/certificates';
|
||||||
|
|
||||||
export const CertificateTitle = () => {
|
export const CertificateTitle = () => {
|
||||||
const { data: certificates } = useSelector(certificatesSelector);
|
const total = useSelector(certificatesSelector);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.uptime.certificates.heading"
|
id="xpack.uptime.certificates.heading"
|
||||||
defaultMessage="TLS Certificates ({total})"
|
defaultMessage="TLS Certificates ({total})"
|
||||||
values={{
|
values={{
|
||||||
total: <span data-test-subj="uptimeCertTotal">{certificates?.total ?? 0}</span>,
|
total: <span data-test-subj="uptimeCertTotal">{total ?? 0}</span>,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallowWithRouter } from '../../lib';
|
|
||||||
import { CertificateList, CertSort } from './certificates_list';
|
import { CertificateList, CertSort } from './certificates_list';
|
||||||
|
import { render } from '../../lib/helper/rtl_helpers';
|
||||||
|
|
||||||
describe('CertificateList', () => {
|
describe('CertificateList', () => {
|
||||||
it('shallow renders expected elements for valid props', () => {
|
it('render empty state', () => {
|
||||||
const page = {
|
const page = {
|
||||||
index: 0,
|
index: 0,
|
||||||
size: 10,
|
size: 10,
|
||||||
|
@ -20,8 +20,59 @@ describe('CertificateList', () => {
|
||||||
direction: 'asc',
|
direction: 'asc',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { getByText } = render(
|
||||||
|
<CertificateList
|
||||||
|
page={page}
|
||||||
|
sort={sort}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
certificates={{ loading: false, total: 0, certs: [] }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
shallowWithRouter(<CertificateList page={page} sort={sort} onChange={jest.fn()} />)
|
getByText('No Certificates found. Note: Certificates are only visible for Heartbeat 7.8+')
|
||||||
).toMatchSnapshot();
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders certificates list', () => {
|
||||||
|
const page = {
|
||||||
|
index: 0,
|
||||||
|
size: 10,
|
||||||
|
};
|
||||||
|
const sort: CertSort = {
|
||||||
|
field: 'not_after',
|
||||||
|
direction: 'asc',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getByText } = render(
|
||||||
|
<CertificateList
|
||||||
|
page={page}
|
||||||
|
sort={sort}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
certificates={{
|
||||||
|
loading: false,
|
||||||
|
total: 1,
|
||||||
|
certs: [
|
||||||
|
{
|
||||||
|
monitors: [
|
||||||
|
{
|
||||||
|
name: 'BadSSL Expired',
|
||||||
|
id: 'expired-badssl',
|
||||||
|
url: 'https://expired.badssl.com/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
issuer: 'COMODO RSA Domain Validation Secure Server CA',
|
||||||
|
sha1: '404bbd2f1f4cc2fdeef13aabdd523ef61f1c71f3',
|
||||||
|
sha256: 'ba105ce02bac76888ecee47cd4eb7941653e9ac993b61b2eb3dcc82014d21b4f',
|
||||||
|
not_after: '2015-04-12T23:59:59.000Z',
|
||||||
|
not_before: '2015-04-09T00:00:00.000Z',
|
||||||
|
common_name: '*.badssl.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getByText('BadSSL Expired')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,13 +7,11 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { Direction, EuiBasicTable } from '@elastic/eui';
|
import { Direction, EuiBasicTable } from '@elastic/eui';
|
||||||
import { certificatesSelector } from '../../state/certificates/certificates';
|
|
||||||
import { CertStatus } from './cert_status';
|
import { CertStatus } from './cert_status';
|
||||||
import { CertMonitors } from './cert_monitors';
|
import { CertMonitors } from './cert_monitors';
|
||||||
import * as labels from './translations';
|
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 { FingerprintCol } from './fingerprint_col';
|
||||||
import { LOADING_CERTIFICATES, NO_CERTS_AVAILABLE } from './translations';
|
import { LOADING_CERTIFICATES, NO_CERTS_AVAILABLE } from './translations';
|
||||||
|
|
||||||
|
@ -40,11 +38,10 @@ interface Props {
|
||||||
page: Page;
|
page: Page;
|
||||||
sort: CertSort;
|
sort: CertSort;
|
||||||
onChange: (page: Page, sort: CertSort) => void;
|
onChange: (page: Page, sort: CertSort) => void;
|
||||||
|
certificates: CertResult & { loading?: boolean };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CertificateList: React.FC<Props> = ({ page, sort, onChange }) => {
|
export const CertificateList: React.FC<Props> = ({ page, certificates, sort, onChange }) => {
|
||||||
const { data: certificates, loading } = useSelector(certificatesSelector);
|
|
||||||
|
|
||||||
const onTableChange = (newVal: Partial<Props>) => {
|
const onTableChange = (newVal: Partial<Props>) => {
|
||||||
onChange(newVal.page as Page, newVal.sort as CertSort);
|
onChange(newVal.page as Page, newVal.sort as CertSort);
|
||||||
};
|
};
|
||||||
|
@ -100,7 +97,7 @@ export const CertificateList: React.FC<Props> = ({ page, sort, onChange }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiBasicTable
|
<EuiBasicTable
|
||||||
loading={loading}
|
loading={certificates.loading}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
items={certificates?.certs ?? []}
|
items={certificates?.certs ?? []}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
|
@ -112,7 +109,7 @@ export const CertificateList: React.FC<Props> = ({ page, sort, onChange }) => {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
noItemsMessage={
|
noItemsMessage={
|
||||||
loading ? (
|
certificates.loading ? (
|
||||||
LOADING_CERTIFICATES
|
LOADING_CERTIFICATES
|
||||||
) : (
|
) : (
|
||||||
<span data-test-subj="uptimeCertsEmptyMessage">{NO_CERTS_AVAILABLE}</span>
|
<span data-test-subj="uptimeCertsEmptyMessage">{NO_CERTS_AVAILABLE}</span>
|
||||||
|
|
|
@ -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<Ping, typeof esParams>(esParams, [
|
||||||
|
settings.settings?.heartbeatIndices,
|
||||||
|
size,
|
||||||
|
pageIndex,
|
||||||
|
lastRefresh,
|
||||||
|
search,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return result ? { ...processCertsResult(result), loading } : { certs: [], total: 0, loading };
|
||||||
|
};
|
|
@ -95,10 +95,7 @@ export const mockState: AppState = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
certificates: {
|
certificates: {
|
||||||
certs: {
|
total: 0,
|
||||||
data: null,
|
|
||||||
loading: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
selectedFilters: null,
|
selectedFilters: null,
|
||||||
alerts: {
|
alerts: {
|
||||||
|
|
|
@ -7,13 +7,13 @@
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { EuiSpacer } from '@elastic/eui';
|
import { EuiSpacer } from '@elastic/eui';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTrackPageview } from '../../../observability/public';
|
import { useTrackPageview } from '../../../observability/public';
|
||||||
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
|
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
|
||||||
import { getDynamicSettings } from '../state/actions/dynamic_settings';
|
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 { 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 DEFAULT_PAGE_SIZE = 10;
|
||||||
const LOCAL_STORAGE_KEY = 'xpack.uptime.certList.pageSize';
|
const LOCAL_STORAGE_KEY = 'xpack.uptime.certList.pageSize';
|
||||||
|
@ -40,22 +40,21 @@ export const CertificatesPage: React.FC = () => {
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getDynamicSettings());
|
dispatch(getDynamicSettings());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const certificates = useCertSearch({
|
||||||
|
search,
|
||||||
|
size: page.size,
|
||||||
|
pageIndex: page.index,
|
||||||
|
sortBy: sort.field,
|
||||||
|
direction: sort.direction,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(
|
dispatch(setCertificatesTotalAction({ total: certificates.total }));
|
||||||
getCertificatesAction.get({
|
}, [certificates.total, dispatch]);
|
||||||
search,
|
|
||||||
...page,
|
|
||||||
sortBy: sort.field,
|
|
||||||
direction: sort.direction,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [dispatch, page, search, sort.direction, sort.field, lastRefresh]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -70,6 +69,7 @@ export const CertificatesPage: React.FC = () => {
|
||||||
localStorage.setItem(LOCAL_STORAGE_KEY, pageVal.size.toString());
|
localStorage.setItem(LOCAL_STORAGE_KEY, pageVal.size.toString());
|
||||||
}}
|
}}
|
||||||
sort={sort}
|
sort={sort}
|
||||||
|
certificates={certificates}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
|
||||||
};
|
|
|
@ -5,40 +5,27 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { handleActions } from 'redux-actions';
|
import { Action, createAction, 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 { AppState } from '../index';
|
import { AppState } from '../index';
|
||||||
import { AsyncInitState } from '../reducers/types';
|
|
||||||
import { fetchEffectFactory } from '../effects/fetch_effect';
|
|
||||||
import { fetchCertificates } from '../api/certificates';
|
|
||||||
|
|
||||||
export const getCertificatesAction = createAsyncAction<GetCertsParams, CertResult>(
|
export const setCertificatesTotalAction = createAction<CertificatesState>('SET_CERTIFICATES_TOTAL');
|
||||||
'GET_CERTIFICATES'
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface CertificatesState {
|
export interface CertificatesState {
|
||||||
certs: AsyncInitState<CertResult>;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
certs: asyncInitState(),
|
total: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const certificatesReducer = handleActions<CertificatesState>(
|
export const certificatesReducer = handleActions<CertificatesState>(
|
||||||
{
|
{
|
||||||
...handleAsyncAction<CertificatesState>('certs', getCertificatesAction),
|
[String(setCertificatesTotalAction)]: (state, action: Action<CertificatesState>) => ({
|
||||||
|
...state,
|
||||||
|
total: action.payload.total,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
initialState
|
initialState
|
||||||
);
|
);
|
||||||
|
|
||||||
export function* fetchCertificatesEffect() {
|
export const certificatesSelector = ({ certificates }: AppState) => certificates.total;
|
||||||
yield takeLatest(
|
|
||||||
getCertificatesAction.get,
|
|
||||||
fetchEffectFactory(fetchCertificates, getCertificatesAction.success, getCertificatesAction.fail)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const certificatesSelector = ({ certificates }: AppState) => certificates.certs;
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import { fetchPingsEffect, fetchPingHistogramEffect } from './ping';
|
||||||
import { fetchMonitorDurationEffect } from './monitor_duration';
|
import { fetchMonitorDurationEffect } from './monitor_duration';
|
||||||
import { fetchMLJobEffect } from './ml_anomaly';
|
import { fetchMLJobEffect } from './ml_anomaly';
|
||||||
import { fetchIndexStatusEffect } from './index_status';
|
import { fetchIndexStatusEffect } from './index_status';
|
||||||
import { fetchCertificatesEffect } from '../certificates/certificates';
|
|
||||||
import { fetchAlertsEffect } from '../alerts/alerts';
|
import { fetchAlertsEffect } from '../alerts/alerts';
|
||||||
import { fetchJourneyStepsEffect } from './journey';
|
import { fetchJourneyStepsEffect } from './journey';
|
||||||
import { fetchNetworkEventsEffect } from './network_events';
|
import { fetchNetworkEventsEffect } from './network_events';
|
||||||
|
@ -39,7 +38,6 @@ export function* rootEffect() {
|
||||||
yield fork(fetchMLJobEffect);
|
yield fork(fetchMLJobEffect);
|
||||||
yield fork(fetchMonitorDurationEffect);
|
yield fork(fetchMonitorDurationEffect);
|
||||||
yield fork(fetchIndexStatusEffect);
|
yield fork(fetchIndexStatusEffect);
|
||||||
yield fork(fetchCertificatesEffect);
|
|
||||||
yield fork(fetchAlertsEffect);
|
yield fork(fetchAlertsEffect);
|
||||||
yield fork(fetchJourneyStepsEffect);
|
yield fork(fetchJourneyStepsEffect);
|
||||||
yield fork(fetchNetworkEventsEffect);
|
yield fork(fetchNetworkEventsEffect);
|
||||||
|
|
|
@ -93,10 +93,7 @@ describe('state selectors', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
certificates: {
|
certificates: {
|
||||||
certs: {
|
total: 0,
|
||||||
data: null,
|
|
||||||
loading: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
selectedFilters: null,
|
selectedFilters: null,
|
||||||
alerts: {
|
alerts: {
|
||||||
|
|
|
@ -6,11 +6,10 @@
|
||||||
*/
|
*/
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ALERT_SEVERITY_WARNING, ALERT_SEVERITY } from '@kbn/rule-data-utils';
|
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 { TLS } from '../../../common/constants/alerts';
|
||||||
import { CertResult, DynamicSettings } from '../../../common/runtime_types';
|
import { CertResult, DynamicSettings } from '../../../common/runtime_types';
|
||||||
import { createRuleTypeMocks, bootstrapDependencies } from './test_utils';
|
import { createRuleTypeMocks, bootstrapDependencies } from './test_utils';
|
||||||
import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs';
|
|
||||||
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
|
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
|
||||||
|
|
||||||
import { savedObjectsAdapter, UMSavedObjectsAdapter } from '../saved_objects';
|
import { savedObjectsAdapter, UMSavedObjectsAdapter } from '../saved_objects';
|
||||||
|
@ -123,10 +122,8 @@ describe('tls alert', () => {
|
||||||
});
|
});
|
||||||
expect(mockGetter).toBeCalledWith(
|
expect(mockGetter).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
from: DEFAULT_FROM,
|
pageIndex: 0,
|
||||||
to: DEFAULT_TO,
|
size: 1000,
|
||||||
index: 0,
|
|
||||||
size: DEFAULT_SIZE,
|
|
||||||
notValidAfter: `now+${DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold}d`,
|
notValidAfter: `now+${DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold}d`,
|
||||||
notValidBefore: `now-${DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold}d`,
|
notValidBefore: `now-${DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold}d`,
|
||||||
sortBy: 'common_name',
|
sortBy: 'common_name',
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { TLS } from '../../../common/constants/alerts';
|
||||||
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
|
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
|
||||||
import { Cert, CertResult } from '../../../common/runtime_types';
|
import { Cert, CertResult } from '../../../common/runtime_types';
|
||||||
import { commonStateTranslations, tlsTranslations } from './translations';
|
import { commonStateTranslations, tlsTranslations } from './translations';
|
||||||
import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs';
|
|
||||||
import { TlsTranslations } from '../../../common/translations';
|
import { TlsTranslations } from '../../../common/translations';
|
||||||
|
|
||||||
import { ActionGroupIdsOf } from '../../../../alerting/common';
|
import { ActionGroupIdsOf } from '../../../../alerting/common';
|
||||||
|
@ -23,8 +22,6 @@ import { createUptimeESClient } from '../lib';
|
||||||
|
|
||||||
export type ActionGroupIds = ActionGroupIdsOf<typeof TLS>;
|
export type ActionGroupIds = ActionGroupIdsOf<typeof TLS>;
|
||||||
|
|
||||||
export const DEFAULT_SIZE = 20;
|
|
||||||
|
|
||||||
interface TlsAlertState {
|
interface TlsAlertState {
|
||||||
commonName: string;
|
commonName: string;
|
||||||
issuer: string;
|
issuer: string;
|
||||||
|
@ -130,10 +127,8 @@ export const tlsAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (_server,
|
||||||
|
|
||||||
const { certs, total }: CertResult = await libs.requests.getCerts({
|
const { certs, total }: CertResult = await libs.requests.getCerts({
|
||||||
uptimeEsClient,
|
uptimeEsClient,
|
||||||
from: DEFAULT_FROM,
|
pageIndex: 0,
|
||||||
to: DEFAULT_TO,
|
size: 1000,
|
||||||
index: 0,
|
|
||||||
size: DEFAULT_SIZE,
|
|
||||||
notValidAfter: `now+${
|
notValidAfter: `now+${
|
||||||
dynamicSettings?.certExpirationThreshold ??
|
dynamicSettings?.certExpirationThreshold ??
|
||||||
DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold
|
DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { TLS_LEGACY } from '../../../common/constants/alerts';
|
||||||
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
|
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
|
||||||
import { Cert, CertResult } from '../../../common/runtime_types';
|
import { Cert, CertResult } from '../../../common/runtime_types';
|
||||||
import { commonStateTranslations, tlsTranslations } from './translations';
|
import { commonStateTranslations, tlsTranslations } from './translations';
|
||||||
import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs';
|
|
||||||
import { ActionGroupIdsOf } from '../../../../alerting/common';
|
import { ActionGroupIdsOf } from '../../../../alerting/common';
|
||||||
|
|
||||||
import { AlertInstanceContext } from '../../../../alerting/common';
|
import { AlertInstanceContext } from '../../../../alerting/common';
|
||||||
|
@ -21,13 +20,16 @@ import { AlertInstance } from '../../../../alerting/server';
|
||||||
|
|
||||||
import { savedObjectsAdapter } from '../saved_objects';
|
import { savedObjectsAdapter } from '../saved_objects';
|
||||||
import { createUptimeESClient } from '../lib';
|
import { createUptimeESClient } from '../lib';
|
||||||
|
import {
|
||||||
|
DEFAULT_FROM,
|
||||||
|
DEFAULT_SIZE,
|
||||||
|
DEFAULT_TO,
|
||||||
|
} from '../../../common/requests/get_certs_request_body';
|
||||||
|
|
||||||
export type ActionGroupIds = ActionGroupIdsOf<typeof TLS_LEGACY>;
|
export type ActionGroupIds = ActionGroupIdsOf<typeof TLS_LEGACY>;
|
||||||
|
|
||||||
type TLSAlertInstance = AlertInstance<Record<string, any>, AlertInstanceContext, ActionGroupIds>;
|
type TLSAlertInstance = AlertInstance<Record<string, any>, AlertInstanceContext, ActionGroupIds>;
|
||||||
|
|
||||||
const DEFAULT_SIZE = 20;
|
|
||||||
|
|
||||||
interface TlsAlertState {
|
interface TlsAlertState {
|
||||||
count: number;
|
count: number;
|
||||||
agingCount: number;
|
agingCount: number;
|
||||||
|
@ -125,7 +127,7 @@ export const tlsLegacyAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (_s
|
||||||
uptimeEsClient,
|
uptimeEsClient,
|
||||||
from: DEFAULT_FROM,
|
from: DEFAULT_FROM,
|
||||||
to: DEFAULT_TO,
|
to: DEFAULT_TO,
|
||||||
index: 0,
|
pageIndex: 0,
|
||||||
size: DEFAULT_SIZE,
|
size: DEFAULT_SIZE,
|
||||||
notValidAfter: `now+${
|
notValidAfter: `now+${
|
||||||
dynamicSettings?.certExpirationThreshold ??
|
dynamicSettings?.certExpirationThreshold ??
|
||||||
|
|
|
@ -58,9 +58,9 @@ export function createUptimeESClient({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseESClient: esClient,
|
baseESClient: esClient,
|
||||||
async search<TParams extends estypes.SearchRequest>(
|
async search<DocumentSource extends unknown, TParams extends estypes.SearchRequest>(
|
||||||
params: TParams
|
params: TParams
|
||||||
): Promise<{ body: ESSearchResponse<unknown, TParams> }> {
|
): Promise<{ body: ESSearchResponse<DocumentSource, TParams> }> {
|
||||||
let res: any;
|
let res: any;
|
||||||
let esError: any;
|
let esError: any;
|
||||||
const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(
|
const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(
|
||||||
|
@ -155,7 +155,3 @@ export function debugESCall({
|
||||||
}
|
}
|
||||||
console.log(`\n`);
|
console.log(`\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEsQuery<T extends estypes.SearchRequest>(params: T): T {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ describe('getCerts', () => {
|
||||||
|
|
||||||
const result = await getCerts({
|
const result = await getCerts({
|
||||||
uptimeEsClient,
|
uptimeEsClient,
|
||||||
index: 1,
|
pageIndex: 1,
|
||||||
from: 'now-2d',
|
from: 'now-2d',
|
||||||
to: 'now+1h',
|
to: 'now+1h',
|
||||||
search: 'my_common_name',
|
search: 'my_common_name',
|
||||||
|
|
|
@ -5,170 +5,37 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { PromiseType } from 'utility-types';
|
||||||
import { UMElasticsearchQueryFn } from '../adapters';
|
import { UMElasticsearchQueryFn } from '../adapters';
|
||||||
import { CertResult, GetCertsParams, Ping } from '../../../common/runtime_types';
|
import { CertResult, GetCertsParams, Ping } from '../../../common/runtime_types';
|
||||||
|
import {
|
||||||
|
getCertsRequestBody,
|
||||||
|
processCertsResult,
|
||||||
|
} from '../../../common/requests/get_certs_request_body';
|
||||||
|
import { UptimeESClient } from '../lib';
|
||||||
|
|
||||||
enum SortFields {
|
export const getCerts: UMElasticsearchQueryFn<GetCertsParams, CertResult> = async (
|
||||||
'issuer' = 'tls.server.x509.issuer.common_name',
|
requestParams
|
||||||
'not_after' = 'tls.server.x509.not_after',
|
) => {
|
||||||
'not_before' = 'tls.server.x509.not_before',
|
const result = await getCertsResults(requestParams);
|
||||||
'common_name' = 'tls.server.x509.subject.common_name',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCerts: UMElasticsearchQueryFn<GetCertsParams, CertResult> = async ({
|
return processCertsResult(result);
|
||||||
uptimeEsClient,
|
};
|
||||||
index,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
size,
|
|
||||||
search,
|
|
||||||
notValidBefore,
|
|
||||||
notValidAfter,
|
|
||||||
sortBy,
|
|
||||||
direction,
|
|
||||||
}) => {
|
|
||||||
const sort = SortFields[sortBy as keyof typeof SortFields];
|
|
||||||
|
|
||||||
const searchBody = {
|
export type CertificatesResults = PromiseType<ReturnType<typeof getCertsResults>>;
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (notValidBefore || notValidAfter) {
|
const getCertsResults = async (
|
||||||
const validityFilters: any = {
|
requestParams: GetCertsParams & { uptimeEsClient: UptimeESClient }
|
||||||
bool: {
|
) => {
|
||||||
should: [],
|
const { uptimeEsClient } = requestParams;
|
||||||
},
|
|
||||||
};
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Ping, typeof request>({
|
||||||
body: searchBody,
|
body: searchBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
const certs = (result?.hits?.hits ?? []).map((hit) => {
|
return result;
|
||||||
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 };
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { GetPingHistogramParams, HistogramResult } from '../../../common/runtime
|
||||||
import { QUERY } from '../../../common/constants';
|
import { QUERY } from '../../../common/constants';
|
||||||
import { getHistogramInterval } from '../helper/get_histogram_interval';
|
import { getHistogramInterval } from '../helper/get_histogram_interval';
|
||||||
import { UMElasticsearchQueryFn } from '../adapters/framework';
|
import { UMElasticsearchQueryFn } from '../adapters/framework';
|
||||||
import { createEsQuery } from '../lib';
|
import { createEsQuery } from '../../../common/utils/es_search';
|
||||||
|
|
||||||
export const getPingHistogram: UMElasticsearchQueryFn<
|
export const getPingHistogram: UMElasticsearchQueryFn<
|
||||||
GetPingHistogramParams,
|
GetPingHistogramParams,
|
||||||
|
|
|
@ -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<any> => {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -5,7 +5,6 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createGetCertsRoute } from './certs/certs';
|
|
||||||
import { createGetOverviewFilters } from './overview_filters';
|
import { createGetOverviewFilters } from './overview_filters';
|
||||||
import {
|
import {
|
||||||
createGetPingHistogramRoute,
|
createGetPingHistogramRoute,
|
||||||
|
@ -35,7 +34,6 @@ export { createRouteWithAuth } from './create_route_with_auth';
|
||||||
export { uptimeRouteWrapper } from './uptime_route_wrapper';
|
export { uptimeRouteWrapper } from './uptime_route_wrapper';
|
||||||
|
|
||||||
export const restApiRoutes: UMRestApiRouteFactory[] = [
|
export const restApiRoutes: UMRestApiRouteFactory[] = [
|
||||||
createGetCertsRoute,
|
|
||||||
createGetOverviewFilters,
|
createGetOverviewFilters,
|
||||||
createGetPingsRoute,
|
createGetPingsRoute,
|
||||||
createGetIndexPatternRoute,
|
createGetIndexPatternRoute,
|
||||||
|
|
|
@ -9,9 +9,12 @@ import expect from '@kbn/expect';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { isRight } from 'fp-ts/lib/Either';
|
import { isRight } from 'fp-ts/lib/Either';
|
||||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||||
import { API_URLS } from '../../../../../plugins/uptime/common/constants';
|
|
||||||
import { CertType } from '../../../../../plugins/uptime/common/runtime_types';
|
import { CertType } from '../../../../../plugins/uptime/common/runtime_types';
|
||||||
import { makeChecksWithStatus } from './helper/make_checks';
|
import { makeChecksWithStatus } from './helper/make_checks';
|
||||||
|
import {
|
||||||
|
processCertsResult,
|
||||||
|
getCertsRequestBody,
|
||||||
|
} from '../../../../../plugins/uptime/common/requests/get_certs_request_body';
|
||||||
|
|
||||||
export default function ({ getService }: FtrProviderContext) {
|
export default function ({ getService }: FtrProviderContext) {
|
||||||
const supertest = getService('supertest');
|
const supertest = getService('supertest');
|
||||||
|
@ -21,8 +24,18 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
describe('certs api', () => {
|
describe('certs api', () => {
|
||||||
describe('empty index', async () => {
|
describe('empty index', async () => {
|
||||||
it('returns empty array for no data', async () => {
|
it('returns empty array for no data', async () => {
|
||||||
const apiResponse = await supertest.get(API_URLS.CERTS);
|
const apiResponse = await supertest
|
||||||
expect(JSON.stringify(apiResponse.body)).to.eql('{"certs":[],"total":0}');
|
.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 () => {
|
it('retrieves expected cert data', async () => {
|
||||||
const apiResponse = await supertest.get(API_URLS.CERTS);
|
const { body } = await supertest
|
||||||
const { body } = apiResponse;
|
.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);
|
const result = processCertsResult(body.rawResponse);
|
||||||
expect(Array.isArray(body.certs)).to.be(true);
|
|
||||||
expect(body.certs).to.have.length(1);
|
|
||||||
|
|
||||||
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);
|
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(Array.isArray(cert.monitors)).to.be(true);
|
||||||
expect(cert.monitors[0]).to.eql({
|
expect(cert.monitors[0]).to.eql({
|
||||||
|
name: undefined,
|
||||||
id: monitorId,
|
id: monitorId,
|
||||||
url: 'http://localhost:5678/pattern?r=200x5,500x1',
|
url: 'http://localhost:5678/pattern?r=200x5,500x1',
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue