Skip to content

Commit

Permalink
Add: integration test for notus
Browse files Browse the repository at this point in the history
This test starts a small notus scan and checks, if it generates the correct results
  • Loading branch information
Kraemii committed Nov 7, 2023
1 parent 4ae0364 commit 5998523
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 51 deletions.
19 changes: 4 additions & 15 deletions rust/models/src/advisories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,23 @@ pub struct Advisory {
pub fixed_packages: Vec<FixedPackage>,
}

/// 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: >, <, >=, <=, =
Expand Down
2 changes: 2 additions & 0 deletions rust/models/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,15 @@ pub enum ResultType {
pub type NotusResults = HashMap<String, Vec<VulnerablePackage>>;

#[cfg_attr(feature = "serde_support", derive(serde::Serialize))]
#[derive(Debug)]
pub struct VulnerablePackage {
pub name: String,
pub installed_version: String,
pub fixed_version: FixedVersion,
}

#[cfg_attr(feature = "serde_support", derive(serde::Serialize))]
#[derive(Debug)]
pub enum FixedVersion {
Single {
version: String,
Expand Down
1 change: 0 additions & 1 deletion rust/notus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
71 changes: 42 additions & 29 deletions rust/notus/src/advisory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
};
}
Expand Down Expand Up @@ -80,59 +81,71 @@ impl<P> Advisory<P>
where
P: Package,
{
pub fn create(oid: String, fixed_package: &FixedPackage) -> Option<Self> {
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 },
},
))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions rust/notus/src/loader/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ where
P: AsRef<Path>,
{
fn load_package_advisories(&self, os: &str) -> Result<Advisories, Error> {
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)?;
Expand All @@ -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();
}
}
10 changes: 6 additions & 4 deletions rust/notus/src/notus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,24 @@ where
}
}
}
// No advisory for package
None => continue,
},
// Unable to parse user input
None => continue, // TODO: Some Error handling, at least Logging
}
}

results
}

pub fn scan(&mut self, os: String, packages: Vec<String>) -> Result<NotusResults, NotusError> {
pub fn scan(&mut self, os: &str, packages: Vec<String>) -> Result<NotusResults, NotusError> {
// 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 {
Expand Down
1 change: 1 addition & 0 deletions rust/notus/src/packages/ebuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
62 changes: 62 additions & 0 deletions rust/notus/tests/notus.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
}
}

0 comments on commit 5998523

Please sign in to comment.