Skip to content

Commit

Permalink
Add: scan scheduler
Browse files Browse the repository at this point in the history
Changes context to contain only a scheduler and not any scanner or
storage directly. This to ensure that each command goes through the
scheduler so that it can keep track if a feed update is running or a
scan is queued.

Additionally the background processes call the scheduler to synchronize
scans and feed. A feed can only be updated when no scans are running and
a scan only started when no feed update is running.

This means essentially that the Scheduler within scheduling plays a core
role in openvasd shifting away from direct scanner access.

Since a scheduler uses only standard interfaces of models::scanner each
scanner needs to keep track by themselves depending on their technical
solution. In a nutshell this means that each scanner must be implemented
within it's crate:
- osp has a scanner implementation
- openvasctl has a scanner implementation
  • Loading branch information
nichtsfrei committed Mar 1, 2024
1 parent 668ef9e commit 51a03d6
Show file tree
Hide file tree
Showing 19 changed files with 704 additions and 787 deletions.
2 changes: 2 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions rust/models/src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::{scanner_preference::ScannerPreference, target::Target, vt::VT};
serde(deny_unknown_fields)
)]
pub struct Scan {
#[cfg_attr(feature = "serde_support", serde(default = "uuid"))]
#[cfg_attr(feature = "serde_support", serde(default))]
/// Unique ID of a scan
pub scan_id: String,
/// Information about the target to scan
Expand All @@ -24,6 +24,3 @@ pub struct Scan {
pub vts: Vec<VT>,
}

fn uuid() -> String {
uuid::Uuid::new_v4().to_string()
}
1 change: 1 addition & 0 deletions rust/openvasctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ sysinfo = "0.30.5"
tokio = { version = "1.36.0", features = ["full"] }
tracing = "0.1.40"
serde = { version = "1", features = ["derive"], optional = true }
async-trait = "0.1.77"

[features]
default = ["serde_support"]
Expand Down
128 changes: 0 additions & 128 deletions rust/openvasctl/src/ctl.rs
Original file line number Diff line number Diff line change
@@ -1,128 +0,0 @@

use std::{collections::HashMap, sync::{Arc, Mutex}, process::Child};

use crate::{
cmd::{self, check_sudo},
error::OpenvasError,
};

#[derive(Default)]
pub struct ScanContainer {
init: HashMap<String, ()>,
running: HashMap<String, Child>,
}

pub struct OpenvasController {
scans: Arc<Mutex<ScanContainer>>,
sudo: bool,
}

impl OpenvasController {
/// Create a new OpenvasController
pub fn new() -> Result<Self, OpenvasError> {
if !cmd::check() {
return Err(OpenvasError::MissingExec);
}

Ok(Self {
scans: Default::default(),
sudo: check_sudo(),
})
}

/// Add a scan into the init phase
fn add_init(&mut self, id: String) -> bool {
self.scans.lock().unwrap().init.insert(id, ()).is_none()
}

/// Remove a scan from the init phase
fn remove_init(&mut self, id: &String) -> bool {
self.scans.lock().unwrap().init.remove(id).is_some()
}

/// Removes a scan from init and add it to the list of running scans
fn add_running(&mut self, id: String) -> Result<bool, OpenvasError> {
let mut container = self.scans.lock().unwrap();
if container.init.remove(&id).is_none() {
return Ok(false);
}
let openvas =
cmd::start(&id, self.sudo, None).map_err(|_| OpenvasError::UnableToRunExec)?;
container.running.insert(id, openvas);
Ok(true)
}

/// Remove a scan from the list of running scans and returns the Child process
fn remove_running(&mut self, id: &String) -> Option<Child> {
self.scans.lock().unwrap().running.remove(id)
}

/// Stops a scan with given ID. This will set a key in redis and indirectly
/// sends SIGUSR1 to the running scan process by running openvas with the
/// --stop-scan option.
pub fn stop_scan(&mut self, id: &str) -> Result<(), OpenvasError> {
let scan_id = id.to_string();

// TODO: Set stop scan flag in redis?

if self.remove_init(&scan_id) {
return Ok(());
}

let mut scan = match self.remove_running(&scan_id) {
Some(scan) => scan,
None => return Err(OpenvasError::ScanNotFound(scan_id)),
};

cmd::stop(&scan_id, self.sudo)
.map_err(OpenvasError::CmdError)?
.wait()
.map_err(OpenvasError::CmdError)?;

scan.wait().map_err(OpenvasError::CmdError)?;

// TODO: Clean redis DB

Ok(())
}

/// Prepare scan and start it
pub fn start_scan(&mut self, scan: models::Scan) -> Result<(), OpenvasError> {
self.add_init(scan.scan_id.clone());

// TODO: Create new DB for Scan
// TODO: Add Scan ID to DB
// TODO: Prepare Target (hosts, ports, credentials)
// TODO: Prepare Plugins
// TODO: Prepare main kbindex
// TODO: Prepare host options
// TODO: Prepare scan params
// TODO: Prepare reverse lookup option (maybe part of target)
// TODO: Prepare alive test option (maybe part of target)

if !self.add_running(scan.scan_id)? {
return Ok(());
}

// TODO: Control loop (check for status and results)?
todo!();
}
}
/// The ScanController is the core for the scheduler to manage scans.
pub trait ScanController {
/// Prepares and starts a requested scan.
fn start_scan(&self, scan: models::Scan) -> Result<(), OpenvasError>;
/// Stops a scan that is either initializing or running. If the Scan is either unknown or
/// already finished, an error is returned.
fn stop_scan(&self, id: &str) -> Result<(), OpenvasError>;
/// Return the number of currently initializing + active running scans.
fn num_running(&self) -> usize;
/// Return the number of currently initializing scans
fn num_init(&self) -> usize;
/// Marks a given scan ID as running. This is used for safely transfer scan status between the
/// scheduler and Controller.
fn set_init(&self, id: &str);
/// Returns if a given scan ID is known by the controller by either being initializing,
/// running or finished.
fn exists(&self, id: &str) -> bool;
}
2 changes: 0 additions & 2 deletions rust/openvasctl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

pub mod cmd;
pub mod config;
pub mod ctl;
pub mod error;
pub mod openvas_redis;
pub mod pref_handler;
pub mod result_collector;
pub mod openvas;
pub mod scheduler;
142 changes: 79 additions & 63 deletions rust/openvasctl/src/openvas.rs
Original file line number Diff line number Diff line change
@@ -1,79 +1,64 @@
use async_trait::async_trait;
use models::{
scanner::{
Error as ScanError, ScanDeleter, ScanResultFetcher, ScanResults, ScanStarter, ScanStopper,
},
Scan,
};
use std::{collections::HashMap, process::Child, sync::Mutex};

use crate::{cmd, ctl::ScanController, error::OpenvasError};
use crate::{cmd, error::OpenvasError};

pub struct OpenvasControl {
init: Mutex<HashMap<String, ()>>,
#[derive(Debug)]
pub struct Scanner {
running: Mutex<HashMap<String, Child>>,
sudo: bool,
}

impl OpenvasControl {
pub fn new() -> Self {
impl From<OpenvasError> for ScanError {
fn from(value: OpenvasError) -> Self {
ScanError::Unexpected(value.to_string())
}
}

impl Scanner {
pub fn with_sudo_enabled() -> Self {
Self {
init: Default::default(),
running: Default::default(),
sudo: cmd::check_sudo(),
sudo: true,
}
}

pub fn with_sudo_disabled() -> Self {
Self {
running: Default::default(),
sudo: false,
}
}
/// Removes a scan from init and add it to the list of running scans
fn add_running(&self, id: String) -> Result<bool, OpenvasError> {
if self.init.lock().unwrap().remove(&id).is_none() {
return Ok(false);
}
let openvas = cmd::start(&id, self.sudo, None).map_err(OpenvasError::CmdError)?;
self.running.lock().unwrap().insert(id, openvas);
Ok(true)
}

/// Remove a scan from the init phase
fn remove_init(&self, id: &str) -> bool {
self.init.lock().unwrap().remove(id).is_some()
}

/// Remove a scan from the list of running scans and returns the process to able to tidy up
fn remove_running(&self, id: &str) -> Option<Child> {
self.running.lock().unwrap().remove(id)
}
}

impl Default for OpenvasControl {
impl Default for Scanner {
fn default() -> Self {
Self::new()
}
}

impl ScanController for OpenvasControl {
/// Stops a scan with given ID. This will set a key in redis and indirectly
/// sends SIGUSR1 to the running scan process by running openvas with the
/// --stop-scan option.
fn stop_scan(&self, id: &str) -> Result<(), OpenvasError> {
let scan_id = id.to_string();

// TODO: Set stop scan flag in redis?

if self.remove_init(&scan_id) {
return Ok(());
Self {
running: Default::default(),
sudo: cmd::check_sudo(),
}

let mut scan = match self.remove_running(&scan_id) {
Some(scan) => scan,
None => return Err(OpenvasError::ScanNotFound(id.to_string())),
};

cmd::stop(&scan_id, self.sudo)
.map_err(OpenvasError::CmdError)?
.wait()
.map_err(OpenvasError::CmdError)?;

scan.wait().map_err(OpenvasError::CmdError)?;
Ok(())

// TODO: Clean redis DB
}

fn start_scan(&self, scan: models::Scan) -> Result<(), OpenvasError> {
}
#[async_trait]
impl ScanStarter for Scanner {
async fn start_scan(&self, scan: Scan) -> Result<(), ScanError> {
// TODO: Create new DB for Scan
// TODO: Add Scan ID to DB
// TODO: Prepare Target (hosts, ports, credentials)
Expand All @@ -83,28 +68,59 @@ impl ScanController for OpenvasControl {
// TODO: Prepare scan params
// TODO: Prepare reverse lookup option (maybe part of target)
// TODO: Prepare alive test option (maybe part of target)
self.add_running(scan.scan_id)?;

if !self.add_running(scan.scan_id)? {
return Ok(());
}

// TODO: Control loop (check for status and results)?
todo!();
return Ok(());
}
}

fn num_running(&self) -> usize {
self.init.lock().unwrap().len() + self.running.lock().unwrap().len()
}
/// Stops a scan
#[async_trait]
impl ScanStopper for Scanner {
/// Stops a scan
async fn stop_scan<I>(&self, id: I) -> Result<(), ScanError>
where
I: AsRef<str> + Send + 'static,
{
let scan_id = id.as_ref();

// TODO: Set stop scan flag in redis?

fn num_init(&self) -> usize {
self.init.lock().unwrap().len()
let mut scan = match self.remove_running(scan_id) {
Some(scan) => scan,
None => return Err(OpenvasError::ScanNotFound(scan_id.to_string()).into()),
};

cmd::stop(scan_id, self.sudo)
.map_err(OpenvasError::CmdError)?
.wait()
.map_err(OpenvasError::CmdError)?;

scan.wait().map_err(OpenvasError::CmdError)?;
// TODO: Clean redis DB
Ok(())
}
}

fn set_init(&self, id: &str) {
self.init.lock().unwrap().insert(id.to_string(), ());
/// Deletes a scan
#[async_trait]
impl ScanDeleter for Scanner {
async fn delete_scan<I>(&self, _id: I) -> Result<(), ScanError>
where
I: AsRef<str> + Send + 'static,
{
// already deleted on stop?
Ok(())
}
}

fn exists(&self, id: &str) -> bool {
self.init.lock().unwrap().contains_key(id) || self.running.lock().unwrap().contains_key(id)
#[async_trait]
impl ScanResultFetcher for Scanner {
/// Fetches the results of a scan and combines the results with response
async fn fetch_results<I>(&self, _id: I) -> Result<ScanResults, ScanError>
where
I: AsRef<str> + Send + 'static,
{
todo!()
}
}
4 changes: 2 additions & 2 deletions rust/openvasctl/src/result_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ mod tests {
ip_address: Some("127.0.0.1".to_string()),
hostname: Some("example.com".to_string()),
oid: Some("12.11.10.9.8.7".to_string()),
port: Some(i16::from(22i16)),
port: Some(22i16),
protocol: Some(models::Protocol::TCP),
message: Some("Something wrong".to_string()),
detail: None,
Expand Down Expand Up @@ -345,7 +345,7 @@ mod tests {
data: HashMap::new(),
};

let mut resh = ResultHelper::init(rc);
let resh = ResultHelper::init(rc);
let _ = resh.process_status(status).unwrap();

let mut r = HashMap::new();
Expand Down
Loading

0 comments on commit 51a03d6

Please sign in to comment.