mockforge_core/security/emitter.rs
1//! Global SIEM emitter manager
2//!
3//! Provides a global SIEM emitter instance that can be accessed throughout the application
4//! for emitting security events. Similar to the global request logger pattern.
5
6use crate::security::siem::{SiemConfig, SiemEmitter};
7use crate::Error;
8use once_cell::sync::Lazy;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11use tracing::{debug, error, warn};
12
13/// Global SIEM emitter instance
14static GLOBAL_SIEM_EMITTER: Lazy<Arc<RwLock<Option<SiemEmitter>>>> =
15 Lazy::new(|| Arc::new(RwLock::new(None)));
16
17/// Initialize the global SIEM emitter from configuration
18///
19/// This should be called once during application startup after loading the configuration.
20/// If called multiple times, it will replace the existing emitter.
21///
22/// # Arguments
23/// * `config` - SIEM configuration from ServerConfig
24///
25/// # Example
26/// ```no_run
27/// use mockforge_core::security::emitter::init_global_siem_emitter;
28/// use mockforge_core::security::siem::SiemConfig;
29///
30/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
31/// let config = SiemConfig::default();
32/// init_global_siem_emitter(config).await?;
33/// # Ok(())
34/// # }
35/// ```
36pub async fn init_global_siem_emitter(config: SiemConfig) -> Result<(), Error> {
37 let emitter = SiemEmitter::from_config(config).await?;
38 let mut global = GLOBAL_SIEM_EMITTER.write().await;
39 *global = Some(emitter);
40 debug!("Global SIEM emitter initialized");
41 Ok(())
42}
43
44/// Get the global SIEM emitter instance (for internal use)
45///
46/// This function is primarily for internal use. Most code should use `emit_security_event`
47/// instead, which handles the emitter access automatically.
48///
49/// Returns a reference to the emitter if initialized, None otherwise.
50pub async fn get_global_siem_emitter() -> Option<Arc<RwLock<Option<SiemEmitter>>>> {
51 Some(GLOBAL_SIEM_EMITTER.clone())
52}
53
54/// Emit a security event using the global SIEM emitter
55///
56/// This is a convenience function that automatically uses the global emitter if available.
57/// If the emitter is not initialized or emission fails, errors are logged but not propagated.
58///
59/// # Arguments
60/// * `event` - Security event to emit
61///
62/// # Example
63/// ```no_run
64/// use mockforge_core::security::emitter::emit_security_event;
65/// use mockforge_core::security::events::{SecurityEvent, SecurityEventType, EventActor};
66///
67/// # async fn example() {
68/// let event = SecurityEvent::new(SecurityEventType::AuthFailure, None, None)
69/// .with_actor(EventActor {
70/// user_id: None,
71/// username: Some("admin".to_string()),
72/// ip_address: Some("192.168.1.100".to_string()),
73/// user_agent: Some("Mozilla/5.0".to_string()),
74/// });
75/// emit_security_event(event).await;
76/// # }
77/// ```
78pub async fn emit_security_event(event: crate::security::events::SecurityEvent) {
79 let global = GLOBAL_SIEM_EMITTER.read().await;
80 if let Some(ref emitter) = *global {
81 if let Err(e) = emitter.emit(event).await {
82 error!("Failed to emit security event to SIEM: {}", e);
83 }
84 } else {
85 debug!("SIEM emitter not initialized, skipping event emission");
86 }
87}
88
89/// Emit a security event synchronously (spawns a task)
90///
91/// This is useful when you need to emit events from synchronous contexts.
92/// The event is emitted in a background task.
93///
94/// # Arguments
95/// * `event` - Security event to emit
96pub fn emit_security_event_async(event: crate::security::events::SecurityEvent) {
97 tokio::spawn(async move {
98 emit_security_event(event).await;
99 });
100}
101
102/// Check if SIEM emitter is initialized
103pub async fn is_siem_emitter_initialized() -> bool {
104 let global = GLOBAL_SIEM_EMITTER.read().await;
105 global.is_some()
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::security::events::SecurityEventType;
112 use crate::security::siem::SiemConfig;
113
114 #[tokio::test]
115 async fn test_init_global_siem_emitter() {
116 let config = SiemConfig::default();
117 assert!(init_global_siem_emitter(config).await.is_ok());
118 assert!(is_siem_emitter_initialized().await);
119 }
120
121 #[tokio::test]
122 async fn test_get_global_siem_emitter() {
123 let config = SiemConfig::default();
124 init_global_siem_emitter(config).await.unwrap();
125
126 let emitter_guard = get_global_siem_emitter().await;
127 assert!(emitter_guard.is_some());
128 let guard = emitter_guard.unwrap();
129 let emitter = guard.read().await;
130 assert!(emitter.is_some());
131 }
132
133 #[tokio::test]
134 async fn test_emit_security_event() {
135 let config = SiemConfig {
136 enabled: false, // Disable to avoid actual network calls in tests
137 protocol: None,
138 destinations: vec![],
139 filters: None,
140 };
141 init_global_siem_emitter(config).await.unwrap();
142
143 let event = crate::security::events::SecurityEvent::new(
144 SecurityEventType::AuthSuccess,
145 None,
146 None,
147 );
148
149 // Should not panic even with disabled emitter
150 emit_security_event(event).await;
151 }
152}