2026-01-12 04:53:31 +00:00

617 lines
19 KiB
Rust

//! Compliance Checking Module
//!
//! Provides compliance validation for various frameworks including SOC2 and
//! HIPAA.
pub mod frameworks;
pub mod hipaa;
pub mod reports;
pub mod soc2;
use std::collections::HashMap;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::error::{policy, ControlCenterError, Result};
use crate::policies::{PolicyCategory, PolicyMetadata, PolicyRequestContext, PolicyResult};
use crate::storage::PolicyMetadata as StoragePolicyMetadata;
use crate::storage::{ComplianceCheckResult, PolicyStorage};
/// Compliance framework types
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum ComplianceFramework {
SOC2,
HIPAA,
GDPR,
ISO27001,
NIST,
#[serde(rename = "PCI_DSS")]
PciDss,
}
/// 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, PartialEq, 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::Policy(policy::PolicyError::Compliance(format!(
"Unsupported compliance framework: {:?}",
framework
)))
})?;
// Get all policies from storage
let policies = self.storage.list_policies().await?;
// Convert storage policies to compliance checker format
let policies_for_check: Vec<PolicyMetadata> = policies
.iter()
.map(|p| PolicyMetadata {
id: p.id.clone(),
name: p.name.clone(),
description: p.description.clone(),
version: p.version.clone(),
created_at: p.created_at,
updated_at: p.updated_at,
created_by: p.created_by.clone(),
tags: p.tags.clone(),
category: parse_category(&p.category),
priority: p.priority,
enabled: p.enabled,
})
.collect();
// Run compliance check
let result = checker
.check_compliance(policies_for_check.as_slice(), 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.keys() {
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::Policy(policy::PolicyError::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.values() {
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::Policy(policy::PolicyError::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::Policy(policy::PolicyError::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.clone());
// 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 (compliance_changes, new_violations, resolved_violations) =
if let Some(old_results) = old_controls {
self.count_control_changes(new_controls, old_results)
} else {
(0, 0, 0)
};
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(),
}
}
/// Count control compliance changes
fn count_control_changes(
&self,
new_controls: &[ControlResult],
old_controls: &[ControlResult],
) -> (usize, usize, usize) {
let mut compliance_changes = 0;
let mut new_violations = 0;
let mut resolved_violations = 0;
for new_control in new_controls {
let old_control = old_controls
.iter()
.find(|c| c.control_id == new_control.control_id);
if let Some(old) = old_control {
match (old.compliant, new_control.compliant) {
(true, false) => {
compliance_changes += 1;
new_violations += 1;
}
(false, true) => {
compliance_changes += 1;
resolved_violations += 1;
}
_ => {}
}
}
}
(compliance_changes, new_violations, resolved_violations)
}
}
/// Helper function to parse category string to PolicyCategory enum
fn parse_category(category: &str) -> PolicyCategory {
match category {
"Authentication" => PolicyCategory::Authentication,
"Authorization" => PolicyCategory::Authorization,
"Infrastructure" => PolicyCategory::Infrastructure,
"Workflow" => PolicyCategory::Workflow,
"Compliance" => PolicyCategory::Compliance,
"Security" => PolicyCategory::Security,
"Anomaly" => PolicyCategory::Anomaly,
_ => PolicyCategory::Security,
}
}
/// 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::PciDss => 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)
}
}
impl std::fmt::Display for ViolationSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ViolationSeverity::Informational => write!(f, "Informational"),
ViolationSeverity::Low => write!(f, "Low"),
ViolationSeverity::Medium => write!(f, "Medium"),
ViolationSeverity::High => write!(f, "High"),
ViolationSeverity::Critical => write!(f, "Critical"),
}
}
}
impl std::fmt::Display for RecommendationPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RecommendationPriority::Low => write!(f, "Low"),
RecommendationPriority::Medium => write!(f, "Medium"),
RecommendationPriority::High => write!(f, "High"),
RecommendationPriority::Urgent => write!(f, "Urgent"),
}
}
}
impl std::fmt::Display for EffortLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EffortLevel::Minimal => write!(f, "Minimal"),
EffortLevel::Low => write!(f, "Low"),
EffortLevel::Medium => write!(f, "Medium"),
EffortLevel::High => write!(f, "High"),
EffortLevel::Extensive => write!(f, "Extensive"),
}
}
}
impl std::fmt::Display for ImpactLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImpactLevel::Low => write!(f, "Low"),
ImpactLevel::Medium => write!(f, "Medium"),
ImpactLevel::High => write!(f, "High"),
ImpactLevel::Transformational => write!(f, "Transformational"),
}
}
}