Skip to content

Commit

Permalink
Add: notus only mode
Browse files Browse the repository at this point in the history
Introduces modes to control openvasd.

Currently it has the modes:
- service - enables all endpoints and the whole functionality (default)
- service_notus - disables /scan endpoint and nasl feed

The configuration is described within rust/examples/openvasd/config.example.toml
and there is the possibility to set `OPENVASD_MOD` environment variable.

For an example to start openvasd in the notus mode you can it from the
examples dir:

```
FEED_PATH=feed/nasl NOTUS_ADVISORIES=feed/notus/advisories/ OPENVASD_LOG=debug OPENVASD_MODE=service_notus ../target/release/openvasd
```

and try to call `localhost:3000/scans`.
  • Loading branch information
nichtsfrei authored and Kraemii committed Mar 11, 2024
1 parent e904a94 commit 10f1b0c
Show file tree
Hide file tree
Showing 14 changed files with 533 additions and 301 deletions.
6 changes: 6 additions & 0 deletions rust/examples/openvasd/config.example.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# openvasd contains several modes to control the behaviour of it.
# Service enables nasl and notus feed observations all endpoints.
# mode = "service"
# Notus disables /scan endpoints and just observes the notus feed.
# mode = "service_notus"

[feed]
# path to the openvas feed. This is required for the /vts endpoint.
path = "/var/lib/openvas/plugins"
Expand Down
5 changes: 5 additions & 0 deletions rust/nasl-syntax/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ where
pub fn new(root: P) -> Self {
Self { root }
}

/// Returns the used path
pub fn root(&self) -> &Path {
self.root.as_ref()
}
}

impl<P> AsBufReader<File> for FSPluginLoader<P>
Expand Down
4 changes: 2 additions & 2 deletions rust/notus/src/notus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ where

fn load_new_product(&self, os: &str) -> Result<(Product, FeedStamp), Error> {
tracing::debug!(
"Loading notus product from {:?}",
self.loader.get_root_dir()
root=?self.loader.get_root_dir(),
"Loading notus product",
);
let (product, stamp) = self.loader.load_product(os)?;

Expand Down
57 changes: 55 additions & 2 deletions rust/openvasd/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,45 @@ impl Default for Listener {
}
}

#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
/// Describes different modes openvasd can be run as.
///
/// `Service` prefix means that openvasd is a http service used by others.
/// `Client` prefix means that openvasd is run as a client and calls other services.
pub enum Mode {
#[default]
#[serde(rename = "service")]
/// Enables all endpoints and moniors feed and active scans.
Service,
/// Disables everything but the notus endpoints
#[serde(rename = "service_notus")]
ServiceNotus,
}

impl TypedValueParser for Mode {
type Value = Self;

fn parse_ref(
&self,
cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
Ok(match value.to_str().unwrap_or_default() {
"service" => Self::Service,
"service_notus" => Self::ServiceNotus,
_ => {
let mut cmd = cmd.clone();
let err = cmd.error(
clap::error::ErrorKind::InvalidValue,
"`{}` is not a wrapper type.",
);
return Err(err);
}
})
}
}

