//! Vulnerability findings from scans of the OS and container images. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Severity { Unknown, Low, Medium, High, Critical, } impl Severity { pub const ALL: [Severity; 5] = [ Severity::Critical, Severity::High, Severity::Medium, Severity::Low, Severity::Unknown, ]; pub fn as_str(self) -> &'static str { match self { Severity::Critical => "critical", Severity::High => "high", Severity::Medium => "medium", Severity::Low => "low", Severity::Unknown => "unknown", } } pub fn parse(s: &str) -> Severity { match s.to_ascii_lowercase().as_str() { "critical" => Severity::Critical, "high" => Severity::High, "medium" => Severity::Medium, "low" => Severity::Low, _ => Severity::Unknown, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum TargetKind { Os, Image, } impl TargetKind { pub fn as_str(self) -> &'static str { match self { TargetKind::Os => "os", TargetKind::Image => "image", } } pub fn parse(s: &str) -> Option { match s { "os" => Some(TargetKind::Os), "image" => Some(TargetKind::Image), _ => None, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum FindingStatus { Open, Acknowledged, Fixed, } impl FindingStatus { pub fn as_str(self) -> &'static str { match self { FindingStatus::Open => "open", FindingStatus::Acknowledged => "acknowledged", FindingStatus::Fixed => "fixed", } } pub fn parse(s: &str) -> Option { match s { "open" => Some(FindingStatus::Open), "acknowledged" => Some(FindingStatus::Acknowledged), "fixed" => Some(FindingStatus::Fixed), _ => None, } } } /// One vulnerability as reported by the scanner, without lifecycle data. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RawFinding { pub cve_id: String, pub severity: Severity, pub package: String, pub installed_version: String, pub fixed_version: Option, pub title: String, pub url: String, } impl RawFinding { /// Identity of a finding within a target. pub fn key(&self) -> String { format!( "{}|{}|{}", self.cve_id, self.package, self.installed_version ) } } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Finding { pub id: Uuid, pub target_kind: TargetKind, /// "os" for the host, otherwise the image reference. pub target: String, #[serde(flatten)] pub raw: RawFinding, pub status: FindingStatus, pub first_seen: DateTime, pub last_seen: DateTime, } #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct FindingFilter { pub min_severity: Option, pub target: Option, pub status: Option, /// Include fixed findings (default: only open + acknowledged). pub include_fixed: bool, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct SeverityCounts { pub critical: usize, pub high: usize, pub medium: usize, pub low: usize, pub unknown: usize, } impl SeverityCounts { pub fn add(&mut self, s: Severity) { match s { Severity::Critical => self.critical += 1, Severity::High => self.high += 1, Severity::Medium => self.medium += 1, Severity::Low => self.low += 1, Severity::Unknown => self.unknown += 1, } } pub fn total(&self) -> usize { self.critical + self.high + self.medium + self.low + self.unknown } } /// Outcome of one scan run. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ScanReport { pub targets: Vec, pub failed_targets: Vec, pub total_open: usize, pub new_findings: Vec, pub fixed: usize, }