//! Policy Validation System //! //! Validates Cedar policies for syntax, semantics, and compliance. use std::collections::HashMap; use std::path::Path; use cedar_policy::{Policy, PolicyId, PolicySet}; use serde::{Deserialize, Serialize}; use crate::config::PolicyConfig; use crate::error::{http, policy, ControlCenterError, Result}; /// Policy validation results #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValidationResult { pub valid: bool, pub errors: Vec, pub warnings: Vec, pub suggestions: Vec, pub metrics: ValidationMetrics, } /// Validation error details #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValidationError { pub error_type: ValidationErrorType, pub message: String, pub line: Option, pub column: Option, pub policy_id: Option, } /// Types of validation errors #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ValidationErrorType { SyntaxError, SemanticError, ComplianceViolation, SecurityIssue, PerformanceIssue, } /// Validation warning details #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValidationWarning { pub warning_type: ValidationWarningType, pub message: String, pub policy_id: Option, pub severity: WarningSeverity, } /// Types of validation warnings #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ValidationWarningType { DeprecatedSyntax, PerformanceImpact, SecurityBestPractice, MaintenanceIssue, } /// Warning severity levels #[derive(Debug, Clone, Serialize, Deserialize)] pub enum WarningSeverity { Low, Medium, High, } /// Validation suggestions for improvement #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValidationSuggestion { pub suggestion_type: SuggestionType, pub message: String, pub proposed_change: Option, pub policy_id: Option, } /// Types of validation suggestions #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SuggestionType { Optimization, Security, Readability, Maintenance, } /// Validation metrics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValidationMetrics { pub total_policies: usize, pub valid_policies: usize, pub invalid_policies: usize, pub total_lines: usize, pub complexity_score: f64, pub validation_time_ms: u64, } /// Policy validator pub struct PolicyValidator { config: PolicyConfig, compliance_rules: HashMap>, security_patterns: Vec, } /// Compliance rule definition #[derive(Debug, Clone)] pub struct ComplianceRule { pub id: String, pub name: String, pub description: String, pub required: bool, pub pattern: String, pub compliance_framework: String, } /// Security pattern for validation #[derive(Debug, Clone)] pub struct SecurityPattern { pub id: String, pub name: String, pub pattern: String, pub severity: WarningSeverity, pub description: String, } impl PolicyValidator { /// Create new policy validator pub fn new(config: &PolicyConfig) -> Self { let mut validator = Self { config: config.clone(), compliance_rules: HashMap::new(), security_patterns: Vec::new(), }; validator.initialize_compliance_rules(); validator.initialize_security_patterns(); validator } /// Initialize compliance rules for various frameworks fn initialize_compliance_rules(&mut self) { // SOC2 compliance rules let soc2_rules = vec![ ComplianceRule { id: "soc2-access-control".to_string(), name: "Access Control Requirements".to_string(), description: "Policies must implement proper access controls".to_string(), required: true, pattern: r#"permit.*when.*has.*"role""#.to_string(), compliance_framework: "SOC2".to_string(), }, ComplianceRule { id: "soc2-audit-logging".to_string(), name: "Audit Logging Requirements".to_string(), description: "All access decisions must be logged".to_string(), required: true, pattern: r#"@audit.*true"#.to_string(), compliance_framework: "SOC2".to_string(), }, ]; // HIPAA compliance rules let hipaa_rules = vec![ ComplianceRule { id: "hipaa-minimum-necessary".to_string(), name: "Minimum Necessary Access".to_string(), description: "Access must be limited to minimum necessary".to_string(), required: true, pattern: r#"permit.*when.*has.*"data_classification".*"phi""#.to_string(), compliance_framework: "HIPAA".to_string(), }, ComplianceRule { id: "hipaa-authorization-tracking".to_string(), name: "Authorization Tracking".to_string(), description: "All PHI access must be tracked".to_string(), required: true, pattern: r#"@track.*"phi_access""#.to_string(), compliance_framework: "HIPAA".to_string(), }, ]; self.compliance_rules.insert("SOC2".to_string(), soc2_rules); self.compliance_rules .insert("HIPAA".to_string(), hipaa_rules); } /// Initialize security patterns for validation fn initialize_security_patterns(&mut self) { self.security_patterns = vec![ SecurityPattern { id: "overly-permissive".to_string(), name: "Overly Permissive Policy".to_string(), pattern: r#"permit\s*\(\s*\*\s*,\s*\*\s*,\s*\*\s*\)"#.to_string(), severity: WarningSeverity::High, description: "Policy allows all principals, actions, and resources".to_string(), }, SecurityPattern { id: "missing-time-constraints".to_string(), name: "Missing Time Constraints".to_string(), pattern: r#"permit.*when.*!.*time"#.to_string(), severity: WarningSeverity::Medium, description: "Consider adding time-based constraints for security".to_string(), }, SecurityPattern { id: "hardcoded-credentials".to_string(), name: "Hardcoded Credentials".to_string(), pattern: r#"(password|secret|key|token)\s*==\s*"[^"]*""#.to_string(), severity: WarningSeverity::High, description: "Avoid hardcoding credentials in policies".to_string(), }, ]; } /// Validate policy content pub fn validate_policy_content(&self, content: &str) -> Result { let start_time = std::time::Instant::now(); let mut result = ValidationResult { valid: true, errors: Vec::new(), warnings: Vec::new(), suggestions: Vec::new(), metrics: ValidationMetrics { total_policies: 1, valid_policies: 0, invalid_policies: 0, total_lines: content.lines().count(), complexity_score: 0.0, validation_time_ms: 0, }, }; // Parse policy for syntax validation let policy_id = PolicyId::new("validation_check"); match Policy::parse(Some(policy_id), content) { Ok(policy) => { result.metrics.valid_policies = 1; // Run semantic validation self.validate_semantics(&policy, &mut result); // Run security validation self.validate_security(content, &mut result); // Run compliance validation self.validate_compliance(content, &mut result); // Calculate complexity score result.metrics.complexity_score = self.calculate_complexity_score(content); } Err(parse_error) => { result.valid = false; result.metrics.invalid_policies = 1; result.errors.push(ValidationError { error_type: ValidationErrorType::SyntaxError, message: parse_error.to_string(), line: None, // Cedar doesn't provide line numbers in parse errors column: None, policy_id: Some("validation_check".to_string()), }); } } result.metrics.validation_time_ms = start_time.elapsed().as_millis() as u64; Ok(result) } /// Validate directory of policies pub fn validate_directory(&self, dir: &Path) -> Result { let start_time = std::time::Instant::now(); let mut combined_result = ValidationResult { valid: true, errors: Vec::new(), warnings: Vec::new(), suggestions: Vec::new(), metrics: ValidationMetrics { total_policies: 0, valid_policies: 0, invalid_policies: 0, total_lines: 0, complexity_score: 0.0, validation_time_ms: 0, }, }; for entry in std::fs::read_dir(dir)? { let entry = entry?; let path = entry.path(); if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("cedar") { let content = std::fs::read_to_string(&path)?; let file_result = self.validate_policy_content(&content)?; // Merge results combined_result.valid &= file_result.valid; combined_result.errors.extend(file_result.errors); combined_result.warnings.extend(file_result.warnings); combined_result.suggestions.extend(file_result.suggestions); combined_result.metrics.total_policies += file_result.metrics.total_policies; combined_result.metrics.valid_policies += file_result.metrics.valid_policies; combined_result.metrics.invalid_policies += file_result.metrics.invalid_policies; combined_result.metrics.total_lines += file_result.metrics.total_lines; combined_result.metrics.complexity_score += file_result.metrics.complexity_score; } } // Average complexity score if combined_result.metrics.total_policies > 0 { combined_result.metrics.complexity_score /= combined_result.metrics.total_policies as f64; } combined_result.metrics.validation_time_ms = start_time.elapsed().as_millis() as u64; Ok(combined_result) } /// Validate policy semantics fn validate_semantics(&self, _policy: &Policy, result: &mut ValidationResult) { // Implementation would check for semantic issues // For now, just add placeholder validations // Check for common semantic issues result.warnings.push(ValidationWarning { warning_type: ValidationWarningType::MaintenanceIssue, message: "Consider adding comments for complex conditions".to_string(), policy_id: Some("semantic_check".to_string()), severity: WarningSeverity::Low, }); } /// Validate security patterns fn validate_security(&self, content: &str, result: &mut ValidationResult) { use regex::Regex; for pattern in &self.security_patterns { if let Ok(regex) = Regex::new(&pattern.pattern) { if regex.is_match(content) { result.warnings.push(ValidationWarning { warning_type: ValidationWarningType::SecurityBestPractice, message: format!("{}: {}", pattern.name, pattern.description), policy_id: Some(pattern.id.clone()), severity: pattern.severity.clone(), }); } } } } /// Validate compliance requirements fn validate_compliance(&self, content: &str, result: &mut ValidationResult) { use regex::Regex; // Check enabled compliance frameworks for (framework, rules) in &self.compliance_rules { for rule in rules { let Ok(regex) = Regex::new(&rule.pattern) else { continue; }; if rule.required && !regex.is_match(content) { result.errors.push(ValidationError { error_type: ValidationErrorType::ComplianceViolation, message: format!("{} violation: {}", framework, rule.description), line: None, column: None, policy_id: Some(rule.id.clone()), }); result.valid = false; } } } } /// Calculate complexity score for policy fn calculate_complexity_score(&self, content: &str) -> f64 { let lines = content.lines().count() as f64; let conditions = content.matches("when").count() as f64; let operators = content.matches("&&").count() + content.matches("||").count(); let nested_levels = self.count_nested_levels(content) as f64; // Simple complexity calculation (lines * 0.1) + (conditions * 0.5) + (operators as f64 * 0.3) + (nested_levels * 0.8) } /// Count nested levels in policy content fn count_nested_levels(&self, content: &str) -> usize { let mut max_depth = 0usize; let mut current_depth = 0usize; for char in content.chars() { match char { '(' | '{' => { current_depth += 1; max_depth = max_depth.max(current_depth); } ')' | '}' => { current_depth = current_depth.saturating_sub(1); } _ => {} } } max_depth } /// Generate validation report pub fn generate_report(&self, result: &ValidationResult, format: &str) -> Result { match format.to_lowercase().as_str() { "json" => Ok(serde_json::to_string_pretty(result)?), "text" => self.generate_text_report(result), "html" => self.generate_html_report(result), _ => Err(ControlCenterError::Http(http::HttpError::Validation( format!("Unsupported report format: {}", format), ))), } } /// Generate text report fn generate_text_report(&self, result: &ValidationResult) -> Result { let mut report = String::new(); report.push_str("Policy Validation Report\n"); report.push_str("========================\n\n"); report.push_str(&format!( "Status: {}\n", if result.valid { "VALID" } else { "INVALID" } )); report.push_str(&format!( "Total Policies: {}\n", result.metrics.total_policies )); report.push_str(&format!( "Valid Policies: {}\n", result.metrics.valid_policies )); report.push_str(&format!( "Invalid Policies: {}\n", result.metrics.invalid_policies )); report.push_str(&format!( "Complexity Score: {:.2}\n", result.metrics.complexity_score )); report.push_str(&format!( "Validation Time: {}ms\n\n", result.metrics.validation_time_ms )); if !result.errors.is_empty() { report.push_str("ERRORS:\n"); for error in &result.errors { report.push_str(&format!( " - {}: {}\n", error.error_type, error.message )); } report.push('\n'); } if !result.warnings.is_empty() { report.push_str("WARNINGS:\n"); for warning in &result.warnings { report.push_str(&format!( " - [{}] {}: {}\n", warning.severity, warning.warning_type, warning.message )); } report.push('\n'); } if !result.suggestions.is_empty() { report.push_str("SUGGESTIONS:\n"); for suggestion in &result.suggestions { report.push_str(&format!( " - {}: {}\n", suggestion.suggestion_type, suggestion.message )); } } Ok(report) } /// Generate HTML report fn generate_html_report(&self, result: &ValidationResult) -> Result { let status_color = if result.valid { "green" } else { "red" }; let status_text = if result.valid { "VALID" } else { "INVALID" }; let html = format!( r#" Policy Validation Report

