mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Synthetics] Fix TLS alert recovery (#161254)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Abdul Wahab Zahid <awahab07@yahoo.com>
This commit is contained in:
parent
bed184b829
commit
0fe62fba0b
15 changed files with 217 additions and 132 deletions
|
@ -131,6 +131,8 @@ export const getCertsRequestBody = ({
|
|||
},
|
||||
},
|
||||
_source: [
|
||||
'@timestamp',
|
||||
'config_id',
|
||||
'monitor.id',
|
||||
'monitor.name',
|
||||
'monitor.type',
|
||||
|
@ -201,7 +203,9 @@ export const processCertsResult = (result: CertificatesResults): CertResult => {
|
|||
not_before: notBefore,
|
||||
common_name: commonName,
|
||||
monitorName: ping?.monitor?.name,
|
||||
configId: ping.config_id!,
|
||||
monitorUrl: ping?.url?.full,
|
||||
'@timestamp': ping['@timestamp'],
|
||||
monitorType: ping?.monitor?.type,
|
||||
locationName: ping?.observer?.geo?.name,
|
||||
};
|
||||
|
|
|
@ -75,7 +75,7 @@ export const SyntheticsMonitorStatusTranslations = {
|
|||
|
||||
export const TlsTranslations = {
|
||||
defaultActionMessage: i18n.translate('xpack.synthetics.rules.tls.defaultActionMessage', {
|
||||
defaultMessage: `Detected TLS certificate {commonName} is {status} - Elastic Synthetics\n\nDetails:\n\n- Summary: {summary}\n- Common name: {commonName}\n- Issuer: {issuer}\n- Monitor: {monitorName} \n- Monitor URL: {monitorUrl} \n- Monitor type: {monitorType} \n- From: {locationName}`,
|
||||
defaultMessage: `TLS certificate {commonName} {status} - Elastic Synthetics\n\nDetails:\n\n- Summary: {summary}\n- Common name: {commonName}\n- Issuer: {issuer}\n- Monitor: {monitorName} \n- Monitor URL: {monitorUrl} \n- Monitor type: {monitorType} \n- From: {locationName}`,
|
||||
values: {
|
||||
commonName: '{{context.commonName}}',
|
||||
issuer: '{{context.issuer}}',
|
||||
|
@ -88,11 +88,11 @@ export const TlsTranslations = {
|
|||
},
|
||||
}),
|
||||
defaultRecoveryMessage: i18n.translate('xpack.synthetics.rules.tls.defaultRecoveryMessage', {
|
||||
defaultMessage: `Alert for TLS certificate {commonName} from issuer {issuer} has recovered - Elastic Synthetics\n\nDetails:\n\n- Summary: {summary}\n- Common name: {commonName}\n- Issuer: {issuer}\n- Monitor: {monitorName} \n- Monitor URL: {monitorUrl} \n- Monitor type: {monitorType} \n- From: {locationName}`,
|
||||
defaultMessage: `TLS alert for monitor "{monitorName}" has recovered - Elastic Synthetics\n\nDetails:\n\n- Summary: {summary}\n- New status : {newStatus}\n- Previous status: {previousStatus}\n- Monitor: {monitorName} \n- URL: {monitorUrl} \n- Monitor type: {monitorType} \n- From: {locationName}`,
|
||||
values: {
|
||||
commonName: '{{context.commonName}}',
|
||||
issuer: '{{context.issuer}}',
|
||||
summary: '{{context.summary}}',
|
||||
previousStatus: '{{context.previousStatus}}',
|
||||
newStatus: '{{context.newStatus}}',
|
||||
monitorName: '{{context.monitorName}}',
|
||||
monitorUrl: '{{{context.monitorUrl}}}',
|
||||
monitorType: '{{context.monitorType}}',
|
||||
|
|
|
@ -34,6 +34,7 @@ export const CertType = t.intersection([
|
|||
t.type({
|
||||
monitors: t.array(CertMonitorType),
|
||||
sha256: t.string,
|
||||
configId: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
not_after: t.string,
|
||||
|
@ -45,6 +46,7 @@ export const CertType = t.intersection([
|
|||
monitorType: t.string,
|
||||
monitorUrl: t.string,
|
||||
locationName: t.string,
|
||||
'@timestamp': t.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ export const PingType = t.intersection([
|
|||
docId: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
'@timestamp': t.string,
|
||||
agent: AgentType,
|
||||
container: t.partial({
|
||||
id: t.string,
|
||||
|
|
|
@ -139,7 +139,9 @@ export class SyntheticsRunner {
|
|||
}
|
||||
|
||||
assertResults(results: PromiseType<ReturnType<typeof syntheticsRun>>) {
|
||||
console.log('Asserting results...');
|
||||
Object.entries(results).forEach(([_journey, result]) => {
|
||||
console.log(`Journey: ${_journey}, Status: ${result.status}`);
|
||||
if (result.status !== 'succeeded') {
|
||||
process.exitCode = 1;
|
||||
process.exit();
|
||||
|
|
|
@ -65,6 +65,7 @@ describe('CertificateList', () => {
|
|||
not_after: '2015-04-12T23:59:59.000Z',
|
||||
not_before: '2015-04-09T00:00:00.000Z',
|
||||
common_name: '*.badssl.com',
|
||||
configId: 'uptime-advanced-http-tls',
|
||||
},
|
||||
],
|
||||
}}
|
||||
|
|
|
@ -46,10 +46,6 @@ interface Props {
|
|||
}
|
||||
|
||||
export const CertificateList: React.FC<Props> = ({ page, certificates, sort, onChange }) => {
|
||||
const onTableChange = (newVal: Partial<Props>) => {
|
||||
onChange(newVal.page as Page, newVal.sort as CertSort);
|
||||
};
|
||||
|
||||
const pagination = {
|
||||
pageIndex: page.index,
|
||||
pageSize: page.size,
|
||||
|
@ -105,7 +101,9 @@ export const CertificateList: React.FC<Props> = ({ page, certificates, sort, onC
|
|||
columns={columns}
|
||||
items={certificates?.certs ?? []}
|
||||
pagination={pagination}
|
||||
onChange={onTableChange}
|
||||
onChange={(newVal) => {
|
||||
onChange(newVal.page as Page, newVal.sort as CertSort);
|
||||
}}
|
||||
sorting={{
|
||||
sort: {
|
||||
field: sort.field,
|
||||
|
|
|
@ -9,9 +9,10 @@ import React from 'react';
|
|||
import moment from 'moment';
|
||||
import { FingerprintCol } from './fingerprint_col';
|
||||
import { render } from '../../utils/testing';
|
||||
import { Cert } from '../../../../../common/runtime_types';
|
||||
|
||||
describe('FingerprintCol', () => {
|
||||
const cert = {
|
||||
const cert: Cert = {
|
||||
monitors: [{ name: '', id: 'github', url: 'https://github.com/' }],
|
||||
not_after: '2020-05-08T00:00:00.000Z',
|
||||
not_before: '2018-05-08T00:00:00.000Z',
|
||||
|
@ -19,6 +20,7 @@ describe('FingerprintCol', () => {
|
|||
sha1: 'ca06f56b258b7a0d4f2b05470939478651151984'.toUpperCase(),
|
||||
sha256: '3111500c4a66012cdae333ec3fca1c9dde45c954440e7ee413716bff3663c074'.toUpperCase(),
|
||||
common_name: 'github.com',
|
||||
configId: '123',
|
||||
};
|
||||
|
||||
it('renders expected elements for valid props', async () => {
|
||||
|
|
|
@ -10,6 +10,8 @@ import { IBasePath } from '@kbn/core-http-server';
|
|||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import { AlertsLocatorParams, getAlertUrl } from '@kbn/observability-plugin/common';
|
||||
import { RuleExecutorServices } from '@kbn/alerting-plugin/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TLSLatestPing } from './tls_rule_executor';
|
||||
import { ALERT_DETAILS_URL } from '../action_variables';
|
||||
import { Cert } from '../../../common/runtime_types';
|
||||
import { tlsTranslations } from '../translations';
|
||||
|
@ -18,10 +20,10 @@ interface TLSContent {
|
|||
status?: string;
|
||||
}
|
||||
|
||||
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');
|
||||
const getValidBefore = (notBefore?: string): TLSContent => {
|
||||
if (!notBefore) return { summary: 'Error, missing `certificate_not_valid_before` date.' };
|
||||
const relativeDate = moment().diff(notBefore, 'days');
|
||||
const formattedDate = moment(notBefore).format('MMM D, YYYY z');
|
||||
return relativeDate >= 0
|
||||
? {
|
||||
summary: tlsTranslations.validBeforeExpiredString(formattedDate, relativeDate),
|
||||
|
@ -32,10 +34,10 @@ const getValidBefore = ({ not_before: date }: Cert): TLSContent => {
|
|||
status: tlsTranslations.invalidLabel,
|
||||
};
|
||||
};
|
||||
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');
|
||||
const getValidAfter = (notAfter?: string): TLSContent => {
|
||||
if (!notAfter) return { summary: 'Error, missing `certificate_not_valid_after` date.' };
|
||||
const relativeDate = moment().diff(notAfter, 'days');
|
||||
const formattedDate = moment(notAfter).format('MMM D, YYYY z');
|
||||
return relativeDate >= 0
|
||||
? {
|
||||
summary: tlsTranslations.validAfterExpiredString(formattedDate, relativeDate),
|
||||
|
@ -46,35 +48,36 @@ const getValidAfter = ({ not_after: date }: Cert): TLSContent => {
|
|||
status: tlsTranslations.expiringLabel,
|
||||
};
|
||||
};
|
||||
const mapCertsToSummaryString = (
|
||||
cert: Cert,
|
||||
certLimitMessage: (cert: Cert) => TLSContent
|
||||
): TLSContent => certLimitMessage(cert);
|
||||
|
||||
export const getCertSummary = (cert: Cert, expirationThreshold: number, ageThreshold: number) => {
|
||||
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);
|
||||
content = getValidAfter(cert.not_after);
|
||||
} else if (isAging) {
|
||||
content = mapCertsToSummaryString(cert, getValidBefore);
|
||||
content = getValidBefore(cert.not_before);
|
||||
}
|
||||
|
||||
const { summary = '', status = '' } = content || {};
|
||||
return {
|
||||
summary,
|
||||
status,
|
||||
sha256: cert.sha256 ?? '',
|
||||
commonName: cert.common_name ?? '',
|
||||
issuer: cert.issuer ?? '',
|
||||
monitorName: cert.monitorName,
|
||||
monitorType: cert.monitorType,
|
||||
locationName: cert.locationName,
|
||||
monitorUrl: cert.monitorUrl,
|
||||
configId: cert.configId,
|
||||
};
|
||||
};
|
||||
|
||||
export const setRecoveredAlertsContext = async ({
|
||||
type CertSummary = ReturnType<typeof getCertSummary>;
|
||||
|
||||
export const setTLSRecoveredAlertsContext = async ({
|
||||
alertFactory,
|
||||
basePath,
|
||||
defaultStartedAt,
|
||||
|
@ -82,6 +85,7 @@ export const setRecoveredAlertsContext = async ({
|
|||
spaceId,
|
||||
alertsLocator,
|
||||
getAlertUuid,
|
||||
latestPings,
|
||||
}: {
|
||||
alertFactory: RuleExecutorServices['alertFactory'];
|
||||
defaultStartedAt: string;
|
||||
|
@ -90,6 +94,7 @@ export const setRecoveredAlertsContext = async ({
|
|||
spaceId: string;
|
||||
alertsLocator?: LocatorPublic<AlertsLocatorParams>;
|
||||
getAlertUuid?: (alertId: string) => string | null;
|
||||
latestPings: TLSLatestPing[];
|
||||
}) => {
|
||||
const { getRecoveredAlerts } = alertFactory.done();
|
||||
|
||||
|
@ -98,7 +103,7 @@ export const setRecoveredAlertsContext = async ({
|
|||
const alertUuid = getAlertUuid?.(recoveredAlertId) || null;
|
||||
const indexedStartedAt = getAlertStartedDate(recoveredAlertId) ?? defaultStartedAt;
|
||||
|
||||
const state = alert.getState();
|
||||
const state = alert.getState() as CertSummary;
|
||||
const alertUrl = await getAlertUrl(
|
||||
alertUuid,
|
||||
spaceId,
|
||||
|
@ -107,8 +112,43 @@ export const setRecoveredAlertsContext = async ({
|
|||
basePath.publicBaseUrl
|
||||
);
|
||||
|
||||
const configId = state.configId;
|
||||
const latestPing = latestPings.find((ping) => ping.config_id === configId);
|
||||
|
||||
const previousStatus = i18n.translate('xpack.synthetics.alerts.tls.previousStatus', {
|
||||
defaultMessage: 'Certificate {commonName} {summary}',
|
||||
values: { commonName: state.commonName, summary: state.summary },
|
||||
});
|
||||
|
||||
const newCommonName = latestPing?.tls?.server?.x509?.subject.common_name ?? '';
|
||||
const newExpiryDate = latestPing?.tls?.server?.x509?.not_after ?? '';
|
||||
|
||||
const { summary } = getValidAfter(newExpiryDate);
|
||||
|
||||
let newStatus = i18n.translate('xpack.synthetics.alerts.tls.newStatus', {
|
||||
defaultMessage: 'Certificate {commonName} {summary}',
|
||||
values: { commonName: newCommonName, summary },
|
||||
});
|
||||
|
||||
let newSummary = '';
|
||||
if (state.sha256 !== latestPing?.tls?.server?.hash?.sha256) {
|
||||
newSummary = i18n.translate('xpack.synthetics.alerts.tls.newSummary', {
|
||||
defaultMessage: 'Monitor certificate has been updated.',
|
||||
});
|
||||
}
|
||||
if (state.sha256 === latestPing?.tls?.server?.hash?.sha256 || !latestPing) {
|
||||
// in this case it seems like threshold has been changed, but the cert is the same
|
||||
newSummary = i18n.translate('xpack.synthetics.alerts.tls.newSummaryThreshold', {
|
||||
defaultMessage: 'Expiry/Age threshold has been updated.',
|
||||
});
|
||||
newStatus = previousStatus;
|
||||
}
|
||||
|
||||
alert.setContext({
|
||||
...state,
|
||||
newStatus,
|
||||
previousStatus,
|
||||
summary: newSummary,
|
||||
[ALERT_DETAILS_URL]: alertUrl,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
CERT_VALID_NOT_AFTER,
|
||||
CERT_VALID_NOT_BEFORE,
|
||||
} from '../../../common/field_names';
|
||||
import { getCertSummary, setRecoveredAlertsContext } from './message_utils';
|
||||
import { getCertSummary, setTLSRecoveredAlertsContext } from './message_utils';
|
||||
import { SyntheticsCommonState } from '../../../common/runtime_types/alert_rules/common';
|
||||
import { TLSRuleExecutor } from './tls_rule_executor';
|
||||
import {
|
||||
|
@ -90,53 +90,51 @@ export const registerSyntheticsTLSCheckRule = (
|
|||
syntheticsMonitorClient
|
||||
);
|
||||
|
||||
const { foundCerts, certs, absoluteExpirationThreshold, absoluteAgeThreshold } =
|
||||
const { foundCerts, certs, absoluteExpirationThreshold, absoluteAgeThreshold, latestPings } =
|
||||
await tlsRule.getExpiredCertificates();
|
||||
|
||||
if (foundCerts) {
|
||||
await asyncForEach(certs, async (cert) => {
|
||||
const summary = getCertSummary(cert, absoluteExpirationThreshold, absoluteAgeThreshold);
|
||||
await asyncForEach(certs, async (cert) => {
|
||||
const summary = getCertSummary(cert, absoluteExpirationThreshold, absoluteAgeThreshold);
|
||||
|
||||
if (!summary.summary || !summary.status) {
|
||||
return;
|
||||
}
|
||||
if (!summary.summary || !summary.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
const alertId = `${cert.common_name}-${cert.issuer?.replace(/\s/g, '_')}-${cert.sha256}`;
|
||||
const alertUuid = getAlertUuid(alertId);
|
||||
const indexedStartedAt = getAlertStartedDate(alertId) ?? startedAt.toISOString();
|
||||
const alertId = cert.sha256;
|
||||
const alertUuid = getAlertUuid(alertId);
|
||||
const indexedStartedAt = getAlertStartedDate(alertId) ?? startedAt.toISOString();
|
||||
|
||||
const alertInstance = alertWithLifecycle({
|
||||
id: alertId,
|
||||
fields: {
|
||||
[CERT_COMMON_NAME]: cert.common_name,
|
||||
[CERT_ISSUER_NAME]: cert.issuer,
|
||||
[CERT_VALID_NOT_AFTER]: cert.not_after,
|
||||
[CERT_VALID_NOT_BEFORE]: cert.not_before,
|
||||
[CERT_HASH_SHA256]: cert.sha256,
|
||||
[ALERT_UUID]: alertUuid,
|
||||
[ALERT_REASON]: generateAlertMessage(TlsTranslations.defaultActionMessage, summary),
|
||||
},
|
||||
});
|
||||
|
||||
alertInstance.replaceState({
|
||||
...updateState(ruleState, foundCerts),
|
||||
...summary,
|
||||
});
|
||||
|
||||
alertInstance.scheduleActions(TLS_CERTIFICATE.id, {
|
||||
[ALERT_DETAILS_URL]: await getAlertUrl(
|
||||
alertUuid,
|
||||
spaceId,
|
||||
indexedStartedAt,
|
||||
alertsLocator,
|
||||
basePath.publicBaseUrl
|
||||
),
|
||||
...summary,
|
||||
});
|
||||
const alertInstance = alertWithLifecycle({
|
||||
id: alertId,
|
||||
fields: {
|
||||
[CERT_COMMON_NAME]: cert.common_name,
|
||||
[CERT_ISSUER_NAME]: cert.issuer,
|
||||
[CERT_VALID_NOT_AFTER]: cert.not_after,
|
||||
[CERT_VALID_NOT_BEFORE]: cert.not_before,
|
||||
[CERT_HASH_SHA256]: cert.sha256,
|
||||
[ALERT_UUID]: alertUuid,
|
||||
[ALERT_REASON]: generateAlertMessage(TlsTranslations.defaultActionMessage, summary),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await setRecoveredAlertsContext({
|
||||
alertInstance.replaceState({
|
||||
...updateState(ruleState, foundCerts),
|
||||
...summary,
|
||||
});
|
||||
|
||||
alertInstance.scheduleActions(TLS_CERTIFICATE.id, {
|
||||
[ALERT_DETAILS_URL]: await getAlertUrl(
|
||||
alertUuid,
|
||||
spaceId,
|
||||
indexedStartedAt,
|
||||
alertsLocator,
|
||||
basePath.publicBaseUrl
|
||||
),
|
||||
...summary,
|
||||
});
|
||||
});
|
||||
|
||||
await setTLSRecoveredAlertsContext({
|
||||
alertFactory,
|
||||
basePath,
|
||||
defaultStartedAt: startedAt.toISOString(),
|
||||
|
@ -144,6 +142,7 @@ export const registerSyntheticsTLSCheckRule = (
|
|||
getAlertUuid,
|
||||
spaceId,
|
||||
alertsLocator,
|
||||
latestPings,
|
||||
});
|
||||
|
||||
return { state: updateState(ruleState, foundCerts) };
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import moment from 'moment';
|
||||
import { SUMMARY_FILTER } from '../../../common/constants/client_defaults';
|
||||
import { formatFilterString } from '../common';
|
||||
import { SyntheticsServerSetup } from '../../types';
|
||||
import { getSyntheticsCerts } from '../../queries/get_certs';
|
||||
|
@ -21,7 +22,12 @@ import {
|
|||
getAllMonitors,
|
||||
processMonitors,
|
||||
} from '../../saved_objects/synthetics_monitor/get_all_monitors';
|
||||
import { CertResult, ConfigKey, EncryptedSyntheticsMonitor } from '../../../common/runtime_types';
|
||||
import {
|
||||
CertResult,
|
||||
ConfigKey,
|
||||
EncryptedSyntheticsMonitor,
|
||||
Ping,
|
||||
} from '../../../common/runtime_types';
|
||||
import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client';
|
||||
import { monitorAttributes } from '../../../common/types/saved_objects';
|
||||
import { AlertConfigKey } from '../../../common/constants/monitor_management';
|
||||
|
@ -105,6 +111,7 @@ export class TLSRuleExecutor {
|
|||
|
||||
if (enabledMonitorQueryIds.length === 0) {
|
||||
return {
|
||||
latestPings: [],
|
||||
certs: [],
|
||||
total: 0,
|
||||
foundCerts: false,
|
||||
|
@ -133,16 +140,90 @@ export class TLSRuleExecutor {
|
|||
monitorIds: enabledMonitorQueryIds,
|
||||
});
|
||||
|
||||
const latestPings = await this.getLatestPingsForMonitors(certs);
|
||||
|
||||
const foundCerts = total > 0;
|
||||
|
||||
return {
|
||||
latestPings,
|
||||
foundCerts,
|
||||
certs,
|
||||
total,
|
||||
expiryThreshold,
|
||||
ageThreshold,
|
||||
absoluteExpirationThreshold,
|
||||
absoluteAgeThreshold,
|
||||
certs: this.filterOutResolvedCerts(certs, latestPings),
|
||||
};
|
||||
}
|
||||
|
||||
filterOutResolvedCerts(certs: CertResult['certs'], latestPings: TLSLatestPing[]) {
|
||||
const latestPingsMap = new Map<string, TLSLatestPing>();
|
||||
latestPings.forEach((ping) => {
|
||||
latestPingsMap.set(ping.config_id!, ping);
|
||||
});
|
||||
return certs.filter((cert) => {
|
||||
const lPing = latestPingsMap.get(cert.configId);
|
||||
if (!lPing) {
|
||||
return true;
|
||||
}
|
||||
return moment(lPing['@timestamp']).isBefore(cert['@timestamp']);
|
||||
});
|
||||
}
|
||||
async getLatestPingsForMonitors(certs: CertResult['certs']) {
|
||||
if (certs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const configIds = certs.map((cert) => cert.configId);
|
||||
const certIds = certs.map((cert) => cert.sha256);
|
||||
const { body } = await this.esClient.search({
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-1d',
|
||||
lt: 'now',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
config_id: configIds,
|
||||
},
|
||||
},
|
||||
SUMMARY_FILTER,
|
||||
],
|
||||
must_not: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'tls.server.hash.sha256': certIds,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
collapse: {
|
||||
field: 'config_id',
|
||||
},
|
||||
_source: ['@timestamp', 'monitor', 'url', 'config_id', 'tls'],
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return body.hits.hits.map((hit) => hit._source as TLSLatestPing);
|
||||
}
|
||||
}
|
||||
|
||||
export type TLSLatestPing = Pick<Ping, '@timestamp' | 'monitor' | 'url' | 'tls' | 'config_id'>;
|
||||
|
|
|
@ -166,47 +166,6 @@ export const commonStateTranslations = [
|
|||
];
|
||||
|
||||
export const tlsTranslations = {
|
||||
actionVariables: [
|
||||
{
|
||||
name: 'count',
|
||||
description: i18n.translate('xpack.synthetics.rules.tls.actionVariables.state.count', {
|
||||
defaultMessage: 'The number of certs detected by the alert executor',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'expiringCount',
|
||||
description: i18n.translate(
|
||||
'xpack.synthetics.rules.tls.actionVariables.state.expiringCount',
|
||||
{
|
||||
defaultMessage: 'The number of expiring certs detected by the alert.',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'expiringCommonNameAndDate',
|
||||
description: i18n.translate(
|
||||
'xpack.synthetics.rules.tls.actionVariables.state.expiringCommonNameAndDate',
|
||||
{
|
||||
defaultMessage: 'The common names and expiration date/time of the detected certs',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'agingCount',
|
||||
description: i18n.translate('xpack.synthetics.rules.tls.actionVariables.state.agingCount', {
|
||||
defaultMessage: 'The number of detected certs that are becoming too old.',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'agingCommonNameAndDate',
|
||||
description: i18n.translate(
|
||||
'xpack.synthetics.rules.tls.actionVariables.state.agingCommonNameAndDate',
|
||||
{
|
||||
defaultMessage: 'The common names and expiration date/time of the detected certs.',
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
validAfterExpiredString: (date: string, relativeDate: number) =>
|
||||
i18n.translate('xpack.synthetics.rules.tls.validAfterExpiredString', {
|
||||
defaultMessage: `Expired on {date}, {relativeDate} days ago.`,
|
||||
|
@ -225,7 +184,7 @@ export const tlsTranslations = {
|
|||
}),
|
||||
validBeforeExpiredString: (date: string, relativeDate: number) =>
|
||||
i18n.translate('xpack.synthetics.rules.tls.validBeforeExpiredString', {
|
||||
defaultMessage: 'valid since {date}, {relativeDate} days ago.',
|
||||
defaultMessage: 'Valid since {date}, {relativeDate} days ago.',
|
||||
values: {
|
||||
date,
|
||||
relativeDate,
|
||||
|
@ -233,22 +192,22 @@ export const tlsTranslations = {
|
|||
}),
|
||||
validBeforeExpiringString: (date: string, relativeDate: number) =>
|
||||
i18n.translate('xpack.synthetics.rules.tls.validBeforeExpiringString', {
|
||||
defaultMessage: 'invalid until {date}, {relativeDate} days from now.',
|
||||
defaultMessage: 'Invalid until {date}, {relativeDate} days from now.',
|
||||
values: {
|
||||
date,
|
||||
relativeDate,
|
||||
},
|
||||
}),
|
||||
expiredLabel: i18n.translate('xpack.synthetics.rules.tls.expiredLabel', {
|
||||
defaultMessage: 'expired',
|
||||
defaultMessage: 'has expired',
|
||||
}),
|
||||
expiringLabel: i18n.translate('xpack.synthetics.rules.tls.expiringLabel', {
|
||||
defaultMessage: 'expiring',
|
||||
defaultMessage: 'is expiring soon',
|
||||
}),
|
||||
agingLabel: i18n.translate('xpack.synthetics.rules.tls.agingLabel', {
|
||||
defaultMessage: 'becoming too old',
|
||||
defaultMessage: 'is becoming too old',
|
||||
}),
|
||||
invalidLabel: i18n.translate('xpack.synthetics.rules.tls.invalidLabel', {
|
||||
defaultMessage: 'invalid',
|
||||
defaultMessage: 'is invalid',
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import expect from '@kbn/expect';
|
|||
import { API_URLS, SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
|
||||
import { SanitizedRule } from '@kbn/alerting-plugin/common';
|
||||
import { omit } from 'lodash';
|
||||
import { TlsTranslations } from '@kbn/synthetics-plugin/common/rules/synthetics/translations';
|
||||
import { FtrProviderContext } from '../common/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@ -359,7 +360,7 @@ const tlsRule = {
|
|||
{
|
||||
group: 'recovered',
|
||||
params: {
|
||||
body: 'Alert for TLS certificate {{context.commonName}} from issuer {{context.issuer}} has recovered - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
body: TlsTranslations.defaultRecoveryMessage,
|
||||
},
|
||||
frequency: { notifyWhen: 'onActionGroupChange', throttle: null, summary: false },
|
||||
uuid: '52070ef7-c288-40e7-ae5b-51c7d77463cb',
|
||||
|
@ -369,7 +370,7 @@ const tlsRule = {
|
|||
{
|
||||
group: 'xpack.synthetics.alerts.actionGroups.tls',
|
||||
params: {
|
||||
body: 'Detected TLS certificate {{context.commonName}} is {{context.status}} - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
body: TlsTranslations.defaultActionMessage,
|
||||
},
|
||||
frequency: { notifyWhen: 'onActionGroupChange', throttle: null, summary: false },
|
||||
uuid: '4d003e7b-e37d-47e6-8ee6-2d80b61fa31f',
|
||||
|
@ -381,8 +382,7 @@ const tlsRule = {
|
|||
params: {
|
||||
to: ['test@gmail.com'],
|
||||
subject: 'Alert has resolved for certificate {{context.commonName}} - Elastic Synthetics',
|
||||
message:
|
||||
'Alert for TLS certificate {{context.commonName}} from issuer {{context.issuer}} has recovered - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
message: TlsTranslations.defaultRecoveryMessage,
|
||||
messageHTML: null,
|
||||
cc: [],
|
||||
bcc: [],
|
||||
|
@ -398,8 +398,7 @@ const tlsRule = {
|
|||
params: {
|
||||
to: ['test@gmail.com'],
|
||||
subject: 'Alert triggered for certificate {{context.commonName}} - Elastic Synthetics',
|
||||
message:
|
||||
'Detected TLS certificate {{context.commonName}} is {{context.status}} - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
message: TlsTranslations.defaultActionMessage,
|
||||
messageHTML: null,
|
||||
cc: [],
|
||||
bcc: [],
|
||||
|
@ -455,10 +454,8 @@ const tlsRule = {
|
|||
subAction: 'pushToService',
|
||||
subActionParams: {
|
||||
incident: {
|
||||
short_description:
|
||||
'Detected TLS certificate {{context.commonName}} is {{context.status}} - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
description:
|
||||
'Detected TLS certificate {{context.commonName}} is {{context.status}} - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
short_description: TlsTranslations.defaultActionMessage,
|
||||
description: TlsTranslations.defaultActionMessage,
|
||||
impact: '2',
|
||||
severity: '2',
|
||||
urgency: '2',
|
||||
|
@ -479,8 +476,7 @@ const tlsRule = {
|
|||
{
|
||||
group: 'recovered',
|
||||
params: {
|
||||
message:
|
||||
'Alert for TLS certificate {{context.commonName}} from issuer {{context.issuer}} has recovered - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
message: TlsTranslations.defaultRecoveryMessage,
|
||||
},
|
||||
frequency: { notifyWhen: 'onActionGroupChange', throttle: null, summary: false },
|
||||
uuid: '25822900-a030-4e59-b9c7-909ff665a862',
|
||||
|
@ -490,8 +486,7 @@ const tlsRule = {
|
|||
{
|
||||
group: 'xpack.synthetics.alerts.actionGroups.tls',
|
||||
params: {
|
||||
message:
|
||||
'Detected TLS certificate {{context.commonName}} is {{context.status}} - Elastic Synthetics\n\nDetails:\n\n- Summary: {{context.summary}}\n- Common name: {{context.commonName}}\n- Issuer: {{context.issuer}}\n- Monitor: {{context.monitorName}} \n- Monitor URL: {{{context.monitorUrl}}} \n- Monitor type: {{context.monitorType}} \n- From: {{context.locationName}}',
|
||||
message: TlsTranslations.defaultActionMessage,
|
||||
},
|
||||
frequency: { notifyWhen: 'onActionGroupChange', throttle: null, summary: false },
|
||||
uuid: '07896abe-5ebe-4e7f-95e4-3944e6831843',
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
import expect from '@kbn/expect';
|
||||
import moment from 'moment';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import { CertType } from '@kbn/synthetics-plugin/common/runtime_types';
|
||||
import {
|
||||
processCertsResult,
|
||||
getCertsRequestBody,
|
||||
} from '@kbn/synthetics-plugin/common/requests/get_certs_request_body';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
import { CertType } from '@kbn/uptime-plugin/common/runtime_types';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { makeChecksWithStatus } from './helper/make_checks';
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@
|
|||
"@kbn/logs-shared-plugin",
|
||||
"@kbn/telemetry-tools",
|
||||
"@kbn/profiling-plugin",
|
||||
"@kbn/observability-onboarding-plugin"
|
||||
"@kbn/observability-onboarding-plugin",
|
||||
"@kbn/uptime-plugin"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue