Skip to content

Commit

Permalink
Change: renaming of advisories to product and advisory to vt (greenbo…
Browse files Browse the repository at this point in the history
…ne#1539)

In order to reduce confusing naming schemes the name advisory is completely dropt for the notus implementation and replaced by either product, when
referencing the file or collection of advisories and to vulnerability tests when tailking about the actual tests that are executed during a scan.

Added missing developer documentation for functions, structs, etc.

Added Readme for the notus rust library
  • Loading branch information
Kraemii authored Dec 18, 2023
1 parent c942d22 commit 27179f3
Show file tree
Hide file tree
Showing 23 changed files with 327 additions and 229 deletions.
2 changes: 1 addition & 1 deletion rust/examples/openvasd/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ path = "/var/lib/openvasd/storage"

[notus]
# path to the notus feed. This is required for the /notus endpoint.
advisory_path = "/var/lib/notus/products/"
products_path = "/var/lib/notus/products/"
4 changes: 2 additions & 2 deletions rust/models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
//
// SPDX-License-Identifier: GPL-2.0-or-later

mod advisories;
mod credential;
mod host_info;
mod parameter;
mod port;
mod product;
mod result;
mod scan;
mod scan_action;
Expand All @@ -15,11 +15,11 @@ mod status;
mod target;
mod vt;

pub use advisories::*;
pub use credential::*;
pub use host_info::*;
pub use parameter::*;
pub use port::*;
pub use product::*;
pub use result::*;
pub use scan::*;
pub use scan_action::*;
Expand Down
13 changes: 7 additions & 6 deletions rust/models/src/advisories.rs → rust/models/src/product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
//
// SPDX-License-Identifier: GPL-2.0-or-later

/// Represents an Advisories json file for notus
/// Represents an product json file for notus
#[cfg_attr(feature = "serde_support", derive(serde::Deserialize))]
#[derive(Debug)]
pub struct Advisories {
pub struct Product {
/// Version of the file, some version might not be supported by notus
pub version: String,
/// Package type, important for parsing the corresponding package
pub package_type: PackageType,
/// List of advisories
pub advisories: Vec<Advisory>,
/// List of vulnerability tests for product
#[cfg_attr(feature = "serde_support", serde(rename = "advisories"))]
pub vulnerability_tests: Vec<VulnerabilityTest>,
}

/// Enum of supported package types
Expand All @@ -28,10 +29,10 @@ pub enum PackageType {
SLACK,
}

/// Representing a single Advisory entry
/// Representing a single Vulnerability Test entry
#[cfg_attr(feature = "serde_support", derive(serde::Deserialize))]
#[derive(Debug)]
pub struct Advisory {
pub struct VulnerabilityTest {
/// OID to identify vulnerability
pub oid: String,
/// List of affected packages, including the fixed version
Expand Down
32 changes: 32 additions & 0 deletions rust/notus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Notus Scanner

This is the rust library implementation of the Notus Scanner originating from https://github.com/greenbone/notus-scanner

Notus Scanner detects vulnerable products in a system environment. The scanning
method is to evaluate internal system information. It does this very fast and
even detects currently inactive products because it does not need to interact
with each of the products.

To report about vulnerabilities, Notus Scanner receives collected system
information on the one hand and accesses the vulnerability information from the
notus feed on the other. Both input elements are in table form: the system
information is specific to each environment and the vulnerability information is
specific to each system type.

Notus Scanner integrates into the Greenbone Vulnerability Management framework
which allows to let it scan entire networks within a single task. Any
vulnerability test in the format of `.notus` files inside the Greenbone Feed
will be considered and automatically matched with the scanned environments.

A system environment can be the operating system of a host. But it could also be
containers like Docker or virtual machines. Neither of these need to be actively
running for scanning.

The Notus Scanner is implemented as a Rust library and published under an Open Source
license. Greenbone Networks maintains and extends it since it is embedded in the
Greenbone Professional Edition as well as in the Greenbone Cloud Services.

Greenbone also keeps the vulnerability information up-to-date via the feed on a
daily basis. The `.notus` format specification is open and part of the
documentation. To get the required notus files use the greenbone feed sync
https://github.com/greenbone/greenbone-feed-sync
50 changes: 26 additions & 24 deletions rust/notus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,43 @@ use std::{fmt::Display, io};
use models::FixedPackage;
use nasl_syntax::LoadError;

/// Error types that can occur, when unable to load a products file.
#[derive(Debug)]
pub enum LoadAdvisoryErrorKind {
pub enum LoadProductErrorKind {
IOError(io::Error),
LoadError(LoadError),
}

impl Display for LoadAdvisoryErrorKind {
impl Display for LoadProductErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LoadAdvisoryErrorKind::IOError(e) => write!(f, "{e}"),
LoadAdvisoryErrorKind::LoadError(e) => write!(f, "{e}"),
LoadProductErrorKind::IOError(e) => write!(f, "{e}"),
LoadProductErrorKind::LoadError(e) => write!(f, "{e}"),
}
}
}

/// Errors that might occur, when working with the notus library.
#[derive(Debug)]
pub enum Error {
// The directory containing the notus advisories does not exist
MissingAdvisoryDir(String),
/// The given notus advisory directory is a file
AdvisoryDirIsFile(String),
/// The given notus advisory directory is not readable
UnreadableAdvisoryDir(String, io::Error),
// The directory containing the notus products does not exist
MissingProductsDir(String),
/// The given notus products directory is a file
ProductsDirIsFile(String),
/// The given notus products directory is not readable
UnreadableProductsDir(String, io::Error),
/// There are no corresponding notus files for the given Operating System
UnknownOs(String),
/// General error while loading notus advisories
LoadAdvisoryError(String, LoadAdvisoryErrorKind),
/// Unable to parse notus advisory file due to a JSON error
UnknownProduct(String),
/// General error while loading notus product
LoadProductError(String, LoadProductErrorKind),
/// Unable to parse notus product file due to a JSON error
JSONParseError(String, serde_json::Error),
/// The version of the notus advisory file is not supported
/// The version of the notus product file is not supported
UnsupportedVersion(String, String, String),
/// Unable to parse a given package
PackageParseError(String),
/// Unable to parse a package in the notus advisory file
AdvisoryParseError(String, FixedPackage),
/// Unable to parse a package in the notus product file
VulnerabilityTestParseError(String, FixedPackage),
/// Some issues caused by a HashsumLoader
HashsumLoadError(feed::VerifyError),
/// Signature check error
Expand All @@ -51,15 +53,15 @@ pub enum Error {
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::UnknownOs(path) => write!(f, "the File {path} was not found, that is either due to a typo or missing notus advisories for the corresponding OS"),
Error::UnknownProduct(path) => write!(f, "the File {path} was not found, that is either due to a typo or missing notus product for the corresponding OS"),
Error::JSONParseError(path, json_err) => write!(f, "unable to parse Notus file {path}. The corresponding parse error was: {json_err}"),
Error::UnsupportedVersion(path, version1, version2) => write!(f, "the version of the parsed advisory file {path} is {version1}. This version is currently not supported, the version {version2} is required"),
Error::MissingAdvisoryDir(path) => write!(f, "The directory {path}, which should contain the notus advisories does not exist"),
Error::AdvisoryDirIsFile(path) => write!(f, "The given notus advisory directory {path} is a file"),
Error::LoadAdvisoryError(path, err) => write!(f, "Unable to load advisories from {path}: {err}"),
Error::UnsupportedVersion(path, version1, version2) => write!(f, "the version of the parsed product file {path} is {version1}. This version is currently not supported, the version {version2} is required"),
Error::MissingProductsDir(path) => write!(f, "The directory {path}, which should contain the notus product does not exist"),
Error::ProductsDirIsFile(path) => write!(f, "The given notus products directory {path} is a file"),
Error::LoadProductError(path, err) => write!(f, "Unable to load product from {path}: {err}"),
Error::PackageParseError(pkg) => write!(f, "Unable to parse the given package {pkg}"),
Error::AdvisoryParseError(path, pkg) => write!(f, "Unable to parse fixed package information {:?} in the advisories {path}", pkg),
Error::UnreadableAdvisoryDir(path, err) => write!(f, "The directory {path} is not readable: {err}"),
Error::VulnerabilityTestParseError(path, pkg) => write!(f, "Unable to parse fixed package information {:?} in the product {path}", pkg),
Error::UnreadableProductsDir(path, err) => write!(f, "The directory {path} is not readable: {err}"),
Error::HashsumLoadError(err) => write!(f, "Hashsum verification failed: {err}"),
Error::SignatureCheckError(err) => write!(f, "Signature check failed: {err}"),
}
Expand Down
2 changes: 1 addition & 1 deletion rust/notus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
pub mod loader;
pub mod packages;

pub mod advisory;
pub mod error;
pub mod notus;
pub mod vts;
70 changes: 33 additions & 37 deletions rust/notus/src/loader/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,34 @@ use std::{
time::SystemTime,
};

use models::Advisories;
use models::Product;

use crate::error::Error;
use feed::SignatureChecker;

use super::{AdvisoriesLoader, FeedStamp};
use super::{FeedStamp, ProductLoader};

#[derive(Debug, Clone)]
pub struct FSAdvisoryLoader<P>
pub struct FSProductLoader<P>
where
P: AsRef<Path>,
{
root: P,
}

impl<P> FSAdvisoryLoader<P>
impl<P> FSProductLoader<P>
where
P: AsRef<Path>,
{
pub fn new(path: P) -> Result<Self, Error> {
if !path.as_ref().exists() {
return Err(Error::MissingAdvisoryDir(
return Err(Error::MissingProductsDir(
path.as_ref().to_string_lossy().to_string(),
));
}

if !path.as_ref().is_dir() {
return Err(Error::AdvisoryDirIsFile(
return Err(Error::ProductsDirIsFile(
path.as_ref().to_string_lossy().to_string(),
));
}
Expand All @@ -45,35 +45,32 @@ where
}
}

impl<P> SignatureChecker for FSAdvisoryLoader<P>
where
P: AsRef<Path>,
{}
impl<P> SignatureChecker for FSProductLoader<P> where P: AsRef<Path> {}

impl<P> AdvisoriesLoader for FSAdvisoryLoader<P>
impl<P> ProductLoader for FSProductLoader<P>
where
P: AsRef<Path>,
{
fn load_package_advisories(&self, os: &str) -> Result<(Advisories, FeedStamp), Error> {
fn load_product(&self, os: &str) -> Result<(Product, FeedStamp), Error> {
let notus_file = self.root.as_ref().join(format!("{os}.notus"));
let notus_file_str = notus_file.to_string_lossy().to_string();
let mut file = match File::open(notus_file) {
Ok(file) => file,
Err(err) => {
if matches!(err.kind(), io::ErrorKind::NotFound) {
return Err(Error::UnknownOs(os.to_string()));
return Err(Error::UnknownProduct(os.to_string()));
}
return Err(Error::LoadAdvisoryError(
return Err(Error::LoadProductError(
notus_file_str,
crate::error::LoadAdvisoryErrorKind::IOError(err),
crate::error::LoadProductErrorKind::IOError(err),
));
}
};
let mut buf = String::new();
if let Err(err) = file.read_to_string(&mut buf) {
return Err(Error::LoadAdvisoryError(
return Err(Error::LoadProductError(
notus_file_str,
crate::error::LoadAdvisoryErrorKind::IOError(err),
crate::error::LoadProductErrorKind::IOError(err),
));
}
let mod_time = match file.metadata() {
Expand All @@ -89,9 +86,9 @@ where
}
}

fn get_available_os(&self) -> Result<Vec<String>, Error> {
fn get_products(&self) -> Result<Vec<String>, Error> {
let paths = fs::read_dir(self.root.as_ref()).map_err(|err| {
Error::UnreadableAdvisoryDir(self.root.as_ref().to_string_lossy().to_string(), err)
Error::UnreadableProductsDir(self.root.as_ref().to_string_lossy().to_string(), err)
})?;

let mut available_os = vec![];
Expand Down Expand Up @@ -131,84 +128,83 @@ where
/// Perform a signature check of the sha256sums file
fn verify_signature(&self) -> Result<(), feed::VerifyError> {
let p = self.root.as_ref().to_str().unwrap_or_default();
<FSAdvisoryLoader<P> as self::SignatureChecker>::signature_check(p)
<FSProductLoader<P> as self::SignatureChecker>::signature_check(p)
}
/// Get the notus products root directory
fn get_root_dir(&self) -> Result<String, Error> {
let path = self.root.as_ref().to_str().unwrap();
Ok(path.to_string())
}

}

#[cfg(test)]
mod tests {

use crate::{error::Error, loader::AdvisoriesLoader};
use crate::{error::Error, loader::ProductLoader};

use super::FSAdvisoryLoader;
use super::FSProductLoader;

#[test]
fn test_load_advisories() {
fn test_load_vts() {
let mut path = env!("CARGO_MANIFEST_DIR").to_string();
path.push_str("/data");
let loader = FSAdvisoryLoader::new(path).unwrap();
let _ = loader.load_package_advisories("debian_10").unwrap();
let loader = FSProductLoader::new(path).unwrap();
let _ = loader.load_product("debian_10").unwrap();
}

#[test]
fn test_err_missing_advisory_dir() {
fn test_err_missing_products_dir() {
let mut path = env!("CARGO_MANIFEST_DIR").to_string();
path.push_str("/data_foo");
assert!(
matches!(FSAdvisoryLoader::new(path.clone()).expect_err("Should fail"), Error::MissingAdvisoryDir(p) if p == path)
matches!(FSProductLoader::new(path.clone()).expect_err("Should fail"), Error::MissingProductsDir(p) if p == path)
);
}

#[test]
fn test_err_advisory_dir_is_file() {
fn test_err_products_dir_is_file() {
let mut path = env!("CARGO_MANIFEST_DIR").to_string();
path.push_str("/data/debian_10.notus");
assert!(
matches!(FSAdvisoryLoader::new(path.clone()).expect_err("Should fail"), Error::AdvisoryDirIsFile(p) if p == path)
matches!(FSProductLoader::new(path.clone()).expect_err("Should fail"), Error::ProductsDirIsFile(p) if p == path)
);
}

#[test]
fn test_err_unknown_os() {
let mut path = env!("CARGO_MANIFEST_DIR").to_string();
path.push_str("/data");
let loader = FSAdvisoryLoader::new(path).unwrap();
let loader = FSProductLoader::new(path).unwrap();

let os = "foo";
assert!(
matches!(loader.load_package_advisories(os).expect_err("Should fail"), Error::UnknownOs(o) if o == os)
matches!(loader.load_product(os).expect_err("Should fail"), Error::UnknownProduct(o) if o == os)
);
}

#[test]
fn test_err_json_parse() {
let mut path = env!("CARGO_MANIFEST_DIR").to_string();
path.push_str("/data");
let loader = FSAdvisoryLoader::new(path.clone()).unwrap();
let loader = FSProductLoader::new(path.clone()).unwrap();

let os = "debian_10_json_parse_err";
assert!(
matches!(loader.load_package_advisories(os).expect_err("Should fail"), Error::JSONParseError(p, _) if p == format!("{path}/{os}.notus"))
matches!(loader.load_product(os).expect_err("Should fail"), Error::JSONParseError(p, _) if p == format!("{path}/{os}.notus"))
);
}

#[test]
fn test_available_os() {
let mut path = env!("CARGO_MANIFEST_DIR").to_string();
path.push_str("/data");
let loader = FSAdvisoryLoader::new(path.clone()).unwrap();
let loader = FSProductLoader::new(path.clone()).unwrap();

let available_os = loader.get_available_os().unwrap();
let available_os = loader.get_products().unwrap();

assert_eq!(available_os.len(), 3);
assert!(available_os.contains(&"debian_10".to_string()));
assert!(available_os.contains(&"debian_10_json_parse_err".to_string()));
assert!(available_os.contains(&"debian_10_advisory_parse_err".to_string()));
assert!(available_os.contains(&"debian_10_product_parse_err".to_string()));
}
}
Loading

0 comments on commit 27179f3

Please sign in to comment.