diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6c37e11..4e32722 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1981,6 +1981,7 @@ name = "vss-server" version = "0.1.0" dependencies = [ "api", + "auth-impls", "bytes", "http-body-util", "hyper 1.4.1", diff --git a/rust/auth-impls/src/lib.rs b/rust/auth-impls/src/lib.rs index 86c6853..76c8786 100644 --- a/rust/auth-impls/src/lib.rs +++ b/rust/auth-impls/src/lib.rs @@ -41,9 +41,11 @@ pub(crate) struct Claims { const BEARER_PREFIX: &str = "Bearer "; impl JWTAuthorizer { - /// Create new instance of [`JWTAuthorizer`] - pub async fn new(jwt_issuer_key: DecodingKey) -> Self { - Self { jwt_issuer_key } + /// Creates a new instance of [`JWTAuthorizer`], fails on failure to parse the PEM formatted RSA public key + pub async fn new(rsa_pem: &str) -> Result { + let jwt_issuer_key = + DecodingKey::from_rsa_pem(rsa_pem.as_bytes()).map_err(|e| e.to_string())?; + Ok(Self { jwt_issuer_key }) } } @@ -74,7 +76,7 @@ mod tests { use crate::JWTAuthorizer; use api::auth::Authorizer; use api::error::VssError; - use jsonwebtoken::{encode, Algorithm, DecodingKey, EncodingKey, Header}; + use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::SystemTime; @@ -132,7 +134,7 @@ mod tests { ) .expect("Failed to create Encoding Key."); - let decoding_key = DecodingKey::from_rsa_pem( + let decoding_key = String::from( "-----BEGIN PUBLIC KEY-----\ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAysGpKU+I9i9b+QZSANu/\ ExaA6w4qiQdFZaXeReiz49r1oDfABwKIFW9gK/kNnrnL9H8P+pYfj7jqUJ/glmgq\ @@ -141,12 +143,10 @@ mod tests { 8YsTa5piV8KgJpG/rwYTGXuu3lcCmnWwjmbeDq1zFFrCDDVkaIHkGJgRuFIDPXaH\ yUw5H2HvKlP94ySbvTDLXWZj6TyzHEHDbstqs4DgvurB/bIhi/dQ7zK3EIXL8KRB\ hwIDAQAB\ - -----END PUBLIC KEY-----" - .as_bytes(), - ) - .expect("Failed to create Decoding Key."); + -----END PUBLIC KEY-----", + ); - let jwt_authorizer = JWTAuthorizer::new(decoding_key).await; + let jwt_authorizer = JWTAuthorizer::new(&decoding_key).await.unwrap(); let valid_jwt_token = encode(&Header::new(Algorithm::RS256), &claims, &valid_encoding_key).unwrap(); diff --git a/rust/server/Cargo.toml b/rust/server/Cargo.toml index 2a0e6f1..6c66812 100644 --- a/rust/server/Cargo.toml +++ b/rust/server/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] api = { path = "../api" } +auth-impls = { path = "../auth-impls" } impls = { path = "../impls" } hyper = { version = "1", default-features = false, features = ["server", "http1"] } diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 38fdccd..7d56343 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -10,6 +10,7 @@ #![deny(missing_docs)] use std::net::SocketAddr; +use std::sync::Arc; use tokio::net::TcpListener; use tokio::signal::unix::SignalKind; @@ -17,14 +18,15 @@ use tokio::signal::unix::SignalKind; use hyper::server::conn::http1; use hyper_util::rt::TokioIo; -use crate::vss_service::VssService; use api::auth::{Authorizer, NoopAuthorizer}; use api::kv_store::KvStore; +use auth_impls::JWTAuthorizer; use impls::postgres_store::{Certificate, PostgresPlaintextBackend, PostgresTlsBackend}; -use std::sync::Arc; +use util::config::{Config, ServerConfig}; +use vss_service::VssService; -pub(crate) mod util; -pub(crate) mod vss_service; +mod util; +mod vss_service; fn main() { let args: Vec = std::env::args().collect(); @@ -33,22 +35,21 @@ fn main() { std::process::exit(1); } - let config = match util::config::load_config(&args[1]) { - Ok(cfg) => cfg, - Err(e) => { - eprintln!("Failed to load configuration: {}", e); - std::process::exit(1); - }, - }; - - let addr: SocketAddr = - match format!("{}:{}", config.server_config.host, config.server_config.port).parse() { - Ok(addr) => addr, + let Config { server_config: ServerConfig { host, port }, jwt_auth_config, postgresql_config } = + match util::config::load_config(&args[1]) { + Ok(cfg) => cfg, Err(e) => { - eprintln!("Invalid host/port configuration: {}", e); + eprintln!("Failed to load configuration: {}", e); std::process::exit(1); }, }; + let addr: SocketAddr = match format!("{}:{}", host, port).parse() { + Ok(addr) => addr, + Err(e) => { + eprintln!("Invalid host/port configuration: {}", e); + std::process::exit(1); + }, + }; let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { Ok(runtime) => Arc::new(runtime), @@ -66,9 +67,33 @@ fn main() { std::process::exit(-1); }, }; - let authorizer: Arc = Arc::new(NoopAuthorizer {}); + + let rsa_pem_env = match std::env::var("VSS_JWT_RSA_PEM") { + Ok(env) => Some(env), + Err(std::env::VarError::NotPresent) => None, + Err(e) => { + println!("Failed to load the VSS_JWT_RSA_PEM env var: {}", e); + std::process::exit(-1); + }, + }; + let rsa_pem = rsa_pem_env.or(jwt_auth_config.map(|config| config.rsa_pem)); + let authorizer: Arc = if let Some(pem) = rsa_pem { + let authorizer = match JWTAuthorizer::new(pem.as_str()).await { + Ok(auth) => auth, + Err(e) => { + println!("Failed to parse the PEM formatted RSA public key: {}", e); + std::process::exit(-1); + }, + }; + println!("Configured JWT authorizer with RSA public key"); + Arc::new(authorizer) + } else { + println!("No JWT authentication method configured"); + Arc::new(NoopAuthorizer {}) + }; + let postgresql_config = - config.postgresql_config.expect("PostgreSQLConfig must be defined in config file."); + postgresql_config.expect("PostgreSQLConfig must be defined in config file."); let endpoint = postgresql_config.to_postgresql_endpoint(); let db_name = postgresql_config.database; let store: Arc = if let Some(tls_config) = postgresql_config.tls { @@ -109,6 +134,7 @@ fn main() { Arc::new(postgres_plaintext_backend) }; println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, db_name); + let rest_svc_listener = TcpListener::bind(&addr).await.expect("Failed to bind listening port"); println!("Listening for incoming connections on {}", addr); diff --git a/rust/server/src/util/config.rs b/rust/server/src/util/config.rs index 801d1bd..1f920c6 100644 --- a/rust/server/src/util/config.rs +++ b/rust/server/src/util/config.rs @@ -3,6 +3,7 @@ use serde::Deserialize; #[derive(Deserialize)] pub(crate) struct Config { pub(crate) server_config: ServerConfig, + pub(crate) jwt_auth_config: Option, pub(crate) postgresql_config: Option, } @@ -12,6 +13,11 @@ pub(crate) struct ServerConfig { pub(crate) port: u16, } +#[derive(Deserialize)] +pub(crate) struct JwtAuthConfig { + pub(crate) rsa_pem: String, +} + #[derive(Deserialize)] pub(crate) struct PostgreSQLConfig { pub(crate) username: Option, // Optional in TOML, can be overridden by env diff --git a/rust/server/vss-server-config.toml b/rust/server/vss-server-config.toml index 8c3d9c0..c42a906 100644 --- a/rust/server/vss-server-config.toml +++ b/rust/server/vss-server-config.toml @@ -2,6 +2,21 @@ host = "127.0.0.1" port = 8080 +# Uncomment the table below to verify JWT tokens in the HTTP Authorization header against the given RSA public key, +# can be overridden by env var `VSS_JWT_RSA_PEM` +# [jwt_auth_config] +# rsa_pem = """ +# -----BEGIN PUBLIC KEY----- +# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAstPJs4ut+tFAI0qrOyGt +# /3FN5jWc5gLv/j9Rc6lgr4hm7lyR05PU/G+4rfxdXGNyGTlQ6dRqcVy78CjxWz9f +# 8l08EKLERPh8JhE5el6vr+ehWD5iQxSP3ejpx0Mr977fKMNKg6jlFiL+y50hOEp2 +# 6iN9QzZQjLxotDT3aQvbCA/DZpI+fV6WKDKWGS+pZGDVgOz5x/RcStJQXxkX3ACK +# WhVdrtN3h6mHlhIt7ZIqVvQmY4NL03QPyljt13sYHoiFaoxINF/funBMCjrfSLcB +# ko1rWE2BWdOrFqi27RtBs5AHOSAWXuz/2SUGpFuTQuJi7U68QUfjKeQO46JpQf+v +# kQIDAQAB +# -----END PUBLIC KEY----- +# """ + [postgresql_config] username = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_USERNAME` password = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_PASSWORD`