#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct Endpoints {
pub enable_get_scans: bool,
Expand Down Expand Up @@ -243,6 +282,8 @@ pub struct Storage {

#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct Config {
#[serde(default)]
pub mode: Mode,
#[serde(default)]
pub feed: Feed,
#[serde(default)]
Expand Down Expand Up @@ -330,14 +371,14 @@ impl Config {
)
.arg(
clap::Arg::new("notus-advisories")
.env("NOTUS_SCANNER_PRODUCTS_DIRECTORY")
.env("NOTUS_ADVISORIES")
.long("advisories")
.value_parser(clap::builder::PathBufValueParser::new())
.action(ArgAction::Set)
.help("Path containing the Notus advisories directory"))
.arg(
clap::Arg::new("notus-products")
.env("NOTUS_SCANNER_PRODUCTS_DIRECTORY")
.env("NOTUS_PRODUCTS")
.long("products")
.value_parser(clap::builder::PathBufValueParser::new())
.action(ArgAction::Set)
Expand Down Expand Up @@ -407,6 +448,7 @@ impl Config {
.long("max-running-scans")
.action(ArgAction::Set)
.help("Maximum number of active running scans, omit for no limits")

)
.arg(
clap::Arg::new("min-free-mem")
Expand Down Expand Up @@ -485,6 +527,14 @@ impl Config {
.short('L')
.help("Level of log messages to be shown. TRACE > DEBUG > INFO > WARN > ERROR"),
)
.arg(
clap::Arg::new("mode")
.env("OPENVASD_MODE")
.long("mode")
.value_name("service,service_notus")
.value_parser(Mode::Service)
.help("Sets the openvasd mode"),
)
.get_matches();
let mut config = match cmds.get_one::<String>("config") {
Some(path) => Self::from_file(path),
Expand Down Expand Up @@ -560,6 +610,9 @@ impl Config {
if let Some(path) = cmds.get_one::<PathBuf>("storage_path") {
config.storage.fs.path = path.clone();
}
if let Some(mode) = cmds.get_one::<Mode>("mode") {
config.mode = mode.clone();
}
if let Some(key) = cmds.get_one::<String>("storage_key") {
if !key.is_empty() {
config.storage.fs.key = Some(key.clone());
Expand Down
13 changes: 13 additions & 0 deletions rust/openvasd/src/controller/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct ContextBuilder<S, DB, T> {
response: response::Response,
notus: Option<NotusWrapper>,
scheduler_config: Option<config::Scheduler>,
mode: config::Mode,
}

impl<S>
Expand All @@ -47,11 +48,17 @@ impl<S>
response: response::Response::default(),
notus: None,
scheduler_config: None,
mode: config::Mode::default(),
}
}
}

impl<S, DB, T> ContextBuilder<S, DB, T> {
/// Sets the mode.
pub fn mode(mut self, mode: config::Mode) -> Self {
self.mode = mode;
self
}
/// Sets the feed config.
pub fn feed_config(mut self, config: config::Feed) -> Self {
self.feed_config = Some(config);
Expand Down Expand Up @@ -104,6 +111,7 @@ impl<S, DB, T> ContextBuilder<S, DB, T> {
response,
notus,
scheduler_config,
mode,
} = self;
ContextBuilder {
scanner,
Expand All @@ -115,6 +123,7 @@ impl<S, DB, T> ContextBuilder<S, DB, T> {
response,
notus,
scheduler_config,
mode,
}
}
}
Expand All @@ -139,6 +148,7 @@ impl<S, DB> ContextBuilder<S, DB, NoScanner> {
storage,
notus,
scheduler_config,
mode,
} = self;
ContextBuilder {
scanner: Scanner(scanner),
Expand All @@ -150,6 +160,7 @@ impl<S, DB> ContextBuilder<S, DB, NoScanner> {
response,
notus,
scheduler_config,
mode,
}
}
}
Expand All @@ -169,6 +180,7 @@ impl<S, DB> ContextBuilder<S, DB, Scanner<S>> {
api_key: self.api_key,
enable_get_scans: self.enable_get_scans,
notus: self.notus,
mode: self.mode,
}
}
}
Expand All @@ -186,6 +198,7 @@ pub struct Context<S, DB> {
pub api_key: Option<String>,
/// Whether to enable the GET /scans endpoint
pub enable_get_scans: bool,
pub mode: config::Mode,
/// Aborts the background loops
pub abort: RwLock<bool>,
/// Notus Scanner
Expand Down
40 changes: 27 additions & 13 deletions rust/openvasd/src/controller/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use hyper::{Method, Request};
use models::scanner::{ScanDeleter, ScanResultFetcher, ScanStarter, ScanStopper};

use crate::{
config,
controller::ClientHash,
notus::NotusScanner,
scheduling,
Expand Down Expand Up @@ -49,24 +50,37 @@ enum KnownPaths {

impl KnownPaths {
pub fn requires_id(&self) -> bool {
!matches!(self, Self::Health(_) | Self::Vts(_) | Self::Notus(_))
!matches!(
self,
Self::Unknown | Self::Health(_) | Self::Vts(_) | Self::Notus(_)
)
}

#[tracing::instrument]
/// Parses a path and returns the corresponding `KnownPaths` variant.
fn from_path(path: &str) -> Self {
fn from_path(path: &str, mode: &config::Mode) -> Self {
let mut parts = path.split('/').filter(|s| !s.is_empty());
match parts.next() {
Some("scans") => match parts.next() {
Some(id) => match parts.next() {
Some("results") => {
KnownPaths::ScanResults(id.to_string(), parts.next().map(|s| s.to_string()))
Some("scans") => match mode {
config::Mode::Service => {
tracing::debug!(?mode, ?path, "Scan endpoint enabled");
match parts.next() {
Some(id) => match parts.next() {
Some("results") => KnownPaths::ScanResults(
id.to_string(),
parts.next().map(|s| s.to_string()),
),
Some("status") => KnownPaths::ScanStatus(id.to_string()),
Some(_) => KnownPaths::Unknown,
None => KnownPaths::Scans(Some(id.to_string())),
},
None => KnownPaths::Scans(None),
}
Some("status") => KnownPaths::ScanStatus(id.to_string()),
Some(_) => KnownPaths::Unknown,
None => KnownPaths::Scans(Some(id.to_string())),
},
None => KnownPaths::Scans(None),
}
config::Mode::ServiceNotus => {
tracing::debug!(?mode, ?path, "Scan endpoint disabled");
KnownPaths::Unknown
}
},
Some("vts") => match parts.next() {
Some(oid) => KnownPaths::Vts(Some(oid.to_string())),
Expand All @@ -83,7 +97,7 @@ impl KnownPaths {
_ => KnownPaths::Unknown,
},
_ => {
tracing::trace!("Unknown path: {path}");
tracing::trace!(?path, "Unknown");
KnownPaths::Unknown
}
}
Expand Down Expand Up @@ -166,7 +180,7 @@ where
if req.method() == Method::HEAD {
return Ok(ctx.response.empty(hyper::StatusCode::OK));
}
let kp = KnownPaths::from_path(req.uri().path());
let kp = KnownPaths::from_path(req.uri().path(), &ctx.mode);
let cid: Option<ClientHash> = {
match &*cid {
ClientIdentifier::Unknown => {
Expand Down
67 changes: 42 additions & 25 deletions rust/openvasd/src/controller/feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,46 @@ use std::sync::Arc;

use models::scanner::Scanner;

use crate::{feed::FeedIdentifier, storage::NVTStorer as _};
use crate::{
feed::FeedIdentifier,
storage::{FeedHash, NVTStorer as _},
};

use super::context::Context;

async fn changed_hash(signature_check: bool, feeds: &[FeedHash]) -> Result<Vec<FeedHash>, ()> {
let mut result = Vec::with_capacity(feeds.len());
for h in feeds {
if signature_check {
if let Err(err) = feed::verify::check_signature(&h.path) {
tracing::warn!(
sumsfile=%h.path.display(),
error=%err,
"Signature is incorrect, skipping",
);
return Err(());
}
}

let path = h.path.clone();
let hash = tokio::task::spawn_blocking(move || match FeedIdentifier::sumfile_hash(path) {
Ok(h) => h,
Err(e) => {
tracing::warn!(%e, "Failed to compute sumfile hash");
"".to_string()
}
})
.await
.unwrap();
if hash != h.hash {
let mut nh = h.clone();
nh.hash = hash;
result.push(nh);
}
}
Ok(result)
}

pub async fn fetch<S, DB>(ctx: Arc<Context<S, DB>>)
where
S: Scanner + 'static + std::marker::Send + std::marker::Sync,
Expand All @@ -20,38 +56,19 @@ where
let interval = cfg.check_interval;
let signature_check = cfg.signature_check;
loop {
let path = cfg.path.clone();
if *ctx.abort.read().unwrap() {
tracing::trace!("aborting");
break;
};
let last_hash = ctx.scheduler.feed_hash().await;
if signature_check {
if let Err(err) = feed::verify::check_signature(&path) {
tracing::warn!(
"Signature of {} is not corredct, skipping: {}",
path.display(),
err
);
}
}

let hash =
tokio::task::spawn_blocking(move || match FeedIdentifier::sumfile_hash(path) {
Ok(h) => h,
Err(e) => {
tracing::warn!("Failed to compute sumfile hash: {e:?}");
"".to_string()
if let Ok(nh) = changed_hash(signature_check, &last_hash).await {
if !nh.is_empty() {
if let Err(err) = ctx.scheduler.synchronize_feeds(nh).await {
tracing::warn!(%err, "Unable to sync feed")
}
})
.await
.unwrap();
if last_hash.is_empty() || last_hash != hash {
match ctx.scheduler.synchronize_feeds(hash).await {
Ok(_) => {}
Err(e) => tracing::warn!("Unable to sync feed: {e}"),
}
}

tokio::time::sleep(interval).await;
}
}
Expand Down
Loading

0 comments on commit 10f1b0c

Please sign in to comment.