Skip to main content

aegis_server/
breach.rs

1//! Aegis Breach Detection and Notification Module
2//!
3//! HIPAA/GDPR compliance module for detecting security breaches and sending notifications.
4//! Monitors security events, detects anomalies, and maintains breach incident records.
5//!
6//! Key Features:
7//! - Security event monitoring (failed logins, unauthorized access, etc.)
8//! - Configurable detection thresholds
9//! - Breach incident tracking with severity levels
10//! - Pluggable notification system (webhooks, log files)
11//! - Compliance reporting for HIPAA/GDPR requirements
12//!
13//! @version 0.1.0
14//! @author AutomataNexus Development Team
15
16use parking_lot::RwLock;
17use serde::{Deserialize, Serialize};
18use std::collections::{HashMap, VecDeque};
19use std::fs::{File, OpenOptions};
20use std::io::{BufWriter, Write};
21use std::path::PathBuf;
22use std::sync::atomic::{AtomicU64, Ordering};
23use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
24
25// =============================================================================
26// Constants
27// =============================================================================
28
29/// Default threshold for failed login attempts (5 attempts in 5 minutes).
30pub const DEFAULT_FAILED_LOGIN_THRESHOLD: u32 = 5;
31
32/// Default time window for failed login detection (5 minutes).
33pub const DEFAULT_FAILED_LOGIN_WINDOW_SECS: u64 = 300;
34
35/// Default threshold for unusual access patterns (100 accesses in 1 minute).
36pub const DEFAULT_UNUSUAL_ACCESS_THRESHOLD: u32 = 100;
37
38/// Default time window for unusual access detection (1 minute).
39pub const DEFAULT_UNUSUAL_ACCESS_WINDOW_SECS: u64 = 60;
40
41/// Default threshold for mass data operations (1000 rows).
42pub const DEFAULT_MASS_DATA_THRESHOLD: u64 = 1000;
43
44/// Maximum security events to keep in memory per type.
45pub const MAX_EVENTS_IN_MEMORY: usize = 10000;
46
47/// Maximum breach incidents to keep in memory.
48pub const MAX_INCIDENTS_IN_MEMORY: usize = 1000;
49
50// =============================================================================
51// Security Event Types
52// =============================================================================
53
54/// Types of security events that the breach detector monitors.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
56#[serde(rename_all = "snake_case")]
57pub enum SecurityEventType {
58    /// Multiple failed login attempts.
59    FailedLogin,
60    /// Unauthorized access attempt (permission denied).
61    UnauthorizedAccess,
62    /// Unusual data access pattern (high volume).
63    UnusualAccessPattern,
64    /// Admin action from unknown/untrusted IP.
65    AdminFromUnknownIp,
66    /// Mass data export operation.
67    MassDataExport,
68    /// Mass data deletion operation.
69    MassDataDeletion,
70    /// Session hijacking attempt.
71    SessionHijacking,
72    /// SQL injection attempt.
73    SqlInjection,
74    /// Brute force attack detected.
75    BruteForceAttack,
76    /// Privilege escalation attempt.
77    PrivilegeEscalation,
78}
79
80impl std::fmt::Display for SecurityEventType {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        match self {
83            SecurityEventType::FailedLogin => write!(f, "failed_login"),
84            SecurityEventType::UnauthorizedAccess => write!(f, "unauthorized_access"),
85            SecurityEventType::UnusualAccessPattern => write!(f, "unusual_access_pattern"),
86            SecurityEventType::AdminFromUnknownIp => write!(f, "admin_from_unknown_ip"),
87            SecurityEventType::MassDataExport => write!(f, "mass_data_export"),
88            SecurityEventType::MassDataDeletion => write!(f, "mass_data_deletion"),
89            SecurityEventType::SessionHijacking => write!(f, "session_hijacking"),
90            SecurityEventType::SqlInjection => write!(f, "sql_injection"),
91            SecurityEventType::BruteForceAttack => write!(f, "brute_force_attack"),
92            SecurityEventType::PrivilegeEscalation => write!(f, "privilege_escalation"),
93        }
94    }
95}
96
97/// A security event recorded by the system.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct SecurityEvent {
100    /// Unique event ID.
101    pub id: String,
102    /// Type of security event.
103    pub event_type: SecurityEventType,
104    /// Timestamp when the event occurred (Unix milliseconds).
105    pub timestamp: u64,
106    /// User associated with the event (if known).
107    pub user: Option<String>,
108    /// IP address associated with the event (if known).
109    pub ip_address: Option<String>,
110    /// Resource being accessed (table, endpoint, etc.).
111    pub resource: Option<String>,
112    /// Detailed description of the event.
113    pub description: String,
114    /// Additional metadata.
115    pub metadata: HashMap<String, String>,
116}
117
118impl SecurityEvent {
119    /// Create a new security event with an auto-generated ID from the given counter.
120    pub fn new(event_type: SecurityEventType, description: &str, counter: &AtomicU64) -> Self {
121        Self {
122            id: generate_event_id(counter),
123            event_type,
124            timestamp: now_timestamp(),
125            user: None,
126            ip_address: None,
127            resource: None,
128            description: description.to_string(),
129            metadata: HashMap::new(),
130        }
131    }
132
133    /// Set the user associated with this event.
134    pub fn with_user(mut self, user: &str) -> Self {
135        self.user = Some(user.to_string());
136        self
137    }
138
139    /// Set the IP address associated with this event.
140    pub fn with_ip(mut self, ip: &str) -> Self {
141        self.ip_address = Some(ip.to_string());
142        self
143    }
144
145    /// Set the resource being accessed.
146    pub fn with_resource(mut self, resource: &str) -> Self {
147        self.resource = Some(resource.to_string());
148        self
149    }
150
151    /// Add metadata to the event.
152    pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
153        self.metadata.insert(key.to_string(), value.to_string());
154        self
155    }
156}
157
158// =============================================================================
159// Breach Severity
160// =============================================================================
161
162/// Severity level for breach incidents.
163#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
164#[serde(rename_all = "lowercase")]
165pub enum BreachSeverity {
166    /// Low severity - minor security concern.
167    Low,
168    /// Medium severity - potential security issue requiring attention.
169    Medium,
170    /// High severity - significant security breach.
171    High,
172    /// Critical severity - immediate action required.
173    Critical,
174}
175
176impl std::fmt::Display for BreachSeverity {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        match self {
179            BreachSeverity::Low => write!(f, "low"),
180            BreachSeverity::Medium => write!(f, "medium"),
181            BreachSeverity::High => write!(f, "high"),
182            BreachSeverity::Critical => write!(f, "critical"),
183        }
184    }
185}
186
187// =============================================================================
188// Breach Incident
189// =============================================================================
190
191/// Status of a breach incident.
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
193#[serde(rename_all = "snake_case")]
194pub enum IncidentStatus {
195    /// Incident is open and being investigated.
196    Open,
197    /// Incident has been acknowledged by an administrator.
198    Acknowledged,
199    /// Incident is being actively investigated.
200    Investigating,
201    /// Incident has been resolved.
202    Resolved,
203    /// Incident was closed as false positive.
204    FalsePositive,
205}
206
207impl std::fmt::Display for IncidentStatus {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        match self {
210            IncidentStatus::Open => write!(f, "open"),
211            IncidentStatus::Acknowledged => write!(f, "acknowledged"),
212            IncidentStatus::Investigating => write!(f, "investigating"),
213            IncidentStatus::Resolved => write!(f, "resolved"),
214            IncidentStatus::FalsePositive => write!(f, "false_positive"),
215        }
216    }
217}
218
219/// A breach incident detected by the system.
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct BreachIncident {
222    /// Unique incident ID.
223    pub id: String,
224    /// Timestamp when the incident was detected (Unix milliseconds).
225    pub detected_at: u64,
226    /// Type of security event that triggered this incident.
227    pub incident_type: SecurityEventType,
228    /// Severity of the incident.
229    pub severity: BreachSeverity,
230    /// Users or subjects potentially affected.
231    pub affected_subjects: Vec<String>,
232    /// Current status of the incident.
233    pub status: IncidentStatus,
234    /// Whether notifications have been sent.
235    pub notified: bool,
236    /// Notification timestamps by notifier type.
237    pub notification_timestamps: HashMap<String, u64>,
238    /// Description of the incident.
239    pub description: String,
240    /// Related security event IDs.
241    pub related_events: Vec<String>,
242    /// IP addresses involved.
243    pub involved_ips: Vec<String>,
244    /// Additional details.
245    pub details: HashMap<String, String>,
246    /// Who acknowledged the incident (if applicable).
247    pub acknowledged_by: Option<String>,
248    /// When the incident was acknowledged.
249    pub acknowledged_at: Option<u64>,
250    /// Resolution notes.
251    pub resolution_notes: Option<String>,
252    /// When the incident was resolved.
253    pub resolved_at: Option<u64>,
254}
255
256impl BreachIncident {
257    /// Create a new breach incident with an auto-generated ID from the given counter.
258    pub fn new(
259        incident_type: SecurityEventType,
260        severity: BreachSeverity,
261        description: &str,
262        counter: &AtomicU64,
263    ) -> Self {
264        Self {
265            id: generate_incident_id(counter),
266            detected_at: now_timestamp(),
267            incident_type,
268            severity,
269            affected_subjects: Vec::new(),
270            status: IncidentStatus::Open,
271            notified: false,
272            notification_timestamps: HashMap::new(),
273            description: description.to_string(),
274            related_events: Vec::new(),
275            involved_ips: Vec::new(),
276            details: HashMap::new(),
277            acknowledged_by: None,
278            acknowledged_at: None,
279            resolution_notes: None,
280            resolved_at: None,
281        }
282    }
283
284    /// Add an affected subject.
285    pub fn with_affected_subject(mut self, subject: &str) -> Self {
286        if !self.affected_subjects.contains(&subject.to_string()) {
287            self.affected_subjects.push(subject.to_string());
288        }
289        self
290    }
291
292    /// Add a related event.
293    pub fn with_related_event(mut self, event_id: &str) -> Self {
294        self.related_events.push(event_id.to_string());
295        self
296    }
297
298    /// Add an involved IP.
299    pub fn with_involved_ip(mut self, ip: &str) -> Self {
300        if !self.involved_ips.contains(&ip.to_string()) {
301            self.involved_ips.push(ip.to_string());
302        }
303        self
304    }
305
306    /// Add a detail.
307    pub fn with_detail(mut self, key: &str, value: &str) -> Self {
308        self.details.insert(key.to_string(), value.to_string());
309        self
310    }
311
312    /// Check if the incident requires immediate notification (HIPAA/GDPR).
313    pub fn requires_immediate_notification(&self) -> bool {
314        self.severity >= BreachSeverity::High
315    }
316}
317
318// =============================================================================
319// Detection Configuration
320// =============================================================================
321
322/// Configuration for breach detection thresholds.
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct DetectionConfig {
325    /// Number of failed logins before triggering an incident.
326    pub failed_login_threshold: u32,
327    /// Time window for failed login detection (seconds).
328    pub failed_login_window_secs: u64,
329    /// Number of access operations before flagging unusual pattern.
330    pub unusual_access_threshold: u32,
331    /// Time window for unusual access detection (seconds).
332    pub unusual_access_window_secs: u64,
333    /// Number of rows before flagging as mass data operation.
334    pub mass_data_threshold: u64,
335    /// List of trusted IP addresses for admin actions.
336    pub trusted_admin_ips: Vec<String>,
337    /// Whether to enable brute force detection.
338    pub enable_brute_force_detection: bool,
339    /// Whether to enable SQL injection detection.
340    pub enable_sql_injection_detection: bool,
341}
342
343impl Default for DetectionConfig {
344    fn default() -> Self {
345        Self {
346            failed_login_threshold: DEFAULT_FAILED_LOGIN_THRESHOLD,
347            failed_login_window_secs: DEFAULT_FAILED_LOGIN_WINDOW_SECS,
348            unusual_access_threshold: DEFAULT_UNUSUAL_ACCESS_THRESHOLD,
349            unusual_access_window_secs: DEFAULT_UNUSUAL_ACCESS_WINDOW_SECS,
350            mass_data_threshold: DEFAULT_MASS_DATA_THRESHOLD,
351            trusted_admin_ips: vec!["127.0.0.1".to_string(), "::1".to_string()],
352            enable_brute_force_detection: true,
353            enable_sql_injection_detection: true,
354        }
355    }
356}
357
358// =============================================================================
359// Notifier Trait
360// =============================================================================
361
362/// Trait for pluggable breach notification handlers.
363pub trait BreachNotifier: Send + Sync {
364    /// Get the notifier's name/identifier.
365    fn name(&self) -> &str;
366
367    /// Send a notification for a breach incident.
368    fn notify(&self, incident: &BreachIncident) -> Result<(), String>;
369}
370
371// =============================================================================
372// Webhook Notifier
373// =============================================================================
374
375/// Webhook notifier that POSTs breach incidents to a configured URL.
376pub struct WebhookNotifier {
377    name: String,
378    url: String,
379    headers: HashMap<String, String>,
380    client: reqwest::blocking::Client,
381}
382
383impl WebhookNotifier {
384    /// Create a new webhook notifier.
385    pub fn new(url: &str) -> Self {
386        Self {
387            name: "webhook".to_string(),
388            url: url.to_string(),
389            headers: HashMap::new(),
390            client: reqwest::blocking::Client::builder()
391                .timeout(Duration::from_secs(30))
392                .build()
393                .unwrap_or_else(|_| reqwest::blocking::Client::new()),
394        }
395    }
396
397    /// Set a custom name for this notifier.
398    pub fn with_name(mut self, name: &str) -> Self {
399        self.name = name.to_string();
400        self
401    }
402
403    /// Add a custom header.
404    pub fn with_header(mut self, key: &str, value: &str) -> Self {
405        self.headers.insert(key.to_string(), value.to_string());
406        self
407    }
408}
409
410impl BreachNotifier for WebhookNotifier {
411    fn name(&self) -> &str {
412        &self.name
413    }
414
415    fn notify(&self, incident: &BreachIncident) -> Result<(), String> {
416        // Build the webhook payload
417        let payload = serde_json::json!({
418            "incident_id": incident.id,
419            "detected_at": format_timestamp(incident.detected_at),
420            "type": incident.incident_type.to_string(),
421            "severity": incident.severity.to_string(),
422            "description": incident.description,
423            "affected_subjects": incident.affected_subjects,
424            "involved_ips": incident.involved_ips,
425            "status": incident.status.to_string(),
426            "details": incident.details,
427            "source": "aegis-db",
428        });
429
430        let mut request = self.client.post(&self.url);
431
432        // Add custom headers
433        for (key, value) in &self.headers {
434            request = request.header(key, value);
435        }
436
437        let response = request
438            .header("Content-Type", "application/json")
439            .json(&payload)
440            .send()
441            .map_err(|e| format!("Failed to send webhook: {}", e))?;
442
443        if response.status().is_success() {
444            Ok(())
445        } else {
446            Err(format!(
447                "Webhook returned error status: {}",
448                response.status()
449            ))
450        }
451    }
452}
453
454// =============================================================================
455// Log Notifier
456// =============================================================================
457
458/// Log notifier that writes breach incidents to a log file.
459pub struct LogNotifier {
460    name: String,
461    log_path: PathBuf,
462    writer: RwLock<Option<BufWriter<File>>>,
463}
464
465impl LogNotifier {
466    /// Create a new log notifier.
467    pub fn new(log_path: PathBuf) -> std::io::Result<Self> {
468        // Ensure parent directory exists
469        if let Some(parent) = log_path.parent() {
470            std::fs::create_dir_all(parent)?;
471        }
472
473        let file = OpenOptions::new()
474            .create(true)
475            .append(true)
476            .open(&log_path)?;
477
478        Ok(Self {
479            name: "log".to_string(),
480            log_path,
481            writer: RwLock::new(Some(BufWriter::new(file))),
482        })
483    }
484
485    /// Set a custom name for this notifier.
486    pub fn with_name(mut self, name: &str) -> Self {
487        self.name = name.to_string();
488        self
489    }
490}
491
492impl BreachNotifier for LogNotifier {
493    fn name(&self) -> &str {
494        &self.name
495    }
496
497    fn notify(&self, incident: &BreachIncident) -> Result<(), String> {
498        let log_entry = serde_json::json!({
499            "timestamp": format_timestamp(now_timestamp()),
500            "incident_id": incident.id,
501            "detected_at": format_timestamp(incident.detected_at),
502            "type": incident.incident_type.to_string(),
503            "severity": incident.severity.to_string(),
504            "description": incident.description,
505            "affected_subjects": incident.affected_subjects,
506            "involved_ips": incident.involved_ips,
507            "status": incident.status.to_string(),
508            "details": incident.details,
509        });
510
511        let mut writer = self.writer.write();
512        // Reopen the file if the writer was lost (e.g., log rotation)
513        if writer.is_none() {
514            match OpenOptions::new().create(true).append(true).open(&self.log_path) {
515                Ok(file) => {
516                    *writer = Some(BufWriter::new(file));
517                }
518                Err(e) => {
519                    return Err(format!(
520                        "Failed to reopen breach log {}: {}",
521                        self.log_path.display(),
522                        e
523                    ));
524                }
525            }
526        }
527        if let Some(ref mut w) = *writer {
528            writeln!(w, "{}", log_entry)
529                .map_err(|e| format!("Failed to write to breach log: {}", e))?;
530            w.flush()
531                .map_err(|e| format!("Failed to flush breach log: {}", e))?;
532            Ok(())
533        } else {
534            Err("Log writer not initialized".to_string())
535        }
536    }
537}
538
539// =============================================================================
540// Breach Detector
541// =============================================================================
542
543/// Internal record for tracking events with timestamps.
544struct EventRecord {
545    event: SecurityEvent,
546    received_at: Instant,
547}
548
549/// Main breach detection service.
550pub struct BreachDetector {
551    /// Detection configuration.
552    config: RwLock<DetectionConfig>,
553    /// Security events by type.
554    events: RwLock<HashMap<SecurityEventType, VecDeque<EventRecord>>>,
555    /// Failed login attempts by user/IP.
556    failed_logins: RwLock<HashMap<String, VecDeque<Instant>>>,
557    /// Access patterns by user.
558    access_patterns: RwLock<HashMap<String, VecDeque<Instant>>>,
559    /// Active breach incidents.
560    incidents: RwLock<VecDeque<BreachIncident>>,
561    /// Incident counter for ID generation.
562    incident_counter: AtomicU64,
563    /// Event counter for ID generation.
564    event_counter: AtomicU64,
565    /// Registered notifiers.
566    notifiers: RwLock<Vec<Box<dyn BreachNotifier>>>,
567    /// Optional data directory for persisting incidents to disk.
568    data_dir: Option<PathBuf>,
569}
570
571impl BreachDetector {
572    /// Create a new breach detector with default configuration (no persistence).
573    pub fn new() -> Self {
574        Self::with_data_dir(None)
575    }
576
577    /// Create a new breach detector with optional disk persistence for incidents.
578    ///
579    /// If `data_dir` is `Some`, incidents are loaded from `{data_dir}/breach_incidents.json`
580    /// on startup and flushed back after every new incident.
581    pub fn with_data_dir(data_dir: Option<PathBuf>) -> Self {
582        let mut incidents = VecDeque::with_capacity(MAX_INCIDENTS_IN_MEMORY);
583        let mut counter: u64 = 1;
584
585        if let Some(ref dir) = data_dir {
586            let path = dir.join("breach_incidents.json");
587            if path.exists() {
588                match std::fs::read_to_string(&path) {
589                    Ok(contents) => match serde_json::from_str::<Vec<BreachIncident>>(&contents) {
590                        Ok(loaded) => {
591                            let count = loaded.len();
592                            for inc in loaded {
593                                incidents.push_back(inc);
594                            }
595                            counter = (count as u64).saturating_add(1);
596                            tracing::info!("Loaded {} breach incidents from disk", count);
597                        }
598                        Err(e) => {
599                            tracing::error!(
600                                "Failed to parse breach incidents from {}: {}",
601                                path.display(),
602                                e
603                            );
604                        }
605                    },
606                    Err(e) => {
607                        tracing::error!(
608                            "Failed to read breach incidents from {}: {}",
609                            path.display(),
610                            e
611                        );
612                    }
613                }
614            }
615        }
616
617        Self {
618            config: RwLock::new(DetectionConfig::default()),
619            events: RwLock::new(HashMap::new()),
620            failed_logins: RwLock::new(HashMap::new()),
621            access_patterns: RwLock::new(HashMap::new()),
622            incidents: RwLock::new(incidents),
623            incident_counter: AtomicU64::new(counter),
624            event_counter: AtomicU64::new(1),
625            notifiers: RwLock::new(Vec::new()),
626            data_dir,
627        }
628    }
629
630    /// Create a new breach detector with custom configuration.
631    pub fn with_config(config: DetectionConfig) -> Self {
632        Self {
633            config: RwLock::new(config),
634            events: RwLock::new(HashMap::new()),
635            failed_logins: RwLock::new(HashMap::new()),
636            access_patterns: RwLock::new(HashMap::new()),
637            incidents: RwLock::new(VecDeque::with_capacity(MAX_INCIDENTS_IN_MEMORY)),
638            incident_counter: AtomicU64::new(1),
639            event_counter: AtomicU64::new(1),
640            notifiers: RwLock::new(Vec::new()),
641            data_dir: None,
642        }
643    }
644
645    /// Flush all incidents to disk (if data_dir is configured).
646    fn flush_incidents_to_disk(&self) {
647        let Some(ref dir) = self.data_dir else {
648            return;
649        };
650        let path = dir.join("breach_incidents.json");
651        let incidents = self.incidents.read();
652        let vec: Vec<&BreachIncident> = incidents.iter().collect();
653        match serde_json::to_string_pretty(&vec) {
654            Ok(json) => {
655                if let Err(e) = std::fs::write(&path, json) {
656                    tracing::error!(
657                        "Failed to write breach incidents to {}: {}",
658                        path.display(),
659                        e
660                    );
661                }
662            }
663            Err(e) => {
664                tracing::error!("Failed to serialize breach incidents: {}", e);
665            }
666        }
667    }
668
669    /// Update the detection configuration.
670    pub fn update_config(&self, config: DetectionConfig) {
671        *self.config.write() = config;
672    }
673
674    /// Get the current detection configuration.
675    pub fn get_config(&self) -> DetectionConfig {
676        self.config.read().clone()
677    }
678
679    /// Register a notifier.
680    pub fn register_notifier(&self, notifier: Box<dyn BreachNotifier>) {
681        self.notifiers.write().push(notifier);
682    }
683
684    /// Record a security event and check for breach patterns.
685    pub fn record_event(&self, event: SecurityEvent) -> Option<BreachIncident> {
686        let event_type = event.event_type;
687        let now = Instant::now();
688
689        // Store the event
690        {
691            let mut events = self.events.write();
692            let queue = events
693                .entry(event_type)
694                .or_insert_with(|| VecDeque::with_capacity(MAX_EVENTS_IN_MEMORY));
695
696            // Enforce max size
697            while queue.len() >= MAX_EVENTS_IN_MEMORY {
698                queue.pop_front();
699            }
700
701            queue.push_back(EventRecord {
702                event: event.clone(),
703                received_at: now,
704            });
705        }
706
707        // Check for breach patterns based on event type
708        match event_type {
709            SecurityEventType::FailedLogin => self.check_failed_login_pattern(&event),
710            SecurityEventType::UnauthorizedAccess => self.check_unauthorized_access(&event),
711            SecurityEventType::UnusualAccessPattern => self.check_unusual_access_pattern(&event),
712            SecurityEventType::AdminFromUnknownIp => self.check_admin_unknown_ip(&event),
713            SecurityEventType::MassDataExport => self.check_mass_data_operation(&event, false),
714            SecurityEventType::MassDataDeletion => self.check_mass_data_operation(&event, true),
715            SecurityEventType::SessionHijacking => self.create_high_severity_incident(&event),
716            SecurityEventType::SqlInjection => self.check_sql_injection(&event),
717            SecurityEventType::BruteForceAttack => self.create_critical_incident(&event),
718            SecurityEventType::PrivilegeEscalation => self.create_critical_incident(&event),
719        }
720    }
721
722    /// Record a failed login attempt.
723    pub fn record_failed_login(&self, username: &str, ip: Option<&str>) -> Option<BreachIncident> {
724        let event = SecurityEvent::new(
725            SecurityEventType::FailedLogin,
726            &format!("Failed login attempt for user: {}", username),
727            &self.event_counter,
728        )
729        .with_user(username);
730
731        let event = if let Some(ip) = ip {
732            event.with_ip(ip)
733        } else {
734            event
735        };
736
737        self.record_event(event)
738    }
739
740    /// Check if a failed login attempt triggers a breach incident.
741    /// Convenience method that combines record and check in one call.
742    /// Returns Some(BreachIncident) if the threshold is exceeded (brute force detected).
743    pub fn check_failed_login(&self, ip: &str, username: &str) -> Option<BreachIncident> {
744        self.record_failed_login(username, Some(ip))
745    }
746
747    /// Check for mass data access patterns that may indicate data exfiltration.
748    /// Returns Some(BreachIncident) if the access pattern is suspicious.
749    pub fn check_mass_access(&self, user: &str, count: u64) -> Option<BreachIncident> {
750        let config = self.config.read();
751        let threshold = config.mass_data_threshold;
752        drop(config);
753
754        if count >= threshold {
755            let event = SecurityEvent::new(
756                SecurityEventType::MassDataExport,
757                &format!(
758                    "Mass data access detected: {} accessed {} records",
759                    user, count
760                ),
761                &self.event_counter,
762            )
763            .with_user(user)
764            .with_metadata("record_count", &count.to_string());
765
766            return self.record_event(event);
767        }
768
769        None
770    }
771
772    /// Record an unauthorized access attempt.
773    pub fn record_unauthorized_access(
774        &self,
775        user: &str,
776        resource: &str,
777        permission: &str,
778        ip: Option<&str>,
779    ) -> Option<BreachIncident> {
780        let event = SecurityEvent::new(
781            SecurityEventType::UnauthorizedAccess,
782            &format!(
783                "Unauthorized access attempt: {} tried to {} on {}",
784                user, permission, resource
785            ),
786            &self.event_counter,
787        )
788        .with_user(user)
789        .with_resource(resource)
790        .with_metadata("permission", permission);
791
792        let event = if let Some(ip) = ip {
793            event.with_ip(ip)
794        } else {
795            event
796        };
797
798        self.record_event(event)
799    }
800
801    /// Record a data access for pattern monitoring.
802    pub fn record_data_access(&self, user: &str, resource: &str, row_count: u64) {
803        let now = Instant::now();
804
805        // Track access pattern
806        {
807            let mut patterns = self.access_patterns.write();
808            let queue = patterns
809                .entry(user.to_string())
810                .or_insert_with(VecDeque::new);
811            queue.push_back(now);
812        }
813
814        // Check for unusual access pattern
815        let config = self.config.read();
816        let window = Duration::from_secs(config.unusual_access_window_secs);
817        let threshold = config.unusual_access_threshold;
818        drop(config);
819
820        let access_count = {
821            let mut patterns = self.access_patterns.write();
822            if let Some(queue) = patterns.get_mut(user) {
823                // Remove old entries
824                while let Some(front) = queue.front() {
825                    if now.duration_since(*front) > window {
826                        queue.pop_front();
827                    } else {
828                        break;
829                    }
830                }
831                queue.len() as u32
832            } else {
833                0
834            }
835        };
836
837        if access_count >= threshold {
838            let event = SecurityEvent::new(
839                SecurityEventType::UnusualAccessPattern,
840                &format!(
841                    "High volume data access detected: {} accessed {} rows from {}",
842                    user, row_count, resource
843                ),
844                &self.event_counter,
845            )
846            .with_user(user)
847            .with_resource(resource)
848            .with_metadata("access_count", &access_count.to_string())
849            .with_metadata("row_count", &row_count.to_string());
850
851            self.record_event(event);
852        }
853    }
854
855    /// Record an admin action from an IP.
856    pub fn record_admin_action(
857        &self,
858        user: &str,
859        action: &str,
860        ip: &str,
861    ) -> Option<BreachIncident> {
862        let config = self.config.read();
863        let trusted_ips = config.trusted_admin_ips.clone();
864        drop(config);
865
866        if !trusted_ips.contains(&ip.to_string()) {
867            let event = SecurityEvent::new(
868                SecurityEventType::AdminFromUnknownIp,
869                &format!(
870                    "Admin action '{}' performed by {} from untrusted IP {}",
871                    action, user, ip
872                ),
873                &self.event_counter,
874            )
875            .with_user(user)
876            .with_ip(ip)
877            .with_metadata("action", action);
878
879            self.record_event(event)
880        } else {
881            None
882        }
883    }
884
885    /// Record a mass data operation.
886    pub fn record_mass_data_operation(
887        &self,
888        user: &str,
889        resource: &str,
890        row_count: u64,
891        is_deletion: bool,
892    ) -> Option<BreachIncident> {
893        let config = self.config.read();
894        let threshold = config.mass_data_threshold;
895        drop(config);
896
897        if row_count >= threshold {
898            let event_type = if is_deletion {
899                SecurityEventType::MassDataDeletion
900            } else {
901                SecurityEventType::MassDataExport
902            };
903
904            let operation = if is_deletion { "deleted" } else { "exported" };
905            let event = SecurityEvent::new(
906                event_type,
907                &format!(
908                    "Mass data operation: {} {} {} rows from {}",
909                    user, operation, row_count, resource
910                ),
911                &self.event_counter,
912            )
913            .with_user(user)
914            .with_resource(resource)
915            .with_metadata("row_count", &row_count.to_string());
916
917            self.record_event(event)
918        } else {
919            None
920        }
921    }
922
923    /// Check for failed login patterns.
924    fn check_failed_login_pattern(&self, event: &SecurityEvent) -> Option<BreachIncident> {
925        let key = event
926            .user
927            .clone()
928            .or_else(|| event.ip_address.clone())
929            .unwrap_or_else(|| "unknown".to_string());
930
931        let now = Instant::now();
932        let config = self.config.read();
933        let window = Duration::from_secs(config.failed_login_window_secs);
934        let threshold = config.failed_login_threshold;
935        drop(config);
936
937        // Track failed login
938        let count = {
939            let mut logins = self.failed_logins.write();
940            let queue = logins.entry(key.clone()).or_insert_with(VecDeque::new);
941            queue.push_back(now);
942
943            // Remove old entries
944            while let Some(front) = queue.front() {
945                if now.duration_since(*front) > window {
946                    queue.pop_front();
947                } else {
948                    break;
949                }
950            }
951            queue.len() as u32
952        };
953
954        if count >= threshold {
955            // Clear the counter to avoid duplicate incidents
956            {
957                let mut logins = self.failed_logins.write();
958                logins.remove(&key);
959            }
960
961            let severity = if count >= threshold * 2 {
962                BreachSeverity::High
963            } else {
964                BreachSeverity::Medium
965            };
966
967            let mut incident = BreachIncident::new(
968                SecurityEventType::FailedLogin,
969                severity,
970                &format!(
971                    "Multiple failed login attempts detected: {} attempts in {} seconds",
972                    count,
973                    window.as_secs()
974                ),
975                &self.incident_counter,
976            )
977            .with_related_event(&event.id)
978            .with_detail("attempt_count", &count.to_string());
979
980            if let Some(ref user) = event.user {
981                incident = incident.with_affected_subject(user);
982            }
983            if let Some(ref ip) = event.ip_address {
984                incident = incident.with_involved_ip(ip);
985            }
986
987            return self.create_and_notify_incident(incident);
988        }
989
990        None
991    }
992
993    /// Check for unauthorized access pattern.
994    fn check_unauthorized_access(&self, event: &SecurityEvent) -> Option<BreachIncident> {
995        let severity = BreachSeverity::Medium;
996
997        let mut incident = BreachIncident::new(
998            SecurityEventType::UnauthorizedAccess,
999            severity,
1000            &event.description,
1001            &self.incident_counter,
1002        )
1003        .with_related_event(&event.id);
1004
1005        if let Some(ref user) = event.user {
1006            incident = incident.with_affected_subject(user);
1007        }
1008        if let Some(ref ip) = event.ip_address {
1009            incident = incident.with_involved_ip(ip);
1010        }
1011        if let Some(ref resource) = event.resource {
1012            incident = incident.with_detail("resource", resource);
1013        }
1014
1015        self.create_and_notify_incident(incident)
1016    }
1017
1018    /// Check for unusual access patterns.
1019    fn check_unusual_access_pattern(&self, event: &SecurityEvent) -> Option<BreachIncident> {
1020        let mut incident = BreachIncident::new(
1021            SecurityEventType::UnusualAccessPattern,
1022            BreachSeverity::Medium,
1023            &event.description,
1024            &self.incident_counter,
1025        )
1026        .with_related_event(&event.id);
1027
1028        if let Some(ref user) = event.user {
1029            incident = incident.with_affected_subject(user);
1030        }
1031        if let Some(ref ip) = event.ip_address {
1032            incident = incident.with_involved_ip(ip);
1033        }
1034
1035        self.create_and_notify_incident(incident)
1036    }
1037
1038    /// Check admin action from unknown IP.
1039    fn check_admin_unknown_ip(&self, event: &SecurityEvent) -> Option<BreachIncident> {
1040        let mut incident = BreachIncident::new(
1041            SecurityEventType::AdminFromUnknownIp,
1042            BreachSeverity::High,
1043            &event.description,
1044            &self.incident_counter,
1045        )
1046        .with_related_event(&event.id);
1047
1048        if let Some(ref user) = event.user {
1049            incident = incident.with_affected_subject(user);
1050        }
1051        if let Some(ref ip) = event.ip_address {
1052            incident = incident.with_involved_ip(ip);
1053        }
1054
1055        self.create_and_notify_incident(incident)
1056    }
1057
1058    /// Check mass data operation.
1059    fn check_mass_data_operation(
1060        &self,
1061        event: &SecurityEvent,
1062        is_deletion: bool,
1063    ) -> Option<BreachIncident> {
1064        let severity = if is_deletion {
1065            BreachSeverity::Critical
1066        } else {
1067            BreachSeverity::High
1068        };
1069
1070        let mut incident =
1071            BreachIncident::new(event.event_type, severity, &event.description, &self.incident_counter)
1072                .with_related_event(&event.id);
1073
1074        if let Some(ref user) = event.user {
1075            incident = incident.with_affected_subject(user);
1076        }
1077        if let Some(ref resource) = event.resource {
1078            incident = incident.with_detail("resource", resource);
1079        }
1080
1081        self.create_and_notify_incident(incident)
1082    }
1083
1084    /// Check for SQL injection attempts.
1085    fn check_sql_injection(&self, event: &SecurityEvent) -> Option<BreachIncident> {
1086        let config = self.config.read();
1087        if !config.enable_sql_injection_detection {
1088            return None;
1089        }
1090        drop(config);
1091
1092        let mut incident = BreachIncident::new(
1093            SecurityEventType::SqlInjection,
1094            BreachSeverity::High,
1095            &event.description,
1096            &self.incident_counter,
1097        )
1098        .with_related_event(&event.id);
1099
1100        if let Some(ref user) = event.user {
1101            incident = incident.with_affected_subject(user);
1102        }
1103        if let Some(ref ip) = event.ip_address {
1104            incident = incident.with_involved_ip(ip);
1105        }
1106
1107        self.create_and_notify_incident(incident)
1108    }
1109
1110    /// Create a high severity incident.
1111    fn create_high_severity_incident(&self, event: &SecurityEvent) -> Option<BreachIncident> {
1112        let mut incident =
1113            BreachIncident::new(event.event_type, BreachSeverity::High, &event.description, &self.incident_counter)
1114                .with_related_event(&event.id);
1115
1116        if let Some(ref user) = event.user {
1117            incident = incident.with_affected_subject(user);
1118        }
1119        if let Some(ref ip) = event.ip_address {
1120            incident = incident.with_involved_ip(ip);
1121        }
1122
1123        self.create_and_notify_incident(incident)
1124    }
1125
1126    /// Create a critical severity incident.
1127    fn create_critical_incident(&self, event: &SecurityEvent) -> Option<BreachIncident> {
1128        let mut incident = BreachIncident::new(
1129            event.event_type,
1130            BreachSeverity::Critical,
1131            &event.description,
1132            &self.incident_counter,
1133        )
1134        .with_related_event(&event.id);
1135
1136        if let Some(ref user) = event.user {
1137            incident = incident.with_affected_subject(user);
1138        }
1139        if let Some(ref ip) = event.ip_address {
1140            incident = incident.with_involved_ip(ip);
1141        }
1142
1143        self.create_and_notify_incident(incident)
1144    }
1145
1146    /// Create an incident and send notifications.
1147    fn create_and_notify_incident(&self, mut incident: BreachIncident) -> Option<BreachIncident> {
1148        // Send notifications
1149        let notifiers = self.notifiers.read();
1150        let now = now_timestamp();
1151
1152        for notifier in notifiers.iter() {
1153            match notifier.notify(&incident) {
1154                Ok(()) => {
1155                    incident
1156                        .notification_timestamps
1157                        .insert(notifier.name().to_string(), now);
1158                    tracing::info!(
1159                        "Breach notification sent via {}: {}",
1160                        notifier.name(),
1161                        incident.id
1162                    );
1163                }
1164                Err(e) => {
1165                    tracing::error!(
1166                        "Failed to send breach notification via {}: {}",
1167                        notifier.name(),
1168                        e
1169                    );
1170                }
1171            }
1172        }
1173        drop(notifiers);
1174
1175        incident.notified = !incident.notification_timestamps.is_empty();
1176
1177        // Store the incident
1178        {
1179            let mut incidents = self.incidents.write();
1180            while incidents.len() >= MAX_INCIDENTS_IN_MEMORY {
1181                incidents.pop_front();
1182            }
1183            incidents.push_back(incident.clone());
1184        }
1185
1186        // Persist incidents to disk for compliance
1187        self.flush_incidents_to_disk();
1188
1189        tracing::warn!(
1190            "Breach incident detected: {} (severity: {})",
1191            incident.id,
1192            incident.severity
1193        );
1194
1195        Some(incident)
1196    }
1197
1198    /// Get all breach incidents.
1199    pub fn list_incidents(&self) -> Vec<BreachIncident> {
1200        self.incidents.read().iter().cloned().collect()
1201    }
1202
1203    /// List recent security events with optional type filter.
1204    pub fn list_events(&self, event_type: Option<&str>, limit: usize) -> Vec<SecurityEvent> {
1205        let events = self.events.read();
1206
1207        let filter_type = event_type.and_then(|t| match t {
1208            "failed_login" => Some(SecurityEventType::FailedLogin),
1209            "unauthorized_access" => Some(SecurityEventType::UnauthorizedAccess),
1210            "unusual_access_pattern" => Some(SecurityEventType::UnusualAccessPattern),
1211            "admin_from_unknown_ip" => Some(SecurityEventType::AdminFromUnknownIp),
1212            "mass_data_export" => Some(SecurityEventType::MassDataExport),
1213            "mass_data_deletion" => Some(SecurityEventType::MassDataDeletion),
1214            "session_hijacking" => Some(SecurityEventType::SessionHijacking),
1215            "sql_injection" => Some(SecurityEventType::SqlInjection),
1216            "brute_force_attack" => Some(SecurityEventType::BruteForceAttack),
1217            "privilege_escalation" => Some(SecurityEventType::PrivilegeEscalation),
1218            _ => None,
1219        });
1220
1221        let mut all_events: Vec<SecurityEvent> = if let Some(filter) = filter_type {
1222            events
1223                .get(&filter)
1224                .map(|q| q.iter().map(|r| r.event.clone()).collect())
1225                .unwrap_or_default()
1226        } else {
1227            events
1228                .values()
1229                .flat_map(|q| q.iter().map(|r| r.event.clone()))
1230                .collect()
1231        };
1232
1233        // Sort by timestamp descending (most recent first)
1234        all_events.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
1235        all_events.truncate(limit);
1236        all_events
1237    }
1238
1239    /// Get incidents with optional filters.
1240    pub fn get_incidents(
1241        &self,
1242        status: Option<IncidentStatus>,
1243        severity: Option<BreachSeverity>,
1244        limit: usize,
1245    ) -> Vec<BreachIncident> {
1246        let incidents = self.incidents.read();
1247        incidents
1248            .iter()
1249            .rev()
1250            .filter(|i| status.is_none() || Some(i.status) == status)
1251            .filter(|i| severity.is_none() || Some(i.severity) == severity)
1252            .take(limit)
1253            .cloned()
1254            .collect()
1255    }
1256
1257    /// Get a specific incident by ID.
1258    pub fn get_incident(&self, id: &str) -> Option<BreachIncident> {
1259        self.incidents.read().iter().find(|i| i.id == id).cloned()
1260    }
1261
1262    /// Acknowledge an incident.
1263    pub fn acknowledge_incident(&self, id: &str, acknowledged_by: &str) -> Option<BreachIncident> {
1264        let mut incidents = self.incidents.write();
1265        for incident in incidents.iter_mut() {
1266            if incident.id == id {
1267                incident.status = IncidentStatus::Acknowledged;
1268                incident.acknowledged_by = Some(acknowledged_by.to_string());
1269                incident.acknowledged_at = Some(now_timestamp());
1270                return Some(incident.clone());
1271            }
1272        }
1273        None
1274    }
1275
1276    /// Resolve an incident.
1277    pub fn resolve_incident(
1278        &self,
1279        id: &str,
1280        resolution_notes: &str,
1281        false_positive: bool,
1282    ) -> Option<BreachIncident> {
1283        let mut incidents = self.incidents.write();
1284        for incident in incidents.iter_mut() {
1285            if incident.id == id {
1286                incident.status = if false_positive {
1287                    IncidentStatus::FalsePositive
1288                } else {
1289                    IncidentStatus::Resolved
1290                };
1291                incident.resolution_notes = Some(resolution_notes.to_string());
1292                incident.resolved_at = Some(now_timestamp());
1293                return Some(incident.clone());
1294            }
1295        }
1296        None
1297    }
1298
1299    /// Generate an incident report for compliance purposes.
1300    pub fn generate_report(&self, id: &str) -> Option<IncidentReport> {
1301        let incident = self.get_incident(id)?;
1302
1303        // Gather related events
1304        let events = self.events.read();
1305        let related_events: Vec<SecurityEvent> = events
1306            .values()
1307            .flat_map(|q| q.iter())
1308            .filter(|r| incident.related_events.contains(&r.event.id))
1309            .map(|r| r.event.clone())
1310            .collect();
1311
1312        Some(IncidentReport {
1313            incident,
1314            related_events,
1315            generated_at: now_timestamp(),
1316            generated_at_formatted: format_timestamp(now_timestamp()),
1317        })
1318    }
1319
1320    /// Get incident statistics.
1321    pub fn get_stats(&self) -> BreachStats {
1322        let incidents = self.incidents.read();
1323
1324        let mut stats = BreachStats {
1325            total_incidents: incidents.len(),
1326            open_incidents: 0,
1327            acknowledged_incidents: 0,
1328            resolved_incidents: 0,
1329            false_positives: 0,
1330            by_severity: HashMap::new(),
1331            by_type: HashMap::new(),
1332        };
1333
1334        for incident in incidents.iter() {
1335            match incident.status {
1336                IncidentStatus::Open => stats.open_incidents += 1,
1337                IncidentStatus::Acknowledged | IncidentStatus::Investigating => {
1338                    stats.acknowledged_incidents += 1
1339                }
1340                IncidentStatus::Resolved => stats.resolved_incidents += 1,
1341                IncidentStatus::FalsePositive => stats.false_positives += 1,
1342            }
1343
1344            *stats
1345                .by_severity
1346                .entry(incident.severity.to_string())
1347                .or_insert(0) += 1;
1348            *stats
1349                .by_type
1350                .entry(incident.incident_type.to_string())
1351                .or_insert(0) += 1;
1352        }
1353
1354        stats
1355    }
1356
1357    /// Clean up old events and patterns.
1358    pub fn cleanup(&self) {
1359        let now = Instant::now();
1360        let retention = Duration::from_secs(24 * 60 * 60); // 24 hours
1361
1362        // Clean up events
1363        {
1364            let mut events = self.events.write();
1365            for queue in events.values_mut() {
1366                while let Some(front) = queue.front() {
1367                    if now.duration_since(front.received_at) > retention {
1368                        queue.pop_front();
1369                    } else {
1370                        break;
1371                    }
1372                }
1373            }
1374        }
1375
1376        // Clean up failed login tracking
1377        {
1378            let mut logins = self.failed_logins.write();
1379            let config = self.config.read();
1380            let window = Duration::from_secs(config.failed_login_window_secs);
1381            drop(config);
1382
1383            for queue in logins.values_mut() {
1384                while let Some(front) = queue.front() {
1385                    if now.duration_since(*front) > window {
1386                        queue.pop_front();
1387                    } else {
1388                        break;
1389                    }
1390                }
1391            }
1392            logins.retain(|_, v| !v.is_empty());
1393        }
1394
1395        // Clean up access patterns
1396        {
1397            let mut patterns = self.access_patterns.write();
1398            let config = self.config.read();
1399            let window = Duration::from_secs(config.unusual_access_window_secs);
1400            drop(config);
1401
1402            for queue in patterns.values_mut() {
1403                while let Some(front) = queue.front() {
1404                    if now.duration_since(*front) > window {
1405                        queue.pop_front();
1406                    } else {
1407                        break;
1408                    }
1409                }
1410            }
1411            patterns.retain(|_, v| !v.is_empty());
1412        }
1413    }
1414}
1415
1416impl Default for BreachDetector {
1417    fn default() -> Self {
1418        Self::new()
1419    }
1420}
1421
1422// =============================================================================
1423// Incident Report
1424// =============================================================================
1425
1426/// Full incident report for compliance documentation.
1427#[derive(Debug, Clone, Serialize, Deserialize)]
1428pub struct IncidentReport {
1429    /// The breach incident.
1430    pub incident: BreachIncident,
1431    /// Related security events.
1432    pub related_events: Vec<SecurityEvent>,
1433    /// When the report was generated (Unix milliseconds).
1434    pub generated_at: u64,
1435    /// Human-readable generation timestamp.
1436    pub generated_at_formatted: String,
1437}
1438
1439// =============================================================================
1440// Breach Statistics
1441// =============================================================================
1442
1443/// Statistics about breach incidents.
1444#[derive(Debug, Clone, Serialize, Deserialize)]
1445pub struct BreachStats {
1446    /// Total number of incidents.
1447    pub total_incidents: usize,
1448    /// Number of open incidents.
1449    pub open_incidents: usize,
1450    /// Number of acknowledged incidents.
1451    pub acknowledged_incidents: usize,
1452    /// Number of resolved incidents.
1453    pub resolved_incidents: usize,
1454    /// Number of false positives.
1455    pub false_positives: usize,
1456    /// Incidents by severity.
1457    pub by_severity: HashMap<String, usize>,
1458    /// Incidents by type.
1459    pub by_type: HashMap<String, usize>,
1460}
1461
1462// =============================================================================
1463// Helper Functions
1464// =============================================================================
1465
1466/// Get current timestamp in milliseconds.
1467fn now_timestamp() -> u64 {
1468    SystemTime::now()
1469        .duration_since(UNIX_EPOCH)
1470        .unwrap_or_default()
1471        .as_millis() as u64
1472}
1473
1474/// Generate a unique event ID using the given counter.
1475fn generate_event_id(counter: &AtomicU64) -> String {
1476    format!("evt-{:012}", counter.fetch_add(1, Ordering::SeqCst))
1477}
1478
1479/// Generate a unique incident ID using the given counter.
1480fn generate_incident_id(counter: &AtomicU64) -> String {
1481    format!("inc-{:012}", counter.fetch_add(1, Ordering::SeqCst))
1482}
1483
1484/// Format a timestamp to RFC3339 string.
1485fn format_timestamp(timestamp_ms: u64) -> String {
1486    let secs = timestamp_ms / 1000;
1487    let datetime = UNIX_EPOCH + Duration::from_secs(secs);
1488    let duration = datetime.duration_since(UNIX_EPOCH).unwrap_or_default();
1489    let total_secs = duration.as_secs();
1490
1491    let days_since_epoch = total_secs / 86400;
1492    let secs_today = total_secs % 86400;
1493
1494    let hours = secs_today / 3600;
1495    let minutes = (secs_today % 3600) / 60;
1496    let seconds = secs_today % 60;
1497
1498    let mut year = 1970u64;
1499    let mut remaining_days = days_since_epoch;
1500
1501    loop {
1502        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
1503        if remaining_days < days_in_year {
1504            break;
1505        }
1506        remaining_days -= days_in_year;
1507        year += 1;
1508    }
1509
1510    let days_in_months: [u64; 12] = if is_leap_year(year) {
1511        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1512    } else {
1513        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1514    };
1515
1516    let mut month = 1u64;
1517    for &days in &days_in_months {
1518        if remaining_days < days {
1519            break;
1520        }
1521        remaining_days -= days;
1522        month += 1;
1523    }
1524    let day = remaining_days + 1;
1525
1526    format!(
1527        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
1528        year, month, day, hours, minutes, seconds
1529    )
1530}
1531
1532fn is_leap_year(year: u64) -> bool {
1533    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
1534}
1535
1536// =============================================================================
1537// API Handler Types
1538// =============================================================================
1539
1540/// Request to acknowledge a breach incident.
1541#[derive(Debug, Deserialize)]
1542pub struct AcknowledgeRequest {
1543    pub acknowledged_by: String,
1544}
1545
1546/// Request to resolve a breach incident.
1547#[derive(Debug, Deserialize)]
1548pub struct ResolveRequest {
1549    pub resolution_notes: String,
1550    #[serde(default)]
1551    pub false_positive: bool,
1552}
1553
1554/// Query parameters for listing breaches.
1555#[derive(Debug, Deserialize)]
1556pub struct ListBreachesQuery {
1557    pub status: Option<String>,
1558    pub severity: Option<String>,
1559    #[serde(default = "default_limit")]
1560    pub limit: usize,
1561}
1562
1563fn default_limit() -> usize {
1564    100
1565}
1566
1567/// Query parameters for listing security events.
1568#[derive(Debug, Deserialize)]
1569pub struct ListEventsQuery {
1570    #[serde(default = "default_limit")]
1571    pub limit: usize,
1572    pub event_type: Option<String>,
1573}
1574
1575// =============================================================================
1576// HTTP Handlers
1577// =============================================================================
1578
1579use crate::state::AppState;
1580use axum::{
1581    extract::{Path, Query, State},
1582    http::StatusCode,
1583    response::IntoResponse,
1584    Json,
1585};
1586
1587/// List breach incidents response.
1588#[derive(Debug, serde::Serialize)]
1589pub struct ListBreachesResponse {
1590    pub incidents: Vec<BreachIncident>,
1591    pub total: usize,
1592    pub stats: BreachStats,
1593}
1594
1595/// Security events response.
1596#[derive(Debug, serde::Serialize)]
1597pub struct SecurityEventsResponse {
1598    pub events: Vec<SecurityEvent>,
1599    pub total: usize,
1600}
1601
1602/// List all breach incidents.
1603/// GET /api/v1/compliance/breaches
1604pub async fn list_breaches(
1605    State(state): State<AppState>,
1606    Query(params): Query<ListBreachesQuery>,
1607) -> Json<ListBreachesResponse> {
1608    let status = params.status.as_ref().and_then(|s| match s.as_str() {
1609        "open" => Some(IncidentStatus::Open),
1610        "acknowledged" => Some(IncidentStatus::Acknowledged),
1611        "investigating" => Some(IncidentStatus::Investigating),
1612        "resolved" => Some(IncidentStatus::Resolved),
1613        "false_positive" => Some(IncidentStatus::FalsePositive),
1614        _ => None,
1615    });
1616
1617    let severity = params.severity.as_ref().and_then(|s| match s.as_str() {
1618        "low" => Some(BreachSeverity::Low),
1619        "medium" => Some(BreachSeverity::Medium),
1620        "high" => Some(BreachSeverity::High),
1621        "critical" => Some(BreachSeverity::Critical),
1622        _ => None,
1623    });
1624
1625    let incidents = state
1626        .breach_detector
1627        .get_incidents(status, severity, params.limit);
1628    let total = incidents.len();
1629    let stats = state.breach_detector.get_stats();
1630
1631    Json(ListBreachesResponse {
1632        incidents,
1633        total,
1634        stats,
1635    })
1636}
1637
1638/// Get a specific breach incident.
1639/// GET /api/v1/compliance/breaches/:id
1640pub async fn get_breach(
1641    State(state): State<AppState>,
1642    Path(id): Path<String>,
1643) -> impl IntoResponse {
1644    match state.breach_detector.get_incident(&id) {
1645        Some(incident) => (
1646            StatusCode::OK,
1647            Json(serde_json::json!({
1648                "success": true,
1649                "incident": incident,
1650            })),
1651        ),
1652        None => (
1653            StatusCode::NOT_FOUND,
1654            Json(serde_json::json!({
1655                "success": false,
1656                "error": format!("Incident '{}' not found", id),
1657            })),
1658        ),
1659    }
1660}
1661
1662/// Acknowledge a breach incident.
1663/// POST /api/v1/compliance/breaches/:id/acknowledge
1664pub async fn acknowledge_breach(
1665    State(state): State<AppState>,
1666    Path(id): Path<String>,
1667    Json(request): Json<AcknowledgeRequest>,
1668) -> impl IntoResponse {
1669    match state
1670        .breach_detector
1671        .acknowledge_incident(&id, &request.acknowledged_by)
1672    {
1673        Some(incident) => {
1674            tracing::info!(
1675                "Breach incident {} acknowledged by {}",
1676                id,
1677                request.acknowledged_by
1678            );
1679            (
1680                StatusCode::OK,
1681                Json(serde_json::json!({
1682                    "success": true,
1683                    "incident": incident,
1684                    "message": "Incident acknowledged successfully",
1685                })),
1686            )
1687        }
1688        None => (
1689            StatusCode::NOT_FOUND,
1690            Json(serde_json::json!({
1691                "success": false,
1692                "error": format!("Incident '{}' not found", id),
1693            })),
1694        ),
1695    }
1696}
1697
1698/// Resolve a breach incident.
1699/// POST /api/v1/compliance/breaches/:id/resolve
1700pub async fn resolve_breach(
1701    State(state): State<AppState>,
1702    Path(id): Path<String>,
1703    Json(request): Json<ResolveRequest>,
1704) -> impl IntoResponse {
1705    match state.breach_detector.resolve_incident(
1706        &id,
1707        &request.resolution_notes,
1708        request.false_positive,
1709    ) {
1710        Some(incident) => {
1711            let status_str = if request.false_positive {
1712                "false positive"
1713            } else {
1714                "resolved"
1715            };
1716            tracing::info!("Breach incident {} marked as {}", id, status_str);
1717            (
1718                StatusCode::OK,
1719                Json(serde_json::json!({
1720                    "success": true,
1721                    "incident": incident,
1722                    "message": format!("Incident marked as {}", status_str),
1723                })),
1724            )
1725        }
1726        None => (
1727            StatusCode::NOT_FOUND,
1728            Json(serde_json::json!({
1729                "success": false,
1730                "error": format!("Incident '{}' not found", id),
1731            })),
1732        ),
1733    }
1734}
1735
1736/// Get an incident report for compliance documentation.
1737/// GET /api/v1/compliance/breaches/:id/report
1738pub async fn get_breach_report(
1739    State(state): State<AppState>,
1740    Path(id): Path<String>,
1741) -> impl IntoResponse {
1742    match state.breach_detector.generate_report(&id) {
1743        Some(report) => (
1744            StatusCode::OK,
1745            Json(serde_json::json!({
1746                "success": true,
1747                "report": report,
1748            })),
1749        ),
1750        None => (
1751            StatusCode::NOT_FOUND,
1752            Json(serde_json::json!({
1753                "success": false,
1754                "error": format!("Incident '{}' not found", id),
1755            })),
1756        ),
1757    }
1758}
1759
1760/// List recent security events.
1761/// GET /api/v1/compliance/security-events
1762pub async fn list_security_events(
1763    State(state): State<AppState>,
1764    Query(params): Query<ListEventsQuery>,
1765) -> Json<SecurityEventsResponse> {
1766    let events = state
1767        .breach_detector
1768        .list_events(params.event_type.as_deref(), params.limit);
1769    let total = events.len();
1770
1771    Json(SecurityEventsResponse { events, total })
1772}
1773
1774/// Get breach detection statistics.
1775/// GET /api/v1/compliance/breaches/stats
1776pub async fn get_breach_stats(State(state): State<AppState>) -> Json<BreachStats> {
1777    Json(state.breach_detector.get_stats())
1778}
1779
1780/// Manually trigger a cleanup of old events and patterns.
1781/// POST /api/v1/compliance/breaches/cleanup
1782pub async fn trigger_cleanup(State(state): State<AppState>) -> Json<serde_json::Value> {
1783    state.breach_detector.cleanup();
1784    Json(serde_json::json!({
1785        "success": true,
1786        "message": "Cleanup completed successfully",
1787    }))
1788}
1789
1790// =============================================================================
1791// Tests
1792// =============================================================================
1793
1794#[cfg(test)]
1795mod tests {
1796    use super::*;
1797
1798    #[test]
1799    fn test_security_event_creation() {
1800        let counter = AtomicU64::new(1);
1801        let event = SecurityEvent::new(SecurityEventType::FailedLogin, "Test failed login", &counter)
1802            .with_user("testuser")
1803            .with_ip("192.168.1.1");
1804
1805        assert!(event.id.starts_with("evt-"));
1806        assert_eq!(event.event_type, SecurityEventType::FailedLogin);
1807        assert_eq!(event.user, Some("testuser".to_string()));
1808        assert_eq!(event.ip_address, Some("192.168.1.1".to_string()));
1809    }
1810
1811    #[test]
1812    fn test_breach_incident_creation() {
1813        let counter = AtomicU64::new(1);
1814        let incident = BreachIncident::new(
1815            SecurityEventType::FailedLogin,
1816            BreachSeverity::Medium,
1817            "Multiple failed logins detected",
1818            &counter,
1819        )
1820        .with_affected_subject("user1")
1821        .with_involved_ip("10.0.0.1");
1822
1823        assert!(incident.id.starts_with("inc-"));
1824        assert_eq!(incident.severity, BreachSeverity::Medium);
1825        assert!(incident.affected_subjects.contains(&"user1".to_string()));
1826        assert!(incident.involved_ips.contains(&"10.0.0.1".to_string()));
1827    }
1828
1829    #[test]
1830    fn test_breach_detector_failed_login_detection() {
1831        let config = DetectionConfig {
1832            failed_login_threshold: 3,
1833            failed_login_window_secs: 300,
1834            ..Default::default()
1835        };
1836        let detector = BreachDetector::with_config(config);
1837
1838        // First two attempts should not trigger
1839        assert!(detector
1840            .record_failed_login("user1", Some("192.168.1.1"))
1841            .is_none());
1842        assert!(detector
1843            .record_failed_login("user1", Some("192.168.1.1"))
1844            .is_none());
1845
1846        // Third attempt should trigger incident
1847        let incident = detector.record_failed_login("user1", Some("192.168.1.1"));
1848        assert!(incident.is_some());
1849
1850        let incident = incident.unwrap();
1851        assert_eq!(incident.incident_type, SecurityEventType::FailedLogin);
1852        assert!(incident.affected_subjects.contains(&"user1".to_string()));
1853    }
1854
1855    #[test]
1856    fn test_breach_detector_unauthorized_access() {
1857        let detector = BreachDetector::new();
1858
1859        let incident =
1860            detector.record_unauthorized_access("user1", "admin/users", "write", Some("10.0.0.1"));
1861
1862        assert!(incident.is_some());
1863        let incident = incident.unwrap();
1864        assert_eq!(
1865            incident.incident_type,
1866            SecurityEventType::UnauthorizedAccess
1867        );
1868    }
1869
1870    #[test]
1871    fn test_breach_detector_mass_data_operation() {
1872        let config = DetectionConfig {
1873            mass_data_threshold: 100,
1874            ..Default::default()
1875        };
1876        let detector = BreachDetector::with_config(config);
1877
1878        // Below threshold should not trigger
1879        assert!(detector
1880            .record_mass_data_operation("user1", "users", 50, false)
1881            .is_none());
1882
1883        // Above threshold should trigger
1884        let incident = detector.record_mass_data_operation("user1", "users", 1000, true);
1885        assert!(incident.is_some());
1886
1887        let incident = incident.unwrap();
1888        assert_eq!(incident.incident_type, SecurityEventType::MassDataDeletion);
1889        assert_eq!(incident.severity, BreachSeverity::Critical);
1890    }
1891
1892    #[test]
1893    fn test_breach_detector_admin_unknown_ip() {
1894        let config = DetectionConfig {
1895            trusted_admin_ips: vec!["127.0.0.1".to_string()],
1896            ..Default::default()
1897        };
1898        let detector = BreachDetector::with_config(config);
1899
1900        // Trusted IP should not trigger
1901        assert!(detector
1902            .record_admin_action("admin", "delete_user", "127.0.0.1")
1903            .is_none());
1904
1905        // Unknown IP should trigger
1906        let incident = detector.record_admin_action("admin", "delete_user", "192.168.1.100");
1907        assert!(incident.is_some());
1908
1909        let incident = incident.unwrap();
1910        assert_eq!(
1911            incident.incident_type,
1912            SecurityEventType::AdminFromUnknownIp
1913        );
1914        assert_eq!(incident.severity, BreachSeverity::High);
1915    }
1916
1917    #[test]
1918    fn test_incident_acknowledge_and_resolve() {
1919        let detector = BreachDetector::new();
1920
1921        // Create an incident
1922        let incident =
1923            detector.record_unauthorized_access("user1", "admin/users", "write", Some("10.0.0.1"));
1924        let incident = incident.expect("should create incident");
1925
1926        // Acknowledge it
1927        let acknowledged = detector.acknowledge_incident(&incident.id, "admin");
1928        assert!(acknowledged.is_some());
1929        let acknowledged = acknowledged.unwrap();
1930        assert_eq!(acknowledged.status, IncidentStatus::Acknowledged);
1931        assert_eq!(acknowledged.acknowledged_by, Some("admin".to_string()));
1932
1933        // Resolve it
1934        let resolved = detector.resolve_incident(&incident.id, "Investigated and addressed", false);
1935        assert!(resolved.is_some());
1936        let resolved = resolved.unwrap();
1937        assert_eq!(resolved.status, IncidentStatus::Resolved);
1938    }
1939
1940    #[test]
1941    fn test_incident_report_generation() {
1942        let detector = BreachDetector::new();
1943
1944        let incident =
1945            detector.record_unauthorized_access("user1", "admin/users", "write", Some("10.0.0.1"));
1946        let incident = incident.expect("should create incident");
1947
1948        let report = detector.generate_report(&incident.id);
1949        assert!(report.is_some());
1950
1951        let report = report.unwrap();
1952        assert_eq!(report.incident.id, incident.id);
1953    }
1954
1955    #[test]
1956    fn test_breach_stats() {
1957        let detector = BreachDetector::new();
1958
1959        // Create some incidents
1960        detector.record_unauthorized_access("user1", "admin", "write", Some("10.0.0.1"));
1961        detector.record_unauthorized_access("user2", "admin", "write", Some("10.0.0.2"));
1962
1963        let stats = detector.get_stats();
1964        assert_eq!(stats.total_incidents, 2);
1965        assert_eq!(stats.open_incidents, 2);
1966    }
1967
1968    #[test]
1969    fn test_severity_ordering() {
1970        assert!(BreachSeverity::Low < BreachSeverity::Medium);
1971        assert!(BreachSeverity::Medium < BreachSeverity::High);
1972        assert!(BreachSeverity::High < BreachSeverity::Critical);
1973    }
1974
1975    #[test]
1976    fn test_requires_immediate_notification() {
1977        let counter = AtomicU64::new(1);
1978        let low_incident =
1979            BreachIncident::new(SecurityEventType::FailedLogin, BreachSeverity::Low, "test", &counter);
1980        assert!(!low_incident.requires_immediate_notification());
1981
1982        let high_incident = BreachIncident::new(
1983            SecurityEventType::MassDataDeletion,
1984            BreachSeverity::High,
1985            "test",
1986            &counter,
1987        );
1988        assert!(high_incident.requires_immediate_notification());
1989
1990        let critical_incident = BreachIncident::new(
1991            SecurityEventType::BruteForceAttack,
1992            BreachSeverity::Critical,
1993            "test",
1994            &counter,
1995        );
1996        assert!(critical_incident.requires_immediate_notification());
1997    }
1998}