mockforge_core/security/
compliance_dashboard.rs

1//! Compliance Monitoring Dashboard
2//!
3//! This module provides real-time compliance monitoring, aggregating data from
4//! various security systems to provide compliance scores, control effectiveness,
5//! gap analysis, and alerts.
6
7use crate::Error;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Compliance standard
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum ComplianceStandard {
16    /// SOC 2 Type II
17    Soc2,
18    /// ISO 27001
19    Iso27001,
20}
21
22/// Control category
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum ControlCategory {
26    /// Access control
27    AccessControl,
28    /// Encryption
29    Encryption,
30    /// Monitoring
31    Monitoring,
32    /// Change management
33    ChangeManagement,
34    /// Incident response
35    IncidentResponse,
36}
37
38/// Gap severity
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
40#[serde(rename_all = "lowercase")]
41pub enum GapSeverity {
42    /// Critical severity
43    Critical,
44    /// High severity
45    High,
46    /// Medium severity
47    Medium,
48    /// Low severity
49    Low,
50}
51
52/// Compliance gap
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ComplianceGap {
55    /// Gap ID
56    pub gap_id: String,
57    /// Gap description
58    pub description: String,
59    /// Severity
60    pub severity: GapSeverity,
61    /// Affected standard
62    pub standard: ComplianceStandard,
63    /// Control ID
64    pub control_id: Option<String>,
65    /// Status
66    pub status: GapStatus,
67    /// Created date
68    pub created_at: DateTime<Utc>,
69    /// Target remediation date
70    pub target_remediation_date: Option<DateTime<Utc>>,
71    /// Remediated date
72    pub remediated_at: Option<DateTime<Utc>>,
73}
74
75/// Gap status
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum GapStatus {
79    /// Gap identified
80    Identified,
81    /// Remediation in progress
82    InProgress,
83    /// Remediated
84    Remediated,
85    /// Overdue
86    Overdue,
87}
88
89/// Compliance alert
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct ComplianceAlert {
92    /// Alert ID
93    pub alert_id: String,
94    /// Alert type
95    pub alert_type: AlertType,
96    /// Severity
97    pub severity: GapSeverity,
98    /// Message
99    pub message: String,
100    /// Affected standard
101    pub standard: Option<ComplianceStandard>,
102    /// Control ID
103    pub control_id: Option<String>,
104    /// Created date
105    pub created_at: DateTime<Utc>,
106    /// Acknowledged date
107    pub acknowledged_at: Option<DateTime<Utc>>,
108    /// Resolved date
109    pub resolved_at: Option<DateTime<Utc>>,
110}
111
112/// Alert type
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum AlertType {
116    /// Compliance violation
117    ComplianceViolation,
118    /// Control failure
119    ControlFailure,
120    /// Remediation overdue
121    RemediationOverdue,
122    /// Audit finding
123    AuditFinding,
124}
125
126/// Control effectiveness metrics
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ControlEffectiveness {
129    /// Control category
130    pub category: ControlCategory,
131    /// Effectiveness percentage (0-100)
132    pub effectiveness: u8,
133    /// Last test date
134    pub last_test_date: Option<DateTime<Utc>>,
135    /// Test results
136    pub test_results: Option<String>,
137}
138
139/// Compliance dashboard data
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ComplianceDashboardData {
142    /// Overall compliance score (0-100)
143    pub overall_compliance: u8,
144    /// SOC 2 compliance score
145    pub soc2_compliance: u8,
146    /// ISO 27001 compliance score
147    pub iso27001_compliance: u8,
148    /// Control effectiveness by category
149    pub control_effectiveness: HashMap<ControlCategory, ControlEffectiveness>,
150    /// Gap summary
151    pub gaps: GapSummary,
152    /// Alert summary
153    pub alerts: AlertSummary,
154    /// Remediation status
155    pub remediation: RemediationStatus,
156    /// Last updated
157    pub last_updated: DateTime<Utc>,
158}
159
160/// Gap summary
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct GapSummary {
163    /// Total gaps
164    pub total: u32,
165    /// Critical gaps
166    pub critical: u32,
167    /// High gaps
168    pub high: u32,
169    /// Medium gaps
170    pub medium: u32,
171    /// Low gaps
172    pub low: u32,
173}
174
175/// Alert summary
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct AlertSummary {
178    /// Total alerts
179    pub total: u32,
180    /// Critical alerts
181    pub critical: u32,
182    /// High alerts
183    pub high: u32,
184    /// Medium alerts
185    pub medium: u32,
186    /// Low alerts
187    pub low: u32,
188}
189
190/// Remediation status
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct RemediationStatus {
193    /// In progress
194    pub in_progress: u32,
195    /// Completed this month
196    pub completed_this_month: u32,
197    /// Overdue
198    pub overdue: u32,
199}
200
201/// Compliance dashboard configuration
202#[derive(Debug, Clone, Serialize, Deserialize)]
203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
204pub struct ComplianceDashboardConfig {
205    /// Whether dashboard is enabled
206    pub enabled: bool,
207    /// Refresh interval in seconds
208    pub refresh_interval_seconds: u64,
209    /// Alert thresholds
210    pub alert_thresholds: AlertThresholds,
211}
212
213/// Alert thresholds
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
216pub struct AlertThresholds {
217    /// Minimum compliance score to trigger alert
218    pub compliance_score: u8,
219    /// Minimum control effectiveness to trigger alert
220    pub control_effectiveness: u8,
221}
222
223impl Default for ComplianceDashboardConfig {
224    fn default() -> Self {
225        Self {
226            enabled: true,
227            refresh_interval_seconds: 300, // 5 minutes
228            alert_thresholds: AlertThresholds {
229                compliance_score: 90,
230                control_effectiveness: 85,
231            },
232        }
233    }
234}
235
236/// Compliance dashboard engine
237///
238/// Aggregates data from various security systems to provide real-time
239/// compliance monitoring and reporting.
240pub struct ComplianceDashboardEngine {
241    config: ComplianceDashboardConfig,
242    /// Compliance gaps
243    gaps: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceGap>>>,
244    /// Compliance alerts
245    alerts: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceAlert>>>,
246    /// Control effectiveness cache
247    control_effectiveness:
248        std::sync::Arc<tokio::sync::RwLock<HashMap<ControlCategory, ControlEffectiveness>>>,
249}
250
251impl ComplianceDashboardEngine {
252    /// Create a new compliance dashboard engine
253    pub fn new(config: ComplianceDashboardConfig) -> Self {
254        Self {
255            config,
256            gaps: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
257            alerts: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
258            control_effectiveness: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
259        }
260    }
261
262    /// Get dashboard data
263    ///
264    /// Aggregates data from all security systems to provide comprehensive
265    /// compliance status.
266    pub async fn get_dashboard_data(&self) -> Result<ComplianceDashboardData, Error> {
267        // Calculate compliance scores
268        let soc2_compliance = self.calculate_soc2_compliance().await?;
269        let iso27001_compliance = self.calculate_iso27001_compliance().await?;
270        let overall_compliance = (soc2_compliance + iso27001_compliance) / 2;
271
272        // Get control effectiveness
273        let control_effectiveness = self.get_control_effectiveness().await?;
274
275        // Get gap summary
276        let gaps = self.get_gap_summary().await?;
277
278        // Get alert summary
279        let alerts = self.get_alert_summary().await?;
280
281        // Get remediation status
282        let remediation = self.get_remediation_status().await?;
283
284        Ok(ComplianceDashboardData {
285            overall_compliance,
286            soc2_compliance,
287            iso27001_compliance,
288            control_effectiveness,
289            gaps,
290            alerts,
291            remediation,
292            last_updated: Utc::now(),
293        })
294    }
295
296    /// Calculate SOC 2 compliance score
297    async fn calculate_soc2_compliance(&self) -> Result<u8, Error> {
298        // TODO: Integrate with actual security systems to calculate real scores
299        // For now, return a placeholder score based on implemented systems
300
301        // Check if key systems are enabled/working
302        // - SIEM integration: 20 points
303        // - Access reviews: 20 points
304        // - Privileged access: 20 points
305        // - Change management: 20 points
306        // - Security events: 20 points
307
308        // Placeholder: assume all systems are working if enabled
309        // In production, this would check actual system status
310        Ok(95) // Placeholder score
311    }
312
313    /// Calculate ISO 27001 compliance score
314    async fn calculate_iso27001_compliance(&self) -> Result<u8, Error> {
315        // TODO: Integrate with actual security systems to calculate real scores
316        // Similar to SOC 2, but with ISO 27001 control mappings
317        Ok(92) // Placeholder score
318    }
319
320    /// Get control effectiveness metrics
321    async fn get_control_effectiveness(
322        &self,
323    ) -> Result<HashMap<ControlCategory, ControlEffectiveness>, Error> {
324        let mut effectiveness = HashMap::new();
325
326        // Access Control
327        effectiveness.insert(
328            ControlCategory::AccessControl,
329            ControlEffectiveness {
330                category: ControlCategory::AccessControl,
331                effectiveness: 98, // TODO: Calculate from access review data
332                last_test_date: Some(Utc::now() - chrono::Duration::days(7)),
333                test_results: Some("All access controls tested and passing".to_string()),
334            },
335        );
336
337        // Encryption
338        effectiveness.insert(
339            ControlCategory::Encryption,
340            ControlEffectiveness {
341                category: ControlCategory::Encryption,
342                effectiveness: 100, // TODO: Calculate from encryption status
343                last_test_date: Some(Utc::now() - chrono::Duration::days(14)),
344                test_results: Some("Encryption controls verified".to_string()),
345            },
346        );
347
348        // Monitoring
349        effectiveness.insert(
350            ControlCategory::Monitoring,
351            ControlEffectiveness {
352                category: ControlCategory::Monitoring,
353                effectiveness: 95, // TODO: Calculate from SIEM status
354                last_test_date: Some(Utc::now() - chrono::Duration::days(3)),
355                test_results: Some("SIEM integration operational".to_string()),
356            },
357        );
358
359        // Change Management
360        effectiveness.insert(
361            ControlCategory::ChangeManagement,
362            ControlEffectiveness {
363                category: ControlCategory::ChangeManagement,
364                effectiveness: 90, // TODO: Calculate from change management data
365                last_test_date: Some(Utc::now() - chrono::Duration::days(10)),
366                test_results: Some("Change management process followed".to_string()),
367            },
368        );
369
370        // Incident Response
371        effectiveness.insert(
372            ControlCategory::IncidentResponse,
373            ControlEffectiveness {
374                category: ControlCategory::IncidentResponse,
375                effectiveness: 95, // TODO: Calculate from incident response data
376                last_test_date: Some(Utc::now() - chrono::Duration::days(5)),
377                test_results: Some("Incident response procedures tested".to_string()),
378            },
379        );
380
381        Ok(effectiveness)
382    }
383
384    /// Get gap summary
385    async fn get_gap_summary(&self) -> Result<GapSummary, Error> {
386        let gaps = self.gaps.read().await;
387
388        let mut summary = GapSummary {
389            total: gaps.len() as u32,
390            critical: 0,
391            high: 0,
392            medium: 0,
393            low: 0,
394        };
395
396        for gap in gaps.values() {
397            match gap.severity {
398                GapSeverity::Critical => summary.critical += 1,
399                GapSeverity::High => summary.high += 1,
400                GapSeverity::Medium => summary.medium += 1,
401                GapSeverity::Low => summary.low += 1,
402            }
403        }
404
405        Ok(summary)
406    }
407
408    /// Get alert summary
409    async fn get_alert_summary(&self) -> Result<AlertSummary, Error> {
410        let alerts = self.alerts.read().await;
411
412        let mut summary = AlertSummary {
413            total: alerts.len() as u32,
414            critical: 0,
415            high: 0,
416            medium: 0,
417            low: 0,
418        };
419
420        for alert in alerts.values() {
421            if alert.resolved_at.is_none() {
422                match alert.severity {
423                    GapSeverity::Critical => summary.critical += 1,
424                    GapSeverity::High => summary.high += 1,
425                    GapSeverity::Medium => summary.medium += 1,
426                    GapSeverity::Low => summary.low += 1,
427                }
428            }
429        }
430
431        Ok(summary)
432    }
433
434    /// Get remediation status
435    async fn get_remediation_status(&self) -> Result<RemediationStatus, Error> {
436        let gaps = self.gaps.read().await;
437        let now = Utc::now();
438        // Get start of current month - use format string approach
439        let month_start_str = format!("{}-{:02}-01T00:00:00Z", now.format("%Y"), now.format("%m"));
440        let start_of_month = DateTime::parse_from_rfc3339(&month_start_str)
441            .map(|dt| dt.with_timezone(&Utc))
442            .unwrap_or(now);
443
444        let mut status = RemediationStatus {
445            in_progress: 0,
446            completed_this_month: 0,
447            overdue: 0,
448        };
449
450        for gap in gaps.values() {
451            match gap.status {
452                GapStatus::InProgress => status.in_progress += 1,
453                GapStatus::Remediated => {
454                    if let Some(remediated_at) = gap.remediated_at {
455                        if remediated_at >= start_of_month {
456                            status.completed_this_month += 1;
457                        }
458                    }
459                }
460                GapStatus::Overdue => status.overdue += 1,
461                GapStatus::Identified => {
462                    // Check if overdue
463                    if let Some(target_date) = gap.target_remediation_date {
464                        if now > target_date {
465                            status.overdue += 1;
466                        }
467                    }
468                }
469            }
470        }
471
472        Ok(status)
473    }
474
475    /// Add a compliance gap
476    pub async fn add_gap(
477        &self,
478        gap_id: String,
479        description: String,
480        severity: GapSeverity,
481        standard: ComplianceStandard,
482        control_id: Option<String>,
483        target_remediation_date: Option<DateTime<Utc>>,
484    ) -> Result<(), Error> {
485        let mut gaps = self.gaps.write().await;
486        let gap = ComplianceGap {
487            gap_id: gap_id.clone(),
488            description,
489            severity,
490            standard,
491            control_id,
492            status: GapStatus::Identified,
493            created_at: Utc::now(),
494            target_remediation_date,
495            remediated_at: None,
496        };
497        gaps.insert(gap_id, gap);
498        Ok(())
499    }
500
501    /// Update gap status
502    pub async fn update_gap_status(&self, gap_id: &str, status: GapStatus) -> Result<(), Error> {
503        let mut gaps = self.gaps.write().await;
504        if let Some(gap) = gaps.get_mut(gap_id) {
505            gap.status = status;
506            if status == GapStatus::Remediated {
507                gap.remediated_at = Some(Utc::now());
508            }
509        } else {
510            return Err(Error::Generic("Gap not found".to_string()));
511        }
512        Ok(())
513    }
514
515    /// Add a compliance alert
516    pub async fn add_alert(
517        &self,
518        alert_id: String,
519        alert_type: AlertType,
520        severity: GapSeverity,
521        message: String,
522        standard: Option<ComplianceStandard>,
523        control_id: Option<String>,
524    ) -> Result<(), Error> {
525        let mut alerts = self.alerts.write().await;
526        let alert = ComplianceAlert {
527            alert_id: alert_id.clone(),
528            alert_type,
529            severity,
530            message,
531            standard,
532            control_id,
533            created_at: Utc::now(),
534            acknowledged_at: None,
535            resolved_at: None,
536        };
537        alerts.insert(alert_id, alert);
538        Ok(())
539    }
540
541    /// Get all gaps
542    pub async fn get_all_gaps(&self) -> Result<Vec<ComplianceGap>, Error> {
543        let gaps = self.gaps.read().await;
544        Ok(gaps.values().cloned().collect())
545    }
546
547    /// Get all alerts
548    pub async fn get_all_alerts(&self) -> Result<Vec<ComplianceAlert>, Error> {
549        let alerts = self.alerts.read().await;
550        Ok(alerts.values().cloned().collect())
551    }
552
553    /// Get gaps by severity
554    pub async fn get_gaps_by_severity(
555        &self,
556        severity: GapSeverity,
557    ) -> Result<Vec<ComplianceGap>, Error> {
558        let gaps = self.gaps.read().await;
559        Ok(gaps.values().filter(|g| g.severity == severity).cloned().collect())
560    }
561
562    /// Get alerts by severity
563    pub async fn get_alerts_by_severity(
564        &self,
565        severity: GapSeverity,
566    ) -> Result<Vec<ComplianceAlert>, Error> {
567        let alerts = self.alerts.read().await;
568        Ok(alerts
569            .values()
570            .filter(|a| a.severity == severity && a.resolved_at.is_none())
571            .cloned()
572            .collect())
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579
580    #[tokio::test]
581    async fn test_dashboard_data() {
582        let config = ComplianceDashboardConfig::default();
583        let engine = ComplianceDashboardEngine::new(config);
584
585        let dashboard = engine.get_dashboard_data().await.unwrap();
586        assert!(dashboard.overall_compliance <= 100);
587        assert!(dashboard.soc2_compliance <= 100);
588        assert!(dashboard.iso27001_compliance <= 100);
589    }
590}