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();
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,
_ => {}
}
}
}
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"),
);
}
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"),
);
}
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"),
);
}
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());
}
}