1use serde::{Deserialize, Serialize};
6use std::collections::VecDeque;
7use std::fs::{File, OpenOptions};
8use std::io::Write as IoWrite;
9use std::path::PathBuf;
10use std::sync::{Arc, Mutex};
11use std::time::SystemTime;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum AuditEventType {
16 Insert,
18 Update,
20 Delete,
22 Query,
24 Batch,
26 IndexCreate,
28 IndexDelete,
30 Auth,
32 Authz,
34 ConfigChange,
36 Backup,
38 Restore,
40 Export,
42 Import,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
48pub enum AuditSeverity {
49 Debug,
50 Info,
51 Warning,
52 Error,
53 Critical,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
58pub enum AuditOutcome {
59 Success,
60 Failure,
61 Denied,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct AuditMetadata {
67 pub user_id: Option<String>,
69
70 pub ip_address: Option<String>,
72
73 pub session_id: Option<String>,
75
76 pub request_id: Option<String>,
78
79 #[serde(flatten)]
81 pub custom: std::collections::HashMap<String, serde_json::Value>,
82}
83
84impl Default for AuditMetadata {
85 fn default() -> Self {
86 Self {
87 user_id: None,
88 ip_address: None,
89 session_id: None,
90 request_id: None,
91 custom: std::collections::HashMap::new(),
92 }
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct AuditEntry {
99 pub id: String,
101
102 pub timestamp: SystemTime,
104
105 pub event_type: AuditEventType,
107
108 pub severity: AuditSeverity,
110
111 pub outcome: AuditOutcome,
113
114 pub resource: Option<String>,
116
117 pub action: String,
119
120 pub details: Option<String>,
122
123 pub metadata: AuditMetadata,
125
126 pub duration_ms: Option<u64>,
128}
129
130impl AuditEntry {
131 pub fn new(event_type: AuditEventType, action: impl Into<String>) -> Self {
133 let timestamp = SystemTime::now();
134 let micros = timestamp
135 .duration_since(SystemTime::UNIX_EPOCH)
136 .unwrap()
137 .as_micros();
138
139 let random_component = (micros % 1000000) as u32;
141 let id = format!("audit-{}-{}", micros, random_component);
142
143 Self {
144 id,
145 timestamp,
146 event_type,
147 severity: AuditSeverity::Info,
148 outcome: AuditOutcome::Success,
149 resource: None,
150 action: action.into(),
151 details: None,
152 metadata: AuditMetadata::default(),
153 duration_ms: None,
154 }
155 }
156
157 pub fn with_severity(mut self, severity: AuditSeverity) -> Self {
159 self.severity = severity;
160 self
161 }
162
163 pub fn with_outcome(mut self, outcome: AuditOutcome) -> Self {
165 self.outcome = outcome;
166 self
167 }
168
169 pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
171 self.resource = Some(resource.into());
172 self
173 }
174
175 pub fn with_details(mut self, details: impl Into<String>) -> Self {
177 self.details = Some(details.into());
178 self
179 }
180
181 pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
183 self.metadata.user_id = Some(user_id.into());
184 self
185 }
186
187 pub fn with_ip(mut self, ip: impl Into<String>) -> Self {
189 self.metadata.ip_address = Some(ip.into());
190 self
191 }
192
193 pub fn with_duration(mut self, duration_ms: u64) -> Self {
195 self.duration_ms = Some(duration_ms);
196 self
197 }
198}
199
200pub trait AuditBackend: Send + Sync {
202 fn write(&mut self, entry: &AuditEntry) -> Result<(), String>;
204
205 fn flush(&mut self) -> Result<(), String>;
207}
208
209pub struct MemoryBackend {
211 entries: Arc<Mutex<VecDeque<AuditEntry>>>,
212 max_size: usize,
213}
214
215impl MemoryBackend {
216 pub fn new(max_size: usize) -> Self {
217 Self {
218 entries: Arc::new(Mutex::new(VecDeque::with_capacity(max_size))),
219 max_size,
220 }
221 }
222
223 pub fn get_entries(&self) -> Vec<AuditEntry> {
224 self.entries.lock().unwrap().iter().cloned().collect()
225 }
226
227 pub fn clear(&self) {
228 self.entries.lock().unwrap().clear();
229 }
230}
231
232impl AuditBackend for MemoryBackend {
233 fn write(&mut self, entry: &AuditEntry) -> Result<(), String> {
234 let mut entries = self.entries.lock().unwrap();
235 if entries.len() >= self.max_size {
236 entries.pop_front();
237 }
238 entries.push_back(entry.clone());
239 Ok(())
240 }
241
242 fn flush(&mut self) -> Result<(), String> {
243 Ok(())
244 }
245}
246
247pub struct FileBackend {
249 file: Arc<Mutex<File>>,
250 buffer: Arc<Mutex<Vec<String>>>,
251 buffer_size: usize,
252}
253
254impl FileBackend {
255 pub fn new(path: PathBuf) -> Result<Self, String> {
256 let file = OpenOptions::new()
257 .create(true)
258 .append(true)
259 .open(path)
260 .map_err(|e| format!("Failed to open audit log file: {}", e))?;
261
262 Ok(Self {
263 file: Arc::new(Mutex::new(file)),
264 buffer: Arc::new(Mutex::new(Vec::new())),
265 buffer_size: 100,
266 })
267 }
268
269 pub fn with_buffer_size(mut self, size: usize) -> Self {
270 self.buffer_size = size;
271 self
272 }
273}
274
275impl AuditBackend for FileBackend {
276 fn write(&mut self, entry: &AuditEntry) -> Result<(), String> {
277 let json = serde_json::to_string(entry)
278 .map_err(|e| format!("Failed to serialize audit entry: {}", e))?;
279
280 let mut buffer = self.buffer.lock().unwrap();
281 buffer.push(json);
282
283 if buffer.len() >= self.buffer_size {
284 drop(buffer);
285 self.flush()?;
286 }
287
288 Ok(())
289 }
290
291 fn flush(&mut self) -> Result<(), String> {
292 let mut buffer = self.buffer.lock().unwrap();
293 if buffer.is_empty() {
294 return Ok(());
295 }
296
297 let mut file = self.file.lock().unwrap();
298
299 for line in buffer.iter() {
300 writeln!(file, "{}", line).map_err(|e| format!("Failed to write audit log: {}", e))?;
301 }
302
303 file.flush()
304 .map_err(|e| format!("Failed to flush audit log: {}", e))?;
305
306 buffer.clear();
307 Ok(())
308 }
309}
310
311pub struct StdoutBackend;
313
314impl AuditBackend for StdoutBackend {
315 fn write(&mut self, entry: &AuditEntry) -> Result<(), String> {
316 let json = serde_json::to_string(entry)
317 .map_err(|e| format!("Failed to serialize audit entry: {}", e))?;
318 println!("{}", json);
319 Ok(())
320 }
321
322 fn flush(&mut self) -> Result<(), String> {
323 Ok(())
324 }
325}
326
327#[derive(Debug, Clone)]
329pub struct AuditConfig {
330 pub enabled: bool,
332
333 pub min_severity: AuditSeverity,
335
336 pub event_types: Vec<AuditEventType>,
338
339 pub include_stack_traces: bool,
341}
342
343impl Default for AuditConfig {
344 fn default() -> Self {
345 Self {
346 enabled: true,
347 min_severity: AuditSeverity::Info,
348 event_types: vec![
349 AuditEventType::Insert,
350 AuditEventType::Update,
351 AuditEventType::Delete,
352 AuditEventType::Query,
353 AuditEventType::Auth,
354 AuditEventType::Authz,
355 AuditEventType::ConfigChange,
356 ],
357 include_stack_traces: false,
358 }
359 }
360}
361
362pub struct AuditLogger {
364 config: AuditConfig,
365 backends: Arc<Mutex<Vec<Box<dyn AuditBackend>>>>,
366}
367
368impl AuditLogger {
369 pub fn new(config: AuditConfig) -> Self {
371 Self {
372 config,
373 backends: Arc::new(Mutex::new(Vec::new())),
374 }
375 }
376
377 pub fn default() -> Self {
379 Self::new(AuditConfig::default())
380 }
381
382 pub fn add_backend(&self, backend: Box<dyn AuditBackend>) {
384 self.backends.lock().unwrap().push(backend);
385 }
386
387 pub fn log(&self, entry: AuditEntry) -> Result<(), String> {
389 if !self.config.enabled {
390 return Ok(());
391 }
392
393 if entry.severity < self.config.min_severity {
395 return Ok(());
396 }
397
398 if !self.config.event_types.is_empty()
400 && !self.config.event_types.contains(&entry.event_type)
401 {
402 return Ok(());
403 }
404
405 let mut backends = self.backends.lock().unwrap();
407 for backend in backends.iter_mut() {
408 backend.write(&entry)?;
409 }
410
411 Ok(())
412 }
413
414 pub fn flush(&self) -> Result<(), String> {
416 let mut backends = self.backends.lock().unwrap();
417 for backend in backends.iter_mut() {
418 backend.flush()?;
419 }
420 Ok(())
421 }
422
423 pub fn log_insert(&self, resource: &str, user_id: Option<&str>) -> Result<(), String> {
425 let mut entry = AuditEntry::new(AuditEventType::Insert, "insert vector");
426 entry = entry.with_resource(resource);
427 if let Some(user) = user_id {
428 entry = entry.with_user(user);
429 }
430 self.log(entry)
431 }
432
433 pub fn log_query(
435 &self,
436 query_type: &str,
437 duration_ms: u64,
438 user_id: Option<&str>,
439 ) -> Result<(), String> {
440 let mut entry = AuditEntry::new(AuditEventType::Query, format!("query: {}", query_type));
441 entry = entry.with_duration(duration_ms);
442 if let Some(user) = user_id {
443 entry = entry.with_user(user);
444 }
445 self.log(entry)
446 }
447
448 pub fn log_delete(&self, resource: &str, user_id: Option<&str>) -> Result<(), String> {
450 let mut entry = AuditEntry::new(AuditEventType::Delete, "delete vector");
451 entry = entry
452 .with_resource(resource)
453 .with_severity(AuditSeverity::Warning);
454 if let Some(user) = user_id {
455 entry = entry.with_user(user);
456 }
457 self.log(entry)
458 }
459
460 pub fn log_auth(&self, user_id: &str, ip: &str, outcome: AuditOutcome) -> Result<(), String> {
462 let entry = AuditEntry::new(AuditEventType::Auth, "authentication attempt")
463 .with_user(user_id)
464 .with_ip(ip)
465 .with_outcome(outcome)
466 .with_severity(if outcome == AuditOutcome::Success {
467 AuditSeverity::Info
468 } else {
469 AuditSeverity::Warning
470 });
471 self.log(entry)
472 }
473
474 pub fn log_authz(
476 &self,
477 user_id: &str,
478 resource: &str,
479 action: &str,
480 outcome: AuditOutcome,
481 ) -> Result<(), String> {
482 let entry = AuditEntry::new(AuditEventType::Authz, format!("authz: {}", action))
483 .with_user(user_id)
484 .with_resource(resource)
485 .with_outcome(outcome)
486 .with_severity(if outcome == AuditOutcome::Denied {
487 AuditSeverity::Warning
488 } else {
489 AuditSeverity::Info
490 });
491 self.log(entry)
492 }
493}
494
495impl Drop for AuditLogger {
496 fn drop(&mut self) {
497 let _ = self.flush();
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use std::sync::Arc;
505
506 #[test]
507 fn test_audit_entry_creation() {
508 let entry = AuditEntry::new(AuditEventType::Insert, "test action");
509 assert_eq!(entry.event_type, AuditEventType::Insert);
510 assert_eq!(entry.action, "test action");
511 assert_eq!(entry.severity, AuditSeverity::Info);
512 assert_eq!(entry.outcome, AuditOutcome::Success);
513 }
514
515 #[test]
516 fn test_audit_entry_builder() {
517 let entry = AuditEntry::new(AuditEventType::Query, "test query")
518 .with_severity(AuditSeverity::Warning)
519 .with_outcome(AuditOutcome::Failure)
520 .with_resource("vector_123")
521 .with_user("user_456")
522 .with_ip("192.168.1.1")
523 .with_duration(100);
524
525 assert_eq!(entry.severity, AuditSeverity::Warning);
526 assert_eq!(entry.outcome, AuditOutcome::Failure);
527 assert_eq!(entry.resource, Some("vector_123".to_string()));
528 assert_eq!(entry.metadata.user_id, Some("user_456".to_string()));
529 assert_eq!(entry.metadata.ip_address, Some("192.168.1.1".to_string()));
530 assert_eq!(entry.duration_ms, Some(100));
531 }
532
533 #[test]
534 fn test_memory_backend() {
535 let mut backend = MemoryBackend::new(10);
536
537 let entry = AuditEntry::new(AuditEventType::Insert, "test");
538 backend.write(&entry).unwrap();
539
540 let entries = backend.get_entries();
541 assert_eq!(entries.len(), 1);
542 assert_eq!(entries[0].action, "test");
543 }
544
545 #[test]
546 fn test_memory_backend_overflow() {
547 let mut backend = MemoryBackend::new(3);
548
549 for i in 0..5 {
550 let entry = AuditEntry::new(AuditEventType::Insert, format!("action_{}", i));
551 backend.write(&entry).unwrap();
552 }
553
554 let entries = backend.get_entries();
555 assert_eq!(entries.len(), 3);
556 assert_eq!(entries[0].action, "action_2");
558 assert_eq!(entries[2].action, "action_4");
559 }
560
561 #[test]
562 fn test_audit_logger() {
563 let logger = AuditLogger::default();
564 let backend = Box::new(MemoryBackend::new(100));
565 let backend_ref = unsafe {
566 let ptr = &*backend as *const MemoryBackend;
567 &*ptr
568 };
569 logger.add_backend(backend);
570
571 let entry = AuditEntry::new(AuditEventType::Query, "test query");
572 logger.log(entry).unwrap();
573
574 let entries = backend_ref.get_entries();
575 assert_eq!(entries.len(), 1);
576 }
577
578 #[test]
579 fn test_severity_filtering() {
580 let mut config = AuditConfig::default();
581 config.min_severity = AuditSeverity::Warning;
582
583 let logger = AuditLogger::new(config);
584 let backend = Box::new(MemoryBackend::new(100));
585 let backend_ref = unsafe {
586 let ptr = &*backend as *const MemoryBackend;
587 &*ptr
588 };
589 logger.add_backend(backend);
590
591 let entry1 =
593 AuditEntry::new(AuditEventType::Query, "info entry").with_severity(AuditSeverity::Info);
594 logger.log(entry1).unwrap();
595
596 let entry2 = AuditEntry::new(AuditEventType::Query, "warning entry")
598 .with_severity(AuditSeverity::Warning);
599 logger.log(entry2).unwrap();
600
601 let entries = backend_ref.get_entries();
602 assert_eq!(entries.len(), 1);
603 assert_eq!(entries[0].action, "warning entry");
604 }
605
606 #[test]
607 fn test_event_type_filtering() {
608 let mut config = AuditConfig::default();
609 config.event_types = vec![AuditEventType::Insert, AuditEventType::Delete];
610
611 let logger = AuditLogger::new(config);
612 let backend = Box::new(MemoryBackend::new(100));
613 let backend_ref = unsafe {
614 let ptr = &*backend as *const MemoryBackend;
615 &*ptr
616 };
617 logger.add_backend(backend);
618
619 logger.log_insert("vec_1", Some("user_1")).unwrap();
621
622 logger.log_query("knn", 100, Some("user_1")).unwrap();
624
625 logger.log_delete("vec_2", Some("user_1")).unwrap();
627
628 let entries = backend_ref.get_entries();
629 assert_eq!(entries.len(), 2);
630 }
631
632 #[test]
633 fn test_helper_methods() {
634 let logger = AuditLogger::default();
635 let backend = Box::new(MemoryBackend::new(100));
636 let backend_ref = unsafe {
637 let ptr = &*backend as *const MemoryBackend;
638 &*ptr
639 };
640 logger.add_backend(backend);
641
642 logger.log_insert("vec_1", Some("user_1")).unwrap();
643 logger.log_query("knn", 50, Some("user_2")).unwrap();
644 logger.log_delete("vec_3", Some("user_3")).unwrap();
645 logger
646 .log_auth("user_4", "192.168.1.1", AuditOutcome::Success)
647 .unwrap();
648 logger
649 .log_authz("user_5", "vec_5", "read", AuditOutcome::Denied)
650 .unwrap();
651
652 let entries = backend_ref.get_entries();
653 assert_eq!(entries.len(), 5);
654 }
655
656 #[test]
657 fn test_disabled_logger() {
658 let mut config = AuditConfig::default();
659 config.enabled = false;
660
661 let logger = AuditLogger::new(config);
662 let backend = Box::new(MemoryBackend::new(100));
663 let backend_ref = unsafe {
664 let ptr = &*backend as *const MemoryBackend;
665 &*ptr
666 };
667 logger.add_backend(backend);
668
669 logger.log_insert("vec_1", Some("user_1")).unwrap();
670
671 let entries = backend_ref.get_entries();
672 assert_eq!(entries.len(), 0);
673 }
674}