[Synthetics] Fix tls alert data !! (#212758)

### Summary
This PR improves and fixes the TLS alert data handling in the Synthetics
plugin. Key updates include:


### Code changes

1. **Request Body Updates**:
   - Added `agent.name`.
- Changed optional chaining to direct property access for
`ping.monitor.name`, `ping.monitor.id`, and `ping.observer.name`.
   - Added `ping.agent.name`.

2. **Cert Type Adjustments**:
- Added fields: `monitorName`, `monitorId`, `monitorType`, `locationId`,
`locationName`, `@timestamp`, `hostName`.

3. **Observer Codec**:
   - Ensured `name` and `geo.name` are required fields.

4. **Monitor Type and Ping Type**:
   - Added `name` to `MonitorType`.
   - Moved `@timestamp` to required fields in `PingType`.

7. **Message Utils**:
- Adjusted `getCertSummary` and `getTLSAlertDocument` so that we can
properly generate alert document
This commit is contained in:
Shahzad 2025-03-05 19:40:04 +01:00 committed by GitHub
parent a1c520c49d
commit 80f4aab305
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 103 additions and 36 deletions

View file

@ -138,6 +138,7 @@ export const getCertsRequestBody = ({
'monitor.type',
'url.full',
'observer.geo.name',
'agent.name',
'tls.server.x509.issuer.common_name',
'tls.server.x509.subject.common_name',
'tls.server.hash.sha1',
@ -206,8 +207,8 @@ export const processCertsResult = (result: CertificatesResults): CertResult => {
not_after: notAfter,
not_before: notBefore,
common_name: commonName,
monitorName: ping?.monitor?.name,
monitorId: ping?.monitor?.id,
monitorName: ping.monitor.name,
monitorId: ping.monitor.id,
serviceName: ping?.service?.name,
configId: ping.config_id!,
monitorUrl: ping?.url?.full,
@ -215,7 +216,8 @@ export const processCertsResult = (result: CertificatesResults): CertResult => {
tags: ping?.tags,
'@timestamp': ping['@timestamp'],
monitorType: ping?.monitor?.type,
locationId: ping?.observer?.name,
locationId: ping.observer.name,
hostName: ping?.agent?.name,
locationName: ping?.observer?.geo?.name,
errorMessage: ping?.error?.message,
errorStackTrace: ping?.error?.stack_trace,

View file

@ -35,6 +35,12 @@ export const CertType = t.intersection([
monitors: t.array(CertMonitorType),
sha256: t.string,
configId: t.string,
monitorName: t.string,
monitorId: t.string,
monitorType: t.string,
locationId: t.string,
locationName: t.string,
'@timestamp': t.string,
}),
t.partial({
not_after: t.string,
@ -42,18 +48,14 @@ export const CertType = t.intersection([
common_name: t.string,
issuer: t.string,
sha1: t.string,
monitorName: t.string,
monitorId: t.string,
monitorType: t.string,
monitorUrl: t.string,
locationId: t.string,
locationName: t.string,
'@timestamp': t.string,
hostName: t.string,
serviceName: t.string,
errorMessage: t.string,
errorStackTrace: t.union([t.string, t.null]),
labels: t.record(t.string, t.string),
tags: t.array(t.string),
monitorTags: t.array(t.string),
}),
]);

View file

@ -7,20 +7,28 @@
import * as t from 'io-ts';
export const ObserverCodec = t.partial({
hostname: t.string,
ip: t.array(t.string),
mac: t.array(t.string),
name: t.union([t.string, t.undefined]),
geo: t.partial({
export const ObserverCodec = t.intersection([
t.type({
name: t.string,
continent_name: t.string,
city_name: t.string,
country_iso_code: t.string,
location: t.union([
t.string,
t.partial({ lat: t.number, lon: t.number }),
t.partial({ lat: t.string, lon: t.string }),
geo: t.intersection([
t.type({
name: t.string,
}),
t.partial({
continent_name: t.string,
city_name: t.string,
country_iso_code: t.string,
location: t.union([
t.string,
t.partial({ lat: t.number, lon: t.number }),
t.partial({ lat: t.string, lon: t.string }),
]),
}),
]),
}),
});
t.partial({
hostname: t.string,
ip: t.array(t.string),
mac: t.array(t.string),
}),
]);

View file

@ -92,6 +92,7 @@ export type Tls = t.TypeOf<typeof TlsType>;
export const MonitorType = t.intersection([
t.type({
name: t.string,
id: t.string,
status: t.string,
type: t.string,
@ -106,7 +107,6 @@ export const MonitorType = t.intersection([
us: t.number,
}),
ip: t.string,
name: t.string,
fleet_managed: t.boolean,
project: t.type({
@ -163,9 +163,9 @@ export const PingType = t.intersection([
monitor: MonitorType,
docId: t.string,
observer: ObserverCodec,
'@timestamp': t.string,
}),
t.partial({
'@timestamp': t.string,
agent: AgentType,
container: t.partial({
id: t.string,

View file

@ -8,6 +8,7 @@
import React from 'react';
import { CertificateList, CertSort } from './certificates_list';
import { render } from '../../utils/testing';
import { Cert } from '../../../../../common/runtime_types';
describe('CertificateList', () => {
it('render empty state', () => {
@ -66,7 +67,7 @@ describe('CertificateList', () => {
not_before: '2015-04-09T00:00:00.000Z',
common_name: '*.badssl.com',
configId: 'uptime-advanced-http-tls',
},
} as Cert,
],
}}
/>

View file

@ -12,7 +12,7 @@ import { render } from '../../utils/testing';
import { Cert } from '../../../../../common/runtime_types';
describe('FingerprintCol', () => {
const cert: Cert = {
const 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',
@ -21,7 +21,7 @@ describe('FingerprintCol', () => {
sha256: '3111500c4a66012cdae333ec3fca1c9dde45c954440e7ee413716bff3663c074'.toUpperCase(),
common_name: 'github.com',
configId: '123',
};
} as Cert;
it('renders expected elements for valid props', async () => {
cert.not_after = moment().add('4', 'months').toISOString();

View file

@ -39,6 +39,7 @@ describe('Monitor Detail Flyout', () => {
docId: 'docId',
timestamp: '2013-03-01 12:54:23',
monitor: {
name: 'test monitor',
id: 'test-id',
status: 'up',
type: 'http',
@ -52,7 +53,13 @@ describe('Monitor Detail Flyout', () => {
full: 'https://www.elastic.co',
},
tags: ['tag1', 'tag2'],
observer: {},
observer: {
name: 'us-east-1',
geo: {
name: 'US East',
},
},
'@timestamp': '2013-03-01 12:54:23',
},
});
jest.spyOn(statusByLocation, 'useStatusByLocation').mockReturnValue({

View file

@ -50,7 +50,7 @@ export const initTlsAlertType: AlertTypeInitializer = ({
},
defaultActionMessage,
defaultRecoveryMessage,
requiresAppContext: true,
requiresAppContext: false,
format: ({ fields }) => ({
reason: fields[ALERT_REASON] || '',
link: `/app/synthetics${CERTIFICATES_ROUTE}`,

View file

@ -241,6 +241,7 @@ function getMonitorDetailsMockSlice() {
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
name: 'us-central',
geo: {
continent_name: 'North America',
city_name: 'Iowa',
@ -307,6 +308,7 @@ function getMonitorDetailsMockSlice() {
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
name: 'us-central',
geo: {
continent_name: 'North America',
city_name: 'Iowa',
@ -362,6 +364,7 @@ function getMonitorDetailsMockSlice() {
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
name: 'us-central',
geo: {
continent_name: 'North America',
city_name: 'Iowa',
@ -417,6 +420,7 @@ function getMonitorDetailsMockSlice() {
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
name: 'us-central',
geo: {
continent_name: 'North America',
city_name: 'Iowa',

View file

@ -16,6 +16,7 @@ describe('setTLSRecoveredAlertsContext', () => {
const basePath = {
publicBaseUrl: 'https://localhost:5601',
} as IBasePath;
const alertState = {
summary: 'test-summary',
status: 'has expired',

View file

@ -17,12 +17,14 @@ import { i18n } from '@kbn/i18n';
import { PublicAlertsClient } from '@kbn/alerting-plugin/server/alerts_client/types';
import { ObservabilityUptimeAlert } from '@kbn/alerts-as-data-utils';
import { ALERT_REASON, ALERT_UUID } from '@kbn/rule-data-utils';
import { MonitorSummaryTLSRule } from './types';
import { TLSLatestPing } from './tls_rule_executor';
import { ALERT_DETAILS_URL } from '../action_variables';
import { Cert } from '../../../common/runtime_types';
import { tlsTranslations } from '../translations';
import { MonitorStatusActionGroup } from '../../../common/constants/synthetics_alerts';
import {
AGENT_NAME,
CERT_COMMON_NAME,
CERT_HASH_SHA256,
CERT_ISSUER_NAME,
@ -76,7 +78,11 @@ const getValidAfter = (notAfter?: string): TLSContent => {
export type CertSummary = ReturnType<typeof getCertSummary>;
export const getCertSummary = (cert: Cert, expirationThreshold: number, ageThreshold: number) => {
export const getCertSummary = (
cert: Cert,
expirationThreshold: number,
ageThreshold: number
): MonitorSummaryTLSRule => {
const isExpiring = new Date(cert.not_after ?? '').valueOf() < expirationThreshold;
const isAging = new Date(cert.not_before ?? '').valueOf() < ageThreshold;
let content: TLSContent | null = null;
@ -103,9 +109,12 @@ export const getCertSummary = (cert: Cert, expirationThreshold: number, ageThres
monitorUrl: cert.monitorUrl,
configId: cert.configId,
monitorTags: cert.tags,
errorMessage: cert.errorMessage,
errorStackTrace: cert.errorStackTrace,
lastErrorMessage: cert.errorMessage,
lastErrorStack: cert.errorStackTrace,
labels: cert.labels,
reason: summary,
hostName: cert.hostName,
checkedAt: cert['@timestamp'],
};
};
@ -124,9 +133,10 @@ export const getTLSAlertDocument = (cert: Cert, monitorSummary: CertSummary, uui
[URL_FULL]: monitorSummary.monitorUrl,
[OBSERVER_GEO_NAME]: monitorSummary.locationName ? [monitorSummary.locationName] : [],
[OBSERVER_NAME]: monitorSummary.locationId ? [monitorSummary.locationId] : [],
[ERROR_MESSAGE]: monitorSummary.errorMessage,
[ERROR_MESSAGE]: monitorSummary.lastErrorMessage,
[AGENT_NAME]: monitorSummary.hostName,
// done to avoid assigning null to the field
[ERROR_STACK_TRACE]: monitorSummary.errorStackTrace ? monitorSummary.errorStackTrace : undefined,
[ERROR_STACK_TRACE]: monitorSummary.lastErrorStack ? monitorSummary.lastErrorStack : undefined,
'location.id': monitorSummary.locationId ? [monitorSummary.locationId] : [],
'location.name': monitorSummary.locationName ? [monitorSummary.locationName] : [],
labels: cert.labels,

View file

@ -0,0 +1,31 @@
/*
* 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.
*/
export interface MonitorSummaryTLSRule {
reason: string;
summary: string;
status: string;
configId: string;
hostName?: string;
monitorId: string;
checkedAt: string;
monitorUrl?: string;
locationId: string;
monitorType: string;
monitorName: string;
serviceName?: string;
locationName: string;
lastErrorMessage?: string;
lastErrorStack?: string | null;
stateId?: string | null;
monitorUrlLabel?: string;
sha256: string;
commonName: string;
issuer: string;
monitorTags?: string[];
labels?: Record<string, string>;
}

View file

@ -6,6 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { MonitorSummaryTLSRule } from './tls_rule/types';
import { MonitorSummaryStatusRule } from './status_rule/types';
export const STATUS_RULE_NAME = i18n.translate('xpack.synthetics.alertRules.monitorStatus', {
@ -13,7 +14,7 @@ export const STATUS_RULE_NAME = i18n.translate('xpack.synthetics.alertRules.moni
});
export const commonMonitorStateI18: Array<{
name: keyof MonitorSummaryStatusRule;
name: keyof MonitorSummaryStatusRule | keyof MonitorSummaryTLSRule;
description: string;
}> = [
{