Skip to content

Commit

Permalink
Add: Preference handler for openvasctl (#1567)
Browse files Browse the repository at this point in the history
* Add: preference handler module

This module gets a scan config and prepare the data to be stored in redis as openvas-scanner expected.
The data is stored in redis as well. For that, a new module openvas_redis was added as well, which adds an implements
the RedisHelper struct.

* Add: excluded hosts to the target model.
  • Loading branch information
jjnicola authored Feb 16, 2024
1 parent 9b193b4 commit bcdaa29
Show file tree
Hide file tree
Showing 15 changed files with 1,064 additions and 86 deletions.
167 changes: 93 additions & 74 deletions rust/Cargo.lock

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions rust/doc/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@ components:
items:
description: "Contains either an IPv4, IPv6, IPv4 range, IPv6 range, IPv4 CIDR, IPv6 CIDR or hostname."
type: "string"
excluded_hosts:
description: "A list of excluded hosts."
type: "array"
items:
description: "Contains either an IPv4, IPv6, IPv4 range, IPv6 range, IPv4 CIDR, IPv6 CIDR or hostname."
type: "string"
ports:
description: "A list of ports."
type: "array"
Expand Down Expand Up @@ -566,6 +572,15 @@ components:
type: "string"
password:
description: "Password for authentication."
privilege_credential:
description: "Privilege username and password for SSH service"
type: "object"
properties:
username:
description: "Privilege username for authentication."
type: "string"
password:
description: "Privilege password for authentication."
required:
- username

Expand Down Expand Up @@ -878,6 +893,10 @@ components:
"2002::1234:abcd:ffff:c0a8:101/64",
"examplehost",
],
"excluded_hosts":
[
"192.168.0.14"
],
"ports":
[
{
Expand Down
10 changes: 9 additions & 1 deletion rust/models/src/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl Default for Credential {
credential_type: CredentialType::UP {
username: "root".to_string(),
password: "".to_string(),
privilege_credential: None,
},
}
}
Expand Down Expand Up @@ -103,6 +104,8 @@ pub enum CredentialType {
username: String,
/// The password for authentication.
password: String,
/// privilege credential
privilege_credential: Option<Box<CredentialType>>,
},
#[cfg_attr(feature = "serde_support", serde(rename = "usk"))]
/// User/ssh-key credentials.
Expand Down Expand Up @@ -140,9 +143,14 @@ impl CredentialType {
F: FnOnce(String) -> Result<String, E>,
{
Ok(match self {
CredentialType::UP { username, password } => CredentialType::UP {
CredentialType::UP {
username,
password,
privilege_credential,
} => CredentialType::UP {
username,
password: f(password)?,
privilege_credential,
},
CredentialType::USK {
username,
Expand Down
3 changes: 3 additions & 0 deletions rust/models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ mod tests {
"2002::1234:abcd:ffff:c0a8:101/64",
"examplehost"
],
"excluded_hosts": [
"192.168.0.14"
],
"ports": [
{
"protocol": "udp",
Expand Down
96 changes: 96 additions & 0 deletions rust/models/src/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,99 @@ impl TryFrom<&str> for Protocol {
}
}
}

pub fn ports_to_openvas_port_list(ports: Vec<Port>) -> Option<String> {
fn add_range_to_list(list: &mut String, start: usize, end: Option<usize>) {
// Add range
if let Some(end) = end {
list.push_str(start.to_string().as_str());
list.push('-');
list.push_str(end.to_string().as_str());
list.push(',');
// Add single port
} else {
list.push_str(start.to_string().as_str());
list.push(',');
}
}
if ports.is_empty() {
return None;
}

let mut udp = String::from("udp:");
let mut tcp = String::from("tcp:");

ports.iter().for_each(|p| match p.protocol {
Some(Protocol::TCP) => {
p.range
.iter()
.for_each(|r| add_range_to_list(&mut tcp, r.start, r.end));
}
Some(Protocol::UDP) => {
p.range
.iter()
.for_each(|r| add_range_to_list(&mut udp, r.start, r.end));
}
None => {
p.range
.iter()
.for_each(|r| add_range_to_list(&mut tcp, r.start, r.end));
p.range
.iter()
.for_each(|r| add_range_to_list(&mut udp, r.start, r.end));
}
});
if udp != *"udp:" {
tcp.push_str(&udp);
}
Some(tcp)
}

#[cfg(test)]
mod tests {

use crate::{ports_to_openvas_port_list, Port, PortRange, Protocol};

#[test]
fn test_port_conversion_to_string() {
let ports = vec![
Port {
protocol: Some(Protocol::TCP),
range: vec![
PortRange {
start: 22,
end: Some(25),
},
PortRange {
start: 80,
end: None,
},
],
},
Port {
protocol: Some(Protocol::UDP),
range: vec![
PortRange {
start: 30,
end: Some(40),
},
PortRange {
start: 5060,
end: None,
},
],
},
Port {
protocol: None,
range: vec![PortRange {
start: 1000,
end: None,
}],
},
];
assert_eq!(
ports_to_openvas_port_list(ports),
Some("tcp:22-25,80,1000,udp:30-40,5060,1000,".to_string())
);
}
}
13 changes: 8 additions & 5 deletions rust/models/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub struct Target {
/// List of ports used for scanning
pub ports: Vec<Port>,
#[cfg_attr(feature = "serde_support", serde(default))]
/// List of excluded hosts to scan
pub excluded_hosts: Vec<String>,
#[cfg_attr(feature = "serde_support", serde(default))]
/// List of credentials used to get access to a system
pub credentials: Vec<Credential>,
#[cfg_attr(feature = "serde_support", serde(default))]
Expand All @@ -43,9 +46,9 @@ pub struct Target {
#[cfg_attr(feature = "bincode_support", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "serde_support", serde(rename_all = "snake_case"))]
pub enum AliveTestMethods {
Icmp,
TcpSyn,
TcpAck,
Arp,
ConsiderAlive,
Icmp = 0x01,
TcpSyn = 0x02,
TcpAck = 0x04,
Arp = 0x08,
ConsiderAlive = 0x16,
}
5 changes: 5 additions & 0 deletions rust/openvasctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
configparser = "3.0.4"
models = { path = "../models" }
redis = "0.22.0"
redis-storage = { version = "0.1.0", path = "../redis-storage" }
storage = { version = "0.1.0", path = "../storage" }
tracing = "0.1.40"
16 changes: 16 additions & 0 deletions rust/openvasctl/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// SPDX-FileCopyrightText: 2024 Greenbone AG
//
// SPDX-License-Identifier: GPL-2.0-or-later

use configparser::ini::Ini;
use std::{
io::Result,
process::{Child, Command},
Expand All @@ -13,6 +18,17 @@ pub fn check_sudo() -> bool {
Command::new("sudo").args(["-n", "openvas"]).spawn().is_ok()
}

pub fn read_openvas_config() -> Result<Ini> {
let oconfig = Command::new("openvas").arg("-s").output()?;

let mut config = Ini::new();
let oconfig = oconfig.stdout.iter().map(|x| *x as char).collect();
config
.read(oconfig)
.expect("Error reading openvas configuration");
Ok(config)
}

/// Start a new scan with the openvas executable with the given string. Before a scan can be
/// started all data needed for the scan must put into redis before.
pub fn start(id: &str, sudo: bool, nice: Option<i8>) -> Result<Child> {
Expand Down
6 changes: 6 additions & 0 deletions rust/openvasctl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// SPDX-FileCopyrightText: 2024 Greenbone AG
//
// SPDX-License-Identifier: GPL-2.0-or-later

pub mod cmd;
pub mod ctl;
pub mod error;
pub mod openvas_redis;
pub mod pref_handler;
124 changes: 124 additions & 0 deletions rust/openvasctl/src/openvas_redis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: 2024 Greenbone AG
//
// SPDX-License-Identifier: GPL-2.0-or-later

use redis_storage::{
dberror::{DbError, RedisStorageResult},
NameSpaceSelector, RedisCtx, RedisGetNvt, RedisWrapper,
};
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use storage::item::Nvt;

#[derive(Debug, Default)]
pub struct RedisHelper<R>
where
R: RedisWrapper,
{
cache: Arc<Mutex<R>>,
task_kb: Arc<Mutex<R>>,
}

impl<R> RedisHelper<R>
where
R: RedisWrapper,
{
/// Initialize a RedisHelper struct with the connection to access the NVT cache
/// and a empty task knowledge base to store the scan configuration to be sent to openvas.
pub fn init(
redis_url: &str,
selector: &[NameSpaceSelector],
) -> RedisStorageResult<RedisHelper<RedisCtx>> {
let mut rctx = RedisCtx::open(redis_url, selector)?;
rctx.delete_namespace()?;
let cache = RedisCtx::open(redis_url, selector)?;

Ok(RedisHelper::<RedisCtx> {
cache: Arc::new(Mutex::new(cache)),
task_kb: Arc::new(Mutex::new(rctx)),
})
}
}

pub trait KbAccess {
fn push_kb_item<T: redis::ToRedisArgs>(
&mut self,
key: &str,
value: T,
) -> RedisStorageResult<()>;
fn kb_id(&self) -> RedisStorageResult<u32>;
}

impl KbAccess for RedisHelper<RedisCtx> {
fn push_kb_item<T: redis::ToRedisArgs>(
&mut self,
key: &str,
value: T,
) -> RedisStorageResult<()> {
let mut kb = Arc::as_ref(&self.task_kb)
.lock()
.map_err(|e| DbError::SystemError(format!("{e:?}")))?;

kb.lpush(key, value)?;
Ok(())
}
/// Provide access to the cache
fn kb_id(&self) -> RedisStorageResult<u32> {
let cache = Arc::as_ref(&self.cache)
.lock()
.map_err(|e| DbError::SystemError(format!("{e:?}")))?;
Ok(cache.db)
}
}

pub trait VtHelper {
fn get_vt(&self, oid: &str) -> RedisStorageResult<Option<Nvt>>;
}

impl VtHelper for RedisHelper<RedisCtx> {
fn get_vt(&self, oid: &str) -> RedisStorageResult<Option<Nvt>> {
let mut cache = Arc::as_ref(&self.cache)
.lock()
.map_err(|e| DbError::SystemError(format!("{e:?}")))?;

cache.redis_get_vt(oid)
}
}

pub struct FakeRedis {
pub data: HashMap<String, Vec<Vec<u8>>>,
}

impl FakeRedis {
pub fn item_exists(&self, key: &str, value: &str) -> bool {
let mut v: Vec<String> = Vec::new();
if let Some(item) = self.data.get(key) {
for i in item {
v.push(String::from_utf8(i.to_vec()).unwrap());
}
}
v.contains(&value.to_string())
}
}

impl VtHelper for FakeRedis {
fn get_vt(&self, _: &str) -> RedisStorageResult<Option<Nvt>> {
Ok(None)
}
}

impl KbAccess for FakeRedis {
fn push_kb_item<T: redis::ToRedisArgs>(
&mut self,
key: &str,
value: T,
) -> RedisStorageResult<()> {
self.data.insert(key.to_string(), value.to_redis_args());
Ok(())
}
fn kb_id(&self) -> RedisStorageResult<u32> {
Ok(3)
}
}
Loading

0 comments on commit bcdaa29

Please sign in to comment.