Policy Validation Report

Status: {}

Total Policies: {}

Valid Policies: {}

Invalid Policies: {}

Complexity Score: {:.2}

Validation Time: {}ms

"#, status_color, status_text, result.metrics.total_policies, result.metrics.valid_policies, result.metrics.invalid_policies, result.metrics.complexity_score, result.metrics.validation_time_ms ); Ok(html) } } // Implement Display for enum types impl std::fmt::Display for ValidationErrorType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ValidationErrorType::SyntaxError => write!(f, "Syntax Error"), ValidationErrorType::SemanticError => write!(f, "Semantic Error"), ValidationErrorType::ComplianceViolation => write!(f, "Compliance Violation"), ValidationErrorType::SecurityIssue => write!(f, "Security Issue"), ValidationErrorType::PerformanceIssue => write!(f, "Performance Issue"), } } } impl std::fmt::Display for ValidationWarningType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ValidationWarningType::DeprecatedSyntax => write!(f, "Deprecated Syntax"), ValidationWarningType::PerformanceImpact => write!(f, "Performance Impact"), ValidationWarningType::SecurityBestPractice => write!(f, "Security Best Practice"), ValidationWarningType::MaintenanceIssue => write!(f, "Maintenance Issue"), } } } impl std::fmt::Display for WarningSeverity { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { WarningSeverity::Low => write!(f, "Low"), WarningSeverity::Medium => write!(f, "Medium"), WarningSeverity::High => write!(f, "High"), } } } impl std::fmt::Display for SuggestionType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SuggestionType::Optimization => write!(f, "Optimization"), SuggestionType::Security => write!(f, "Security"), SuggestionType::Readability => write!(f, "Readability"), SuggestionType::Maintenance => write!(f, "Maintenance"), } } }