450 lines
15 KiB
Rust
Raw Normal View History

2025-10-07 10:59:52 +01:00
//! Compliance Checking Module
//!
//! Provides compliance validation for various frameworks including SOC2 and HIPAA.
pub mod soc2;
pub mod hipaa;
pub mod frameworks;
pub mod reports;
use crate::error::{ControlCenterError, Result};
use crate::policies::{PolicyMetadata, PolicyRequestContext, PolicyResult};
use crate::storage::{PolicyStorage, ComplianceCheckResult};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use chrono::{DateTime, Utc};
/// Compliance framework types
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ComplianceFramework {
SOC2,
HIPAA,
GDPR,
ISO27001,
NIST,
PCI_DSS,
}
/// Compliance check result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceResult {
pub framework: ComplianceFramework,
pub overall_compliant: bool,
pub control_results: Vec<ControlResult>,
pub violations: Vec<ComplianceViolation>,
pub recommendations: Vec<ComplianceRecommendation>,
pub score: f64, // 0.0 to 100.0
pub timestamp: DateTime<Utc>,
pub next_review_date: DateTime<Utc>,
}
/// Individual control check result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlResult {
pub control_id: String,
pub control_name: String,
pub category: String,
pub compliant: bool,
pub evidence: Vec<String>,
pub gaps: Vec<String>,
pub risk_rating: RiskRating,
pub remediation_timeline: Option<String>,
}
/// Compliance violation details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceViolation {
pub violation_id: String,
pub control_id: String,
pub severity: ViolationSeverity,
pub description: String,
pub policy_id: Option<String>,
pub resource_id: Option<String>,
pub detected_at: DateTime<Utc>,
pub remediation_required: bool,
pub remediation_deadline: Option<DateTime<Utc>>,
}
/// Compliance recommendation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceRecommendation {
pub recommendation_id: String,
pub control_id: String,
pub priority: RecommendationPriority,
pub title: String,
pub description: String,
pub implementation_effort: EffortLevel,
pub expected_impact: ImpactLevel,
pub resources_required: Vec<String>,
}
/// Risk rating levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskRating {
Low,
Medium,
High,
Critical,
}
/// Violation severity levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ViolationSeverity {
Informational,
Low,
Medium,
High,
Critical,
}
/// Recommendation priority levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RecommendationPriority {
Low,
Medium,
High,
Urgent,
}
/// Implementation effort levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EffortLevel {
Minimal,
Low,
Medium,
High,
Extensive,
}
/// Expected impact levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ImpactLevel {
Low,
Medium,
High,
Transformational,
}
/// Main compliance checker
pub struct ComplianceChecker {
storage: Arc<dyn PolicyStorage>,
frameworks: HashMap<ComplianceFramework, Box<dyn ComplianceFrameworkChecker>>,
}
/// Trait for compliance framework checkers
#[async_trait::async_trait]
pub trait ComplianceFrameworkChecker: Send + Sync {
/// Get framework name
fn framework_name(&self) -> &str;
/// Check compliance for the framework
async fn check_compliance(&self, policies: &[PolicyMetadata], storage: Arc<dyn PolicyStorage>) -> Result<ComplianceResult>;
/// Validate policy against framework requirements
async fn validate_policy(&self, policy_content: &str, metadata: &PolicyMetadata) -> Result<Vec<ControlResult>>;
/// Get required controls for this framework
fn get_required_controls(&self) -> Vec<ComplianceControl>;
/// Generate compliance report
async fn generate_report(&self, result: &ComplianceResult) -> Result<String>;
}
/// Compliance control definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceControl {
pub control_id: String,
pub control_name: String,
pub category: String,
pub description: String,
pub required: bool,
pub policy_patterns: Vec<String>, // Regex patterns to match in policies
pub evidence_requirements: Vec<String>,
pub test_procedures: Vec<String>,
}
impl ComplianceChecker {
/// Create new compliance checker
pub async fn new(storage: Arc<dyn PolicyStorage>) -> Result<Self> {
let mut checker = Self {
storage: storage.clone(),
frameworks: HashMap::new(),
};
// Register compliance frameworks
checker.frameworks.insert(
ComplianceFramework::SOC2,
Box::new(soc2::SOC2Checker::new()),
);
checker.frameworks.insert(
ComplianceFramework::HIPAA,
Box::new(hipaa::HIPAAChecker::new()),
);
Ok(checker)
}
/// Check compliance for specific framework
pub async fn check_framework_compliance(&self, framework: &ComplianceFramework) -> Result<ComplianceResult> {
let checker = self.frameworks.get(framework)
.ok_or_else(|| ControlCenterError::Compliance(
format!("Unsupported compliance framework: {:?}", framework)
))?;
// Get all policies from storage
let policies = self.storage.list_policies().await?;
// Run compliance check
let result = checker.check_compliance(&policies, self.storage.clone()).await?;
// Store result
let check_result = ComplianceCheckResult {
id: uuid::Uuid::new_v4().to_string(),
framework: framework.to_string(),
check_type: "full_assessment".to_string(),
policy_id: None,
compliant: result.overall_compliant,
violations: result.violations.iter().map(|v| v.description.clone()).collect(),
recommendations: result.recommendations.iter().map(|r| r.description.clone()).collect(),
timestamp: Utc::now(),
metadata: serde_json::to_value(&result)?,
};
self.storage.store_compliance_check(&check_result).await?;
Ok(result)
}
/// Check compliance for all supported frameworks
pub async fn check_all_compliance(&self) -> Result<HashMap<ComplianceFramework, ComplianceResult>> {
let mut results = HashMap::new();
for (framework, _) in &self.frameworks {
match self.check_framework_compliance(framework).await {
Ok(result) => {
results.insert(framework.clone(), result);
}
Err(e) => {
tracing::error!("Failed to check compliance for {:?}: {}", framework, e);
}
}
}
Ok(results)
}
/// Validate single policy against framework
pub async fn validate_policy_compliance(&self, framework: &ComplianceFramework, policy_content: &str, metadata: &PolicyMetadata) -> Result<Vec<ControlResult>> {
let checker = self.frameworks.get(framework)
.ok_or_else(|| ControlCenterError::Compliance(
format!("Unsupported compliance framework: {:?}", framework)
))?;
checker.validate_policy(policy_content, metadata).await
}
/// Get compliance summary across all frameworks
pub async fn get_compliance_summary(&self) -> Result<ComplianceSummary> {
let all_results = self.check_all_compliance().await?;
let mut total_controls = 0;
let mut compliant_controls = 0;
let mut total_violations = 0;
let mut critical_violations = 0;
for (_, result) in &all_results {
total_controls += result.control_results.len();
compliant_controls += result.control_results.iter().filter(|c| c.compliant).count();
total_violations += result.violations.len();
critical_violations += result.violations.iter()
.filter(|v| matches!(v.severity, ViolationSeverity::Critical))
.count();
}
let overall_compliance_rate = if total_controls > 0 {
(compliant_controls as f64 / total_controls as f64) * 100.0
} else {
0.0
};
Ok(ComplianceSummary {
overall_compliance_rate,
framework_results: all_results,
total_controls,
compliant_controls,
total_violations,
critical_violations,
last_assessment: Utc::now(),
next_assessment: Utc::now() + chrono::Duration::days(90), // Quarterly
})
}
/// Generate compliance report for framework
pub async fn generate_compliance_report(&self, framework: &ComplianceFramework, format: &str) -> Result<String> {
let result = self.check_framework_compliance(framework).await?;
let checker = self.frameworks.get(framework)
.ok_or_else(|| ControlCenterError::Compliance(
format!("Unsupported compliance framework: {:?}", framework)
))?;
match format.to_lowercase().as_str() {
"json" => Ok(serde_json::to_string_pretty(&result)?),
"html" | "pdf" => checker.generate_report(&result).await,
_ => Err(ControlCenterError::Compliance(
format!("Unsupported report format: {}", format)
)),
}
}
/// Monitor policy changes for compliance impact
pub async fn assess_policy_change_impact(&self, old_policy: Option<&str>, new_policy: &str, metadata: &PolicyMetadata) -> Result<ComplianceImpactAssessment> {
let mut impact = ComplianceImpactAssessment {
policy_id: metadata.id.clone(),
change_type: if old_policy.is_some() { "modification" } else { "creation" }.to_string(),
framework_impacts: HashMap::new(),
overall_risk_level: RiskRating::Low,
requires_review: false,
reviewer_notifications: Vec::new(),
};
for (framework, checker) in &self.frameworks {
let control_results = checker.validate_policy(new_policy, metadata).await?;
let old_control_results = if let Some(old_content) = old_policy {
checker.validate_policy(old_content, metadata).await.ok()
} else {
None
};
let framework_impact = self.calculate_framework_impact(&control_results, old_control_results.as_ref());
impact.framework_impacts.insert(framework.clone(), framework_impact);
// Update overall risk level
if framework_impact.risk_level > impact.overall_risk_level {
impact.overall_risk_level = framework_impact.risk_level.clone();
}
// Determine if review is required
if framework_impact.compliance_changes > 0 || matches!(framework_impact.risk_level, RiskRating::High | RiskRating::Critical) {
impact.requires_review = true;
}
}
Ok(impact)
}
/// Calculate framework-specific impact
fn calculate_framework_impact(&self, new_controls: &[ControlResult], old_controls: Option<&Vec<ControlResult>>) -> FrameworkImpactAssessment {
let mut compliance_changes = 0;
let mut new_violations = 0;
let mut resolved_violations = 0;
if let Some(old_results) = old_controls {
// Compare control compliance status
for new_control in new_controls {
if let Some(old_control) = old_results.iter().find(|c| c.control_id == new_control.control_id) {
match (old_control.compliant, new_control.compliant) {
(true, false) => {
compliance_changes += 1;
new_violations += 1;
}
(false, true) => {
compliance_changes += 1;
resolved_violations += 1;
}
_ => {}
}
}
}
}
let risk_level = if new_violations > 0 {
RiskRating::High
} else if compliance_changes > 0 {
RiskRating::Medium
} else {
RiskRating::Low
};
FrameworkImpactAssessment {
compliance_changes,
new_violations,
resolved_violations,
risk_level,
affected_controls: new_controls.iter()
.filter(|c| !c.compliant)
.map(|c| c.control_id.clone())
.collect(),
}
}
}
/// Compliance summary across all frameworks
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceSummary {
pub overall_compliance_rate: f64,
pub framework_results: HashMap<ComplianceFramework, ComplianceResult>,
pub total_controls: usize,
pub compliant_controls: usize,
pub total_violations: usize,
pub critical_violations: usize,
pub last_assessment: DateTime<Utc>,
pub next_assessment: DateTime<Utc>,
}
/// Policy change compliance impact assessment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceImpactAssessment {
pub policy_id: String,
pub change_type: String,
pub framework_impacts: HashMap<ComplianceFramework, FrameworkImpactAssessment>,
pub overall_risk_level: RiskRating,
pub requires_review: bool,
pub reviewer_notifications: Vec<String>,
}
/// Framework-specific impact assessment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameworkImpactAssessment {
pub compliance_changes: usize,
pub new_violations: usize,
pub resolved_violations: usize,
pub risk_level: RiskRating,
pub affected_controls: Vec<String>,
}
// Helper implementations
impl std::fmt::Display for ComplianceFramework {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ComplianceFramework::SOC2 => write!(f, "SOC2"),
ComplianceFramework::HIPAA => write!(f, "HIPAA"),
ComplianceFramework::GDPR => write!(f, "GDPR"),
ComplianceFramework::ISO27001 => write!(f, "ISO27001"),
ComplianceFramework::NIST => write!(f, "NIST"),
ComplianceFramework::PCI_DSS => write!(f, "PCI_DSS"),
}
}
}
impl PartialOrd for RiskRating {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let self_val = match self {
RiskRating::Low => 1,
RiskRating::Medium => 2,
RiskRating::High => 3,
RiskRating::Critical => 4,
};
let other_val = match other {
RiskRating::Low => 1,
RiskRating::Medium => 2,
RiskRating::High => 3,
RiskRating::Critical => 4,
};
self_val.partial_cmp(&other_val)
}
}