import { z } from 'zod'; import crypto from 'node:crypto'; import pino from 'pino'; const logger = pino({ name: 'ingestion' }); // --- Canonical Alert Schema --- export const canonicalAlertSchema = z.object({ sourceProvider: z.enum(['datadog', 'pagerduty', 'opsgenie', 'grafana', 'custom']), sourceId: z.string(), fingerprint: z.string(), title: z.string(), severity: z.enum(['critical', 'high', 'medium', 'low', 'info']), status: z.enum(['firing', 'resolved']), service: z.string().optional(), environment: z.string().optional(), tags: z.record(z.string()).default({}), rawPayload: z.any(), timestamp: z.number(), // Unix ms }); export type CanonicalAlert = z.infer; // --- HMAC Validation (BMad Must-Have: Replay Prevention) --- const MAX_TIMESTAMP_DRIFT_SECONDS = 300; // 5 minutes export interface HmacValidationResult { valid: boolean; error?: string; } export function validateDatadogHmac( body: string, signature: string | undefined, timestamp: string | undefined, secret: string, ): HmacValidationResult { if (!signature || !timestamp) { return { valid: false, error: 'Missing signature or timestamp header' }; } // Timestamp freshness check const ts = parseInt(timestamp, 10); const now = Math.floor(Date.now() / 1000); if (Math.abs(now - ts) > MAX_TIMESTAMP_DRIFT_SECONDS) { return { valid: false, error: 'stale timestamp' }; } const expected = crypto .createHmac('sha256', secret) .update(timestamp + body) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { return { valid: false, error: 'Invalid signature' }; } return { valid: true }; } export function validatePagerdutyHmac( body: string, signature: string | undefined, secret: string, ): HmacValidationResult { if (!signature) { return { valid: false, error: 'Missing signature header' }; } // PagerDuty v1 signatures include timestamp in the signature header const parts = signature.split(','); const tsPart = parts.find(p => p.startsWith('t=')); const sigPart = parts.find(p => p.startsWith('v1=')); if (!tsPart || !sigPart) { return { valid: false, error: 'Malformed PagerDuty signature' }; } const ts = parseInt(tsPart.slice(2), 10); const now = Math.floor(Date.now() / 1000); if (Math.abs(now - ts) > MAX_TIMESTAMP_DRIFT_SECONDS) { return { valid: false, error: 'stale timestamp' }; } const expected = crypto .createHmac('sha256', secret) .update(`${ts}.${body}`) .digest('hex'); const sig = sigPart.slice(3); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return { valid: false, error: 'Invalid signature' }; } return { valid: true }; } export function validateOpsgenieHmac( body: string, signature: string | undefined, secret: string, ): HmacValidationResult { if (!signature) { return { valid: false, error: 'Missing signature header' }; } // OpsGenie: extract timestamp from payload body let payload: any; try { payload = JSON.parse(body); } catch { return { valid: false, error: 'Invalid JSON body' }; } const ts = payload?.timestamp; if (ts) { const tsSeconds = typeof ts === 'number' ? ts / 1000 : parseInt(ts, 10); const now = Math.floor(Date.now() / 1000); if (Math.abs(now - tsSeconds) > MAX_TIMESTAMP_DRIFT_SECONDS) { return { valid: false, error: 'stale timestamp' }; } } const expected = crypto .createHmac('sha256', secret) .update(body) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { return { valid: false, error: 'Invalid signature' }; } return { valid: true }; } // --- Payload Normalizers --- export function normalizeDatadog(payload: any): CanonicalAlert { return { sourceProvider: 'datadog', sourceId: payload.id ?? payload.alert_id ?? crypto.randomUUID(), fingerprint: payload.aggregation_key ?? payload.alert_id ?? '', title: payload.title ?? payload.msg_title ?? 'Datadog Alert', severity: mapDatadogPriority(payload.priority), status: payload.alert_transition === 'Recovered' ? 'resolved' : 'firing', service: payload.tags?.service, environment: payload.tags?.env, tags: payload.tags ?? {}, rawPayload: payload, timestamp: payload.date_happened ? payload.date_happened * 1000 : Date.now(), }; } export function normalizePagerduty(payload: any): CanonicalAlert { const event = payload.event ?? payload; const data = event.data ?? event.incident ?? {}; return { sourceProvider: 'pagerduty', sourceId: data.id ?? crypto.randomUUID(), fingerprint: data.incident_key ?? data.id ?? '', title: data.title ?? data.description ?? 'PagerDuty Incident', severity: mapPagerdutyUrgency(data.urgency), status: event.event_type?.includes('resolve') ? 'resolved' : 'firing', service: data.service?.name, environment: data.body?.details?.environment, tags: {}, rawPayload: payload, timestamp: data.created_at ? new Date(data.created_at).getTime() : Date.now(), }; } export function normalizeOpsgenie(payload: any): CanonicalAlert { return { sourceProvider: 'opsgenie', sourceId: payload.alert?.alertId ?? crypto.randomUUID(), fingerprint: payload.alert?.alias ?? payload.alert?.alertId ?? '', title: payload.alert?.message ?? 'OpsGenie Alert', severity: mapOpsgeniePriority(payload.alert?.priority), status: payload.action === 'Close' ? 'resolved' : 'firing', service: payload.alert?.tags?.find((t: string) => t.startsWith('service:'))?.slice(8), tags: {}, rawPayload: payload, timestamp: payload.alert?.createdAt ? new Date(payload.alert.createdAt).getTime() : Date.now(), }; } // --- Severity Mappers --- function mapDatadogPriority(p: string | undefined): CanonicalAlert['severity'] { switch (p) { case 'P1': return 'critical'; case 'P2': return 'high'; case 'P3': return 'medium'; case 'P4': return 'low'; default: return 'medium'; } } function mapPagerdutyUrgency(u: string | undefined): CanonicalAlert['severity'] { switch (u) { case 'high': return 'critical'; case 'low': return 'low'; default: return 'medium'; } } function mapOpsgeniePriority(p: string | undefined): CanonicalAlert['severity'] { switch (p) { case 'P1': return 'critical'; case 'P2': return 'high'; case 'P3': return 'medium'; case 'P4': case 'P5': return 'low'; default: return 'medium'; } }