1use crate::errors::{SecurityError, SecurityResult};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct AuditConfig {
9 pub enable_integrity_check: bool,
11 pub enable_completeness_check: bool,
13 pub expected_event_rate: Option<f64>,
15 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), }
27 }
28}
29
30pub struct AuditValidator {
32 config: AuditConfig,
33}
34
35impl AuditValidator {
36 pub fn new(config: AuditConfig) -> Self {
38 Self { config }
39 }
40
41 pub fn default() -> Self {
43 Self::new(AuditConfig::default())
44 }
45
46 pub fn validate_event(&self, event: &AuditEvent) -> SecurityResult<()> {
48 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 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 match event.severity {
80 EventSeverity::Low
81 | EventSeverity::Medium
82 | EventSeverity::High
83 | EventSeverity::Critical => Ok(()),
84 }
85 }
86
87 pub fn check_suspicious_patterns(&self, event: &AuditEvent) -> SecurityResult<()> {
89 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 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 if event.action.contains("access") {
113 if let Some(ip) = event.metadata.get("ip_address") {
114 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 pub fn validate_sequence(&self, events: &[AuditEvent]) -> SecurityResult<()> {
128 if events.is_empty() {
129 return Ok(());
130 }
131
132 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 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 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 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 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 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 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233pub enum EventSeverity {
234 Low,
235 Medium,
236 High,
237 Critical,
238}
239
240#[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 let event = create_test_event("user123", "read");
276 assert!(validator.validate_event(&event).is_ok());
277
278 let mut event = create_test_event("", "read");
280 assert!(validator.validate_event(&event).is_err());
281
282 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 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 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 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 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 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 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 let mut event1 = create_test_event("user123", "read");
369 event1.timestamp = Some(base);
370 events.push(event1);
371
372 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}