Skip to main content

fraiseql_server/encryption/
audit_logging.rs

1// Phase 12.3 Cycle 5: Audit Logging & Advanced Features (GREEN)
2//! Audit logging for encryption/decryption operations
3//!
4//! Provides comprehensive logging of all field-level encryption operations
5//! for compliance (HIPAA, PCI-DSS, GDPR, SOC 2) and security monitoring.
6
7use std::collections::HashMap;
8
9use chrono::{DateTime, Utc};
10
11use crate::secrets_manager::SecretsError;
12
13/// Encryption operation type
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum OperationType {
16    /// INSERT operation
17    Insert,
18    /// SELECT operation
19    Select,
20    /// UPDATE operation
21    Update,
22    /// DELETE operation
23    Delete,
24}
25
26impl std::fmt::Display for OperationType {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            Self::Insert => write!(f, "insert"),
30            Self::Select => write!(f, "select"),
31            Self::Update => write!(f, "update"),
32            Self::Delete => write!(f, "delete"),
33        }
34    }
35}
36
37/// Encryption event status
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum EventStatus {
40    /// Operation succeeded
41    Success,
42    /// Operation failed
43    Failure,
44}
45
46impl std::fmt::Display for EventStatus {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            Self::Success => write!(f, "success"),
50            Self::Failure => write!(f, "failure"),
51        }
52    }
53}
54
55/// Single audit log entry for encryption operation
56#[derive(Debug, Clone)]
57pub struct AuditLogEntry {
58    /// Timestamp of operation
59    timestamp:     DateTime<Utc>,
60    /// User performing operation
61    user_id:       String,
62    /// Field name being encrypted/decrypted
63    field_name:    String,
64    /// Operation type
65    operation:     OperationType,
66    /// Success or failure
67    status:        EventStatus,
68    /// Error message if failed
69    error_message: Option<String>,
70    /// Request ID for correlation
71    request_id:    String,
72    /// Session ID for tracking
73    session_id:    String,
74    /// Additional context data
75    context:       HashMap<String, String>,
76}
77
78impl AuditLogEntry {
79    /// Create new audit log entry
80    pub fn new(
81        user_id: impl Into<String>,
82        field_name: impl Into<String>,
83        operation: OperationType,
84        request_id: impl Into<String>,
85        session_id: impl Into<String>,
86    ) -> Self {
87        Self {
88            timestamp: Utc::now(),
89            user_id: user_id.into(),
90            field_name: field_name.into(),
91            operation,
92            status: EventStatus::Success,
93            error_message: None,
94            request_id: request_id.into(),
95            session_id: session_id.into(),
96            context: HashMap::new(),
97        }
98    }
99
100    /// Mark entry as failed
101    pub fn with_failure(mut self, error: impl Into<String>) -> Self {
102        self.status = EventStatus::Failure;
103        self.error_message = Some(error.into());
104        self
105    }
106
107    /// Add context data
108    pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
109        self.context.insert(key.into(), value.into());
110        self
111    }
112
113    /// Add common security context data
114    pub fn with_security_context(self, ip_address: Option<&str>, user_role: Option<&str>) -> Self {
115        let mut entry = self;
116        if let Some(ip) = ip_address {
117            entry = entry.with_context("ip_address", ip);
118        }
119        if let Some(role) = user_role {
120            entry = entry.with_context("user_role", role);
121        }
122        entry
123    }
124
125    /// Get timestamp
126    pub fn timestamp(&self) -> DateTime<Utc> {
127        self.timestamp
128    }
129
130    /// Get user ID
131    pub fn user_id(&self) -> &str {
132        &self.user_id
133    }
134
135    /// Get field name
136    pub fn field_name(&self) -> &str {
137        &self.field_name
138    }
139
140    /// Get operation type
141    pub fn operation(&self) -> OperationType {
142        self.operation
143    }
144
145    /// Get status
146    pub fn status(&self) -> EventStatus {
147        self.status
148    }
149
150    /// Get error message
151    pub fn error_message(&self) -> Option<&str> {
152        self.error_message.as_deref()
153    }
154
155    /// Get request ID
156    pub fn request_id(&self) -> &str {
157        &self.request_id
158    }
159
160    /// Get session ID
161    pub fn session_id(&self) -> &str {
162        &self.session_id
163    }
164
165    /// Get context data
166    pub fn context(&self) -> &HashMap<String, String> {
167        &self.context
168    }
169
170    /// Convert to CSV for logging
171    pub fn to_csv(&self) -> String {
172        let error = self.error_message.as_deref().unwrap_or("");
173        format!(
174            "{},{},{},{},{},{},{},{}",
175            self.timestamp.to_rfc3339(),
176            self.user_id,
177            self.field_name,
178            self.operation,
179            self.status,
180            error,
181            self.request_id,
182            self.session_id
183        )
184    }
185
186    /// Convert to JSON-like string for logging
187    pub fn to_json_like(&self) -> String {
188        format!(
189            "{{ \"timestamp\": \"{}\", \"user_id\": \"{}\", \"field_name\": \"{}\", \
190             \"operation\": \"{}\", \"status\": \"{}\", \"error\": \"{}\", \
191             \"request_id\": \"{}\", \"session_id\": \"{}\" }}",
192            self.timestamp.to_rfc3339(),
193            self.user_id,
194            self.field_name,
195            self.operation,
196            self.status,
197            self.error_message.as_deref().unwrap_or(""),
198            self.request_id,
199            self.session_id
200        )
201    }
202}
203
204/// Audit logger for encryption operations
205///
206/// Handles logging of all encryption/decryption events for compliance.
207pub struct AuditLogger {
208    /// In-memory log entries (for testing)
209    entries:     Vec<AuditLogEntry>,
210    /// Maximum entries to keep in memory
211    max_entries: usize,
212}
213
214impl AuditLogger {
215    /// Create new audit logger
216    pub fn new(max_entries: usize) -> Self {
217        Self {
218            entries: Vec::new(),
219            max_entries,
220        }
221    }
222
223    /// Log encryption operation
224    pub fn log_entry(&mut self, entry: AuditLogEntry) -> Result<(), SecretsError> {
225        // Keep bounded history
226        if self.entries.len() >= self.max_entries {
227            self.entries.remove(0);
228        }
229
230        self.entries.push(entry);
231        Ok(())
232    }
233
234    /// Internal filter helper for reducing duplication
235    fn filter_entries<F>(&self, predicate: F) -> Vec<AuditLogEntry>
236    where
237        F: Fn(&&AuditLogEntry) -> bool,
238    {
239        self.entries.iter().filter(predicate).cloned().collect()
240    }
241
242    /// Get recent entries
243    pub fn recent_entries(&self, count: usize) -> Vec<AuditLogEntry> {
244        let start = if self.entries.len() > count {
245            self.entries.len() - count
246        } else {
247            0
248        };
249        self.entries[start..].to_vec()
250    }
251
252    /// Get entries for specific user
253    pub fn entries_for_user(&self, user_id: &str) -> Vec<AuditLogEntry> {
254        self.filter_entries(|e| e.user_id == user_id)
255    }
256
257    /// Get entries for specific field
258    pub fn entries_for_field(&self, field_name: &str) -> Vec<AuditLogEntry> {
259        self.filter_entries(|e| e.field_name == field_name)
260    }
261
262    /// Get entries for specific operation type
263    pub fn entries_for_operation(&self, operation: OperationType) -> Vec<AuditLogEntry> {
264        self.filter_entries(|e| e.operation == operation)
265    }
266
267    /// Get failed operations
268    pub fn failed_entries(&self) -> Vec<AuditLogEntry> {
269        self.filter_entries(|e| e.status == EventStatus::Failure)
270    }
271
272    /// Get successful operations
273    pub fn successful_entries(&self) -> Vec<AuditLogEntry> {
274        self.filter_entries(|e| e.status == EventStatus::Success)
275    }
276
277    /// Get entries for specific user and operation
278    pub fn entries_for_user_operation(
279        &self,
280        user_id: &str,
281        operation: OperationType,
282    ) -> Vec<AuditLogEntry> {
283        self.filter_entries(|e| e.user_id == user_id && e.operation == operation)
284    }
285
286    /// Get entry count
287    pub fn entry_count(&self) -> usize {
288        self.entries.len()
289    }
290
291    /// Clear all entries
292    pub fn clear(&mut self) {
293        self.entries.clear();
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn test_audit_log_entry_creation() {
303        let entry =
304            AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
305        assert_eq!(entry.user_id(), "user123");
306        assert_eq!(entry.field_name(), "email");
307        assert_eq!(entry.operation(), OperationType::Insert);
308        assert_eq!(entry.status(), EventStatus::Success);
309    }
310
311    #[test]
312    fn test_audit_log_entry_with_failure() {
313        let entry =
314            AuditLogEntry::new("user123", "email", OperationType::Select, "req456", "sess789")
315                .with_failure("Decryption failed: wrong key");
316        assert_eq!(entry.status(), EventStatus::Failure);
317        assert_eq!(entry.error_message(), Some("Decryption failed: wrong key"));
318    }
319
320    #[test]
321    fn test_audit_log_entry_with_context() {
322        let entry =
323            AuditLogEntry::new("user123", "email", OperationType::Update, "req456", "sess789")
324                .with_context("ip_address", "192.168.1.1")
325                .with_context("user_role", "admin");
326        assert_eq!(entry.context().get("ip_address"), Some(&"192.168.1.1".to_string()));
327        assert_eq!(entry.context().get("user_role"), Some(&"admin".to_string()));
328    }
329
330    #[test]
331    fn test_audit_log_entry_to_csv() {
332        let entry =
333            AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
334        let csv = entry.to_csv();
335        assert!(csv.contains("user123"));
336        assert!(csv.contains("email"));
337        assert!(csv.contains("insert"));
338        assert!(csv.contains("success"));
339    }
340
341    #[test]
342    fn test_audit_log_entry_to_json_like() {
343        let entry =
344            AuditLogEntry::new("user123", "email", OperationType::Select, "req456", "sess789");
345        let json = entry.to_json_like();
346        assert!(json.contains("user123"));
347        assert!(json.contains("email"));
348        assert!(json.contains("select"));
349    }
350
351    #[test]
352    fn test_audit_logger_logging() {
353        let mut logger = AuditLogger::new(10);
354        let entry =
355            AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
356        let result = logger.log_entry(entry);
357        assert!(result.is_ok());
358        assert_eq!(logger.entry_count(), 1);
359    }
360
361    #[test]
362    fn test_audit_logger_recent_entries() {
363        let mut logger = AuditLogger::new(10);
364        for i in 0..5 {
365            let entry = AuditLogEntry::new(
366                format!("user{}", i),
367                "email",
368                OperationType::Insert,
369                "req456",
370                "sess789",
371            );
372            let _ = logger.log_entry(entry);
373        }
374        let recent = logger.recent_entries(2);
375        assert_eq!(recent.len(), 2);
376    }
377
378    #[test]
379    fn test_audit_logger_entries_for_user() {
380        let mut logger = AuditLogger::new(10);
381        for i in 0..3 {
382            let entry = AuditLogEntry::new(
383                "user123",
384                format!("field{}", i),
385                OperationType::Insert,
386                "req456",
387                "sess789",
388            );
389            let _ = logger.log_entry(entry);
390        }
391        for i in 0..2 {
392            let entry = AuditLogEntry::new(
393                "user456",
394                format!("field{}", i),
395                OperationType::Select,
396                "req456",
397                "sess789",
398            );
399            let _ = logger.log_entry(entry);
400        }
401        let user_entries = logger.entries_for_user("user123");
402        assert_eq!(user_entries.len(), 3);
403    }
404
405    #[test]
406    fn test_audit_logger_entries_for_field() {
407        let mut logger = AuditLogger::new(10);
408        for i in 0..3 {
409            let entry = AuditLogEntry::new(
410                format!("user{}", i),
411                "email",
412                OperationType::Insert,
413                "req456",
414                "sess789",
415            );
416            let _ = logger.log_entry(entry);
417        }
418        let email_entries = logger.entries_for_field("email");
419        assert_eq!(email_entries.len(), 3);
420    }
421
422    #[test]
423    fn test_audit_logger_failed_entries() {
424        let mut logger = AuditLogger::new(10);
425        let success =
426            AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
427        let failure =
428            AuditLogEntry::new("user456", "phone", OperationType::Select, "req789", "sess123")
429                .with_failure("Key not found");
430        let _ = logger.log_entry(success);
431        let _ = logger.log_entry(failure);
432        let failed = logger.failed_entries();
433        assert_eq!(failed.len(), 1);
434    }
435
436    #[test]
437    fn test_audit_logger_bounded_history() {
438        let mut logger = AuditLogger::new(3);
439        for i in 0..5 {
440            let entry = AuditLogEntry::new(
441                format!("user{}", i),
442                "email",
443                OperationType::Insert,
444                "req456",
445                "sess789",
446            );
447            let _ = logger.log_entry(entry);
448        }
449        assert_eq!(logger.entry_count(), 3);
450    }
451
452    #[test]
453    fn test_audit_logger_clear() {
454        let mut logger = AuditLogger::new(10);
455        let entry =
456            AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
457        let _ = logger.log_entry(entry);
458        assert_eq!(logger.entry_count(), 1);
459        logger.clear();
460        assert_eq!(logger.entry_count(), 0);
461    }
462
463    #[test]
464    fn test_operation_type_display() {
465        assert_eq!(OperationType::Insert.to_string(), "insert");
466        assert_eq!(OperationType::Select.to_string(), "select");
467        assert_eq!(OperationType::Update.to_string(), "update");
468        assert_eq!(OperationType::Delete.to_string(), "delete");
469    }
470
471    #[test]
472    fn test_event_status_display() {
473        assert_eq!(EventStatus::Success.to_string(), "success");
474        assert_eq!(EventStatus::Failure.to_string(), "failure");
475    }
476
477    #[test]
478    fn test_audit_log_entry_with_security_context() {
479        let entry =
480            AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789")
481                .with_security_context(Some("192.168.1.1"), Some("admin"));
482        assert_eq!(entry.context().get("ip_address"), Some(&"192.168.1.1".to_string()));
483        assert_eq!(entry.context().get("user_role"), Some(&"admin".to_string()));
484    }
485
486    #[test]
487    fn test_audit_log_entry_with_partial_security_context() {
488        let entry =
489            AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789")
490                .with_security_context(Some("192.168.1.1"), None);
491        assert_eq!(entry.context().get("ip_address"), Some(&"192.168.1.1".to_string()));
492        assert!(!entry.context().contains_key("user_role"));
493    }
494
495    #[test]
496    fn test_audit_logger_entries_for_operation() {
497        let mut logger = AuditLogger::new(10);
498        let entry1 = AuditLogEntry::new("user1", "email", OperationType::Insert, "req1", "sess1");
499        let entry2 = AuditLogEntry::new("user2", "phone", OperationType::Select, "req2", "sess2");
500        let entry3 = AuditLogEntry::new("user3", "ssn", OperationType::Insert, "req3", "sess3");
501        let _ = logger.log_entry(entry1);
502        let _ = logger.log_entry(entry2);
503        let _ = logger.log_entry(entry3);
504        let inserts = logger.entries_for_operation(OperationType::Insert);
505        assert_eq!(inserts.len(), 2);
506        let selects = logger.entries_for_operation(OperationType::Select);
507        assert_eq!(selects.len(), 1);
508    }
509
510    #[test]
511    fn test_audit_logger_successful_entries() {
512        let mut logger = AuditLogger::new(10);
513        let success = AuditLogEntry::new("user1", "email", OperationType::Insert, "req1", "sess1");
514        let failure = AuditLogEntry::new("user2", "phone", OperationType::Select, "req2", "sess2")
515            .with_failure("Key not found");
516        let _ = logger.log_entry(success);
517        let _ = logger.log_entry(failure);
518        let successful = logger.successful_entries();
519        assert_eq!(successful.len(), 1);
520        assert_eq!(successful[0].user_id(), "user1");
521    }
522
523    #[test]
524    fn test_audit_logger_entries_for_user_operation() {
525        let mut logger = AuditLogger::new(10);
526        let entry1 = AuditLogEntry::new("user1", "email", OperationType::Insert, "req1", "sess1");
527        let entry2 = AuditLogEntry::new("user1", "phone", OperationType::Select, "req2", "sess2");
528        let entry3 = AuditLogEntry::new("user2", "email", OperationType::Insert, "req3", "sess3");
529        let _ = logger.log_entry(entry1);
530        let _ = logger.log_entry(entry2);
531        let _ = logger.log_entry(entry3);
532        let user1_inserts = logger.entries_for_user_operation("user1", OperationType::Insert);
533        assert_eq!(user1_inserts.len(), 1);
534        let user1_selects = logger.entries_for_user_operation("user1", OperationType::Select);
535        assert_eq!(user1_selects.len(), 1);
536    }
537}