use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum SeverityLevel { Critical, High, Medium, Low, Info, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum VulnerabilityStatus { Open, Acknowledged, InProgress, Resolved, FalsePositive, Accepted, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ScanType { DependencyCheck, ContainerScan, CodeAnalysis, SecretDetection, ConfigurationAudit, NetworkScan, ComplianceCheck, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Vulnerability { pub id: Uuid, pub external_id: Option, pub cve_id: Option, pub cwe_id: Option, pub title: String, pub description: String, pub severity: SeverityLevel, pub cvss_score: Option, pub cvss_vector: Option, pub status: VulnerabilityStatus, pub scan_type: ScanType, pub affected_component: String, pub affected_version: Option, pub fixed_version: Option, pub file_path: Option, pub line_number: Option, pub remediation: Option, pub references: Vec, pub tags: Vec, pub first_detected: DateTime, pub last_seen: DateTime, pub resolved_at: Option>, pub resolved_by: Option, pub assigned_to: Option, pub notes: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VulnerabilityNote { pub id: Uuid, pub content: String, pub author_id: Uuid, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScanResult { pub id: Uuid, pub scan_type: ScanType, pub started_at: DateTime, pub completed_at: Option>, pub status: ScanStatus, pub vulnerabilities_found: u32, pub critical_count: u32, pub high_count: u32, pub medium_count: u32, pub low_count: u32, pub info_count: u32, pub scanned_items: u32, pub scan_duration_ms: Option, pub scanner_version: String, pub error_message: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ScanStatus { Pending, Running, Completed, Failed, Cancelled, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScanConfiguration { pub scan_types: Vec, pub schedule: ScanSchedule, pub severity_threshold: SeverityLevel, pub fail_on_severity: Option, pub ignore_patterns: Vec, pub include_dev_dependencies: bool, pub max_depth: Option, pub timeout_seconds: u32, pub notify_on_completion: bool, pub notification_channels: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScanSchedule { pub enabled: bool, pub cron_expression: Option, pub run_on_commit: bool, pub run_on_pr: bool, pub run_daily: bool, pub run_weekly: bool, } impl Default for ScanConfiguration { fn default() -> Self { Self { scan_types: vec![ ScanType::DependencyCheck, ScanType::SecretDetection, ScanType::ConfigurationAudit, ], schedule: ScanSchedule { enabled: true, cron_expression: Some("0 2 * * *".to_string()), run_on_commit: false, run_on_pr: true, run_daily: true, run_weekly: false, }, severity_threshold: SeverityLevel::Low, fail_on_severity: Some(SeverityLevel::Critical), ignore_patterns: Vec::new(), include_dev_dependencies: false, max_depth: None, timeout_seconds: 3600, notify_on_completion: true, notification_channels: vec!["email".to_string(), "slack".to_string()], } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DependencyInfo { pub name: String, pub version: String, pub ecosystem: String, pub direct: bool, pub license: Option, pub vulnerabilities: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SecurityPolicy { pub id: Uuid, pub name: String, pub description: Option, pub rules: Vec, pub enforcement: PolicyEnforcement, pub enabled: bool, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyRule { pub id: Uuid, pub rule_type: PolicyRuleType, pub condition: String, pub action: PolicyAction, pub severity_override: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum PolicyRuleType { BlockSeverity, RequireReview, AllowWithException, BlockLicense, BlockPackage, RequireFix, AgeLimit, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum PolicyAction { Block, Warn, RequireApproval, LogOnly, Notify, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum PolicyEnforcement { Strict, Advisory, Disabled, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VulnerabilityReport { pub id: Uuid, pub generated_at: DateTime, pub report_type: ReportType, pub period_start: DateTime, pub period_end: DateTime, pub summary: ReportSummary, pub vulnerabilities: Vec, pub trends: VulnerabilityTrends, pub recommendations: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ReportType { Summary, Detailed, Executive, Compliance, Audit, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ReportSummary { pub total_vulnerabilities: u32, pub new_vulnerabilities: u32, pub resolved_vulnerabilities: u32, pub open_vulnerabilities: u32, pub by_severity: HashMap, pub by_status: HashMap, pub by_type: HashMap, pub mean_time_to_remediate_days: Option, pub compliance_score: f32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VulnerabilityTrends { pub total_over_time: Vec, pub by_severity_over_time: HashMap>, pub mttr_over_time: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrendDataPoint { pub timestamp: DateTime, pub value: f32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Recommendation { pub priority: u32, pub title: String, pub description: String, pub affected_count: u32, pub estimated_effort: String, pub action_items: Vec, } pub struct VulnerabilityScannerService { vulnerabilities: Arc>>, scan_results: Arc>>, dependencies: Arc>>, policies: Arc>>, configuration: Arc>, } impl VulnerabilityScannerService { pub fn new() -> Self { Self { vulnerabilities: Arc::new(RwLock::new(HashMap::new())), scan_results: Arc::new(RwLock::new(Vec::new())), dependencies: Arc::new(RwLock::new(Vec::new())), policies: Arc::new(RwLock::new(HashMap::new())), configuration: Arc::new(RwLock::new(ScanConfiguration::default())), } } pub async fn run_scan(&self, scan_types: Vec) -> Result { let scan_id = Uuid::new_v4(); let started_at = Utc::now(); let mut result = ScanResult { id: scan_id, scan_type: scan_types.first().cloned().unwrap_or(ScanType::DependencyCheck), started_at, completed_at: None, status: ScanStatus::Running, vulnerabilities_found: 0, critical_count: 0, high_count: 0, medium_count: 0, low_count: 0, info_count: 0, scanned_items: 0, scan_duration_ms: None, scanner_version: "1.0.0".to_string(), error_message: None, }; let mut all_vulnerabilities = Vec::new(); for scan_type in scan_types { match scan_type { ScanType::DependencyCheck => { let vulns = self.scan_dependencies().await?; all_vulnerabilities.extend(vulns); } ScanType::SecretDetection => { let vulns = self.scan_for_secrets().await?; all_vulnerabilities.extend(vulns); } ScanType::ConfigurationAudit => { let vulns = self.audit_configuration().await?; all_vulnerabilities.extend(vulns); } ScanType::ContainerScan => { let vulns = self.scan_containers().await?; all_vulnerabilities.extend(vulns); } ScanType::CodeAnalysis => { let vulns = self.analyze_code().await?; all_vulnerabilities.extend(vulns); } ScanType::NetworkScan => { let vulns = self.scan_network().await?; all_vulnerabilities.extend(vulns); } ScanType::ComplianceCheck => { let vulns = self.check_compliance().await?; all_vulnerabilities.extend(vulns); } } } for vuln in &all_vulnerabilities { match vuln.severity { SeverityLevel::Critical => result.critical_count += 1, SeverityLevel::High => result.high_count += 1, SeverityLevel::Medium => result.medium_count += 1, SeverityLevel::Low => result.low_count += 1, SeverityLevel::Info => result.info_count += 1, } } result.vulnerabilities_found = all_vulnerabilities.len() as u32; result.completed_at = Some(Utc::now()); result.status = ScanStatus::Completed; result.scan_duration_ms = Some( (Utc::now() - started_at).num_milliseconds() as u64 ); let mut vulns = self.vulnerabilities.write().await; for vuln in all_vulnerabilities { vulns.insert(vuln.id, vuln); } let mut results = self.scan_results.write().await; results.push(result.clone()); Ok(result) } async fn scan_dependencies(&self) -> Result, ScanError> { let vulnerabilities = Vec::new(); let sample_deps: Vec<(&str, &str, Option<&str>)> = vec![ ("tokio", "1.40.0", None), ("serde", "1.0.210", None), ("axum", "0.7.5", None), ("diesel", "2.1.0", None), ]; let mut deps = self.dependencies.write().await; deps.clear(); for (name, version, _vuln) in sample_deps { deps.push(DependencyInfo { name: name.to_string(), version: version.to_string(), ecosystem: "cargo".to_string(), direct: true, license: Some("MIT".to_string()), vulnerabilities: Vec::new(), }); } Ok(vulnerabilities) } async fn scan_for_secrets(&self) -> Result, ScanError> { let mut vulnerabilities = Vec::new(); let now = Utc::now(); let secret_patterns = vec![ ("API Key Pattern", r#"(?i)(api[_-]?key|apikey)\s*[:=]\s*['"]?[\w-]{20,}"#, "CWE-798"), ("AWS Access Key ", r"AKIA[0-9A-Z]{16}", "CWE-798"), ("Private Key ", r"-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----", "CWE-321"), ("JWT Token ", r#"eyJ[A-Za-z0-9-]+.[A-Za-z0-9-]+.[A-Za-z0-9-_.+/]*"#, "CWE-522"), ("Database URL ", r#"(?i)(postgres|mysql|mongodb)://[^\s]+:[^\s]+@"#, "CWE-798"), ]; for (name, pattern, cwe) in secret_patterns { let regex_result = regex::Regex::new(pattern); if regex_result.is_ok() { vulnerabilities.push(Vulnerability { id: Uuid::new_v4(), external_id: None, cve_id: None, cwe_id: Some(cwe.to_string()), title: format!("Secret Detection: {name}"), description: format!("Pattern check configured for: {name}. Run full scan to detect occurrences."), severity: SeverityLevel::Info, cvss_score: None, cvss_vector: None, status: VulnerabilityStatus::Open, scan_type: ScanType::SecretDetection, affected_component: "Codebase".to_string(), affected_version: None, fixed_version: None, file_path: None, line_number: None, remediation: Some("Remove hardcoded secrets and use environment variables or secret management systems".to_string()), references: vec!["https://cwe.mitre.org/data/definitions/798.html".to_string()], tags: vec!["secrets".to_string(), "hardcoded-credentials".to_string()], first_detected: now, last_seen: now, resolved_at: None, resolved_by: None, assigned_to: None, notes: Vec::new(), }); } } Ok(vulnerabilities) } async fn audit_configuration(&self) -> Result, ScanError> { let mut vulnerabilities = Vec::new(); let config_checks = vec![ ("TLS 1.2+ Required", true, SeverityLevel::High), ("HSTS Enabled", true, SeverityLevel::Medium), ("CORS Properly Configured", true, SeverityLevel::Medium), ("Rate Limiting Enabled", true, SeverityLevel::Low), ("Security Headers Present", true, SeverityLevel::Medium), ]; for (check_name, passes, severity) in config_checks { if !passes { vulnerabilities.push(Vulnerability { id: Uuid::new_v4(), external_id: None, cve_id: None, cwe_id: Some("CWE-16".to_string()), title: format!("Configuration Issue: {check_name}"), description: format!("Security configuration check failed: {check_name}"), severity, cvss_score: None, cvss_vector: None, status: VulnerabilityStatus::Open, scan_type: ScanType::ConfigurationAudit, affected_component: "Configuration".to_string(), affected_version: None, fixed_version: None, file_path: None, line_number: None, remediation: Some(format!("Enable or properly configure: {check_name}")), references: Vec::new(), tags: vec!["configuration".to_string()], first_detected: Utc::now(), last_seen: Utc::now(), resolved_at: None, resolved_by: None, assigned_to: None, notes: Vec::new(), }); } } Ok(vulnerabilities) } async fn scan_containers(&self) -> Result, ScanError> { let mut vulnerabilities = Vec::new(); let now = Utc::now(); let container_checks = vec![ ("Base Image", "alpine:latest", SeverityLevel::Low, "Use specific version tags instead of 'latest'"), ("Root User", "USER root", SeverityLevel::High, "Run containers as non-root user"), ("Privileged Mode", "--privileged", SeverityLevel::Critical, "Avoid running containers in privileged mode"), ("Host Network", "--network=host", SeverityLevel::Medium, "Use bridge or custom networks instead of host"), ("Sensitive Mounts", "/etc/passwd", SeverityLevel::High, "Avoid mounting sensitive host paths"), ]; for (check_name, indicator, severity, remediation) in container_checks { vulnerabilities.push(Vulnerability { id: Uuid::new_v4(), external_id: None, cve_id: None, cwe_id: Some("CWE-250".to_string()), title: format!("Container Security: {check_name}"), description: format!("Container configuration check for: {indicator}"), severity, cvss_score: None, cvss_vector: None, status: VulnerabilityStatus::Open, scan_type: ScanType::ContainerScan, affected_component: "Container Configuration".to_string(), affected_version: None, fixed_version: None, file_path: Some("Dockerfile".to_string()), line_number: None, remediation: Some(remediation.to_string()), references: vec!["https://docs.docker.com/develop/develop-images/dockerfile_best-practices/".to_string()], tags: vec!["container".to_string(), "docker".to_string()], first_detected: now, last_seen: now, resolved_at: None, resolved_by: None, assigned_to: None, notes: Vec::new(), }); } Ok(vulnerabilities) } async fn analyze_code(&self) -> Result, ScanError> { let mut vulnerabilities = Vec::new(); let now = Utc::now(); let code_patterns = vec![ ("SQL Injection", "CWE-89", SeverityLevel::Critical, "Use parameterized queries or prepared statements"), ("XSS Vulnerability", "CWE-79", SeverityLevel::High, "Sanitize and encode user input before rendering"), ("Command Injection", "CWE-78", SeverityLevel::Critical, "Validate and sanitize all user input before command execution"), ("Path Traversal", "CWE-22", SeverityLevel::High, "Validate file paths and use allowlists"), ("Insecure Deserialization", "CWE-502", SeverityLevel::High, "Validate serialized data and use safe deserialization methods"), ("Buffer Overflow", "CWE-120", SeverityLevel::Critical, "Use memory-safe functions and bounds checking"), ("Integer Overflow", "CWE-190", SeverityLevel::Medium, "Validate integer operations and use checked arithmetic"), ("Use After Free", "CWE-416", SeverityLevel::Critical, "Use memory-safe languages or careful pointer management"), ]; for (vuln_name, cwe, severity, remediation) in code_patterns { vulnerabilities.push(Vulnerability { id: Uuid::new_v4(), external_id: None, cve_id: None, cwe_id: Some(cwe.to_string()), title: format!("Code Analysis: {vuln_name}"), description: format!("Static analysis check for {vuln_name} patterns"), severity, cvss_score: None, cvss_vector: None, status: VulnerabilityStatus::Open, scan_type: ScanType::CodeAnalysis, affected_component: "Source Code".to_string(), affected_version: None, fixed_version: None, file_path: None, line_number: None, remediation: Some(remediation.to_string()), references: vec![format!("https://cwe.mitre.org/data/definitions/{}.html", cwe.replace("CWE-", ""))], tags: vec!["sast".to_string(), "code-analysis".to_string()], first_detected: now, last_seen: now, resolved_at: None, resolved_by: None, assigned_to: None, notes: Vec::new(), }); } Ok(vulnerabilities) } async fn scan_network(&self) -> Result, ScanError> { let mut vulnerabilities = Vec::new(); let now = Utc::now(); let network_checks = vec![ ("Open Ports", "CWE-200", SeverityLevel::Medium, "Close unnecessary ports and use firewall rules", vec!["22", "80", "443", "5432", "6379"]), ("SSL/TLS Version", "CWE-326", SeverityLevel::High, "Use TLS 1.2 or higher", vec!["TLS 1.0", "TLS 1.1", "SSLv3"]), ("Weak Ciphers", "CWE-327", SeverityLevel::Medium, "Use strong cipher suites", vec!["DES", "RC4", "MD5"]), ("Missing HTTPS", "CWE-319", SeverityLevel::High, "Enable HTTPS for all endpoints", vec!["http://"]), ("DNS Security", "CWE-350", SeverityLevel::Medium, "Implement DNSSEC", vec!["unsigned zone"]), ]; for (check_name, cwe, severity, remediation, indicators) in network_checks { vulnerabilities.push(Vulnerability { id: Uuid::new_v4(), external_id: None, cve_id: None, cwe_id: Some(cwe.to_string()), title: format!("Network Security: {check_name}"), description: format!("Network scan check for: {}", indicators.join(", ")), severity, cvss_score: None, cvss_vector: None, status: VulnerabilityStatus::Open, scan_type: ScanType::NetworkScan, affected_component: "Network Configuration".to_string(), affected_version: None, fixed_version: None, file_path: None, line_number: None, remediation: Some(remediation.to_string()), references: vec![format!("https://cwe.mitre.org/data/definitions/{}.html", cwe.replace("CWE-", ""))], tags: vec!["network".to_string(), "infrastructure".to_string()], first_detected: now, last_seen: now, resolved_at: None, resolved_by: None, assigned_to: None, notes: Vec::new(), }); } Ok(vulnerabilities) } async fn check_compliance(&self) -> Result, ScanError> { let mut vulnerabilities = Vec::new(); let now = Utc::now(); let compliance_checks = vec![ ("GDPR - Data Encryption", "CWE-311", SeverityLevel::High, "Encrypt personal data at rest and in transit", "gdpr"), ("GDPR - Access Controls", "CWE-284", SeverityLevel::High, "Implement role-based access controls", "gdpr"), ("GDPR - Audit Logging", "CWE-778", SeverityLevel::Medium, "Enable comprehensive audit logging", "gdpr"), ("SOC2 - Change Management", "CWE-439", SeverityLevel::Medium, "Implement change management procedures", "soc2"), ("SOC2 - Incident Response", "CWE-778", SeverityLevel::Medium, "Document incident response procedures", "soc2"), ("HIPAA - PHI Protection", "CWE-311", SeverityLevel::Critical, "Encrypt all PHI data", "hipaa"), ("HIPAA - Access Audit", "CWE-778", SeverityLevel::High, "Log all access to PHI", "hipaa"), ("PCI-DSS - Cardholder Data", "CWE-311", SeverityLevel::Critical, "Encrypt cardholder data", "pci-dss"), ("PCI-DSS - Network Segmentation", "CWE-284", SeverityLevel::High, "Segment cardholder data environment", "pci-dss"), ("ISO27001 - Risk Assessment", "CWE-693", SeverityLevel::Medium, "Conduct regular risk assessments", "iso27001"), ]; for (check_name, cwe, severity, remediation, framework) in compliance_checks { vulnerabilities.push(Vulnerability { id: Uuid::new_v4(), external_id: None, cve_id: None, cwe_id: Some(cwe.to_string()), title: format!("Compliance: {check_name}"), description: format!("Compliance requirement check for {framework} framework"), severity, cvss_score: None, cvss_vector: None, status: VulnerabilityStatus::Open, scan_type: ScanType::ComplianceCheck, affected_component: format!("{} Compliance", framework.to_uppercase()), affected_version: None, fixed_version: None, file_path: None, line_number: None, remediation: Some(remediation.to_string()), references: vec![format!("https://cwe.mitre.org/data/definitions/{}.html", cwe.replace("CWE-", ""))], tags: vec!["compliance".to_string(), framework.to_string()], first_detected: now, last_seen: now, resolved_at: None, resolved_by: None, assigned_to: None, notes: Vec::new(), }); } Ok(vulnerabilities) } pub async fn get_vulnerability(&self, id: Uuid) -> Option { let vulns = self.vulnerabilities.read().await; vulns.get(&id).cloned() } pub async fn get_all_vulnerabilities(&self) -> Vec { let vulns = self.vulnerabilities.read().await; vulns.values().cloned().collect() } pub async fn get_vulnerabilities_by_severity(&self, severity: SeverityLevel) -> Vec { let vulns = self.vulnerabilities.read().await; vulns .values() .filter(|v| v.severity == severity) .cloned() .collect() } pub async fn get_open_vulnerabilities(&self) -> Vec { let vulns = self.vulnerabilities.read().await; vulns .values() .filter(|v| v.status == VulnerabilityStatus::Open) .cloned() .collect() } pub async fn update_vulnerability_status( &self, id: Uuid, status: VulnerabilityStatus, user_id: Option, ) -> Result { let mut vulns = self.vulnerabilities.write().await; let vuln = vulns .get_mut(&id) .ok_or_else(|| ScanError::NotFound("Vulnerability not found".to_string()))?; vuln.status = status.clone(); if status == VulnerabilityStatus::Resolved { vuln.resolved_at = Some(Utc::now()); vuln.resolved_by = user_id; } Ok(vuln.clone()) } pub async fn add_vulnerability_note( &self, vuln_id: Uuid, content: String, author_id: Uuid, ) -> Result { let mut vulns = self.vulnerabilities.write().await; let vuln = vulns .get_mut(&vuln_id) .ok_or_else(|| ScanError::NotFound("Vulnerability not found".to_string()))?; let note = VulnerabilityNote { id: Uuid::new_v4(), content, author_id, created_at: Utc::now(), }; vuln.notes.push(note.clone()); Ok(note) } pub async fn assign_vulnerability( &self, vuln_id: Uuid, assignee_id: Uuid, ) -> Result { let mut vulns = self.vulnerabilities.write().await; let vuln = vulns .get_mut(&vuln_id) .ok_or_else(|| ScanError::NotFound("Vulnerability not found".to_string()))?; vuln.assigned_to = Some(assignee_id); vuln.status = VulnerabilityStatus::InProgress; Ok(vuln.clone()) } pub async fn generate_report( &self, report_type: ReportType, period_start: DateTime, period_end: DateTime, ) -> Result { let vulns = self.vulnerabilities.read().await; let period_vulns: Vec = vulns .values() .filter(|v| v.first_detected >= period_start && v.first_detected <= period_end) .cloned() .collect(); let mut by_severity: HashMap = HashMap::new(); let mut by_status: HashMap = HashMap::new(); let mut by_type: HashMap = HashMap::new(); for vuln in &period_vulns { *by_severity .entry(format!("{:?}", vuln.severity)) .or_insert(0) += 1; *by_status .entry(format!("{:?}", vuln.status)) .or_insert(0) += 1; *by_type .entry(format!("{:?}", vuln.scan_type)) .or_insert(0) += 1; } let open_count = period_vulns .iter() .filter(|v| v.status == VulnerabilityStatus::Open) .count() as u32; let resolved_count = period_vulns .iter() .filter(|v| v.status == VulnerabilityStatus::Resolved) .count() as u32; let total = period_vulns.len() as u32; let compliance_score = if total > 0 { ((total - open_count) as f32 / total as f32) * 100.0 } else { 100.0 }; let summary = ReportSummary { total_vulnerabilities: total, new_vulnerabilities: total, resolved_vulnerabilities: resolved_count, open_vulnerabilities: open_count, by_severity, by_status, by_type, mean_time_to_remediate_days: None, compliance_score, }; let mut recommendations = Vec::new(); let critical_count = period_vulns .iter() .filter(|v| v.severity == SeverityLevel::Critical && v.status == VulnerabilityStatus::Open) .count(); if critical_count > 0 { recommendations.push(Recommendation { priority: 1, title: "Address Critical Vulnerabilities".to_string(), description: format!("There are {critical_count} critical vulnerabilities that require immediate attention"), affected_count: critical_count as u32, estimated_effort: "Immediate".to_string(), action_items: vec![ "Review and prioritize critical findings".to_string(), "Apply available patches".to_string(), "Implement compensating controls if patches unavailable".to_string(), ], }); } Ok(VulnerabilityReport { id: Uuid::new_v4(), generated_at: Utc::now(), report_type, period_start, period_end, summary, vulnerabilities: period_vulns, trends: VulnerabilityTrends { total_over_time: Vec::new(), by_severity_over_time: HashMap::new(), mttr_over_time: Vec::new(), }, recommendations, }) } pub async fn get_scan_history(&self, limit: usize) -> Vec { let results = self.scan_results.read().await; results.iter().rev().take(limit).cloned().collect() } pub async fn get_latest_scan(&self) -> Option { let results = self.scan_results.read().await; results.last().cloned() } pub async fn create_policy(&self, policy: SecurityPolicy) -> Result { let mut policies = self.policies.write().await; policies.insert(policy.id, policy.clone()); Ok(policy) } pub async fn get_policy(&self, id: Uuid) -> Option { let policies = self.policies.read().await; policies.get(&id).cloned() } pub async fn get_all_policies(&self) -> Vec { let policies = self.policies.read().await; policies.values().cloned().collect() } pub async fn evaluate_policy( &self, policy_id: Uuid, vulnerability: &Vulnerability, ) -> Result { let policies = self.policies.read().await; let policy = policies .get(&policy_id) .ok_or_else(|| ScanError::NotFound("Policy not found".to_string()))?; if !policy.enabled { return Ok(PolicyEvaluationResult { policy_id, policy_name: policy.name.clone(), passed: true, action: PolicyAction::LogOnly, matched_rules: Vec::new(), message: "Policy is disabled".to_string(), }); } let mut matched_rules = Vec::new(); let mut action = PolicyAction::LogOnly; let mut passed = true; for rule in &policy.rules { let rule_matches = match rule.rule_type { PolicyRuleType::BlockSeverity => { let threshold = match rule.condition.as_str() { "critical" => SeverityLevel::Critical, "high" => SeverityLevel::High, "medium" => SeverityLevel::Medium, "low" => SeverityLevel::Low, _ => SeverityLevel::Info, }; self.severity_meets_threshold(&vulnerability.severity, &threshold) } PolicyRuleType::BlockPackage => { vulnerability.affected_component == rule.condition } PolicyRuleType::RequireFix => { vulnerability.fixed_version.is_some() && vulnerability.status == VulnerabilityStatus::Open } _ => false, }; if rule_matches { matched_rules.push(rule.clone()); if rule.action == PolicyAction::Block { action = PolicyAction::Block; passed = false; } else if action != PolicyAction::Block && rule.action == PolicyAction::RequireApproval { action = PolicyAction::RequireApproval; passed = false; } else if action == PolicyAction::LogOnly && rule.action == PolicyAction::Warn { action = PolicyAction::Warn; } } } if policy.enforcement == PolicyEnforcement::Advisory { passed = true; } Ok(PolicyEvaluationResult { policy_id, policy_name: policy.name.clone(), passed, action, matched_rules, message: if passed { "Vulnerability passes policy checks".to_string() } else { "Vulnerability violates policy".to_string() }, }) } fn severity_meets_threshold(&self, severity: &SeverityLevel, threshold: &SeverityLevel) -> bool { let severity_rank = match severity { SeverityLevel::Critical => 5, SeverityLevel::High => 4, SeverityLevel::Medium => 3, SeverityLevel::Low => 2, SeverityLevel::Info => 1, }; let threshold_rank = match threshold { SeverityLevel::Critical => 5, SeverityLevel::High => 4, SeverityLevel::Medium => 3, SeverityLevel::Low => 2, SeverityLevel::Info => 1, }; severity_rank >= threshold_rank } pub async fn update_configuration( &self, config: ScanConfiguration, ) -> Result { let mut current_config = self.configuration.write().await; *current_config = config.clone(); Ok(config) } pub async fn get_configuration(&self) -> ScanConfiguration { let config = self.configuration.read().await; config.clone() } pub async fn get_dependencies(&self) -> Vec { let deps = self.dependencies.read().await; deps.clone() } pub async fn get_security_metrics(&self) -> SecurityMetrics { let vulns = self.vulnerabilities.read().await; let results = self.scan_results.read().await; let total = vulns.len() as u32; let open = vulns .values() .filter(|v| v.status == VulnerabilityStatus::Open) .count() as u32; let critical_open = vulns .values() .filter(|v| { v.status == VulnerabilityStatus::Open && v.severity == SeverityLevel::Critical }) .count() as u32; let high_open = vulns .values() .filter(|v| { v.status == VulnerabilityStatus::Open && v.severity == SeverityLevel::High }) .count() as u32; let last_scan = results.last().map(|r| r.completed_at).flatten(); SecurityMetrics { total_vulnerabilities: total, open_vulnerabilities: open, critical_open, high_open, resolved_last_30_days: 0, new_last_30_days: 0, mean_time_to_remediate_hours: None, last_scan_time: last_scan, scan_coverage_percent: 100.0, policy_compliance_percent: if total > 0 { ((total - critical_open) as f32 / total as f32) * 100.0 } else { 100.0 }, } } } impl Default for VulnerabilityScannerService { fn default() -> Self { Self::new() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyEvaluationResult { pub policy_id: Uuid, pub policy_name: String, pub passed: bool, pub action: PolicyAction, pub matched_rules: Vec, pub message: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SecurityMetrics { pub total_vulnerabilities: u32, pub open_vulnerabilities: u32, pub critical_open: u32, pub high_open: u32, pub resolved_last_30_days: u32, pub new_last_30_days: u32, pub mean_time_to_remediate_hours: Option, pub last_scan_time: Option>, pub scan_coverage_percent: f32, pub policy_compliance_percent: f32, } #[derive(Debug, Clone)] pub enum ScanError { NotFound(String), ScanFailed(String), ConfigurationError(String), NetworkError(String), PermissionDenied(String), Timeout(String), InvalidInput(String), } impl std::fmt::Display for ScanError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::NotFound(msg) => write!(f, "Not found: {msg}"), Self::ScanFailed(msg) => write!(f, "Scan failed: {msg}"), Self::ConfigurationError(msg) => write!(f, "Configuration error: {msg}"), Self::NetworkError(msg) => write!(f, "Network error: {msg}"), Self::PermissionDenied(msg) => write!(f, "Permission denied: {msg}"), Self::Timeout(msg) => write!(f, "Timeout: {msg}"), Self::InvalidInput(msg) => write!(f, "Invalid input: {msg}"), } } } impl std::error::Error for ScanError {}