llm_config_security/
audit.rs

1//! Audit log validation
2
3use crate::errors::{SecurityError, SecurityResult};
4use serde::{Deserialize, Serialize};
5
6/// Audit configuration
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct AuditConfig {
9    /// Enable integrity verification
10    pub enable_integrity_check: bool,
11    /// Enable completeness check
12    pub enable_completeness_check: bool,
13    /// Expected event rate (events per second)
14    pub expected_event_rate: Option<f64>,
15    /// Maximum gap between events (seconds)
16    pub max_event_gap_seconds: Option<u64>,
17}
18
19impl Default for AuditConfig {
20    fn default() -> Self {
21        Self {
22            enable_integrity_check: true,
23            enable_completeness_check: true,
24            expected_event_rate: None,
25            max_event_gap_seconds: Some(300), // 5 minutes
26        }
27    }
28}
29
30/// Audit validator
31pub struct AuditValidator {
32    config: AuditConfig,
33}
34
35impl AuditValidator {
36    /// Create a new audit validator
37    pub fn new(config: AuditConfig) -> Self {
38        Self { config }
39    }
40
41    /// Create with default configuration
42    pub fn default() -> Self {
43        Self::new(AuditConfig::default())
44    }
45
46    /// Validate audit event
47    pub fn validate_event(&self, event: &AuditEvent) -> SecurityResult<()> {
48        // Validate required fields
49        if event.timestamp.is_none() {
50            return Err(SecurityError::AuditError(
51                "Missing timestamp".to_string(),
52            ));
53        }
54
55        if event.user_id.is_empty() {
56            return Err(SecurityError::AuditError("Missing user_id".to_string()));
57        }
58
59        if event.action.is_empty() {
60            return Err(SecurityError::AuditError("Missing action".to_string()));
61        }
62
63        if event.resource.is_empty() {
64            return Err(SecurityError::AuditError(
65                "Missing resource".to_string(),
66            ));
67        }
68
69        // Validate timestamp is not in the future
70        if let Some(timestamp) = event.timestamp {
71            if timestamp > chrono::Utc::now() {
72                return Err(SecurityError::AuditError(
73                    "Timestamp in the future".to_string(),
74                ));
75            }
76        }
77
78        // Validate severity
79        match event.severity {
80            EventSeverity::Low
81            | EventSeverity::Medium
82            | EventSeverity::High
83            | EventSeverity::Critical => Ok(()),
84        }
85    }
86
87    /// Check for suspicious patterns
88    pub fn check_suspicious_patterns(&self, event: &AuditEvent) -> SecurityResult<()> {
89        // Check for potential privilege escalation
90        if event.action.contains("permission") && event.action.contains("grant") {
91            if event.metadata.get("new_role") == Some(&"admin".to_string()) {
92                return Err(SecurityError::SuspiciousActivity(
93                    "Potential privilege escalation detected".to_string(),
94                ));
95            }
96        }
97
98        // Check for mass deletion
99        if event.action.contains("delete") {
100            if let Some(count_str) = event.metadata.get("count") {
101                if let Ok(count) = count_str.parse::<usize>() {
102                    if count > 1000 {
103                        return Err(SecurityError::SuspiciousActivity(
104                            "Mass deletion detected".to_string(),
105                        ));
106                    }
107                }
108            }
109        }
110
111        // Check for unusual access patterns
112        if event.action.contains("access") {
113            if let Some(ip) = event.metadata.get("ip_address") {
114                // In a real implementation, we would check against known patterns
115                if ip.starts_with("0.") || ip.starts_with("255.") {
116                    return Err(SecurityError::SuspiciousActivity(
117                        "Access from suspicious IP".to_string(),
118                    ));
119                }
120            }
121        }
122
123        Ok(())
124    }
125
126    /// Validate audit log sequence
127    pub fn validate_sequence(&self, events: &[AuditEvent]) -> SecurityResult<()> {
128        if events.is_empty() {
129            return Ok(());
130        }
131
132        // Check chronological order
133        for i in 1..events.len() {
134            if let (Some(prev), Some(curr)) = (events[i - 1].timestamp, events[i].timestamp) {
135                if curr < prev {
136                    return Err(SecurityError::AuditError(
137                        "Events not in chronological order".to_string(),
138                    ));
139                }
140
141                // Check for suspicious gaps
142                if let Some(max_gap) = self.config.max_event_gap_seconds {
143                    let gap = curr.signed_duration_since(prev).num_seconds();
144                    if gap > max_gap as i64 {
145                        return Err(SecurityError::AuditError(format!(
146                            "Suspicious gap of {} seconds between events",
147                            gap
148                        )));
149                    }
150                }
151            }
152        }
153
154        // Check for missing sequence numbers (if present)
155        let sequence_numbers: Vec<_> = events
156            .iter()
157            .filter_map(|e| e.metadata.get("sequence_number"))
158            .filter_map(|s| s.parse::<u64>().ok())
159            .collect();
160
161        if !sequence_numbers.is_empty() {
162            for i in 1..sequence_numbers.len() {
163                if sequence_numbers[i] != sequence_numbers[i - 1] + 1 {
164                    return Err(SecurityError::AuditError(
165                        "Missing sequence number in audit log".to_string(),
166                    ));
167                }
168            }
169        }
170
171        Ok(())
172    }
173
174    /// Calculate audit statistics
175    pub fn calculate_stats(&self, events: &[AuditEvent]) -> AuditStats {
176        if events.is_empty() {
177            return AuditStats::default();
178        }
179
180        let mut stats = AuditStats::default();
181        stats.total_events = events.len();
182
183        // Count by severity
184        for event in events {
185            match event.severity {
186                EventSeverity::Low => stats.low_severity += 1,
187                EventSeverity::Medium => stats.medium_severity += 1,
188                EventSeverity::High => stats.high_severity += 1,
189                EventSeverity::Critical => stats.critical_severity += 1,
190            }
191        }
192
193        // Calculate time range
194        if let (Some(first), Some(last)) = (
195            events.first().and_then(|e| e.timestamp),
196            events.last().and_then(|e| e.timestamp),
197        ) {
198            stats.time_range_seconds = last.signed_duration_since(first).num_seconds();
199
200            if stats.time_range_seconds > 0 {
201                stats.events_per_second =
202                    stats.total_events as f64 / stats.time_range_seconds as f64;
203            }
204        }
205
206        // Check against expected rate
207        if let Some(expected_rate) = self.config.expected_event_rate {
208            if stats.events_per_second < expected_rate * 0.5 {
209                stats.anomalies.push("Event rate significantly below expected".to_string());
210            } else if stats.events_per_second > expected_rate * 2.0 {
211                stats.anomalies.push("Event rate significantly above expected".to_string());
212            }
213        }
214
215        stats
216    }
217}
218
219/// Audit event
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct AuditEvent {
222    pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
223    pub user_id: String,
224    pub action: String,
225    pub resource: String,
226    pub result: String,
227    pub severity: EventSeverity,
228    pub metadata: std::collections::HashMap<String, String>,
229}
230
231/// Event severity
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233pub enum EventSeverity {
234    Low,
235    Medium,
236    High,
237    Critical,
238}
239
240/// Audit statistics
241#[derive(Debug, Clone, Default)]
242pub struct AuditStats {
243    pub total_events: usize,
244    pub low_severity: usize,
245    pub medium_severity: usize,
246    pub high_severity: usize,
247    pub critical_severity: usize,
248    pub time_range_seconds: i64,
249    pub events_per_second: f64,
250    pub anomalies: Vec<String>,
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use std::collections::HashMap;
257
258    fn create_test_event(user_id: &str, action: &str) -> AuditEvent {
259        AuditEvent {
260            timestamp: Some(chrono::Utc::now()),
261            user_id: user_id.to_string(),
262            action: action.to_string(),
263            resource: "test_resource".to_string(),
264            result: "success".to_string(),
265            severity: EventSeverity::Medium,
266            metadata: HashMap::new(),
267        }
268    }
269
270    #[test]
271    fn test_event_validation() {
272        let validator = AuditValidator::default();
273
274        // Valid event
275        let event = create_test_event("user123", "read");
276        assert!(validator.validate_event(&event).is_ok());
277
278        // Missing user_id
279        let mut event = create_test_event("", "read");
280        assert!(validator.validate_event(&event).is_err());
281
282        // Missing action
283        event = create_test_event("user123", "");
284        assert!(validator.validate_event(&event).is_err());
285    }
286
287    #[test]
288    fn test_suspicious_patterns() {
289        let validator = AuditValidator::default();
290
291        // Privilege escalation
292        let mut event = create_test_event("user123", "permission_grant");
293        event.metadata.insert("new_role".to_string(), "admin".to_string());
294        assert!(validator.check_suspicious_patterns(&event).is_err());
295
296        // Mass deletion
297        let mut event = create_test_event("user123", "delete");
298        event.metadata.insert("count".to_string(), "5000".to_string());
299        assert!(validator.check_suspicious_patterns(&event).is_err());
300
301        // Suspicious IP
302        let mut event = create_test_event("user123", "access");
303        event
304            .metadata
305            .insert("ip_address".to_string(), "0.0.0.1".to_string());
306        assert!(validator.check_suspicious_patterns(&event).is_err());
307    }
308
309    #[test]
310    fn test_sequence_validation() {
311        let validator = AuditValidator::default();
312
313        // Valid sequence
314        let mut events = vec![];
315        let base = chrono::Utc::now();
316        for i in 0..5 {
317            let mut event = create_test_event("user123", "read");
318            event.timestamp = Some(base + chrono::Duration::seconds(i));
319            events.push(event);
320        }
321        assert!(validator.validate_sequence(&events).is_ok());
322
323        // Out of order
324        events.reverse();
325        assert!(validator.validate_sequence(&events).is_err());
326    }
327
328    #[test]
329    fn test_stats_calculation() {
330        let validator = AuditValidator::default();
331
332        let mut events = vec![];
333        let base = chrono::Utc::now();
334
335        // Add events with different severities
336        for i in 0..10 {
337            let mut event = create_test_event("user123", "read");
338            event.timestamp = Some(base + chrono::Duration::seconds(i));
339            event.severity = match i % 4 {
340                0 => EventSeverity::Low,
341                1 => EventSeverity::Medium,
342                2 => EventSeverity::High,
343                _ => EventSeverity::Critical,
344            };
345            events.push(event);
346        }
347
348        let stats = validator.calculate_stats(&events);
349        assert_eq!(stats.total_events, 10);
350        assert!(stats.low_severity > 0);
351        assert!(stats.medium_severity > 0);
352        assert!(stats.high_severity > 0);
353        assert!(stats.critical_severity > 0);
354    }
355
356    #[test]
357    fn test_gap_detection() {
358        let config = AuditConfig {
359            max_event_gap_seconds: Some(60),
360            ..Default::default()
361        };
362        let validator = AuditValidator::new(config);
363
364        let base = chrono::Utc::now();
365        let mut events = vec![];
366
367        // First event
368        let mut event1 = create_test_event("user123", "read");
369        event1.timestamp = Some(base);
370        events.push(event1);
371
372        // Second event with large gap
373        let mut event2 = create_test_event("user123", "read");
374        event2.timestamp = Some(base + chrono::Duration::seconds(120));
375        events.push(event2);
376
377        assert!(validator.validate_sequence(&events).is_err());
378    }
379}