diff --git a/rust/models/src/advisories.rs b/rust/models/src/advisories.rs index fc62b3123..41f192305 100644 --- a/rust/models/src/advisories.rs +++ b/rust/models/src/advisories.rs @@ -38,34 +38,23 @@ pub struct Advisory { pub fixed_packages: Vec, } -/// Fixed Package entry -#[cfg_attr(feature = "serde_support", derive(serde::Deserialize))] -#[derive(Debug)] -pub struct FixedPackage { - /// Name of the affected package - pub name: String, - /// Field containing information about vulnerable package version - #[cfg_attr(feature = "serde_support", serde(flatten))] - pub version: VersionEntry, -} - /// Version entry #[cfg_attr(feature = "serde_support", derive(serde::Deserialize), serde(untagged))] #[derive(Debug)] -pub enum VersionEntry { +pub enum FixedPackage { ByFullName { + full_name: String, specifier: Specifier, }, /// Contains a version and a specifier ByNameAndFullVersion { + name: String, full_version: String, specifier: Specifier, }, /// Contains a version Range - ByRange { - range: Range, - }, + ByRange { name: String, range: Range }, } /// A specifier can be one of: >, <, >=, <=, = diff --git a/rust/models/src/result.rs b/rust/models/src/result.rs index 9581522ad..4c6db539c 100644 --- a/rust/models/src/result.rs +++ b/rust/models/src/result.rs @@ -102,6 +102,7 @@ pub enum ResultType { pub type NotusResults = HashMap>; #[cfg_attr(feature = "serde_support", derive(serde::Serialize))] +#[derive(Debug)] pub struct VulnerablePackage { pub name: String, pub installed_version: String, @@ -109,6 +110,7 @@ pub struct VulnerablePackage { } #[cfg_attr(feature = "serde_support", derive(serde::Serialize))] +#[derive(Debug)] pub enum FixedVersion { Single { version: String, diff --git a/rust/notus/Cargo.toml b/rust/notus/Cargo.toml index b717b876e..030566817 100644 --- a/rust/notus/Cargo.toml +++ b/rust/notus/Cargo.toml @@ -10,4 +10,3 @@ regex = "1.10.2" serde_json = "1.0.96" serde = { version = "1.0.163", features = ["derive"] } models = { path = "../models" } -version-compare = "0.1.1" diff --git a/rust/notus/src/advisory.rs b/rust/notus/src/advisory.rs index be5971465..a20f89609 100644 --- a/rust/notus/src/advisory.rs +++ b/rust/notus/src/advisory.rs @@ -23,17 +23,18 @@ impl PackageAdvisories { // Iterate through fixed_packages of single advisories for fixed_package in advisory.fixed_packages { // Create Advisory for fixed package information - let adv = match Advisory::create(advisory.oid.clone(), &fixed_package) { + let (pkg_name, adv) = match Advisory::create(advisory.oid.clone(), &fixed_package) { Some(adv) => adv, + // Notus data on system are wrong! None => continue, // TODO: Some handling, at least logging }; // Add advisory to map - match advisory_map.get_mut(&fixed_package.name) { + match advisory_map.get_mut(&pkg_name) { Some(advisories) => { advisories.push(adv); } None => { - advisory_map.insert(fixed_package.name, vec![adv]); + advisory_map.insert(pkg_name, vec![adv]); } }; } @@ -80,59 +81,71 @@ impl

Advisory

