mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Uptime] Refactor cert alerts from batched to individual (#102138)
* refactor cert alerts from batched to individual * remove old translations * create new certificate alert rule type and transition old cert rule type to legacy * update translations * maintain legacy tls rule UI to support legacy rule editing * update translations * update TLS alert content, rule type id, and alert instance id schema * remove extraneous logic and format date content Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e582549500
commit
450ababee5
13 changed files with 494 additions and 128 deletions
|
@ -23486,7 +23486,6 @@
|
|||
"xpack.uptime.alerts.tls.criteriaExpression.ariaLabel": "このアラートで監視されるモニターの条件を示す式",
|
||||
"xpack.uptime.alerts.tls.criteriaExpression.description": "タイミング",
|
||||
"xpack.uptime.alerts.tls.criteriaExpression.value": "任意のモニター",
|
||||
"xpack.uptime.alerts.tls.defaultActionMessage": "期限切れになるか古くなりすぎた{count} TLS個のTLS証明書証明書を検知しました。\n\n{expiringConditionalOpen}\n期限切れになる証明書数:{expiringCount}\n期限切れになる証明書:{expiringCommonNameAndDate}\n{expiringConditionalClose}\n\n{agingConditionalOpen}\n古い証明書数:{agingCount}\n古い証明書:{agingCommonNameAndDate}\n{agingConditionalClose}\n",
|
||||
"xpack.uptime.alerts.tls.description": "アップタイム監視の TLS 証明書の有効期限が近いときにアラートを発行します。",
|
||||
"xpack.uptime.alerts.tls.expirationExpression.ariaLabel": "証明書有効期限の TLS アラートをトリガーするしきい値を示す式",
|
||||
"xpack.uptime.alerts.tls.expirationExpression.description": "証明書が",
|
||||
|
@ -24337,4 +24336,4 @@
|
|||
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。",
|
||||
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23852,7 +23852,6 @@
|
|||
"xpack.uptime.alerts.tls.criteriaExpression.ariaLabel": "显示此告警监视的监测条件的表达式",
|
||||
"xpack.uptime.alerts.tls.criteriaExpression.description": "当",
|
||||
"xpack.uptime.alerts.tls.criteriaExpression.value": "任意监测",
|
||||
"xpack.uptime.alerts.tls.defaultActionMessage": "已检测到 {count} 个即将过期或即将过时的 TLS 证书。\n\n{expiringConditionalOpen}\n即将过期的证书计数:{expiringCount}\n即将过期的证书:{expiringCommonNameAndDate}\n{expiringConditionalClose}\n\n{agingConditionalOpen}\n过时的证书计数:{agingCount}\n过时的证书:{agingCommonNameAndDate}\n{agingConditionalClose}\n",
|
||||
"xpack.uptime.alerts.tls.description": "运行时间监测的 TLS 证书即将过期时告警。",
|
||||
"xpack.uptime.alerts.tls.expirationExpression.ariaLabel": "显示将触发证书过期 TLS 告警的阈值的表达式",
|
||||
"xpack.uptime.alerts.tls.expirationExpression.description": "具有将在",
|
||||
|
@ -24713,4 +24712,4 @@
|
|||
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。",
|
||||
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,8 @@
|
|||
import { ActionGroup } from '../../../alerting/common';
|
||||
|
||||
export type MonitorStatusActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.monitorStatus'>;
|
||||
export type TLSActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.tls'>;
|
||||
export type TLSLegacyActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.tls'>;
|
||||
export type TLSActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.tlsCertificate'>;
|
||||
export type DurationAnomalyActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.durationAnomaly'>;
|
||||
|
||||
export const MONITOR_STATUS: MonitorStatusActionGroup = {
|
||||
|
@ -16,8 +17,13 @@ export const MONITOR_STATUS: MonitorStatusActionGroup = {
|
|||
name: 'Uptime Down Monitor',
|
||||
};
|
||||
|
||||
export const TLS: TLSActionGroup = {
|
||||
export const TLS_LEGACY: TLSLegacyActionGroup = {
|
||||
id: 'xpack.uptime.alerts.actionGroups.tls',
|
||||
name: 'Uptime TLS Alert (Legacy)',
|
||||
};
|
||||
|
||||
export const TLS: TLSActionGroup = {
|
||||
id: 'xpack.uptime.alerts.actionGroups.tlsCertificate',
|
||||
name: 'Uptime TLS Alert',
|
||||
};
|
||||
|
||||
|
@ -28,16 +34,19 @@ export const DURATION_ANOMALY: DurationAnomalyActionGroup = {
|
|||
|
||||
export const ACTION_GROUP_DEFINITIONS: {
|
||||
MONITOR_STATUS: MonitorStatusActionGroup;
|
||||
TLS_LEGACY: TLSLegacyActionGroup;
|
||||
TLS: TLSActionGroup;
|
||||
DURATION_ANOMALY: DurationAnomalyActionGroup;
|
||||
} = {
|
||||
MONITOR_STATUS,
|
||||
TLS_LEGACY,
|
||||
TLS,
|
||||
DURATION_ANOMALY,
|
||||
};
|
||||
|
||||
export const CLIENT_ALERT_TYPES = {
|
||||
MONITOR_STATUS: 'xpack.uptime.alerts.monitorStatus',
|
||||
TLS: 'xpack.uptime.alerts.tls',
|
||||
TLS_LEGACY: 'xpack.uptime.alerts.tls',
|
||||
TLS: 'xpack.uptime.alerts.tlsCertificate',
|
||||
DURATION_ANOMALY: 'xpack.uptime.alerts.durationAnomaly',
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { CoreStart } from 'kibana/public';
|
|||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public';
|
||||
import { initMonitorStatusAlertType } from './monitor_status';
|
||||
import { initTlsAlertType } from './tls';
|
||||
import { initTlsLegacyAlertType } from './tls_legacy';
|
||||
import { ClientPluginsStart } from '../../apps/plugin';
|
||||
import { initDurationAnomalyAlertType } from './duration_anomaly';
|
||||
|
||||
|
@ -20,5 +21,6 @@ export type AlertTypeInitializer = (dependenies: {
|
|||
export const alertTypeInitializers: AlertTypeInitializer[] = [
|
||||
initMonitorStatusAlertType,
|
||||
initTlsAlertType,
|
||||
initTlsLegacyAlertType,
|
||||
initDurationAnomalyAlertType,
|
||||
];
|
||||
|
|
32
x-pack/plugins/uptime/public/lib/alert_types/tls_legacy.tsx
Normal file
32
x-pack/plugins/uptime/public/lib/alert_types/tls_legacy.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public';
|
||||
import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts';
|
||||
import { TlsTranslationsLegacy } from './translations';
|
||||
import { AlertTypeInitializer } from '.';
|
||||
|
||||
const { defaultActionMessage, description } = TlsTranslationsLegacy;
|
||||
const TLSAlert = React.lazy(() => import('./lazy_wrapper/tls_alert'));
|
||||
export const initTlsLegacyAlertType: AlertTypeInitializer = ({
|
||||
core,
|
||||
plugins,
|
||||
}): AlertTypeModel => ({
|
||||
id: CLIENT_ALERT_TYPES.TLS_LEGACY,
|
||||
iconClass: 'uptimeApp',
|
||||
documentationUrl(docLinks) {
|
||||
return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/uptime/${docLinks.DOC_LINK_VERSION}/uptime-alerting.html#_tls_alerts`;
|
||||
},
|
||||
alertParamsExpression: (params: any) => (
|
||||
<TLSAlert core={core} plugins={plugins} params={params} />
|
||||
),
|
||||
description,
|
||||
validate: () => ({ errors: {} }),
|
||||
defaultActionMessage,
|
||||
requiresAppContext: false,
|
||||
});
|
|
@ -8,14 +8,32 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TlsTranslations = {
|
||||
defaultActionMessage: i18n.translate('xpack.uptime.alerts.tls.legacy.defaultActionMessage', {
|
||||
defaultMessage: `Detected TLS certificate {commonName} from issuer {issuer} is {status}. Certificate {summary}
|
||||
`,
|
||||
values: {
|
||||
commonName: '{{state.commonName}}',
|
||||
issuer: '{{state.issuer}}',
|
||||
summary: '{{state.summary}}',
|
||||
status: '{{state.status}}',
|
||||
},
|
||||
}),
|
||||
name: i18n.translate('xpack.uptime.alerts.tls.legacy.clientName', {
|
||||
defaultMessage: 'Uptime TLS (Legacy)',
|
||||
}),
|
||||
description: i18n.translate('xpack.uptime.alerts.tls.legacy.description', {
|
||||
defaultMessage:
|
||||
'Alert when the TLS certificate of an Uptime monitor is about to expire. This alert will be deprecated in a future version.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const TlsTranslationsLegacy = {
|
||||
defaultActionMessage: i18n.translate('xpack.uptime.alerts.tls.defaultActionMessage', {
|
||||
defaultMessage: `Detected {count} TLS certificates expiring or becoming too old.
|
||||
|
||||
{expiringConditionalOpen}
|
||||
Expiring cert count: {expiringCount}
|
||||
Expiring Certificates: {expiringCommonNameAndDate}
|
||||
{expiringConditionalClose}
|
||||
|
||||
{agingConditionalOpen}
|
||||
Aging cert count: {agingCount}
|
||||
Aging Certificates: {agingCommonNameAndDate}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { UptimeAlertTypeFactory } from './types';
|
||||
import { statusCheckAlertFactory, ActionGroupIds as statusCheckActionGroup } from './status_check';
|
||||
import { tlsAlertFactory, ActionGroupIds as tlsActionGroup } from './tls';
|
||||
import { tlsLegacyAlertFactory, ActionGroupIds as tlsLegacyActionGroup } from './tls_legacy';
|
||||
import {
|
||||
durationAnomalyAlertFactory,
|
||||
ActionGroupIds as durationAnomalyActionGroup,
|
||||
|
@ -16,5 +17,6 @@ import {
|
|||
export const uptimeAlertTypeFactories: [
|
||||
UptimeAlertTypeFactory<statusCheckActionGroup>,
|
||||
UptimeAlertTypeFactory<tlsActionGroup>,
|
||||
UptimeAlertTypeFactory<tlsLegacyActionGroup>,
|
||||
UptimeAlertTypeFactory<durationAnomalyActionGroup>
|
||||
] = [statusCheckAlertFactory, tlsAlertFactory, durationAnomalyAlertFactory];
|
||||
] = [statusCheckAlertFactory, tlsAlertFactory, tlsLegacyAlertFactory, durationAnomalyAlertFactory];
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('tls alert', () => {
|
|||
common_name: 'Common-One',
|
||||
monitors: [{ name: 'monitor-one', id: 'monitor1' }],
|
||||
sha256: 'abc',
|
||||
issuer: 'Cloudflare Inc ECC CA-3',
|
||||
},
|
||||
{
|
||||
not_after: '2020-07-18T03:15:39.000Z',
|
||||
|
@ -30,6 +31,7 @@ describe('tls alert', () => {
|
|||
common_name: 'Common-Two',
|
||||
monitors: [{ name: 'monitor-two', id: 'monitor2' }],
|
||||
sha256: 'bcd',
|
||||
issuer: 'Cloudflare Inc ECC CA-3',
|
||||
},
|
||||
{
|
||||
not_after: '2020-07-19T03:15:39.000Z',
|
||||
|
@ -37,6 +39,7 @@ describe('tls alert', () => {
|
|||
common_name: 'Common-Three',
|
||||
monitors: [{ name: 'monitor-three', id: 'monitor3' }],
|
||||
sha256: 'cde',
|
||||
issuer: 'Cloudflare Inc ECC CA-3',
|
||||
},
|
||||
{
|
||||
not_after: '2020-07-25T03:15:39.000Z',
|
||||
|
@ -44,6 +47,7 @@ describe('tls alert', () => {
|
|||
common_name: 'Common-Four',
|
||||
monitors: [{ name: 'monitor-four', id: 'monitor4' }],
|
||||
sha256: 'def',
|
||||
issuer: 'Cloudflare Inc ECC CA-3',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
@ -52,88 +56,66 @@ describe('tls alert', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('sorts expiring certs appropriately when creating summary', () => {
|
||||
diffSpy.mockReturnValueOnce(900).mockReturnValueOnce(901).mockReturnValueOnce(902);
|
||||
it('handles positive diffs for expired certs appropriately', () => {
|
||||
diffSpy.mockReturnValueOnce(900);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
mockCerts[0],
|
||||
new Date('2020-07-20T05:00:00.000Z').valueOf(),
|
||||
new Date('2019-03-01T00:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "",
|
||||
"agingCount": 0,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z 900 days ago.; Common-Two, expired on 2020-07-18T03:15:39.000Z 901 days ago.; Common-Three, expired on 2020-07-19T03:15:39.000Z 902 days ago.",
|
||||
"expiringCount": 3,
|
||||
"hasAging": null,
|
||||
"hasExpired": true,
|
||||
}
|
||||
`);
|
||||
expect(result).toEqual({
|
||||
commonName: mockCerts[0].common_name,
|
||||
issuer: mockCerts[0].issuer,
|
||||
summary: 'expired on Jul 15, 2020 EDT, 900 days ago.',
|
||||
status: 'expired',
|
||||
});
|
||||
});
|
||||
|
||||
it('sorts aging certs appropriate when creating summary', () => {
|
||||
diffSpy.mockReturnValueOnce(702).mockReturnValueOnce(701).mockReturnValueOnce(700);
|
||||
it('handles positive diffs for agining certs appropriately', () => {
|
||||
diffSpy.mockReturnValueOnce(702);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
mockCerts[0],
|
||||
new Date('2020-07-01T12:00:00.000Z').valueOf(),
|
||||
new Date('2019-09-01T03:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 702 days ago.; Common-Three, valid since 2019-07-22T03:15:39.000Z, 701 days ago.; Common-One, valid since 2019-07-24T03:15:39.000Z, 700 days ago.",
|
||||
"agingCount": 4,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "",
|
||||
"expiringCount": 0,
|
||||
"hasAging": true,
|
||||
"hasExpired": null,
|
||||
}
|
||||
`);
|
||||
expect(result).toEqual({
|
||||
commonName: mockCerts[0].common_name,
|
||||
issuer: mockCerts[0].issuer,
|
||||
summary: 'valid since Jul 23, 2019 EDT, 702 days ago.',
|
||||
status: 'becoming too old',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles negative diff values appropriately for aging certs', () => {
|
||||
diffSpy.mockReturnValueOnce(700).mockReturnValueOnce(-90).mockReturnValueOnce(-80);
|
||||
diffSpy.mockReturnValueOnce(-90);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
mockCerts[0],
|
||||
new Date('2020-07-01T12:00:00.000Z').valueOf(),
|
||||
new Date('2019-09-01T03:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 700 days ago.; Common-Three, invalid until 2019-07-22T03:15:39.000Z, 90 days from now.; Common-One, invalid until 2019-07-24T03:15:39.000Z, 80 days from now.",
|
||||
"agingCount": 4,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "",
|
||||
"expiringCount": 0,
|
||||
"hasAging": true,
|
||||
"hasExpired": null,
|
||||
}
|
||||
`);
|
||||
expect(result).toEqual({
|
||||
commonName: mockCerts[0].common_name,
|
||||
issuer: mockCerts[0].issuer,
|
||||
summary: 'invalid until Jul 23, 2019 EDT, 90 days from now.',
|
||||
status: 'invalid',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles negative diff values appropriately for expiring certs', () => {
|
||||
diffSpy
|
||||
// negative days are in the future, positive days are in the past
|
||||
.mockReturnValueOnce(-96)
|
||||
.mockReturnValueOnce(-94)
|
||||
.mockReturnValueOnce(2);
|
||||
.mockReturnValueOnce(-96);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
mockCerts[0],
|
||||
new Date('2020-07-20T05:00:00.000Z').valueOf(),
|
||||
new Date('2019-03-01T00:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "",
|
||||
"agingCount": 0,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 96 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 94 days.; Common-Three, expired on 2020-07-19T03:15:39.000Z 2 days ago.",
|
||||
"expiringCount": 3,
|
||||
"hasAging": null,
|
||||
"hasExpired": true,
|
||||
}
|
||||
`);
|
||||
expect(result).toEqual({
|
||||
commonName: mockCerts[0].common_name,
|
||||
issuer: mockCerts[0].issuer,
|
||||
summary: 'expires on Jul 15, 2020 EDT in 96 days.',
|
||||
status: 'expiring',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,71 +22,80 @@ export type ActionGroupIds = ActionGroupIdsOf<typeof TLS>;
|
|||
const DEFAULT_SIZE = 20;
|
||||
|
||||
interface TlsAlertState {
|
||||
count: number;
|
||||
agingCount: number;
|
||||
agingCommonNameAndDate: string;
|
||||
expiringCount: number;
|
||||
expiringCommonNameAndDate: string;
|
||||
hasAging: true | null;
|
||||
hasExpired: true | null;
|
||||
commonName: string;
|
||||
issuer: string;
|
||||
summary: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf();
|
||||
interface TLSContent {
|
||||
summary: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
const mapCertsToSummaryString = (
|
||||
certs: Cert[],
|
||||
certLimitMessage: (cert: Cert) => string,
|
||||
maxSummaryItems: number
|
||||
): string =>
|
||||
certs
|
||||
.slice(0, maxSummaryItems)
|
||||
.map((cert) => `${cert.common_name}, ${certLimitMessage(cert)}`)
|
||||
.reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), '');
|
||||
cert: Cert,
|
||||
certLimitMessage: (cert: Cert) => TLSContent
|
||||
): TLSContent => certLimitMessage(cert);
|
||||
|
||||
const getValidAfter = ({ not_after: date }: Cert) => {
|
||||
if (!date) return 'Error, missing `certificate_not_valid_after` date.';
|
||||
const getValidAfter = ({ not_after: date }: Cert): TLSContent => {
|
||||
if (!date) return { summary: 'Error, missing `certificate_not_valid_after` date.' };
|
||||
const relativeDate = moment().diff(date, 'days');
|
||||
const formattedDate = moment(date).format('MMM D, YYYY z');
|
||||
return relativeDate >= 0
|
||||
? tlsTranslations.validAfterExpiredString(date, relativeDate)
|
||||
: tlsTranslations.validAfterExpiringString(date, Math.abs(relativeDate));
|
||||
? {
|
||||
summary: tlsTranslations.validAfterExpiredString(formattedDate, relativeDate),
|
||||
status: tlsTranslations.expiredLabel,
|
||||
}
|
||||
: {
|
||||
summary: tlsTranslations.validAfterExpiringString(formattedDate, Math.abs(relativeDate)),
|
||||
status: tlsTranslations.expiringLabel,
|
||||
};
|
||||
};
|
||||
|
||||
const getValidBefore = ({ not_before: date }: Cert): string => {
|
||||
if (!date) return 'Error, missing `certificate_not_valid_before` date.';
|
||||
const getValidBefore = ({ not_before: date }: Cert): TLSContent => {
|
||||
if (!date) return { summary: 'Error, missing `certificate_not_valid_before` date.' };
|
||||
const relativeDate = moment().diff(date, 'days');
|
||||
const formattedDate = moment(date).format('MMM D, YYYY z');
|
||||
return relativeDate >= 0
|
||||
? tlsTranslations.validBeforeExpiredString(date, relativeDate)
|
||||
: tlsTranslations.validBeforeExpiringString(date, Math.abs(relativeDate));
|
||||
? {
|
||||
summary: tlsTranslations.validBeforeExpiredString(formattedDate, relativeDate),
|
||||
status: tlsTranslations.agingLabel,
|
||||
}
|
||||
: {
|
||||
summary: tlsTranslations.validBeforeExpiringString(formattedDate, Math.abs(relativeDate)),
|
||||
status: tlsTranslations.invalidLabel,
|
||||
};
|
||||
};
|
||||
|
||||
export const getCertSummary = (
|
||||
certs: Cert[],
|
||||
cert: Cert,
|
||||
expirationThreshold: number,
|
||||
ageThreshold: number,
|
||||
maxSummaryItems: number = 3
|
||||
ageThreshold: number
|
||||
): TlsAlertState => {
|
||||
certs.sort((a, b) => sortCerts(a.not_after ?? '', b.not_after ?? ''));
|
||||
const expiring = certs.filter(
|
||||
(cert) => new Date(cert.not_after ?? '').valueOf() < expirationThreshold
|
||||
);
|
||||
const isExpiring = new Date(cert.not_after ?? '').valueOf() < expirationThreshold;
|
||||
const isAging = new Date(cert.not_before ?? '').valueOf() < ageThreshold;
|
||||
let content: TLSContent | null = null;
|
||||
|
||||
certs.sort((a, b) => sortCerts(a.not_before ?? '', b.not_before ?? ''));
|
||||
const aging = certs.filter((cert) => new Date(cert.not_before ?? '').valueOf() < ageThreshold);
|
||||
if (isExpiring) {
|
||||
content = mapCertsToSummaryString(cert, getValidAfter);
|
||||
} else if (isAging) {
|
||||
content = mapCertsToSummaryString(cert, getValidBefore);
|
||||
}
|
||||
|
||||
const { summary = '', status = '' } = content || {};
|
||||
|
||||
return {
|
||||
count: certs.length,
|
||||
agingCount: aging.length,
|
||||
agingCommonNameAndDate: mapCertsToSummaryString(aging, getValidBefore, maxSummaryItems),
|
||||
expiringCommonNameAndDate: mapCertsToSummaryString(expiring, getValidAfter, maxSummaryItems),
|
||||
expiringCount: expiring.length,
|
||||
hasAging: aging.length > 0 ? true : null,
|
||||
hasExpired: expiring.length > 0 ? true : null,
|
||||
commonName: cert.common_name ?? '',
|
||||
issuer: cert.issuer ?? '',
|
||||
summary,
|
||||
status,
|
||||
};
|
||||
};
|
||||
|
||||
export const tlsAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (_server, libs) =>
|
||||
uptimeAlertWrapper<ActionGroupIds>({
|
||||
id: 'xpack.uptime.alerts.tls',
|
||||
id: 'xpack.uptime.alerts.tlsCertificate',
|
||||
name: tlsTranslations.alertFactoryName,
|
||||
validate: {
|
||||
params: schema.object({}),
|
||||
|
@ -129,26 +138,30 @@ export const tlsAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (_server,
|
|||
const foundCerts = total > 0;
|
||||
|
||||
if (foundCerts) {
|
||||
const absoluteExpirationThreshold = moment()
|
||||
.add(
|
||||
dynamicSettings.certExpirationThreshold ??
|
||||
DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold,
|
||||
'd'
|
||||
)
|
||||
.valueOf();
|
||||
const absoluteAgeThreshold = moment()
|
||||
.subtract(
|
||||
dynamicSettings.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold,
|
||||
'd'
|
||||
)
|
||||
.valueOf();
|
||||
const alertInstance = alertInstanceFactory(TLS.id);
|
||||
const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold);
|
||||
alertInstance.replaceState({
|
||||
...updateState(state, foundCerts),
|
||||
...summary,
|
||||
certs.forEach((cert) => {
|
||||
const absoluteExpirationThreshold = moment()
|
||||
.add(
|
||||
dynamicSettings.certExpirationThreshold ??
|
||||
DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold,
|
||||
'd'
|
||||
)
|
||||
.valueOf();
|
||||
const absoluteAgeThreshold = moment()
|
||||
.subtract(
|
||||
dynamicSettings.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold,
|
||||
'd'
|
||||
)
|
||||
.valueOf();
|
||||
const alertInstance = alertInstanceFactory(
|
||||
`${cert.common_name}-${cert.issuer?.replace(/\s/g, '_')}-${cert.sha256}`
|
||||
);
|
||||
const summary = getCertSummary(cert, absoluteExpirationThreshold, absoluteAgeThreshold);
|
||||
alertInstance.replaceState({
|
||||
...updateState(state, foundCerts),
|
||||
...summary,
|
||||
});
|
||||
alertInstance.scheduleActions(TLS.id);
|
||||
});
|
||||
alertInstance.scheduleActions(TLS.id);
|
||||
}
|
||||
|
||||
return updateState(state, foundCerts);
|
||||
|
|
139
x-pack/plugins/uptime/server/lib/alerts/tls_legacy.test.ts
Normal file
139
x-pack/plugins/uptime/server/lib/alerts/tls_legacy.test.ts
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* 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 moment from 'moment';
|
||||
import { getCertSummary } from './tls_legacy';
|
||||
import { Cert } from '../../../common/runtime_types';
|
||||
|
||||
describe('tls alert', () => {
|
||||
describe('getCertSummary', () => {
|
||||
let mockCerts: Cert[];
|
||||
let diffSpy: jest.SpyInstance<any, unknown[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
diffSpy = jest.spyOn(moment.prototype, 'diff');
|
||||
mockCerts = [
|
||||
{
|
||||
not_after: '2020-07-16T03:15:39.000Z',
|
||||
not_before: '2019-07-24T03:15:39.000Z',
|
||||
common_name: 'Common-One',
|
||||
monitors: [{ name: 'monitor-one', id: 'monitor1' }],
|
||||
sha256: 'abc',
|
||||
},
|
||||
{
|
||||
not_after: '2020-07-18T03:15:39.000Z',
|
||||
not_before: '2019-07-20T03:15:39.000Z',
|
||||
common_name: 'Common-Two',
|
||||
monitors: [{ name: 'monitor-two', id: 'monitor2' }],
|
||||
sha256: 'bcd',
|
||||
},
|
||||
{
|
||||
not_after: '2020-07-19T03:15:39.000Z',
|
||||
not_before: '2019-07-22T03:15:39.000Z',
|
||||
common_name: 'Common-Three',
|
||||
monitors: [{ name: 'monitor-three', id: 'monitor3' }],
|
||||
sha256: 'cde',
|
||||
},
|
||||
{
|
||||
not_after: '2020-07-25T03:15:39.000Z',
|
||||
not_before: '2019-07-25T03:15:39.000Z',
|
||||
common_name: 'Common-Four',
|
||||
monitors: [{ name: 'monitor-four', id: 'monitor4' }],
|
||||
sha256: 'def',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('sorts expiring certs appropriately when creating summary', () => {
|
||||
diffSpy.mockReturnValueOnce(900).mockReturnValueOnce(901).mockReturnValueOnce(902);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
new Date('2020-07-20T05:00:00.000Z').valueOf(),
|
||||
new Date('2019-03-01T00:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "",
|
||||
"agingCount": 0,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z, 900 days ago.; Common-Two, expired on 2020-07-18T03:15:39.000Z, 901 days ago.; Common-Three, expired on 2020-07-19T03:15:39.000Z, 902 days ago.",
|
||||
"expiringCount": 3,
|
||||
"hasAging": null,
|
||||
"hasExpired": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('sorts aging certs appropriate when creating summary', () => {
|
||||
diffSpy.mockReturnValueOnce(702).mockReturnValueOnce(701).mockReturnValueOnce(700);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
new Date('2020-07-01T12:00:00.000Z').valueOf(),
|
||||
new Date('2019-09-01T03:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 702 days ago.; Common-Three, valid since 2019-07-22T03:15:39.000Z, 701 days ago.; Common-One, valid since 2019-07-24T03:15:39.000Z, 700 days ago.",
|
||||
"agingCount": 4,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "",
|
||||
"expiringCount": 0,
|
||||
"hasAging": true,
|
||||
"hasExpired": null,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles negative diff values appropriately for aging certs', () => {
|
||||
diffSpy.mockReturnValueOnce(700).mockReturnValueOnce(-90).mockReturnValueOnce(-80);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
new Date('2020-07-01T12:00:00.000Z').valueOf(),
|
||||
new Date('2019-09-01T03:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 700 days ago.; Common-Three, invalid until 2019-07-22T03:15:39.000Z, 90 days from now.; Common-One, invalid until 2019-07-24T03:15:39.000Z, 80 days from now.",
|
||||
"agingCount": 4,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "",
|
||||
"expiringCount": 0,
|
||||
"hasAging": true,
|
||||
"hasExpired": null,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles negative diff values appropriately for expiring certs', () => {
|
||||
diffSpy
|
||||
// negative days are in the future, positive days are in the past
|
||||
.mockReturnValueOnce(-96)
|
||||
.mockReturnValueOnce(-94)
|
||||
.mockReturnValueOnce(2);
|
||||
const result = getCertSummary(
|
||||
mockCerts,
|
||||
new Date('2020-07-20T05:00:00.000Z').valueOf(),
|
||||
new Date('2019-03-01T00:00:00.000Z').valueOf()
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"agingCommonNameAndDate": "",
|
||||
"agingCount": 0,
|
||||
"count": 4,
|
||||
"expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 96 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 94 days.; Common-Three, expired on 2020-07-19T03:15:39.000Z, 2 days ago.",
|
||||
"expiringCount": 3,
|
||||
"hasAging": null,
|
||||
"hasExpired": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
156
x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts
Normal file
156
x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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 moment from 'moment';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { UptimeAlertTypeFactory } from './types';
|
||||
import { updateState } from './common';
|
||||
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 { uptimeAlertWrapper } from './uptime_alert_wrapper';
|
||||
import { ActionGroupIdsOf } from '../../../../alerting/common';
|
||||
|
||||
export type ActionGroupIds = ActionGroupIdsOf<typeof TLS_LEGACY>;
|
||||
|
||||
const DEFAULT_SIZE = 20;
|
||||
|
||||
interface TlsAlertState {
|
||||
count: number;
|
||||
agingCount: number;
|
||||
agingCommonNameAndDate: string;
|
||||
expiringCount: number;
|
||||
expiringCommonNameAndDate: string;
|
||||
hasAging: true | null;
|
||||
hasExpired: true | null;
|
||||
}
|
||||
|
||||
const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf();
|
||||
|
||||
const mapCertsToSummaryString = (
|
||||
certs: Cert[],
|
||||
certLimitMessage: (cert: Cert) => string,
|
||||
maxSummaryItems: number
|
||||
): string =>
|
||||
certs
|
||||
.slice(0, maxSummaryItems)
|
||||
.map((cert) => `${cert.common_name}, ${certLimitMessage(cert)}`)
|
||||
.reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), '');
|
||||
|
||||
const getValidAfter = ({ not_after: date }: Cert) => {
|
||||
if (!date) return 'Error, missing `certificate_not_valid_after` date.';
|
||||
const relativeDate = moment().diff(date, 'days');
|
||||
return relativeDate >= 0
|
||||
? tlsTranslations.validAfterExpiredString(date, relativeDate)
|
||||
: tlsTranslations.validAfterExpiringString(date, Math.abs(relativeDate));
|
||||
};
|
||||
|
||||
const getValidBefore = ({ not_before: date }: Cert): string => {
|
||||
if (!date) return 'Error, missing `certificate_not_valid_before` date.';
|
||||
const relativeDate = moment().diff(date, 'days');
|
||||
return relativeDate >= 0
|
||||
? tlsTranslations.validBeforeExpiredString(date, relativeDate)
|
||||
: tlsTranslations.validBeforeExpiringString(date, Math.abs(relativeDate));
|
||||
};
|
||||
|
||||
export const getCertSummary = (
|
||||
certs: Cert[],
|
||||
expirationThreshold: number,
|
||||
ageThreshold: number,
|
||||
maxSummaryItems: number = 3
|
||||
): TlsAlertState => {
|
||||
certs.sort((a, b) => sortCerts(a.not_after ?? '', b.not_after ?? ''));
|
||||
const expiring = certs.filter(
|
||||
(cert) => new Date(cert.not_after ?? '').valueOf() < expirationThreshold
|
||||
);
|
||||
|
||||
certs.sort((a, b) => sortCerts(a.not_before ?? '', b.not_before ?? ''));
|
||||
const aging = certs.filter((cert) => new Date(cert.not_before ?? '').valueOf() < ageThreshold);
|
||||
|
||||
return {
|
||||
count: certs.length,
|
||||
agingCount: aging.length,
|
||||
agingCommonNameAndDate: mapCertsToSummaryString(aging, getValidBefore, maxSummaryItems),
|
||||
expiringCommonNameAndDate: mapCertsToSummaryString(expiring, getValidAfter, maxSummaryItems),
|
||||
expiringCount: expiring.length,
|
||||
hasAging: aging.length > 0 ? true : null,
|
||||
hasExpired: expiring.length > 0 ? true : null,
|
||||
};
|
||||
};
|
||||
|
||||
export const tlsLegacyAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (_server, libs) =>
|
||||
uptimeAlertWrapper<ActionGroupIds>({
|
||||
id: 'xpack.uptime.alerts.tls',
|
||||
name: tlsTranslations.legacyAlertFactoryName,
|
||||
validate: {
|
||||
params: schema.object({}),
|
||||
},
|
||||
defaultActionGroupId: TLS_LEGACY.id,
|
||||
actionGroups: [
|
||||
{
|
||||
id: TLS_LEGACY.id,
|
||||
name: TLS_LEGACY.name,
|
||||
},
|
||||
],
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [...tlsTranslations.actionVariables, ...commonStateTranslations],
|
||||
},
|
||||
minimumLicenseRequired: 'basic',
|
||||
async executor({ options, dynamicSettings, uptimeEsClient }) {
|
||||
const {
|
||||
services: { alertInstanceFactory },
|
||||
state,
|
||||
} = options;
|
||||
|
||||
const { certs, total }: CertResult = await libs.requests.getCerts({
|
||||
uptimeEsClient,
|
||||
from: DEFAULT_FROM,
|
||||
to: DEFAULT_TO,
|
||||
index: 0,
|
||||
size: DEFAULT_SIZE,
|
||||
notValidAfter: `now+${
|
||||
dynamicSettings?.certExpirationThreshold ??
|
||||
DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold
|
||||
}d`,
|
||||
notValidBefore: `now-${
|
||||
dynamicSettings?.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold
|
||||
}d`,
|
||||
sortBy: 'common_name',
|
||||
direction: 'desc',
|
||||
});
|
||||
|
||||
const foundCerts = total > 0;
|
||||
|
||||
if (foundCerts) {
|
||||
const absoluteExpirationThreshold = moment()
|
||||
.add(
|
||||
dynamicSettings.certExpirationThreshold ??
|
||||
DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold,
|
||||
'd'
|
||||
)
|
||||
.valueOf();
|
||||
const absoluteAgeThreshold = moment()
|
||||
.subtract(
|
||||
dynamicSettings.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold,
|
||||
'd'
|
||||
)
|
||||
.valueOf();
|
||||
const alertInstance = alertInstanceFactory(TLS_LEGACY.id);
|
||||
const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold);
|
||||
alertInstance.replaceState({
|
||||
...updateState(state, foundCerts),
|
||||
...summary,
|
||||
});
|
||||
alertInstance.scheduleActions(TLS_LEGACY.id);
|
||||
}
|
||||
|
||||
return updateState(state, foundCerts);
|
||||
},
|
||||
});
|
|
@ -151,6 +151,9 @@ export const tlsTranslations = {
|
|||
alertFactoryName: i18n.translate('xpack.uptime.alerts.tls', {
|
||||
defaultMessage: 'Uptime TLS',
|
||||
}),
|
||||
legacyAlertFactoryName: i18n.translate('xpack.uptime.alerts.tlsLegacy', {
|
||||
defaultMessage: 'Uptime TLS (Legacy)',
|
||||
}),
|
||||
actionVariables: [
|
||||
{
|
||||
name: 'count',
|
||||
|
@ -191,7 +194,7 @@ export const tlsTranslations = {
|
|||
],
|
||||
validAfterExpiredString: (date: string, relativeDate: number) =>
|
||||
i18n.translate('xpack.uptime.alerts.tls.validAfterExpiredString', {
|
||||
defaultMessage: `expired on {date} {relativeDate} days ago.`,
|
||||
defaultMessage: `expired on {date}, {relativeDate} days ago.`,
|
||||
values: {
|
||||
date,
|
||||
relativeDate,
|
||||
|
@ -221,6 +224,18 @@ export const tlsTranslations = {
|
|||
relativeDate,
|
||||
},
|
||||
}),
|
||||
expiredLabel: i18n.translate('xpack.uptime.alerts.tls.expiredLabel', {
|
||||
defaultMessage: 'expired',
|
||||
}),
|
||||
expiringLabel: i18n.translate('xpack.uptime.alerts.tls.expiringLabel', {
|
||||
defaultMessage: 'expiring',
|
||||
}),
|
||||
agingLabel: i18n.translate('xpack.uptime.alerts.tls.agingLabel', {
|
||||
defaultMessage: 'becoming too old',
|
||||
}),
|
||||
invalidLabel: i18n.translate('xpack.uptime.alerts.tls.invalidLabel', {
|
||||
defaultMessage: 'invalid',
|
||||
}),
|
||||
};
|
||||
|
||||
export const durationAnomalyTranslations = {
|
||||
|
|
|
@ -201,7 +201,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
} = alert;
|
||||
try {
|
||||
expect(actions).to.eql([]);
|
||||
expect(alertTypeId).to.eql('xpack.uptime.alerts.tls');
|
||||
expect(alertTypeId).to.eql('xpack.uptime.alerts.tlsCertificate');
|
||||
expect(consumer).to.eql('uptime');
|
||||
expect(tags).to.eql(['uptime', 'certs']);
|
||||
expect(params).to.eql({});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue