Skip to main content

netwatch_rs/
security.rs

1//! Security monitoring and intrusion detection for netwatch
2//!
3//! This module provides security monitoring capabilities to detect
4//! potential attacks and suspicious behavior during operation.
5
6use crate::error::{NetwatchError, Result};
7use std::collections::HashMap;
8use std::time::{Duration, Instant};
9
10/// Security event types that can be monitored
11#[derive(Debug, Clone, PartialEq)]
12pub enum SecurityEvent {
13    /// Invalid input attempt (injection, traversal, etc.)
14    InvalidInput {
15        input_type: String,
16        attempted_value: String,
17        source: String,
18    },
19    /// Suspicious file access pattern
20    SuspiciousFileAccess { path: String, access_type: String },
21    /// Rate limiting triggered
22    RateLimitExceeded { source: String, attempt_count: u32 },
23    /// Configuration tampering detected
24    ConfigTampering {
25        config_field: String,
26        old_value: String,
27        new_value: String,
28    },
29    /// Resource exhaustion attempt
30    ResourceExhaustion {
31        resource_type: String,
32        usage_amount: u64,
33        limit: u64,
34    },
35}
36
37/// Security monitor that tracks and analyzes security events
38pub struct SecurityMonitor {
39    events: Vec<(Instant, SecurityEvent)>,
40    event_counts: HashMap<String, u32>,
41    rate_limits: HashMap<String, (Instant, u32)>,
42    max_events: usize,
43    event_cursor: usize, // For circular buffer
44    last_cleanup: Instant,
45    high_performance_mode: bool,
46    events_this_second: u32,
47    current_second: u64,
48}
49
50impl SecurityMonitor {
51    /// Create a new security monitor
52    pub fn new() -> Self {
53        let now = Instant::now();
54        Self {
55            events: Vec::with_capacity(1000),
56            event_counts: HashMap::new(),
57            rate_limits: HashMap::new(),
58            max_events: 1000, // Keep last 1000 events
59            event_cursor: 0,
60            last_cleanup: now,
61            high_performance_mode: false,
62            events_this_second: 0,
63            current_second: now.elapsed().as_secs(),
64        }
65    }
66
67    /// Enable high performance mode for heavy traffic scenarios
68    pub fn set_high_performance_mode(&mut self, enabled: bool) {
69        self.high_performance_mode = enabled;
70        if enabled {
71            // Reduce buffer size for better performance
72            self.max_events = 500;
73            // Pre-allocate at reduced size
74            if self.events.capacity() > self.max_events {
75                self.events.truncate(self.max_events);
76                self.events.shrink_to_fit();
77            }
78        }
79    }
80
81    /// Record a security event
82    pub fn record_event(&mut self, event: SecurityEvent) {
83        let now = Instant::now();
84        let current_second = now.elapsed().as_secs();
85
86        // Reset counter if we're in a new second
87        if current_second != self.current_second {
88            self.current_second = current_second;
89            self.events_this_second = 0;
90        }
91
92        // Throttle in high performance mode under heavy load
93        if self.high_performance_mode {
94            self.events_this_second += 1;
95
96            // Skip non-critical events if we're seeing too many per second
97            if self.events_this_second > 100 && !self.is_critical_event(&event) {
98                return;
99            }
100
101            // Hard limit even for critical events
102            if self.events_this_second > 500 {
103                return;
104            }
105        }
106
107        // Use circular buffer approach for better performance
108        if self.events.len() < self.max_events {
109            self.events.push((now, event.clone()));
110        } else {
111            // Overwrite oldest event (circular buffer)
112            self.events[self.event_cursor] = (now, event.clone());
113            self.event_cursor = (self.event_cursor + 1) % self.max_events;
114        }
115
116        // Update event counts (only for critical events in high perf mode)
117        if !self.high_performance_mode || self.is_critical_event(&event) {
118            let event_key = self.event_key(&event);
119            *self.event_counts.entry(event_key).or_insert(0) += 1;
120        }
121
122        // Log critical events
123        if self.is_critical_event(&event) {
124            eprintln!("SECURITY ALERT: {event:?}");
125        }
126
127        // Periodic cleanup to prevent HashMap growth
128        if now.duration_since(self.last_cleanup) > Duration::from_secs(300) {
129            self.cleanup_old_data(now);
130        }
131    }
132
133    /// Clean up old data to prevent memory growth
134    fn cleanup_old_data(&mut self, now: Instant) {
135        self.last_cleanup = now;
136
137        // In high performance mode, clear old event counts more aggressively
138        if self.high_performance_mode && self.event_counts.len() > 100 {
139            let keys_to_remove: Vec<String> = self
140                .event_counts
141                .iter()
142                .filter(|(_, &count)| count < 5) // Remove entries with low counts
143                .map(|(key, _)| key.clone())
144                .collect();
145
146            for key in keys_to_remove {
147                self.event_counts.remove(&key);
148            }
149        }
150
151        // Clean up old rate limit entries
152        let cutoff = now - Duration::from_secs(120); // Keep 2 minutes of rate limit data
153        self.rate_limits.retain(|_, (time, _)| *time > cutoff);
154    }
155
156    /// Check if rate limiting should be applied
157    pub fn check_rate_limit(&mut self, source: &str, max_per_minute: u32) -> Result<()> {
158        let now = Instant::now();
159        let key = format!("rate_limit_{source}");
160
161        match self.rate_limits.get_mut(&key) {
162            Some((last_reset, count)) => {
163                if now.duration_since(*last_reset) > Duration::from_secs(60) {
164                    // Reset counter after 1 minute
165                    *last_reset = now;
166                    *count = 1;
167                } else {
168                    *count += 1;
169                    if *count > max_per_minute {
170                        return Err(NetwatchError::Security(format!(
171                            "Rate limit exceeded for source: {source}"
172                        )));
173                    }
174                }
175            }
176            None => {
177                self.rate_limits.insert(key, (now, 1));
178            }
179        }
180
181        Ok(())
182    }
183
184    /// Get security event statistics (optimized for high performance mode)
185    pub fn get_statistics(&self) -> SecurityStatistics {
186        // In high performance mode, provide simplified statistics
187        if self.high_performance_mode {
188            return SecurityStatistics {
189                total_events: self.events.len(),
190                events_last_hour: self.events.len().min(100), // Approximate
191                events_last_day: self.events.len(),
192                critical_events: 0,          // Skip expensive calculation
193                event_types: HashMap::new(), // Skip expensive clone
194            };
195        }
196
197        // Full statistics computation for normal mode
198        let now = Instant::now();
199        let last_hour = now - Duration::from_secs(3600);
200        let last_day = now - Duration::from_secs(86400);
201
202        let mut events_last_hour = 0;
203        let mut events_last_day = 0;
204        let mut critical_events = 0;
205
206        // Single pass through events for efficiency
207        for (time, event) in &self.events {
208            if *time > last_day {
209                events_last_day += 1;
210                if *time > last_hour {
211                    events_last_hour += 1;
212                }
213            }
214            if self.is_critical_event(event) {
215                critical_events += 1;
216            }
217        }
218
219        SecurityStatistics {
220            total_events: self.events.len(),
221            events_last_hour,
222            events_last_day,
223            critical_events,
224            event_types: self.event_counts.clone(),
225        }
226    }
227
228    /// Check for security anomalies (optimized for high performance mode)
229    pub fn check_anomalies(&self) -> Vec<SecurityAnomaly> {
230        // In high performance mode, skip expensive anomaly detection
231        if self.high_performance_mode {
232            return Vec::new();
233        }
234
235        let mut anomalies = Vec::new();
236        let now = Instant::now();
237        let last_minute = now - Duration::from_secs(60);
238
239        let mut recent_events = 0;
240        let mut invalid_input_sources = HashMap::new();
241
242        // Single pass through events for efficiency
243        for (time, event) in &self.events {
244            if *time > last_minute {
245                recent_events += 1;
246
247                // Track invalid input sources
248                if let SecurityEvent::InvalidInput { source, .. } = event {
249                    *invalid_input_sources.entry(source.clone()).or_insert(0) += 1;
250                }
251            }
252        }
253
254        // Check for burst of events
255        if recent_events > 10 {
256            anomalies.push(SecurityAnomaly::EventBurst {
257                event_count: recent_events,
258                time_window: Duration::from_secs(60),
259            });
260        }
261
262        for (source, count) in invalid_input_sources {
263            if count > 3 {
264                anomalies.push(SecurityAnomaly::RepeatedInvalidInput {
265                    source,
266                    attempt_count: count,
267                });
268            }
269        }
270
271        anomalies
272    }
273
274    fn event_key(&self, event: &SecurityEvent) -> String {
275        match event {
276            SecurityEvent::InvalidInput { input_type, .. } => format!("invalid_input_{input_type}"),
277            SecurityEvent::SuspiciousFileAccess { .. } => "suspicious_file_access".to_string(),
278            SecurityEvent::RateLimitExceeded { .. } => "rate_limit_exceeded".to_string(),
279            SecurityEvent::ConfigTampering { .. } => "config_tampering".to_string(),
280            SecurityEvent::ResourceExhaustion { .. } => "resource_exhaustion".to_string(),
281        }
282    }
283
284    fn is_critical_event(&self, event: &SecurityEvent) -> bool {
285        matches!(
286            event,
287            SecurityEvent::ConfigTampering { .. }
288                | SecurityEvent::SuspiciousFileAccess { .. }
289                | SecurityEvent::ResourceExhaustion { .. }
290        )
291    }
292}
293
294impl Default for SecurityMonitor {
295    fn default() -> Self {
296        Self::new()
297    }
298}
299
300/// Security statistics for monitoring
301#[derive(Debug, Clone)]
302pub struct SecurityStatistics {
303    pub total_events: usize,
304    pub events_last_hour: usize,
305    pub events_last_day: usize,
306    pub critical_events: usize,
307    pub event_types: HashMap<String, u32>,
308}
309
310/// Security anomalies detected by the monitor
311#[derive(Debug, Clone)]
312pub enum SecurityAnomaly {
313    /// Burst of security events in short time
314    EventBurst {
315        event_count: usize,
316        time_window: Duration,
317    },
318    /// Repeated invalid input from same source
319    RepeatedInvalidInput { source: String, attempt_count: u32 },
320    /// Unusual access pattern detected
321    UnusualAccessPattern {
322        pattern_description: String,
323        confidence: f32,
324    },
325}
326
327/// Global security monitor instance
328static mut SECURITY_MONITOR: Option<SecurityMonitor> = None;
329static mut MONITOR_INITIALIZED: bool = false;
330
331/// Initialize the global security monitor
332pub fn init_security_monitor() {
333    unsafe {
334        if !MONITOR_INITIALIZED {
335            SECURITY_MONITOR = Some(SecurityMonitor::new());
336            MONITOR_INITIALIZED = true;
337        }
338    }
339}
340
341/// Enable high performance mode for security monitoring
342pub fn enable_high_performance_security(enabled: bool) {
343    unsafe {
344        if let Some(ref mut monitor) = SECURITY_MONITOR {
345            monitor.set_high_performance_mode(enabled);
346        }
347    }
348}
349
350/// Record a security event to the global monitor
351pub fn record_security_event(event: SecurityEvent) {
352    unsafe {
353        if let Some(ref mut monitor) = SECURITY_MONITOR {
354            monitor.record_event(event);
355        }
356    }
357}
358
359/// Check rate limit using the global monitor
360pub fn check_security_rate_limit(source: &str, max_per_minute: u32) -> Result<()> {
361    unsafe {
362        if let Some(ref mut monitor) = SECURITY_MONITOR {
363            monitor.check_rate_limit(source, max_per_minute)
364        } else {
365            Ok(())
366        }
367    }
368}
369
370/// Get security statistics from the global monitor
371#[allow(static_mut_refs)]
372pub fn get_security_statistics() -> Option<SecurityStatistics> {
373    unsafe {
374        SECURITY_MONITOR
375            .as_ref()
376            .map(|monitor| monitor.get_statistics())
377    }
378}
379
380/// Check for security anomalies
381#[allow(static_mut_refs)]
382pub fn check_security_anomalies() -> Vec<SecurityAnomaly> {
383    unsafe {
384        SECURITY_MONITOR
385            .as_ref()
386            .map(|monitor| monitor.check_anomalies())
387            .unwrap_or_default()
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn test_security_event_recording() {
397        let mut monitor = SecurityMonitor::new();
398
399        let event = SecurityEvent::InvalidInput {
400            input_type: "interface_name".to_string(),
401            attempted_value: "../etc/passwd".to_string(),
402            source: "cli".to_string(),
403        };
404
405        monitor.record_event(event);
406
407        let stats = monitor.get_statistics();
408        assert_eq!(stats.total_events, 1);
409        assert_eq!(
410            stats.event_types.get("invalid_input_interface_name"),
411            Some(&1)
412        );
413    }
414
415    #[test]
416    fn test_rate_limiting() {
417        let mut monitor = SecurityMonitor::new();
418
419        // First few attempts should succeed
420        for _i in 0..3 {
421            assert!(monitor.check_rate_limit("test_source", 5).is_ok());
422        }
423
424        // Should still be under limit
425        assert!(monitor.check_rate_limit("test_source", 5).is_ok());
426        assert!(monitor.check_rate_limit("test_source", 5).is_ok());
427
428        // This should exceed the limit
429        assert!(monitor.check_rate_limit("test_source", 5).is_err());
430    }
431
432    #[test]
433    fn test_anomaly_detection() {
434        let mut monitor = SecurityMonitor::new();
435
436        // Generate burst of events
437        for i in 0..15 {
438            let event = SecurityEvent::InvalidInput {
439                input_type: "test".to_string(),
440                attempted_value: format!("attack_{i}"),
441                source: "attacker".to_string(),
442            };
443            monitor.record_event(event);
444        }
445
446        let anomalies = monitor.check_anomalies();
447        assert!(!anomalies.is_empty());
448
449        // Should detect event burst
450        assert!(anomalies
451            .iter()
452            .any(|a| matches!(a, SecurityAnomaly::EventBurst { .. })));
453
454        // Should detect repeated invalid input
455        assert!(anomalies
456            .iter()
457            .any(|a| matches!(a, SecurityAnomaly::RepeatedInvalidInput { .. })));
458    }
459
460    #[test]
461    fn test_critical_event_detection() {
462        let mut monitor = SecurityMonitor::new();
463
464        let critical_event = SecurityEvent::ConfigTampering {
465            config_field: "log_file".to_string(),
466            old_value: "/tmp/netwatch.log".to_string(),
467            new_value: "/etc/passwd".to_string(),
468        };
469
470        monitor.record_event(critical_event);
471
472        let stats = monitor.get_statistics();
473        assert_eq!(stats.critical_events, 1);
474    }
475}