where P: Package, { - pub fn create(oid: String, fixed_package: &FixedPackage) -> Option { - match &fixed_package.version { + pub fn create(oid: String, fixed_package: &FixedPackage) -> Option<(String, Self)> { + match &fixed_package { // Package information can be either given by full name, name and full version // or as a range - models::VersionEntry::ByFullName { specifier } => { + models::FixedPackage::ByFullName { + specifier, + full_name, + } => { // Parse package from full name - let package = match P::from_full_name(&fixed_package.name) { + let package = match P::from_full_name(full_name) { Some(pkg) => pkg, None => return None, }; // Create Advisory Entry - Some(Advisory { - oid, - package_information: PackageInformation::Single { - specifier: specifier.clone(), - package, + Some(( + package.get_name(), + Advisory { + oid, + package_information: PackageInformation::Single { + specifier: specifier.clone(), + package, + }, }, - }) + )) } - models::VersionEntry::ByNameAndFullVersion { + models::FixedPackage::ByNameAndFullVersion { full_version, specifier, + name, } => { // Parse package from name and full version - let package = match P::from_name_and_full_version(&fixed_package.name, full_version) - { + let package = match P::from_name_and_full_version(name, full_version) { Some(pkg) => pkg, None => return None, }; // Create Advisory Entry - Some(Advisory { - oid, - package_information: PackageInformation::Single { - specifier: specifier.clone(), - package, + Some(( + package.get_name(), + Advisory { + oid, + package_information: PackageInformation::Single { + specifier: specifier.clone(), + package, + }, }, - }) + )) } - models::VersionEntry::ByRange { range } => { + models::FixedPackage::ByRange { range, name } => { // Parse both packages from name and full version - let start = match P::from_name_and_full_version(&fixed_package.name, &range.start) { + let start = match P::from_name_and_full_version(name, &range.start) { Some(pkg) => pkg, None => return None, }; - let end = match P::from_name_and_full_version(&fixed_package.name, &range.end) { + let end = match P::from_name_and_full_version(name, &range.end) { Some(pkg) => pkg, None => return None, }; // Create Advisory Entry - Some(Advisory { - oid, - package_information: PackageInformation::Range { start, end }, - }) + Some(( + start.get_name(), + Advisory { + oid, + package_information: PackageInformation::Range { start, end }, + }, + )) } } } diff --git a/rust/notus/src/loader/json.rs b/rust/notus/src/loader/json.rs index 95057918b..84c6ce908 100644 --- a/rust/notus/src/loader/json.rs +++ b/rust/notus/src/loader/json.rs @@ -37,7 +37,7 @@ where P: AsRef, { fn load_package_advisories(&self, os: &str) -> Result { - let notus_file = self.path.as_ref().join(os); + let notus_file = self.path.as_ref().join(format!("{os}.notus")); let mut file = File::open(notus_file)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; @@ -60,6 +60,6 @@ mod tests { let mut path = env!("CARGO_MANIFEST_DIR").to_string(); path.push_str("/data"); let loader = JSONAdvisoriesLoader::new(path).unwrap(); - let _ = loader.load_package_advisories("debian_10.notus").unwrap(); + let _ = loader.load_package_advisories("debian_10").unwrap(); } } diff --git a/rust/notus/src/notus.rs b/rust/notus/src/notus.rs index 41fac7f15..c03c9fae7 100644 --- a/rust/notus/src/notus.rs +++ b/rust/notus/src/notus.rs @@ -79,8 +79,10 @@ where } } } + // No advisory for package None => continue, }, + // Unable to parse user input None => continue, // TODO: Some Error handling, at least Logging } } @@ -88,13 +90,13 @@ where results } - pub fn scan(&mut self, os: String, packages: Vec) -> Result { + pub fn scan(&mut self, os: &str, packages: Vec) -> Result { // Load advisories if not loaded - if !self.loaded_advisories.contains_key(&os) { + if !self.loaded_advisories.contains_key(&os.to_string()) { self.loaded_advisories - .insert(os.clone(), self.load_new_advisories(&os)?); + .insert(os.to_string(), self.load_new_advisories(&os)?); } - let advisories = &self.loaded_advisories[&os]; + let advisories = &self.loaded_advisories[&os.to_string()]; // Parse and compare package list depending on package type of loaded advisories let results = match advisories { diff --git a/rust/notus/src/packages/ebuild.rs b/rust/notus/src/packages/ebuild.rs index 139f958fa..484dcbb59 100644 --- a/rust/notus/src/packages/ebuild.rs +++ b/rust/notus/src/packages/ebuild.rs @@ -114,6 +114,7 @@ mod ebuild_tests { } } + #[test] pub fn test_comparability() { let apache1 = EBuild::from_full_name("www-servers/apache-2.4.51-r2").unwrap(); let apache2 = diff --git a/rust/notus/tests/notus.rs b/rust/notus/tests/notus.rs new file mode 100644 index 000000000..282bbf9d9 --- /dev/null +++ b/rust/notus/tests/notus.rs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#[cfg(test)] +mod tests { + use models::{FixedVersion, Specifier}; + use notus::{loader::json::JSONAdvisoriesLoader, notus::Notus}; + + #[test] + fn test_notus() { + let mut path = env!("CARGO_MANIFEST_DIR").to_string(); + path.push_str("/data"); + let loader = JSONAdvisoriesLoader::new(path).unwrap(); + let mut notus = Notus::new(loader); + + let packages = vec![ + "gitlab-ce-16.0.1".to_string(), // vul + "bar-7.6.5".to_string(), // no vul + "grafana-8.5.25".to_string(), // no vul + "grafana8-8.5.23".to_string(), // vul + "grafana9-9.4.7".to_string(), // vul + "foo-1.2.3".to_string(), // no vul + ]; + + let results = notus.scan("debian_10", packages).unwrap(); + assert_eq!(results.len(), 2); + + let result1 = &results["1.3.6.1.4.1.25623.1.1.7.2.2023.10089729899100"]; + let result2 = &results["1.3.6.1.4.1.25623.1.1.7.2.2023.0988598199100"]; + + assert_eq!(result1.len(), 1); + let vul_pkg = &result1[0]; + assert_eq!(vul_pkg.name, "gitlab-ce".to_string()); + assert_eq!(vul_pkg.installed_version, "16.0.1".to_string()); + assert!(matches!( + &vul_pkg.fixed_version, + FixedVersion::Range { start, end } if start == "16.0.0" && end == "16.0.7" + )); + + assert_eq!(result2.len(), 2); + for vul_pkg in result2 { + match vul_pkg.name.as_str() { + "grafana8" => { + assert_eq!(vul_pkg.installed_version, "8.5.23".to_string()); + assert!(matches!( + &vul_pkg.fixed_version, + FixedVersion::Single { version, specifier } if version == "8.5.24" && matches!(specifier, Specifier::GE) + )); + } + "grafana9" => { + assert_eq!(vul_pkg.installed_version, "9.4.7".to_string()); + assert!(matches!( + &vul_pkg.fixed_version, + FixedVersion::Range { start, end } if start == "9.4.0" && end == "9.4.9" + )); + } + _ => panic!("Unexpected vulnerable package: {}", vul_pkg.name), + } + } + } +}