Skip to main content

rustfs_audit/
global.rs

1//  Copyright 2024 RustFS Team
2//
3//  Licensed under the Apache License, Version 2.0 (the "License");
4//  you may not use this file except in compliance with the License.
5//  You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14
15use crate::{AuditEntry, AuditError, AuditResult, AuditSystem, system::AuditTargetMetricSnapshot};
16use rustfs_config::server_config::Config;
17use std::sync::{Arc, OnceLock};
18use tracing::{debug, error, trace};
19
20const LOG_COMPONENT_AUDIT: &str = "audit";
21const LOG_SUBSYSTEM_GLOBAL: &str = "global";
22const EVENT_AUDIT_GLOBAL_SKIPPED: &str = "audit_global_skipped";
23const EVENT_AUDIT_ENTRY_DROPPED: &str = "audit_entry_dropped";
24const EVENT_AUDIT_DISPATCH_FAILED: &str = "audit_dispatch_failed";
25
26/// Global audit system instance
27static AUDIT_SYSTEM: OnceLock<Arc<AuditSystem>> = OnceLock::new();
28
29/// Initialize the global audit system
30pub fn init_audit_system() -> Arc<AuditSystem> {
31    AUDIT_SYSTEM.get_or_init(|| Arc::new(AuditSystem::new())).clone()
32}
33
34/// Get the global audit system instance
35pub fn audit_system() -> Option<Arc<AuditSystem>> {
36    AUDIT_SYSTEM.get().cloned()
37}
38
39/// A helper macro for executing closures if the global audit system is initialized.
40/// If not initialized, log a warning and return `Ok(())`.
41macro_rules! with_audit_system {
42    ($async_closure:expr) => {
43        if let Some(system) = audit_system() {
44            (async move { $async_closure(system).await }).await
45        } else {
46            debug!(
47                event = EVENT_AUDIT_GLOBAL_SKIPPED,
48                component = LOG_COMPONENT_AUDIT,
49                subsystem = LOG_SUBSYSTEM_GLOBAL,
50                reason = "system_not_initialized",
51                "Skipped audit system operation"
52            );
53            Ok(())
54        }
55    };
56}
57
58/// Start the global audit system with configuration
59pub async fn start_audit_system(config: Config) -> AuditResult<()> {
60    let system = init_audit_system();
61    system.start(config).await
62}
63
64/// Stop the global audit system
65pub async fn stop_audit_system() -> AuditResult<()> {
66    with_audit_system!(|system: Arc<AuditSystem>| async move { system.close().await })
67}
68
69/// Pause the global audit system
70pub async fn pause_audit_system() -> AuditResult<()> {
71    with_audit_system!(|system: Arc<AuditSystem>| async move { system.pause().await })
72}
73
74/// Resume the global audit system
75pub async fn resume_audit_system() -> AuditResult<()> {
76    with_audit_system!(|system: Arc<AuditSystem>| async move { system.resume().await })
77}
78
79/// Dispatch an audit log entry to all targets
80pub async fn dispatch_audit_log(entry: Arc<AuditEntry>) -> AuditResult<()> {
81    let Some(system) = audit_system() else {
82        debug!(
83            event = EVENT_AUDIT_ENTRY_DROPPED,
84            component = LOG_COMPONENT_AUDIT,
85            subsystem = LOG_SUBSYSTEM_GLOBAL,
86            reason = "system_not_initialized",
87            "Dropped audit entry"
88        );
89        return Ok(());
90    };
91
92    // Single state read (backlog#984): the previous code checked `is_running()`
93    // and then called `dispatch()`, which re-read the state. Between the two
94    // reads the system could transition (e.g. Running -> Stopping) and
95    // `dispatch()` would return an error the caller never expected. Let
96    // `dispatch()` be the single authority on the current state and interpret
97    // its "not accepting" errors as a deliberate skip, while still surfacing
98    // real delivery failures (backlog#962).
99    match system.dispatch(entry).await {
100        Ok(()) => Ok(()),
101        Err(AuditError::NotInitialized(_)) | Err(AuditError::Paused) => {
102            trace!(
103                event = EVENT_AUDIT_ENTRY_DROPPED,
104                component = LOG_COMPONENT_AUDIT,
105                subsystem = LOG_SUBSYSTEM_GLOBAL,
106                reason = "system_not_running",
107                "Dropped audit entry"
108            );
109            Ok(())
110        }
111        Err(e) => Err(e),
112    }
113}
114
115/// Reload the global audit system configuration
116pub async fn reload_audit_config(config: Config) -> AuditResult<()> {
117    with_audit_system!(|system: Arc<AuditSystem>| async move { system.reload_config(config).await })
118}
119
120/// Returns per-target audit delivery metrics for Prometheus collection.
121pub async fn audit_target_metrics() -> Vec<AuditTargetMetricSnapshot> {
122    if let Some(system) = audit_system() {
123        system.snapshot_target_metrics().await
124    } else {
125        Vec::new()
126    }
127}
128
129/// Check if the global audit system is running
130pub async fn is_audit_system_running() -> bool {
131    if let Some(system) = audit_system() {
132        system.is_running().await
133    } else {
134        false
135    }
136}
137
138/// AuditLogger singleton for easy access
139pub struct AuditLogger;
140
141impl AuditLogger {
142    /// Log an audit entry
143    pub async fn log(entry: AuditEntry) {
144        if let Err(e) = dispatch_audit_log(Arc::new(entry)).await {
145            error!(
146                event = EVENT_AUDIT_DISPATCH_FAILED,
147                component = LOG_COMPONENT_AUDIT,
148                subsystem = LOG_SUBSYSTEM_GLOBAL,
149                error = %e,
150                "Failed to dispatch audit entry"
151            );
152        }
153    }
154
155    /// Check if audit logging is enabled
156    pub async fn is_enabled() -> bool {
157        is_audit_system_running().await
158    }
159
160    /// Get singleton instance
161    pub fn instance() -> &'static Self {
162        static INSTANCE: AuditLogger = AuditLogger;
163        &INSTANCE
164    }
165}