Skip to content

Commit

Permalink
WIP: Add: client authorization on endpoints
Browse files Browse the repository at this point in the history
To prevent that a registered client can see results
or scan of another client a differentiation factor
is introduces.

The scans and results will now be stored as an u64->information and the
key is either calculated by the used client certificate or, when openvasd
is started without client certifactes, by the used API key.

When client certificates and the API key is configured than the client
certificates are getting used.

When neither is configured that differntiation feature is turned off as
there is no identifier available.

Additionally it can be turned off via the configuration option TBD.
  • Loading branch information
nichtsfrei committed Oct 31, 2023
1 parent b97e497 commit 424f0fa
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 157 deletions.
2 changes: 2 additions & 0 deletions rust/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target/
libsrc/
*.rsa
*.cert
2 changes: 2 additions & 0 deletions rust/examples/openvasd/config.example.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[feed]
# path to the openvas feed. This is required for the /vts endpoint.
path = "/var/lib/openvas/plugins"
# disables or enables the signnature check
signature_check = true

[feed.check_interval]
# how often the feed should be checked for updates
Expand Down
122 changes: 68 additions & 54 deletions rust/examples/tls/Self-Signed mTLS Method/server_certificates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,71 @@
#
# SPDX-License-Identifier: GPL-2.0-or-later

set -xe

openssl req -nodes \
-x509 \
-days 3650 \
-newkey rsa:4096 \
-keyout ca.key \
-out ca.cert \
-sha256 \
-batch \
-subj "/CN=ponytown RSA CA"

openssl req -nodes \
-newkey rsa:3072 \
-keyout inter.key \
-out inter.req \
-sha256 \
-batch \
-subj "/CN=ponytown RSA level 2 intermediate"

openssl req -nodes \
-newkey rsa:2048 \
-keyout end.key \
-out end.req \
-sha256 \
-batch \
-subj "/CN=testserver.com"

openssl rsa \
-in end.key \
-out server.rsa

openssl x509 -req \
-in inter.req \
-out inter.cert \
-CA ca.cert \
-CAkey ca.key \
-sha256 \
-days 3650 \
-set_serial 123 \
-extensions v3_inter -extfile ../openssl.cnf

openssl x509 -req \
-in end.req \
-out end.cert \
-CA inter.cert \
-CAkey inter.key \
-sha256 \
-days 2000 \
-set_serial 456 \
-extensions v3_end -extfile ../openssl.cnf

cat end.cert inter.cert ca.cert > server.pem
rm *.key *.cert *.req
set -e
generate_certificates()
{
out="$1"
name="$2"
printf "generating $out for $name\t"
openssl req -nodes \
-x509 \
-days 3650 \
-newkey rsa:4096 \
-keyout ca.key \
-out ca.cert \
-sha256 \
-batch \
-subj "/CN=$name RSA CA"

openssl req -nodes \
-newkey rsa:3072 \
-keyout inter.key \
-out inter.req \
-sha256 \
-batch \
-subj "/CN=$name RSA level 2 intermediate"

openssl req -nodes \
-newkey rsa:2048 \
-keyout end.key \
-out end.req \
-sha256 \
-batch \
-subj "/CN=testserver.com"

openssl rsa \
-in end.key \
-out $out.rsa

openssl x509 -req \
-in inter.req \
-out inter.cert \
-CA ca.cert \
-CAkey ca.key \
-sha256 \
-days 3650 \
-set_serial 123 \
-extensions v3_inter -extfile openssl.cnf

openssl x509 -req \
-in end.req \
-out end.cert \
-CA inter.cert \
-CAkey inter.key \
-sha256 \
-days 2000 \
-set_serial 456 \
-extensions v3_end -extfile openssl.cnf

cat end.cert inter.cert ca.cert > $out.pem
rm *.key *.cert *.req
printf "done\n"
}

