//! Local CA-bundle freshness alert/recovery messages. Domain half of the //! Alerter split; shared dispatch/cooldown plumbing lives in the parent module. use tracing::instrument; use super::{AlertCategory, AlertMeta, Alerter}; impl Alerter { /// Fire when a host's trust-anchor bundle transitions out of `ok`. Carries /// the issue lines that fired, which say which of the three signals tripped. #[instrument(skip_all)] pub async fn send_ca_bundle_alert( &self, target: &str, label: &str, status: &str, issues: &[String], ) { let alert_key = format!("ca_bundle:{target}"); let subject = format!("[PoM] {label}: CA bundle {status}"); let body = format!( "Target: {label} ({target})\n\ Status: {status}\n\ Issues:\n{}\n\ Instance: {}\n\ Time: {}\n\n\ Every outbound TLS client on this host reads its trust anchors from\n\ this bundle, and multithreaded has no in-binary roots to fall back on.\n\n\ - PoM", issues .iter() .map(|i| format!(" - {i}")) .collect::>() .join("\n"), self.instance_name, chrono::Utc::now().to_rfc3339(), ); // `thin` means the bundle cannot validate the public web PKI any more, // so outbound TLS is either broken now or one handshake away from it. // `stale` and `unknown` are drift: worth a ticket, not worth a page. let priority = if status == "thin" { "critical" } else { "high" }; self.fire_failure( &subject, &body, priority, "pom-ca-bundle", Some(target), AlertMeta { key: &alert_key, category: AlertCategory::CaBundleStale, from: None, to: Some(status), error: None, }, ) .await; } /// Fire on recovery: the package is current, the lists are fresh, and the /// bundle holds a plausible number of certificates again. #[instrument(skip_all)] pub async fn send_ca_bundle_recovery(&self, target: &str, label: &str) { let alert_key = format!("ca_bundle:{target}"); let subject = format!("[PoM] {label}: CA bundle current"); let body = format!( "Target: {label} ({target})\n\ The host trust-anchor bundle is current.\n\ Instance: {}\n\ Time: {}\n\n\ - PoM", self.instance_name, chrono::Utc::now().to_rfc3339(), ); self.fire_recovery( &subject, &body, AlertMeta { key: &alert_key, category: AlertCategory::CaBundleRecovery, from: None, to: Some("ok"), error: None, }, ) .await; } }