kaccy_bitcoin/
audit.rs

1//! Audit logging for compliance and security
2//!
3//! This module provides immutable audit trail functionality for tracking
4//! all sensitive operations, transactions, and administrative actions.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use uuid::Uuid;
13
14use crate::error::{BitcoinError, Result};
15
16/// Audit event severity levels
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum AuditSeverity {
19    /// Informational event
20    Info,
21    /// Warning event
22    Warning,
23    /// Critical event (requires attention)
24    Critical,
25    /// Security-related event
26    Security,
27}
28
29/// Types of auditable events
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub enum AuditEventType {
32    /// Transaction created
33    TransactionCreated,
34    /// Transaction signed
35    TransactionSigned,
36    /// Transaction broadcasted
37    TransactionBroadcasted,
38    /// Transaction confirmed
39    TransactionConfirmed,
40    /// Withdrawal requested
41    WithdrawalRequested,
42    /// Withdrawal approved
43    WithdrawalApproved,
44    /// Withdrawal rejected
45    WithdrawalRejected,
46    /// Withdrawal completed
47    WithdrawalCompleted,
48    /// Address generated
49    AddressGenerated,
50    /// Key accessed
51    KeyAccessed,
52    /// Configuration changed
53    ConfigurationChanged,
54    /// Admin action
55    AdminAction,
56    /// Security alert
57    SecurityAlert,
58    /// Limit exceeded
59    LimitExceeded,
60    /// Authentication event
61    Authentication,
62    /// Authorization event
63    Authorization,
64    /// PSBT created
65    PsbtCreated,
66    /// PSBT signed
67    PsbtSigned,
68    /// Multi-sig operation
69    MultisigOperation,
70    /// Hardware wallet operation
71    HardwareWalletOperation,
72    /// Custom event
73    Custom(String),
74}
75
76/// Audit event record
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct AuditEvent {
79    /// Unique event ID
80    pub id: Uuid,
81    /// Timestamp of the event
82    pub timestamp: DateTime<Utc>,
83    /// Event type
84    pub event_type: AuditEventType,
85    /// Severity level
86    pub severity: AuditSeverity,
87    /// User or system that triggered the event
88    pub actor: String,
89    /// Resource affected (e.g., transaction ID, address)
90    pub resource: Option<String>,
91    /// Action description
92    pub action: String,
93    /// Additional metadata
94    pub metadata: HashMap<String, String>,
95    /// IP address (if applicable)
96    pub ip_address: Option<String>,
97    /// Session ID (if applicable)
98    pub session_id: Option<String>,
99    /// Result of the action (success/failure)
100    pub success: bool,
101    /// Error message (if failed)
102    pub error: Option<String>,
103}
104
105impl AuditEvent {
106    /// Create a new audit event
107    pub fn new(
108        event_type: AuditEventType,
109        severity: AuditSeverity,
110        actor: String,
111        action: String,
112    ) -> Self {
113        Self {
114            id: Uuid::new_v4(),
115            timestamp: Utc::now(),
116            event_type,
117            severity,
118            actor,
119            resource: None,
120            action,
121            metadata: HashMap::new(),
122            ip_address: None,
123            session_id: None,
124            success: true,
125            error: None,
126        }
127    }
128
129    /// Set the resource identifier
130    pub fn with_resource(mut self, resource: String) -> Self {
131        self.resource = Some(resource);
132        self
133    }
134
135    /// Add metadata field
136    pub fn with_metadata(mut self, key: String, value: String) -> Self {
137        self.metadata.insert(key, value);
138        self
139    }
140
141    /// Set IP address
142    pub fn with_ip_address(mut self, ip: String) -> Self {
143        self.ip_address = Some(ip);
144        self
145    }
146
147    /// Set session ID
148    pub fn with_session_id(mut self, session: String) -> Self {
149        self.session_id = Some(session);
150        self
151    }
152
153    /// Mark as failed with error message
154    pub fn with_error(mut self, error: String) -> Self {
155        self.success = false;
156        self.error = Some(error);
157        self
158    }
159}
160
161/// Audit log storage backend trait
162#[allow(dead_code)]
163#[async_trait::async_trait]
164pub trait AuditStorage: Send + Sync {
165    /// Store an audit event
166    async fn store(&self, event: &AuditEvent) -> Result<()>;
167
168    /// Query audit events by criteria
169    async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>>;
170
171    /// Get event count
172    async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize>;
173}
174
175/// Query criteria for audit events
176#[derive(Debug, Clone, Default)]
177pub struct AuditQueryCriteria {
178    /// Filter by event type
179    pub event_type: Option<AuditEventType>,
180    /// Filter by severity
181    pub severity: Option<AuditSeverity>,
182    /// Filter by actor
183    pub actor: Option<String>,
184    /// Filter by resource
185    pub resource: Option<String>,
186    /// Filter by time range (start)
187    pub time_from: Option<DateTime<Utc>>,
188    /// Filter by time range (end)
189    pub time_to: Option<DateTime<Utc>>,
190    /// Filter by success/failure
191    pub success: Option<bool>,
192    /// Limit number of results
193    pub limit: Option<usize>,
194    /// Skip first N results
195    pub offset: Option<usize>,
196}
197
198/// In-memory audit storage (for testing and development)
199#[derive(Clone)]
200pub struct InMemoryAuditStorage {
201    events: Arc<RwLock<Vec<AuditEvent>>>,
202}
203
204impl InMemoryAuditStorage {
205    /// Create a new in-memory storage
206    pub fn new() -> Self {
207        Self {
208            events: Arc::new(RwLock::new(Vec::new())),
209        }
210    }
211
212    /// Get all events (for testing)
213    pub async fn all_events(&self) -> Vec<AuditEvent> {
214        self.events.read().await.clone()
215    }
216
217    /// Clear all events (for testing)
218    pub async fn clear(&self) {
219        self.events.write().await.clear();
220    }
221}
222
223impl Default for InMemoryAuditStorage {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229#[async_trait::async_trait]
230impl AuditStorage for InMemoryAuditStorage {
231    async fn store(&self, event: &AuditEvent) -> Result<()> {
232        self.events.write().await.push(event.clone());
233        Ok(())
234    }
235
236    async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>> {
237        let events = self.events.read().await;
238        let mut filtered: Vec<AuditEvent> = events
239            .iter()
240            .filter(|e| {
241                if let Some(ref event_type) = criteria.event_type {
242                    if &e.event_type != event_type {
243                        return false;
244                    }
245                }
246                if let Some(ref severity) = criteria.severity {
247                    if &e.severity != severity {
248                        return false;
249                    }
250                }
251                if let Some(ref actor) = criteria.actor {
252                    if &e.actor != actor {
253                        return false;
254                    }
255                }
256                if let Some(ref resource) = criteria.resource {
257                    if e.resource.as_ref() != Some(resource) {
258                        return false;
259                    }
260                }
261                if let Some(time_from) = criteria.time_from {
262                    if e.timestamp < time_from {
263                        return false;
264                    }
265                }
266                if let Some(time_to) = criteria.time_to {
267                    if e.timestamp > time_to {
268                        return false;
269                    }
270                }
271                if let Some(success) = criteria.success {
272                    if e.success != success {
273                        return false;
274                    }
275                }
276                true
277            })
278            .cloned()
279            .collect();
280
281        // Apply offset
282        if let Some(offset) = criteria.offset {
283            filtered = filtered.into_iter().skip(offset).collect();
284        }
285
286        // Apply limit
287        if let Some(limit) = criteria.limit {
288            filtered.truncate(limit);
289        }
290
291        Ok(filtered)
292    }
293
294    async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize> {
295        let events = self.query(criteria).await?;
296        Ok(events.len())
297    }
298}
299
300/// File-based audit storage (append-only log file)
301pub struct FileAuditStorage {
302    file_path: PathBuf,
303}
304
305impl FileAuditStorage {
306    /// Create a new file-based storage
307    pub fn new(file_path: PathBuf) -> Result<Self> {
308        // Ensure the directory exists
309        if let Some(parent) = file_path.parent() {
310            std::fs::create_dir_all(parent).map_err(|e| {
311                BitcoinError::Validation(format!("Failed to create audit log directory: {}", e))
312            })?;
313        }
314
315        Ok(Self { file_path })
316    }
317}
318
319#[async_trait::async_trait]
320impl AuditStorage for FileAuditStorage {
321    async fn store(&self, event: &AuditEvent) -> Result<()> {
322        let json = serde_json::to_string(event).map_err(|e| {
323            BitcoinError::Validation(format!("Failed to serialize audit event: {}", e))
324        })?;
325
326        use std::io::Write;
327        let mut file = std::fs::OpenOptions::new()
328            .create(true)
329            .append(true)
330            .open(&self.file_path)
331            .map_err(|e| {
332                BitcoinError::Validation(format!("Failed to open audit log file: {}", e))
333            })?;
334
335        writeln!(file, "{}", json).map_err(|e| {
336            BitcoinError::Validation(format!("Failed to write to audit log: {}", e))
337        })?;
338
339        Ok(())
340    }
341
342    async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>> {
343        use std::io::{BufRead, BufReader};
344
345        let file = std::fs::File::open(&self.file_path).map_err(|e| {
346            BitcoinError::Validation(format!("Failed to open audit log file: {}", e))
347        })?;
348
349        let reader = BufReader::new(file);
350        let mut filtered = Vec::new();
351
352        for line in reader.lines() {
353            let line = line.map_err(|e| {
354                BitcoinError::Validation(format!("Failed to read audit log line: {}", e))
355            })?;
356
357            let event: AuditEvent = serde_json::from_str(&line).map_err(|e| {
358                BitcoinError::Validation(format!("Failed to parse audit event: {}", e))
359            })?;
360
361            // Apply filters
362            if let Some(ref event_type) = criteria.event_type {
363                if &event.event_type != event_type {
364                    continue;
365                }
366            }
367            if let Some(ref severity) = criteria.severity {
368                if &event.severity != severity {
369                    continue;
370                }
371            }
372            if let Some(ref actor) = criteria.actor {
373                if &event.actor != actor {
374                    continue;
375                }
376            }
377            if let Some(ref resource) = criteria.resource {
378                if event.resource.as_ref() != Some(resource) {
379                    continue;
380                }
381            }
382            if let Some(time_from) = criteria.time_from {
383                if event.timestamp < time_from {
384                    continue;
385                }
386            }
387            if let Some(time_to) = criteria.time_to {
388                if event.timestamp > time_to {
389                    continue;
390                }
391            }
392            if let Some(success) = criteria.success {
393                if event.success != success {
394                    continue;
395                }
396            }
397
398            filtered.push(event);
399        }
400
401        // Apply offset
402        if let Some(offset) = criteria.offset {
403            filtered = filtered.into_iter().skip(offset).collect();
404        }
405
406        // Apply limit
407        if let Some(limit) = criteria.limit {
408            filtered.truncate(limit);
409        }
410
411        Ok(filtered)
412    }
413
414    async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize> {
415        let events = self.query(criteria).await?;
416        Ok(events.len())
417    }
418}
419
420/// Audit logger for tracking operations
421pub struct AuditLogger<S: AuditStorage> {
422    storage: Arc<S>,
423    enabled: bool,
424}
425
426impl<S: AuditStorage> AuditLogger<S> {
427    /// Create a new audit logger
428    pub fn new(storage: S) -> Self {
429        Self {
430            storage: Arc::new(storage),
431            enabled: true,
432        }
433    }
434
435    /// Log an audit event
436    pub async fn log(&self, event: AuditEvent) -> Result<()> {
437        if !self.enabled {
438            return Ok(());
439        }
440
441        tracing::info!(
442            event_id = %event.id,
443            event_type = ?event.event_type,
444            severity = ?event.severity,
445            actor = %event.actor,
446            "Audit event logged"
447        );
448
449        self.storage.store(&event).await
450    }
451
452    /// Query audit events
453    pub async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>> {
454        self.storage.query(criteria).await
455    }
456
457    /// Get event count
458    pub async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize> {
459        self.storage.count(criteria).await
460    }
461
462    /// Enable or disable logging
463    pub fn set_enabled(&mut self, enabled: bool) {
464        self.enabled = enabled;
465    }
466}
467
468/// Builder for creating audit events with fluent API
469pub struct AuditEventBuilder {
470    event: AuditEvent,
471}
472
473impl AuditEventBuilder {
474    /// Start building a new audit event
475    pub fn new(event_type: AuditEventType, actor: String, action: String) -> Self {
476        Self {
477            event: AuditEvent::new(event_type, AuditSeverity::Info, actor, action),
478        }
479    }
480
481    /// Set severity
482    pub fn severity(mut self, severity: AuditSeverity) -> Self {
483        self.event.severity = severity;
484        self
485    }
486
487    /// Set resource
488    pub fn resource(mut self, resource: String) -> Self {
489        self.event.resource = Some(resource);
490        self
491    }
492
493    /// Add metadata
494    pub fn metadata(mut self, key: String, value: String) -> Self {
495        self.event.metadata.insert(key, value);
496        self
497    }
498
499    /// Set IP address
500    pub fn ip_address(mut self, ip: String) -> Self {
501        self.event.ip_address = Some(ip);
502        self
503    }
504
505    /// Set session ID
506    pub fn session_id(mut self, session: String) -> Self {
507        self.event.session_id = Some(session);
508        self
509    }
510
511    /// Mark as failed
512    pub fn failed(mut self, error: String) -> Self {
513        self.event.success = false;
514        self.event.error = Some(error);
515        self
516    }
517
518    /// Build the audit event
519    pub fn build(self) -> AuditEvent {
520        self.event
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527
528    #[test]
529    fn test_audit_event_creation() {
530        let event = AuditEvent::new(
531            AuditEventType::TransactionCreated,
532            AuditSeverity::Info,
533            "system".to_string(),
534            "Created new transaction".to_string(),
535        );
536
537        assert_eq!(event.event_type, AuditEventType::TransactionCreated);
538        assert_eq!(event.severity, AuditSeverity::Info);
539        assert_eq!(event.actor, "system");
540        assert!(event.success);
541        assert!(event.error.is_none());
542    }
543
544    #[test]
545    fn test_audit_event_builder() {
546        let event = AuditEventBuilder::new(
547            AuditEventType::WithdrawalRequested,
548            "user123".to_string(),
549            "Requested withdrawal of 1 BTC".to_string(),
550        )
551        .severity(AuditSeverity::Critical)
552        .resource("tx_abc123".to_string())
553        .metadata("amount".to_string(), "100000000".to_string())
554        .build();
555
556        assert_eq!(event.severity, AuditSeverity::Critical);
557        assert_eq!(event.resource, Some("tx_abc123".to_string()));
558        assert_eq!(event.metadata.get("amount"), Some(&"100000000".to_string()));
559    }
560
561    #[tokio::test]
562    async fn test_in_memory_storage() {
563        let storage = InMemoryAuditStorage::new();
564        let event = AuditEvent::new(
565            AuditEventType::TransactionCreated,
566            AuditSeverity::Info,
567            "test".to_string(),
568            "test action".to_string(),
569        );
570
571        storage.store(&event).await.unwrap();
572
573        let events = storage.all_events().await;
574        assert_eq!(events.len(), 1);
575        assert_eq!(events[0].actor, "test");
576    }
577
578    #[tokio::test]
579    async fn test_audit_query() {
580        let storage = InMemoryAuditStorage::new();
581
582        // Store multiple events
583        for i in 0..5 {
584            let event = AuditEvent::new(
585                AuditEventType::TransactionCreated,
586                AuditSeverity::Info,
587                format!("user{}", i),
588                "test".to_string(),
589            );
590            storage.store(&event).await.unwrap();
591        }
592
593        // Query with actor filter
594        let criteria = AuditQueryCriteria {
595            actor: Some("user2".to_string()),
596            ..Default::default()
597        };
598
599        let results = storage.query(&criteria).await.unwrap();
600        assert_eq!(results.len(), 1);
601        assert_eq!(results[0].actor, "user2");
602    }
603
604    #[tokio::test]
605    async fn test_audit_logger() {
606        let storage = InMemoryAuditStorage::new();
607        let logger = AuditLogger::new(storage.clone());
608
609        let event = AuditEvent::new(
610            AuditEventType::SecurityAlert,
611            AuditSeverity::Security,
612            "system".to_string(),
613            "Suspicious activity detected".to_string(),
614        );
615
616        logger.log(event).await.unwrap();
617
618        let events = storage.all_events().await;
619        assert_eq!(events.len(), 1);
620        assert_eq!(events[0].severity, AuditSeverity::Security);
621    }
622}