generate_certificates "server" "ponytown"
generate_certificates "client1" "onehause"
generate_certificates "client2" "zweidorf"
mkdir -p clients/certificates
mkdir -p clients/keys
mv client*.pem clients/certificates/
mv client*.rsa clients/keys/
2 changes: 1 addition & 1 deletion rust/openvasd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ serde_json = "1.0.96"
serde = { version = "1.0.163", features = ["derive"] }
uuid = {version = "1", features = ["v4", "fast-rng", "serde"]}
hyper-rustls = "0.24.0"
rustls = "0.21.1"
rustls = {version = "0.21.1", features = ["secret_extraction", "dangerous_configuration"]}
tokio-rustls = "0.24.0"
futures-util = "0.3.28"
rustls-pemfile = "1.0.2"
Expand Down
6 changes: 4 additions & 2 deletions rust/openvasd/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,9 @@ impl Config {
where
P: AsRef<std::path::Path> + std::fmt::Display,
{
let config = std::fs::read_to_string(path).unwrap_or_default();
toml::from_str(&config).unwrap_or_default()
println!("loading {path} ...");
let config = std::fs::read_to_string(path).unwrap();
toml::from_str(&config).unwrap()
}

pub fn load() -> Self {
Expand Down Expand Up @@ -330,6 +331,7 @@ impl Config {
let mut config = match cmds.get_one::<String>("config") {
Some(path) => Self::from_file(path),
None => {
println!("no config provided");
if let Some(config) = Self::load_user() {
config
} else {
Expand Down
3 changes: 2 additions & 1 deletion rust/openvasd/src/controller/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ impl<S, DB, T> ContextBuilder<S, DB, T> {
if let Some(fp) = self.feed_config.as_ref() {
let loader = nasl_interpreter::FSPluginLoader::new(fp.path.clone());
let dispatcher: DefaultDispatcher<String> = DefaultDispatcher::default();
let version = feed::version(&loader, &dispatcher).unwrap();
let version =
feed::version(&loader, &dispatcher).unwrap_or_else(|_| String::from("UNDEFINED"));
self.response.set_feed_version(&version);
}
self
Expand Down
28 changes: 18 additions & 10 deletions rust/openvasd/src/controller/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@
//!
//! All known paths must be handled in the entrypoint function.
use std::{fmt::Display, sync::Arc};
use std::{
fmt::Display,
sync::{Arc, RwLock},
};

use super::context::Context;
use super::{context::Context, ClientIdentifier};
use hyper::{Body, Method, Request, Response};

use crate::scan::{self, Error, ScanDeleter, ScanStarter, ScanStopper};
use crate::{
scan::{self, Error, ScanDeleter, ScanStarter, ScanStopper},
tls::TlsStream,
};

enum HealthOpts {
/// Ready
Expand Down Expand Up @@ -60,7 +66,7 @@ impl KnownPaths {
Some("alive") => KnownPaths::Health(HealthOpts::Alive),
Some("started") => KnownPaths::Health(HealthOpts::Started),
_ => KnownPaths::Unknown,
}
},
_ => {
tracing::trace!("Unknown path: {path}");
KnownPaths::Unknown
Expand Down Expand Up @@ -95,6 +101,7 @@ impl Display for KnownPaths {
pub async fn entrypoint<'a, S, DB>(
req: Request<Body>,
ctx: Arc<Context<S, DB>>,
num: Arc<RwLock<ClientIdentifier>>,
) -> Result<Response<Body>, Error>
where
S: ScanStarter
Expand All @@ -108,11 +115,12 @@ where
{
use KnownPaths::*;
// on head requests we just return an empty response without checking the api key
tracing::trace!(
"{} {}:{:?}",
tracing::warn!(
"{} {}:{:?} -> {:?}",
req.method(),
req.uri().path(),
req.uri().query()
req.uri().query(),
num.read().unwrap(),
);
if req.method() == Method::HEAD {
return Ok(ctx.response.empty(hyper::StatusCode::OK));
Expand All @@ -133,9 +141,9 @@ where
}

match (req.method(), kp) {
(&Method::GET, Health(HealthOpts::Alive)) |
(&Method::GET, Health(HealthOpts::Started)) =>
Ok(ctx.response.empty(hyper::StatusCode::OK)),
(&Method::GET, Health(HealthOpts::Alive)) | (&Method::GET, Health(HealthOpts::Started)) => {
Ok(ctx.response.empty(hyper::StatusCode::OK))
}
(&Method::GET, Health(HealthOpts::Ready)) => {
let oids = ctx.db.oids().await?;
if oids.count() == 0 {
Expand Down
43 changes: 22 additions & 21 deletions rust/openvasd/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod entry;
pub mod feed;
pub mod results;

use std::default;

use crate::scan::{ScanDeleter, ScanResultFetcher, ScanStarter, ScanStopper};
pub use context::{Context, ContextBuilder, NoOpScanner};
pub use entry::entrypoint;
Expand All @@ -20,29 +22,28 @@ pub(crate) fn quit_on_poison<T>() -> T {
/// Combines all traits needed for a scanner.
pub trait Scanner: ScanStarter + ScanStopper + ScanDeleter + ScanResultFetcher {}

impl<T> Scanner for T where T: ScanStarter + ScanStopper + ScanDeleter + ScanResultFetcher {}

macro_rules! make_svc {
($controller:expr) => {{
// start background service
use std::sync::Arc;

tokio::spawn(crate::controller::results::fetch(Arc::clone(&$controller)));
tokio::spawn(crate::controller::feed::fetch(Arc::clone(&$controller)));

use hyper::service::{make_service_fn, service_fn};
make_service_fn(|_conn| {
let controller = Arc::clone($controller);
async {
Ok::<_, crate::scan::Error>(service_fn(move |req| {
crate::controller::entrypoint(req, Arc::clone(&controller))
}))
}
})
}};
/// Contains information about an authorization model of a connection (e.g. mtls)
#[derive(Default, Debug)]
pub enum ClientIdentifier {
/// When there in no information available
#[default]
Unknown,
/// Contains a hashed number of an identifier
///
/// openvasd uses the identifier as a key for results. This key is usually calculated by an
/// subject of a known client certificate. Based on that we don't need more information.
Known(u64),
}
/// Is used to transfer the information if there is an identifier present within the connection
pub trait ClientInformationRetriever {
/// Gets the identifier
///
/// Based on the concurrent nature, the actual information is boxed within a Arc and locked for
/// concurrent read write accesses.
fn client_identifier(&self) -> &std::sync::Arc<std::sync::RwLock<ClientIdentifier>>;
}

pub(crate) use make_svc;
impl<T> Scanner for T where T: ScanStarter + ScanStopper + ScanDeleter + ScanResultFetcher {}

#[cfg(test)]
mod tests {
Expand Down
Loading

0 comments on commit 424f0fa

Please sign in to comment.