Skip to main content

faucet_cli/serve/
audit.rs

1//! Audit-log writing for the control plane (RBAC, #205). One choke point both
2//! the auth middleware (denials) and the run handlers (submit / cancel / delete)
3//! funnel through, so every audit record is built the same way and a write
4//! failure is logged — never silently dropped, and never fails the action.
5
6use crate::serve::history::AuditEntry;
7use crate::serve::rbac::AuthContext;
8use crate::serve::state::ServerState;
9
10/// Build + persist one audit record (best-effort). A backend write failure is
11/// logged at WARN and swallowed: auditing must never fail the underlying action,
12/// but the failure is made visible rather than lost.
13pub async fn write(
14    state: &ServerState,
15    ctx: &AuthContext,
16    action: &str,
17    run_id: Option<String>,
18    config_fingerprint: Option<String>,
19    result: &str,
20) {
21    let entry = AuditEntry {
22        id: uuid::Uuid::now_v7().to_string(),
23        timestamp: chrono::Utc::now(),
24        principal: ctx.principal.clone(),
25        role: ctx.role.as_str().to_string(),
26        action: action.to_string(),
27        run_id,
28        config_fingerprint,
29        source_ip: ctx.source_ip.clone(),
30        result: result.to_string(),
31    };
32    if let Err(e) = state.history().record_audit(&entry).await {
33        tracing::warn!(
34            action, principal = %ctx.principal, error = %e,
35            "failed to write audit record"
36        );
37    }
38}