617 lines
19 KiB
Rust
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"),
|
|
}
|
|
}
|
|
}
|