450 lines
15 KiB
Rust
450 lines
15 KiB
Rust
|
|
//! 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)
|
||
|
|
}
|
||
|
|
}
|