Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onboard Synthetics TLS rule type with FAAD #191127

Merged
merged 17 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,9 @@ export const syntheticsRuleTypeFieldMap = {
...legacyExperimentalFieldMap,
};

export const SyntheticsRuleTypeAlertDefinition: IRuleTypeAlerts = {
export const SyntheticsRuleTypeAlertDefinition: IRuleTypeAlerts<ObservabilityUptimeAlert> = {
context: SYNTHETICS_RULE_TYPES_ALERT_CONTEXT,
mappings: { fieldMap: syntheticsRuleTypeFieldMap },
useLegacyAlerts: true,
shouldWrite: true,
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { isEmpty } from 'lodash';
import { ActionGroupIdsOf } from '@kbn/alerting-plugin/common';
import { PluginSetupContract } from '@kbn/alerting-plugin/server';
import {
GetViewInAppRelativeUrlFnOpts,
AlertInstanceContext as AlertContext,
RuleExecutorOptions,
AlertsClientError,
IRuleTypeAlerts,
} from '@kbn/alerting-plugin/server';
import { observabilityPaths } from '@kbn/observability-plugin/common';
import { ObservabilityUptimeAlert } from '@kbn/alerts-as-data-utils';
Expand Down Expand Up @@ -55,16 +53,15 @@ type MonitorStatusAlert = ObservabilityUptimeAlert;
export const registerSyntheticsStatusCheckRule = (
server: SyntheticsServerSetup,
plugins: SyntheticsPluginsSetupDependencies,
syntheticsMonitorClient: SyntheticsMonitorClient,
alerting: PluginSetupContract
syntheticsMonitorClient: SyntheticsMonitorClient
) => {
if (!alerting) {
if (!plugins.alerting) {
throw new Error(
'Cannot register the synthetics monitor status rule type. The alerting plugin needs to be enabled.'
);
}

alerting.registerType({
plugins.alerting.registerType({
id: SYNTHETICS_ALERT_RULE_TYPES.MONITOR_STATUS,
category: DEFAULT_APP_CATEGORIES.observability.id,
producer: 'uptime',
Expand Down Expand Up @@ -172,10 +169,7 @@ export const registerSyntheticsStatusCheckRule = (
state: updateState(ruleState, !isEmpty(downConfigs), { downConfigs }),
};
},
alerts: {
...SyntheticsRuleTypeAlertDefinition,
shouldWrite: true,
} as IRuleTypeAlerts<MonitorStatusAlert>,
alerts: SyntheticsRuleTypeAlertDefinition,
fieldsForAAD: Object.keys(syntheticsRuleFieldMap),
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* 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 { IBasePath } from '@kbn/core/server';
import { AlertsLocatorParams } from '@kbn/observability-plugin/common';
import { LocatorPublic } from '@kbn/share-plugin/common';
import { setTLSRecoveredAlertsContext } from './message_utils';
import { TLSLatestPing } from './tls_rule_executor';

describe('setTLSRecoveredAlertsContext', () => {
const timestamp = new Date().toISOString();
const alertUuid = 'alert-id';
const configId = '12345';
const basePath = {
publicBaseUrl: 'https://localhost:5601',
} as IBasePath;
const alertsLocatorMock = {
getLocation: jest.fn().mockImplementation(() => ({
path: 'https://localhost:5601/app/observability/alerts/alert-id',
})),
} as any as LocatorPublic<AlertsLocatorParams>;
const alertState = {
summary: 'test-summary',
status: 'has expired',
sha256: 'cert-1-sha256',
commonName: 'cert-1',
issuer: 'test-issuer',
monitorName: 'test-monitor',
monitorType: 'test-monitor-type',
locationName: 'test-location-name',
monitorUrl: 'test-monitor-url',
configId,
};

it('sets context correctly when monitor cert has been updated', async () => {
const alertsClientMock = {
report: jest.fn(),
getAlertLimitValue: jest.fn().mockReturnValue(10),
setAlertLimitReached: jest.fn(),
getRecoveredAlerts: jest.fn().mockReturnValue([
{
alert: {
getId: () => alertUuid,
getState: () => alertState,
setContext: jest.fn(),
getUuid: () => alertUuid,
getStart: () => new Date().toISOString(),
},
},
]),
setAlertData: jest.fn(),
isTrackedAlert: jest.fn(),
};
await setTLSRecoveredAlertsContext({
alertsClient: alertsClientMock,
basePath,
defaultStartedAt: timestamp,
spaceId: 'default',
alertsLocator: alertsLocatorMock,
latestPings: [
{
config_id: configId,
'@timestamp': timestamp,
tls: {
server: {
hash: {
sha256: 'cert-2-sha256',
},
x509: {
subject: {
common_name: 'cert-2',
},
not_after: timestamp,
},
},
},
} as TLSLatestPing,
],
});
expect(alertsClientMock.setAlertData).toBeCalledWith({
context: {
alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id',
commonName: 'cert-1',
configId: '12345',
issuer: 'test-issuer',
locationName: 'test-location-name',
monitorName: 'test-monitor',
monitorType: 'test-monitor-type',
monitorUrl: 'test-monitor-url',
newStatus: expect.stringContaining('Certificate cert-2 Expired on'),
previousStatus: 'Certificate cert-1 test-summary',
sha256: 'cert-1-sha256',
status: 'has expired',
summary: 'Monitor certificate has been updated.',
},
id: 'alert-id',
});
});

it('sets context correctly when monitor cert expiry/age threshold has been updated', async () => {
const alertsClientMock = {
report: jest.fn(),
getAlertLimitValue: jest.fn().mockReturnValue(10),
setAlertLimitReached: jest.fn(),
getRecoveredAlerts: jest.fn().mockReturnValue([
{
alert: {
getId: () => alertUuid,
getState: () => alertState,
setContext: jest.fn(),
getUuid: () => alertUuid,
getStart: () => new Date().toISOString(),
},
},
]),
setAlertData: jest.fn(),
isTrackedAlert: jest.fn(),
};
await setTLSRecoveredAlertsContext({
alertsClient: alertsClientMock,
basePath,
defaultStartedAt: timestamp,
spaceId: 'default',
alertsLocator: alertsLocatorMock,
latestPings: [
{
config_id: configId,
'@timestamp': timestamp,
tls: {
server: {
hash: {
sha256: 'cert-1-sha256',
},
x509: {
subject: {
common_name: 'cert-1',
},
not_after: timestamp,
},
},
},
} as TLSLatestPing,
],
});
expect(alertsClientMock.setAlertData).toBeCalledWith({
context: {
alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id',
commonName: 'cert-1',
configId: '12345',
issuer: 'test-issuer',
locationName: 'test-location-name',
monitorName: 'test-monitor',
monitorType: 'test-monitor-type',
monitorUrl: 'test-monitor-url',
newStatus: 'Certificate cert-1 test-summary',
previousStatus: 'Certificate cert-1 test-summary',
sha256: 'cert-1-sha256',
status: 'has expired',
summary: 'Expiry/Age threshold has been updated.',
},
id: 'alert-id',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ import moment from 'moment/moment';
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 {
AlertInstanceContext as AlertContext,
AlertInstanceState as AlertState,
ActionGroupIdsOf,
} from '@kbn/alerting-plugin/server';
import { i18n } from '@kbn/i18n';
import { PublicAlertsClient } from '@kbn/alerting-plugin/server/alerts_client/types';
import { ObservabilityUptimeAlert } from '@kbn/alerts-as-data-utils';
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';
interface TLSContent {
summary: string;
status?: string;
Expand Down Expand Up @@ -75,35 +82,34 @@ export const getCertSummary = (cert: Cert, expirationThreshold: number, ageThres
};
};

type CertSummary = ReturnType<typeof getCertSummary>;

export const setTLSRecoveredAlertsContext = async ({
alertFactory,
alertsClient,
basePath,
defaultStartedAt,
getAlertStartedDate,
spaceId,
alertsLocator,
getAlertUuid,
latestPings,
}: {
alertFactory: RuleExecutorServices['alertFactory'];
alertsClient: PublicAlertsClient<
ObservabilityUptimeAlert,
AlertState,
AlertContext,
ActionGroupIdsOf<MonitorStatusActionGroup>
>;
defaultStartedAt: string;
getAlertStartedDate: (alertInstanceId: string) => string | null;
basePath: IBasePath;
spaceId: string;
alertsLocator?: LocatorPublic<AlertsLocatorParams>;
getAlertUuid?: (alertId: string) => string | null;
latestPings: TLSLatestPing[];
}) => {
const { getRecoveredAlerts } = alertFactory.done();
const recoveredAlerts = alertsClient.getRecoveredAlerts() ?? [];

for await (const alert of getRecoveredAlerts()) {
const recoveredAlertId = alert.getId();
const alertUuid = getAlertUuid?.(recoveredAlertId) || null;
const indexedStartedAt = getAlertStartedDate(recoveredAlertId) ?? defaultStartedAt;
for (const recoveredAlert of recoveredAlerts) {
const recoveredAlertId = recoveredAlert.alert.getId();
const alertUuid = recoveredAlert.alert.getUuid();
const indexedStartedAt = recoveredAlert.alert.getStart() ?? defaultStartedAt;

const state = alert.getState() as CertSummary;
const state = recoveredAlert.alert.getState();
const alertUrl = await getAlertUrl(
alertUuid,
spaceId,
Expand Down Expand Up @@ -144,12 +150,13 @@ export const setTLSRecoveredAlertsContext = async ({
newStatus = previousStatus;
}

alert.setContext({
const context = {
...state,
newStatus,
previousStatus,
summary: newSummary,
[ALERT_DETAILS_URL]: alertUrl,
});
};
alertsClient.setAlertData({ id: recoveredAlertId, context });
}
};
Loading