Skip to main content

key_vault/monitor/
log_monitor.rs

1//! [`LogMonitor`] — `tracing`-backed `SecurityMonitor`.
2//!
3//! Gated behind the `monitor-tracing` Cargo feature. The monitor emits
4//! `tracing` events at three levels:
5//!
6//! - `on_decryption_failure` → `warn!` with structured fields
7//! - `on_anomalous_access` → `warn!`
8//! - `on_threshold_breach` → `error!`
9//!
10//! All fields are sanitized: only `key_name`, counters, and the
11//! caller-supplied note are emitted. Nothing the monitor receives is
12//! itself a secret (the `SecurityMonitor` trait contract forbids passing
13//! key material in context structs), so the log lines are safe to ship
14//! to any centralized log aggregator.
15
16use super::{AccessContext, FailureContext, SecurityMonitor, ThresholdContext};
17
18/// `SecurityMonitor` implementation that emits `tracing` events.
19///
20/// Construct with [`LogMonitor::new`]; the type holds no state and is
21/// `Copy`. Each event becomes a `tracing` log entry with structured
22/// fields suitable for filtering in `tracing-subscriber`.
23///
24/// # Examples
25///
26/// ```
27/// use key_vault::{KeyVaultBuilder, LogMonitor};
28///
29/// let _vault = KeyVaultBuilder::new()
30///     .with_monitor(LogMonitor::new())
31///     .build();
32/// ```
33#[derive(Debug, Default, Clone, Copy)]
34pub struct LogMonitor;
35
36impl LogMonitor {
37    /// Construct a new log monitor. Stateless; consider sharing one
38    /// instance across all vaults.
39    #[must_use]
40    pub fn new() -> Self {
41        Self
42    }
43}
44
45impl SecurityMonitor for LogMonitor {
46    fn on_decryption_failure(&self, ctx: &FailureContext) {
47        // Saturating cast: u128→u64. Durations exceeding 2^64 ms (~585
48        // million years) would lose precision; we cap rather than fail
49        // because losing a few high bits in a log timestamp is fine.
50        let elapsed_ms = u64::try_from(ctx.window_elapsed.as_millis()).unwrap_or(u64::MAX);
51        tracing::warn!(
52            target: "key_vault::monitor",
53            key_name = %ctx.key_name,
54            consecutive_failures = ctx.consecutive_failures,
55            window_elapsed_ms = elapsed_ms,
56            note = %ctx.note,
57            "key access failure",
58        );
59    }
60
61    fn on_anomalous_access(&self, ctx: &AccessContext) {
62        tracing::warn!(
63            target: "key_vault::monitor",
64            key_name = %ctx.key_name,
65            note = %ctx.note,
66            "anomalous key access",
67        );
68    }
69
70    fn on_threshold_breach(&self, ctx: &ThresholdContext) {
71        let window_ms = u64::try_from(ctx.window.as_millis()).unwrap_or(u64::MAX);
72        tracing::error!(
73            target: "key_vault::monitor",
74            key_name = %ctx.key_name,
75            failures_in_window = ctx.failures_in_window,
76            window_ms = window_ms,
77            lockout_triggered = ctx.lockout_triggered,
78            "threshold breach",
79        );
80    }
81}