//! Policy Checker Module //! //! Provides automated security policy checking and enforcement //! for compliance with organizational and regulatory requirements. use anyhow::{anyhow, Result}; use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use uuid::Uuid; /// Policy type enumeration #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum PolicyType { AccessControl, DataRetention, PasswordStrength, SessionTimeout, EncryptionRequired, AuditLogging, BackupFrequency, NetworkSecurity, ComplianceStandard, } /// Policy status #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum PolicyStatus { Active, Draft, Deprecated, Archived, } /// Policy severity #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum PolicySeverity { Low, Medium, High, Critical, } /// Security policy definition #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SecurityPolicy { pub id: Uuid, pub name: String, pub description: String, pub policy_type: PolicyType, pub status: PolicyStatus, pub severity: PolicySeverity, pub rules: Vec, pub created_at: DateTime, pub updated_at: DateTime, pub effective_date: DateTime, pub expiry_date: Option>, pub tags: Vec, } /// Policy rule #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyRule { pub id: Uuid, pub name: String, pub condition: String, pub action: PolicyAction, pub parameters: HashMap, } /// Policy action #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PolicyAction { Allow, Deny, Alert, Enforce, Log, } /// Policy violation #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyViolation { pub id: Uuid, pub policy_id: Uuid, pub rule_id: Uuid, pub timestamp: DateTime, pub user_id: Option, pub resource: String, pub action_attempted: String, pub violation_details: String, pub severity: PolicySeverity, pub resolved: bool, } /// Policy check result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyCheckResult { pub policy_id: Uuid, pub passed: bool, pub violations: Vec, pub warnings: Vec, pub timestamp: DateTime, } /// Policy checker service #[derive(Debug, Clone)] pub struct PolicyChecker { policies: HashMap, violations: Vec, check_history: Vec, } impl PolicyChecker { /// Create new policy checker pub fn new() -> Self { let mut checker = Self { policies: HashMap::new(), violations: Vec::new(), check_history: Vec::new(), }; // Initialize with default policies checker.initialize_default_policies(); checker } /// Initialize default security policies fn initialize_default_policies(&mut self) { // Password policy let password_policy = SecurityPolicy { id: Uuid::new_v4(), name: "Password Strength Policy".to_string(), description: "Enforces strong password requirements".to_string(), policy_type: PolicyType::PasswordStrength, status: PolicyStatus::Active, severity: PolicySeverity::High, rules: vec![ PolicyRule { id: Uuid::new_v4(), name: "Minimum Length".to_string(), condition: "password.length >= 12".to_string(), action: PolicyAction::Enforce, parameters: HashMap::from([("min_length".to_string(), "12".to_string())]), }, PolicyRule { id: Uuid::new_v4(), name: "Complexity Requirements".to_string(), condition: "has_uppercase && has_lowercase && has_digit && has_special" .to_string(), action: PolicyAction::Enforce, parameters: HashMap::new(), }, ], created_at: Utc::now(), updated_at: Utc::now(), effective_date: Utc::now(), expiry_date: None, tags: vec!["security".to_string(), "authentication".to_string()], }; self.policies.insert(password_policy.id, password_policy); // Session timeout policy let session_policy = SecurityPolicy { id: Uuid::new_v4(), name: "Session Timeout Policy".to_string(), description: "Enforces session timeout limits".to_string(), policy_type: PolicyType::SessionTimeout, status: PolicyStatus::Active, severity: PolicySeverity::Medium, rules: vec![PolicyRule { id: Uuid::new_v4(), name: "Maximum Session Duration".to_string(), condition: "session.duration <= 8_hours".to_string(), action: PolicyAction::Enforce, parameters: HashMap::from([ ("max_duration".to_string(), "28800".to_string()), // 8 hours in seconds ]), }], created_at: Utc::now(), updated_at: Utc::now(), effective_date: Utc::now(), expiry_date: None, tags: vec!["security".to_string(), "session".to_string()], }; self.policies.insert(session_policy.id, session_policy); } /// Add a security policy pub fn add_policy(&mut self, policy: SecurityPolicy) -> Result<()> { if self.policies.contains_key(&policy.id) { return Err(anyhow!("Policy already exists")); } log::info!("Adding security policy: {}", policy.name); self.policies.insert(policy.id, policy); Ok(()) } /// Update a security policy pub fn update_policy(&mut self, policy_id: Uuid, updates: SecurityPolicy) -> Result<()> { if let Some(existing) = self.policies.get_mut(&policy_id) { *existing = updates; existing.updated_at = Utc::now(); log::info!("Updated policy: {}", existing.name); Ok(()) } else { Err(anyhow!("Policy not found")) } } /// Check password against policy pub fn check_password_policy(&mut self, password: &str) -> PolicyCheckResult { let policy = self .policies .values() .find(|p| { p.policy_type == PolicyType::PasswordStrength && p.status == PolicyStatus::Active }) .cloned(); if let Some(policy) = policy { let mut violations = Vec::new(); let mut warnings = Vec::new(); // Check minimum length if password.len() < 12 { violations.push(PolicyViolation { id: Uuid::new_v4(), policy_id: policy.id, rule_id: policy.rules[0].id, timestamp: Utc::now(), user_id: None, resource: "password".to_string(), action_attempted: "set_password".to_string(), violation_details: format!( "Password length {} is less than required 12", password.len() ), severity: PolicySeverity::High, resolved: false, }); } // Check complexity let has_uppercase = password.chars().any(|c| c.is_uppercase()); let has_lowercase = password.chars().any(|c| c.is_lowercase()); let has_digit = password.chars().any(|c| c.is_numeric()); let has_special = password.chars().any(|c| !c.is_alphanumeric()); if !(has_uppercase && has_lowercase && has_digit && has_special) { violations.push(PolicyViolation { id: Uuid::new_v4(), policy_id: policy.id, rule_id: policy.rules[1].id, timestamp: Utc::now(), user_id: None, resource: "password".to_string(), action_attempted: "set_password".to_string(), violation_details: "Password does not meet complexity requirements".to_string(), severity: PolicySeverity::High, resolved: false, }); } // Add warnings for common patterns if password.to_lowercase().contains("password") { warnings.push("Password contains the word 'password'".to_string()); } let result = PolicyCheckResult { policy_id: policy.id, passed: violations.is_empty(), violations: violations.clone(), warnings, timestamp: Utc::now(), }; self.violations.extend(violations); self.check_history.push(result.clone()); result } else { PolicyCheckResult { policy_id: Uuid::nil(), passed: true, violations: Vec::new(), warnings: vec!["No password policy configured".to_string()], timestamp: Utc::now(), } } } /// Check session against policy pub fn check_session_policy(&mut self, session_duration_seconds: u64) -> PolicyCheckResult { let policy = self .policies .values() .find(|p| { p.policy_type == PolicyType::SessionTimeout && p.status == PolicyStatus::Active }) .cloned(); if let Some(policy) = policy { let mut violations = Vec::new(); if session_duration_seconds > 28800 { // 8 hours violations.push(PolicyViolation { id: Uuid::new_v4(), policy_id: policy.id, rule_id: policy.rules[0].id, timestamp: Utc::now(), user_id: None, resource: "session".to_string(), action_attempted: "extend_session".to_string(), violation_details: format!( "Session duration {} seconds exceeds maximum 28800 seconds", session_duration_seconds ), severity: PolicySeverity::Medium, resolved: false, }); } let result = PolicyCheckResult { policy_id: policy.id, passed: violations.is_empty(), violations: violations.clone(), warnings: Vec::new(), timestamp: Utc::now(), }; self.violations.extend(violations); self.check_history.push(result.clone()); result } else { PolicyCheckResult { policy_id: Uuid::nil(), passed: true, violations: Vec::new(), warnings: vec!["No session policy configured".to_string()], timestamp: Utc::now(), } } } /// Check all active policies pub fn check_all_policies(&mut self, context: &PolicyContext) -> Vec { let mut results = Vec::new(); for policy in self.policies.values() { if policy.status != PolicyStatus::Active { continue; } let result = self.check_policy(policy.id, context); if let Ok(result) = result { results.push(result); } } results } /// Check a specific policy pub fn check_policy( &mut self, policy_id: Uuid, context: &PolicyContext, ) -> Result { let policy = self .policies .get(&policy_id) .ok_or_else(|| anyhow!("Policy not found"))? .clone(); let mut violations = Vec::new(); let mut warnings = Vec::new(); for rule in &policy.rules { if !self.evaluate_rule(rule, context) { violations.push(PolicyViolation { id: Uuid::new_v4(), policy_id: policy.id, rule_id: rule.id, timestamp: Utc::now(), user_id: context.user_id, resource: context.resource.clone(), action_attempted: context.action.clone(), violation_details: format!("Rule '{}' failed", rule.name), severity: policy.severity.clone(), resolved: false, }); } } let result = PolicyCheckResult { policy_id: policy.id, passed: violations.is_empty(), violations: violations.clone(), warnings, timestamp: Utc::now(), }; self.violations.extend(violations); self.check_history.push(result.clone()); Ok(result) } /// Evaluate a policy rule fn evaluate_rule(&self, rule: &PolicyRule, _context: &PolicyContext) -> bool { // Simplified rule evaluation // In production, this would use a proper expression evaluator match rule.action { PolicyAction::Allow => true, PolicyAction::Deny => false, _ => true, // For Alert, Enforce, Log actions, consider as passed but take action } } /// Get policy violations pub fn get_violations(&self, unresolved_only: bool) -> Vec { if unresolved_only { self.violations .iter() .filter(|v| !v.resolved) .cloned() .collect() } else { self.violations.clone() } } /// Resolve a violation pub fn resolve_violation(&mut self, violation_id: Uuid) -> Result<()> { if let Some(violation) = self.violations.iter_mut().find(|v| v.id == violation_id) { violation.resolved = true; log::info!("Resolved violation: {}", violation_id); Ok(()) } else { Err(anyhow!("Violation not found")) } } /// Get policy compliance report pub fn get_compliance_report(&self) -> PolicyComplianceReport { let total_policies = self.policies.len(); let active_policies = self .policies .values() .filter(|p| p.status == PolicyStatus::Active) .count(); let total_violations = self.violations.len(); let unresolved_violations = self.violations.iter().filter(|v| !v.resolved).count(); let critical_violations = self .violations .iter() .filter(|v| v.severity == PolicySeverity::Critical) .count(); let recent_checks = self .check_history .iter() .filter(|c| c.timestamp > Utc::now() - Duration::days(7)) .count(); let compliance_rate = if !self.check_history.is_empty() { let passed = self.check_history.iter().filter(|c| c.passed).count(); (passed as f64 / self.check_history.len() as f64) * 100.0 } else { 100.0 }; PolicyComplianceReport { generated_at: Utc::now(), total_policies, active_policies, total_violations, unresolved_violations, critical_violations, recent_checks, compliance_rate, } } } /// Policy context for evaluation #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyContext { pub user_id: Option, pub resource: String, pub action: String, pub parameters: HashMap, } /// Policy compliance report #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyComplianceReport { pub generated_at: DateTime, pub total_policies: usize, pub active_policies: usize, pub total_violations: usize, pub unresolved_violations: usize, pub critical_violations: usize, pub recent_checks: usize, pub compliance_rate: f64, } impl Default for PolicyChecker { fn default() -> Self { Self::new() } }