cipherrun 0.3.0

A fast, modular, and scalable TLS/SSL security scanner written in Rust
// Vulnerability policy rules

use crate::Result;
use crate::policy::VulnerabilityPolicy;
use crate::policy::violation::PolicyViolation;
use crate::vulnerabilities::{Severity, VulnerabilityResult};

pub struct VulnerabilityRule<'a> {
    policy: &'a VulnerabilityPolicy,
    results: &'a [VulnerabilityResult],
}

impl<'a> VulnerabilityRule<'a> {
    pub fn new(policy: &'a VulnerabilityPolicy, results: &'a [VulnerabilityResult]) -> Self {
        Self { policy, results }
    }

    pub fn evaluate(&self, _target: &str) -> Result<Vec<PolicyViolation>> {
        let mut violations = Vec::new();

        // Count vulnerabilities by severity
        let mut critical_count = 0;
        let mut high_count = 0;
        let mut medium_count = 0;

        for result in self.results {
            if result.vulnerable {
                match result.severity {
                    Severity::Critical => critical_count += 1,
                    Severity::High => high_count += 1,
                    Severity::Medium => medium_count += 1,
                    _ => {}
                }
            }
        }

        // Check critical threshold
        if let Some(max_critical) = self.policy.max_critical
            && critical_count > max_critical
        {
            violations.push(
                PolicyViolation::new(
                    "vulnerabilities.max_critical",
                    "Critical Vulnerability Threshold",
                    self.policy.action,
                    format!(
                        "{} critical vulnerabilities found (max: {})",
                        critical_count, max_critical
                    ),
                )
                .with_evidence(format!("Critical vulnerabilities: {}", critical_count))
                .with_remediation("Fix all critical vulnerabilities immediately"),
            );
        }

        // Check high threshold
        if let Some(max_high) = self.policy.max_high
            && high_count > max_high
        {
            violations.push(
                PolicyViolation::new(
                    "vulnerabilities.max_high",
                    "High Vulnerability Threshold",
                    self.policy.action,
                    format!(
                        "{} high vulnerabilities found (max: {})",
                        high_count, max_high
                    ),
                )
                .with_evidence(format!("High vulnerabilities: {}", high_count))
                .with_remediation("Address high severity vulnerabilities"),
            );
        }

        // Check medium threshold
        if let Some(max_medium) = self.policy.max_medium
            && medium_count > max_medium
        {
            violations.push(
                PolicyViolation::new(
                    "vulnerabilities.max_medium",
                    "Medium Vulnerability Threshold",
                    self.policy.action,
                    format!(
                        "{} medium vulnerabilities found (max: {})",
                        medium_count, max_medium
                    ),
                )
                .with_evidence(format!("Medium vulnerabilities: {}", medium_count))
                .with_remediation("Fix medium severity vulnerabilities"),
            );
        }

        // Check for prohibited specific vulnerabilities
        if let Some(ref prohibited) = self.policy.prohibited {
            for result in self.results {
                if result.vulnerable {
                    let vuln_name = format!("{:?}", result.vuln_type);

                    for prohibited_vuln in prohibited {
                        if vuln_name
                            .to_lowercase()
                            .contains(&prohibited_vuln.to_lowercase())
                        {
                            violations.push(
                                PolicyViolation::new(
                                    "vulnerabilities.prohibited",
                                    "Prohibited Vulnerability",
                                    self.policy.action,
                                    format!(
                                        "Prohibited vulnerability detected: {:?}",
                                        result.vuln_type
                                    ),
                                )
                                .with_evidence(format!(
                                    "{:?} ({:?}): {}",
                                    result.vuln_type, result.severity, result.details
                                ))
                                .with_remediation(format!(
                                    "Fix {:?} vulnerability immediately",
                                    result.vuln_type
                                )),
                            );
                        }
                    }
                }
            }
        }

        Ok(violations)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::policy::PolicyAction;
    use crate::vulnerabilities::VulnerabilityType;

    #[test]
    fn test_critical_threshold_violation() {
        let policy = VulnerabilityPolicy {
            max_critical: Some(0),
            max_high: None,
            max_medium: None,
            prohibited: None,
            action: PolicyAction::Fail,
        };

        let results = vec![VulnerabilityResult {
            vuln_type: VulnerabilityType::Heartbleed,
            vulnerable: true,
            severity: Severity::Critical,
            cve: Some("CVE-2014-0160".to_string()),
            cwe: None,
            details: "Heartbleed vulnerability detected".to_string(),
        }];

        let rule = VulnerabilityRule::new(&policy, &results);
        let violations = rule
            .evaluate("example.com:443")
            .expect("test assertion should succeed");

        assert!(!violations.is_empty());
        assert_eq!(violations[0].rule_path, "vulnerabilities.max_critical");
    }

    #[test]
    fn test_prohibited_vulnerability() {
        let policy = VulnerabilityPolicy {
            max_critical: None,
            max_high: None,
            max_medium: None,
            prohibited: Some(vec!["POODLE".to_string()]),
            action: PolicyAction::Fail,
        };

        let results = vec![VulnerabilityResult {
            vuln_type: VulnerabilityType::POODLE,
            vulnerable: true,
            severity: Severity::High,
            cve: Some("CVE-2014-3566".to_string()),
            cwe: None,
            details: "POODLE vulnerability detected".to_string(),
        }];

        let rule = VulnerabilityRule::new(&policy, &results);
        let violations = rule
            .evaluate("example.com:443")
            .expect("test assertion should succeed");

        assert!(!violations.is_empty());
        assert_eq!(violations[0].rule_path, "vulnerabilities.prohibited");
    }

    #[test]
    fn test_no_violations() {
        let policy = VulnerabilityPolicy {
            max_critical: Some(0),
            max_high: Some(0),
            max_medium: Some(5),
            prohibited: None,
            action: PolicyAction::Fail,
        };

        let results = vec![VulnerabilityResult {
            vuln_type: VulnerabilityType::Heartbleed,
            vulnerable: false,
            severity: Severity::Critical,
            cve: Some("CVE-2014-0160".to_string()),
            cwe: None,
            details: "Not vulnerable".to_string(),
        }];

        let rule = VulnerabilityRule::new(&policy, &results);
        let violations = rule
            .evaluate("example.com:443")
            .expect("test assertion should succeed");

        assert!(violations.is_empty());
    }
}