//! Policy Testing Framework //! //! Comprehensive testing framework for Cedar policies with mock data and scenarios. use control_center::policies::{PolicyEngine, PolicyRequestContext, Principal, Action, Resource, PolicyDecision}; use control_center::config::ControlCenterConfig; use serde_json::json; use std::collections::HashMap; /// Test case for policy evaluation #[derive(Debug)] pub struct PolicyTestCase { pub name: String, pub description: String, pub principal: Principal, pub action: Action, pub resource: Resource, pub environment: HashMap, pub expected_decision: PolicyDecision, pub expected_policy_id: Option, } /// Mock data builder for testing pub struct MockDataBuilder; impl MockDataBuilder { /// Create a test user principal pub fn create_user(id: &str, roles: Vec<&str>, mfa_enabled: bool) -> Principal { let mut attributes = HashMap::new(); attributes.insert("roles".to_string(), json!(roles)); attributes.insert("mfa_enabled".to_string(), json!(mfa_enabled)); attributes.insert("account_type".to_string(), json!("user")); attributes.insert("authentication_status".to_string(), json!("authenticated")); Principal { id: id.to_string(), entity_type: "User".to_string(), attributes, } } /// Create a service account principal pub fn create_service_account(id: &str, service_type: &str) -> Principal { let mut attributes = HashMap::new(); attributes.insert("account_type".to_string(), json!("service")); attributes.insert("service_type".to_string(), json!(service_type)); attributes.insert("mfa_enabled".to_string(), json!(false)); Principal { id: id.to_string(), entity_type: "ServiceAccount".to_string(), attributes, } } /// Create an admin user with elevated privileges pub fn create_admin_user(id: &str, clearance_level: &str) -> Principal { let mut attributes = HashMap::new(); attributes.insert("roles".to_string(), json!(["Admin", "SRE"])); attributes.insert("mfa_enabled".to_string(), json!(true)); attributes.insert("mfa_last_verified".to_string(), json!(chrono::Utc::now().timestamp() - 300)); // 5 minutes ago attributes.insert("clearance_level".to_string(), json!(clearance_level)); attributes.insert("security_training".to_string(), json!({"completed": true, "expires_at": chrono::Utc::now().timestamp() + 86400})); Principal { id: id.to_string(), entity_type: "User".to_string(), attributes, } } /// Create a test action pub fn create_action(action_type: &str) -> Action { let mut attributes = HashMap::new(); attributes.insert("category".to_string(), json!("system")); Action { id: action_type.to_string(), entity_type: "Action".to_string(), attributes, } } /// Create a sensitive resource pub fn create_sensitive_resource(id: &str, classification: &str, environment: &str) -> Resource { let mut attributes = HashMap::new(); attributes.insert("classification".to_string(), json!(classification)); attributes.insert("environment".to_string(), json!(environment)); attributes.insert("criticality".to_string(), json!("high")); Resource { id: id.to_string(), entity_type: "Resource".to_string(), attributes, } } /// Create production database resource pub fn create_production_db(id: &str) -> Resource { let mut attributes = HashMap::new(); attributes.insert("resource_type".to_string(), json!("Database")); attributes.insert("environment".to_string(), json!("production")); attributes.insert("classification".to_string(), json!("confidential")); attributes.insert("data_type".to_string(), json!("financial")); attributes.insert("requires_dual_control".to_string(), json!(true)); Resource { id: id.to_string(), entity_type: "Database".to_string(), attributes, } } /// Create standard business hours environment pub fn create_business_hours_env() -> HashMap { let mut env = HashMap::new(); let now = chrono::Utc::now(); env.insert("time".to_string(), json!({ "timestamp": now.timestamp(), "hour": 10, // 10 AM "day_of_week": 2, // Tuesday "utc": now.to_rfc3339() })); env.insert("geo".to_string(), json!({ "country": "US", "ip": "192.168.1.100" })); env.insert("system".to_string(), json!({ "environment": "production", "service": "control-center" })); env } /// Create after-hours environment pub fn create_after_hours_env() -> HashMap { let mut env = HashMap::new(); let now = chrono::Utc::now(); env.insert("time".to_string(), json!({ "timestamp": now.timestamp(), "hour": 22, // 10 PM "day_of_week": 2, // Tuesday "utc": now.to_rfc3339() })); env.insert("geo".to_string(), json!({ "country": "US", "ip": "192.168.1.100" })); env } /// Create weekend environment pub fn create_weekend_env() -> HashMap { let mut env = HashMap::new(); let now = chrono::Utc::now(); env.insert("time".to_string(), json!({ "timestamp": now.timestamp(), "hour": 14, // 2 PM "day_of_week": 6, // Saturday "utc": now.to_rfc3339() })); env.insert("geo".to_string(), json!({ "country": "US", "ip": "192.168.1.100" })); env } /// Create international access environment pub fn create_international_env(country: &str) -> HashMap { let mut env = HashMap::new(); let now = chrono::Utc::now(); env.insert("time".to_string(), json!({ "timestamp": now.timestamp(), "hour": 10, "day_of_week": 2 })); env.insert("geo".to_string(), json!({ "country": country, "ip": "203.0.113.1" // Example international IP })); env } } /// Policy test runner pub struct PolicyTestRunner { engine: PolicyEngine, } impl PolicyTestRunner { pub async fn new() -> Result> { let config = ControlCenterConfig::default(); let engine = PolicyEngine::new(config).await?; Ok(Self { engine }) } /// Run a single test case pub async fn run_test(&self, test_case: PolicyTestCase) -> Result<(), Box> { let context = PolicyRequestContext { principal: test_case.principal, action: test_case.action, resource: test_case.resource, environment: test_case.environment, }; let result = self.engine.evaluate(&context).await?; // Assert expected decision match (result.decision, test_case.expected_decision) { (PolicyDecision::Allow, PolicyDecision::Allow) => { println!("✓ Test '{}' passed: Allow decision", test_case.name); } (PolicyDecision::Deny, PolicyDecision::Deny) => { println!("✓ Test '{}' passed: Deny decision", test_case.name); } (actual, expected) => { panic!( "✗ Test '{}' failed: Expected {:?}, got {:?}", test_case.name, expected, actual ); } } // Assert policy ID if specified if let Some(expected_policy_id) = test_case.expected_policy_id { match result.policy_id { Some(actual_policy_id) if actual_policy_id == expected_policy_id => { println!("✓ Test '{}' passed: Policy ID match", test_case.name); } Some(actual_policy_id) => { panic!( "✗ Test '{}' failed: Expected policy ID '{}', got '{}'", test_case.name, expected_policy_id, actual_policy_id ); } None => { panic!( "✗ Test '{}' failed: Expected policy ID '{}', got None", test_case.name, expected_policy_id ); } } } Ok(()) } /// Run multiple test cases pub async fn run_test_suite(&self, test_cases: Vec) -> Result<(), Box> { let mut passed = 0; let mut failed = 0; for test_case in test_cases { match self.run_test(test_case).await { Ok(()) => passed += 1, Err(e) => { eprintln!("Test failed: {}", e); failed += 1; } } } println!("\nTest Results: {} passed, {} failed", passed, failed); if failed > 0 { return Err(format!("{} tests failed", failed).into()); } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_mfa_policy() { let runner = PolicyTestRunner::new().await.expect("Failed to create test runner"); let test_cases = vec![ PolicyTestCase { name: "User with MFA accessing sensitive resource".to_string(), description: "Should allow access when MFA is enabled".to_string(), principal: { let mut user = MockDataBuilder::create_user("user1", vec!["Developer"], true); user.attributes.insert("mfa_last_verified".to_string(), json!(chrono::Utc::now().timestamp() - 300)); user }, action: MockDataBuilder::create_action("access"), resource: MockDataBuilder::create_sensitive_resource("sensitive-db", "sensitive", "production"), environment: MockDataBuilder::create_business_hours_env(), expected_decision: PolicyDecision::Allow, expected_policy_id: None, }, PolicyTestCase { name: "User without MFA accessing sensitive resource".to_string(), description: "Should deny access when MFA is not enabled".to_string(), principal: MockDataBuilder::create_user("user2", vec!["Developer"], false), action: MockDataBuilder::create_action("access"), resource: MockDataBuilder::create_sensitive_resource("sensitive-db", "sensitive", "production"), environment: MockDataBuilder::create_business_hours_env(), expected_decision: PolicyDecision::Deny, expected_policy_id: None, }, PolicyTestCase { name: "User accessing non-sensitive resource without MFA".to_string(), description: "Should allow access to public resources without MFA".to_string(), principal: MockDataBuilder::create_user("user3", vec!["Developer"], false), action: MockDataBuilder::create_action("access"), resource: MockDataBuilder::create_sensitive_resource("public-docs", "public", "production"), environment: MockDataBuilder::create_business_hours_env(), expected_decision: PolicyDecision::Allow, expected_policy_id: None, }, ]; runner.run_test_suite(test_cases).await.expect("Test suite failed"); } #[tokio::test] async fn test_production_approval_policy() { let runner = PolicyTestRunner::new().await.expect("Failed to create test runner"); let test_cases = vec![ PolicyTestCase { name: "Admin with approval deploying to production".to_string(), description: "Should allow deployment with valid approval".to_string(), principal: { let mut admin = MockDataBuilder::create_admin_user("admin1", "confidential"); admin.attributes.insert("approval".to_string(), json!({ "environment": "production", "approved_by": "ProductionAdmin", "approved_at": chrono::Utc::now().timestamp() - 3600, // 1 hour ago "expires_at": chrono::Utc::now().timestamp() + 82800, // 23 hours from now "change_ticket": "CHG-2024-001", "risk_assessment": "low" })); admin }, action: MockDataBuilder::create_action("deploy"), resource: MockDataBuilder::create_production_db("prod-db-1"), environment: MockDataBuilder::create_business_hours_env(), expected_decision: PolicyDecision::Allow, expected_policy_id: None, }, PolicyTestCase { name: "Developer without approval trying to deploy".to_string(), description: "Should deny deployment without proper approval".to_string(), principal: MockDataBuilder::create_user("dev1", vec!["Developer"], true), action: MockDataBuilder::create_action("deploy"), resource: MockDataBuilder::create_production_db("prod-db-1"), environment: MockDataBuilder::create_business_hours_env(), expected_decision: PolicyDecision::Deny, expected_policy_id: None, }, ]; runner.run_test_suite(test_cases).await.expect("Test suite failed"); } #[tokio::test] async fn test_geographic_restrictions() { let runner = PolicyTestRunner::new().await.expect("Failed to create test runner"); let test_cases = vec![ PolicyTestCase { name: "US user accessing general resource".to_string(), description: "Should allow access from approved country".to_string(), principal: MockDataBuilder::create_user("user1", vec!["Employee"], false), action: MockDataBuilder::create_action("read"), resource: MockDataBuilder::create_sensitive_resource("internal-docs", "internal", "production"), environment: MockDataBuilder::create_international_env("US"), expected_decision: PolicyDecision::Allow, expected_policy_id: None, }, PolicyTestCase { name: "Restricted country access attempt".to_string(), description: "Should deny access from sanctioned country".to_string(), principal: MockDataBuilder::create_user("user2", vec!["Employee"], false), action: MockDataBuilder::create_action("read"), resource: MockDataBuilder::create_sensitive_resource("internal-docs", "internal", "production"), environment: MockDataBuilder::create_international_env("IR"), // Iran - sanctioned expected_decision: PolicyDecision::Deny, expected_policy_id: None, }, ]; runner.run_test_suite(test_cases).await.expect("Test suite failed"); } #[tokio::test] async fn test_time_based_access() { let runner = PolicyTestRunner::new().await.expect("Failed to create test runner"); let test_cases = vec![ PolicyTestCase { name: "Employee access during business hours".to_string(), description: "Should allow access during business hours".to_string(), principal: MockDataBuilder::create_user("emp1", vec!["Employee"], false), action: MockDataBuilder::create_action("read"), resource: MockDataBuilder::create_sensitive_resource("company-docs", "internal", "production"), environment: MockDataBuilder::create_business_hours_env(), expected_decision: PolicyDecision::Allow, expected_policy_id: None, }, PolicyTestCase { name: "Employee access after hours without approval".to_string(), description: "Should deny access outside business hours without approval".to_string(), principal: MockDataBuilder::create_user("emp2", vec!["Employee"], false), action: MockDataBuilder::create_action("read"), resource: MockDataBuilder::create_sensitive_resource("company-docs", "internal", "production"), environment: MockDataBuilder::create_after_hours_env(), expected_decision: PolicyDecision::Deny, expected_policy_id: None, }, ]; runner.run_test_suite(test_cases).await.expect("Test suite failed"); } }