diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 89d5ebfc16f0f..0925c7c6db35f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -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": "アラートの作成、管理、監視によりデータへの変更を検知します。"
}
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 09ccc43c2c532..8dd2dd3ed985c 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -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": "通过创建、管理和监测警报来检测数据中的更改。"
}
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/uptime/common/constants/alerts.ts b/x-pack/plugins/uptime/common/constants/alerts.ts
index 37258fca3bc4d..cb31d83839590 100644
--- a/x-pack/plugins/uptime/common/constants/alerts.ts
+++ b/x-pack/plugins/uptime/common/constants/alerts.ts
@@ -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',
};
diff --git a/x-pack/plugins/uptime/public/lib/alert_types/index.ts b/x-pack/plugins/uptime/public/lib/alert_types/index.ts
index 36c84fe4c64cd..406b730fa1e6c 100644
--- a/x-pack/plugins/uptime/public/lib/alert_types/index.ts
+++ b/x-pack/plugins/uptime/public/lib/alert_types/index.ts
@@ -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,
];
diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls_legacy.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls_legacy.tsx
new file mode 100644
index 0000000000000..1abcdb2c98662
--- /dev/null
+++ b/x-pack/plugins/uptime/public/lib/alert_types/tls_legacy.tsx
@@ -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) => (
+
+ ),
+ description,
+ validate: () => ({ errors: {} }),
+ defaultActionMessage,
+ requiresAppContext: false,
+});
diff --git a/x-pack/plugins/uptime/public/lib/alert_types/translations.ts b/x-pack/plugins/uptime/public/lib/alert_types/translations.ts
index ea445e3d63c09..bb4af761d240d 100644
--- a/x-pack/plugins/uptime/public/lib/alert_types/translations.ts
+++ b/x-pack/plugins/uptime/public/lib/alert_types/translations.ts
@@ -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}
diff --git a/x-pack/plugins/uptime/server/lib/alerts/index.ts b/x-pack/plugins/uptime/server/lib/alerts/index.ts
index 1559ceaae8bb6..c695a4b052cd9 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/index.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/index.ts
@@ -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,
UptimeAlertTypeFactory,
+ UptimeAlertTypeFactory,
UptimeAlertTypeFactory
-] = [statusCheckAlertFactory, tlsAlertFactory, durationAnomalyAlertFactory];
+] = [statusCheckAlertFactory, tlsAlertFactory, tlsLegacyAlertFactory, durationAnomalyAlertFactory];
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 dde6ef8535365..a77fe10f0b9a4 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/tls.test.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/tls.test.ts
@@ -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',
+ });
});
});
});
diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts
index 2a2406a3629d0..f29744fdbb70f 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts
@@ -22,71 +22,80 @@ export type ActionGroupIds = ActionGroupIdsOf;
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}`)), '');
-
-const getValidAfter = ({ not_after: date }: Cert) => {
- if (!date) return 'Error, missing `certificate_not_valid_after` date.';
+ cert: Cert,
+ certLimitMessage: (cert: Cert) => TLSContent
+): TLSContent => certLimitMessage(cert);
+
+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;
+
+ if (isExpiring) {
+ content = mapCertsToSummaryString(cert, getValidAfter);
+ } else if (isAging) {
+ content = mapCertsToSummaryString(cert, getValidBefore);
+ }
- certs.sort((a, b) => sortCerts(a.not_before ?? '', b.not_before ?? ''));
- const aging = certs.filter((cert) => new Date(cert.not_before ?? '').valueOf() < ageThreshold);
+ 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 = (_server, libs) =>
uptimeAlertWrapper({
- 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 = (_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);
diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.test.ts b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.test.ts
new file mode 100644
index 0000000000000..4c6a721e92159
--- /dev/null
+++ b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.test.ts
@@ -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;
+
+ 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,
+ }
+ `);
+ });
+ });
+});
diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts
new file mode 100644
index 0000000000000..8f1c0093e60ac
--- /dev/null
+++ b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts
@@ -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;
+
+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 = (_server, libs) =>
+ uptimeAlertWrapper({
+ 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);
+ },
+ });
diff --git a/x-pack/plugins/uptime/server/lib/alerts/translations.ts b/x-pack/plugins/uptime/server/lib/alerts/translations.ts
index 3630185e19ab0..ee356eb68a626 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/translations.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/translations.ts
@@ -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 = {
diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
index bbd212b61e439..afc6dca936bbf 100644
--- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
@@ -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({});