From 18d6ff2a012eca9cf5770ea6ae660983a79feb7b Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Wed, 17 Dec 2025 23:18:57 +0000 Subject: [PATCH 1/4] Let `JwtAuthorizer::new` set the RSA pubkey parse error message Allow only a subset of `JwtAuthConfig` fields to be set in config file, as we allow any members of the configuration tables to not be set in the file, and instead be set by the corresponding environment variable. --- rust/auth-impls/src/jwt.rs | 4 ++-- rust/server/src/main.rs | 4 ++-- rust/server/src/util/config.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/auth-impls/src/jwt.rs b/rust/auth-impls/src/jwt.rs index 7bffb98..f06e4c7 100644 --- a/rust/auth-impls/src/jwt.rs +++ b/rust/auth-impls/src/jwt.rs @@ -34,8 +34,8 @@ const BEARER_PREFIX: &str = "Bearer "; impl JWTAuthorizer { /// 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())?; + let jwt_issuer_key = DecodingKey::from_rsa_pem(rsa_pem.as_bytes()) + .map_err(|e| format!("Failed to parse the PEM formatted RSA public key: {}", e))?; Ok(Self { jwt_issuer_key }) } } diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 1c5e88d..7247c20 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -82,7 +82,7 @@ fn main() { std::process::exit(-1); }, }; - let rsa_pem = rsa_pem_env.or(jwt_auth_config.map(|config| config.rsa_pem)); + let rsa_pem = rsa_pem_env.or(jwt_auth_config.and_then(|config| config.rsa_pem)); if let Some(pem) = rsa_pem { authorizer = match JWTAuthorizer::new(pem.as_str()).await { Ok(auth) => { @@ -90,7 +90,7 @@ fn main() { Some(Arc::new(auth)) }, Err(e) => { - println!("Failed to parse the PEM formatted RSA public key: {}", e); + println!("Failed to configure JWT authorizer: {}", e); std::process::exit(-1); }, }; diff --git a/rust/server/src/util/config.rs b/rust/server/src/util/config.rs index 1f920c6..c4ccbb1 100644 --- a/rust/server/src/util/config.rs +++ b/rust/server/src/util/config.rs @@ -15,7 +15,7 @@ pub(crate) struct ServerConfig { #[derive(Deserialize)] pub(crate) struct JwtAuthConfig { - pub(crate) rsa_pem: String, + pub(crate) rsa_pem: Option, } #[derive(Deserialize)] From 983adc5e6c6bf98f9c816c3d557f21a950042f86 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Wed, 17 Dec 2025 20:02:16 +0000 Subject: [PATCH 2/4] Read the additional root certificate directly from the config file... ...just like we do for the RSA public key used to authenticate incoming JWTs. Also parse the root certificate in `PostgresTlsBackend::new`. This makes `PostgresTlsBackend` consistent with `JWTAuthorizer`; both structs take cryptographic objects as `&str`'s and parse them in their constructors. --- rust/impls/src/postgres_store.rs | 12 +++++++++--- rust/server/src/main.rs | 22 ++++------------------ rust/server/src/util/config.rs | 2 +- rust/server/vss-server-config.toml | 18 +++++++++--------- 4 files changed, 23 insertions(+), 31 deletions(-) diff --git a/rust/impls/src/postgres_store.rs b/rust/impls/src/postgres_store.rs index 4424a00..d6230f1 100644 --- a/rust/impls/src/postgres_store.rs +++ b/rust/impls/src/postgres_store.rs @@ -141,11 +141,17 @@ impl PostgresPlaintextBackend { impl PostgresTlsBackend { /// Constructs a [`PostgresTlsBackend`] using `postgres_endpoint` for PostgreSQL connection information. pub async fn new( - postgres_endpoint: &str, db_name: &str, additional_certificate: Option, + postgres_endpoint: &str, db_name: &str, crt_pem: Option<&str>, ) -> Result { let mut builder = TlsConnector::builder(); - if let Some(cert) = additional_certificate { - builder.add_root_certificate(cert); + if let Some(pem) = crt_pem { + let crt = Certificate::from_pem(pem.as_bytes()).map_err(|e| { + Error::new( + ErrorKind::Other, + format!("Failed to parse the PEM formatted certificate: {}", e), + ) + })?; + builder.add_root_certificate(crt); } let connector = builder.build().map_err(|e| { Error::new(ErrorKind::Other, format!("Error building tls connector: {}", e)) diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 7247c20..219189e 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -24,7 +24,7 @@ use api::kv_store::KvStore; use auth_impls::jwt::JWTAuthorizer; #[cfg(feature = "sigs")] use auth_impls::signature::SignatureValidatingAuthorizer; -use impls::postgres_store::{Certificate, PostgresPlaintextBackend, PostgresTlsBackend}; +use impls::postgres_store::{PostgresPlaintextBackend, PostgresTlsBackend}; use util::config::{Config, ServerConfig}; use vss_service::VssService; @@ -115,24 +115,10 @@ fn main() { let endpoint = postgresql_config.to_postgresql_endpoint(); let db_name = postgresql_config.database; let store: Arc = if let Some(tls_config) = postgresql_config.tls { - let additional_certificate = tls_config.ca_file.map(|file| { - let certificate = match std::fs::read(&file) { - Ok(cert) => cert, - Err(e) => { - println!("Failed to read certificate file: {}", e); - std::process::exit(-1); - }, - }; - match Certificate::from_pem(&certificate) { - Ok(cert) => cert, - Err(e) => { - println!("Failed to parse certificate file: {}", e); - std::process::exit(-1); - }, - } - }); let postgres_tls_backend = - match PostgresTlsBackend::new(&endpoint, &db_name, additional_certificate).await { + match PostgresTlsBackend::new(&endpoint, &db_name, tls_config.crt_pem.as_deref()) + .await + { Ok(backend) => backend, Err(e) => { println!("Failed to start postgres tls backend: {}", e); diff --git a/rust/server/src/util/config.rs b/rust/server/src/util/config.rs index c4ccbb1..8149a64 100644 --- a/rust/server/src/util/config.rs +++ b/rust/server/src/util/config.rs @@ -30,7 +30,7 @@ pub(crate) struct PostgreSQLConfig { #[derive(Deserialize)] pub(crate) struct TlsConfig { - pub(crate) ca_file: Option, + pub(crate) crt_pem: Option, } impl PostgreSQLConfig { diff --git a/rust/server/vss-server-config.toml b/rust/server/vss-server-config.toml index c42a906..322209e 100644 --- a/rust/server/vss-server-config.toml +++ b/rust/server/vss-server-config.toml @@ -7,13 +7,6 @@ port = 8080 # [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----- # """ @@ -23,5 +16,12 @@ password = "postgres" # Optional in TOML, can be overridden by env var `VSS_POS host = "localhost" port = 5432 database = "postgres" -# tls = { } # Uncomment to make TLS connections to the postgres database using your machine's PKI -# tls = { ca_file = "ca.pem" } # Uncomment to make TLS connections to the postgres database with an additional root certificate + +# [postgresql_config.tls] # Uncomment to make TLS connections to the postgres database +# +# Uncomment the lines below to add a root certificate to your trusted root certificates +# +# crt_pem = """ +# -----BEGIN CERTIFICATE----- +# -----END CERTIFICATE----- +# """ From 32cfbcd5e6ac06550cd0b4ae9a06754506a44b77 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 6 Dec 2025 03:10:59 +0000 Subject: [PATCH 3/4] Add option to specify default postgres db name Not all postgres hosted services use `postgres` Also rename `database` config parameter to `vss_database` --- rust/impls/src/postgres_store.rs | 87 +++++++++++++++++++----------- rust/server/src/main.rs | 31 ++++++----- rust/server/src/util/config.rs | 3 +- rust/server/vss-server-config.toml | 4 +- 4 files changed, 78 insertions(+), 47 deletions(-) diff --git a/rust/impls/src/postgres_store.rs b/rust/impls/src/postgres_store.rs index d6230f1..3bc176c 100644 --- a/rust/impls/src/postgres_store.rs +++ b/rust/impls/src/postgres_store.rs @@ -64,14 +64,16 @@ pub type PostgresPlaintextBackend = PostgresBackend; /// A postgres backend with TLS connections to the database pub type PostgresTlsBackend = PostgresBackend; -async fn make_postgres_db_connection(postgres_endpoint: &str, tls: T) -> Result +async fn make_db_connection( + postgres_endpoint: &str, db_name: &str, tls: T, +) -> Result where T: MakeTlsConnect + Clone + Send + Sync + 'static, T::Stream: Send + Sync, T::TlsConnect: Send, <>::TlsConnect as TlsConnect>::Future: Send, { - let dsn = format!("{}/{}", postgres_endpoint, "postgres"); + let dsn = format!("{}/{}", postgres_endpoint, db_name); let (client, connection) = tokio_postgres::connect(&dsn, tls) .await .map_err(|e| Error::new(ErrorKind::Other, format!("Connection error: {}", e)))?; @@ -84,8 +86,8 @@ where Ok(client) } -async fn initialize_vss_database( - postgres_endpoint: &str, db_name: &str, tls: T, +async fn create_database( + postgres_endpoint: &str, default_db: &str, db_name: &str, tls: T, ) -> Result<(), Error> where T: MakeTlsConnect + Clone + Send + Sync + 'static, @@ -93,7 +95,7 @@ where T::TlsConnect: Send, <>::TlsConnect as TlsConnect>::Future: Send, { - let client = make_postgres_db_connection(&postgres_endpoint, tls).await?; + let client = make_db_connection(postgres_endpoint, default_db, tls).await?; let num_rows = client.execute(CHECK_DB_STMT, &[&db_name]).await.map_err(|e| { Error::new( @@ -113,14 +115,16 @@ where } #[cfg(test)] -async fn drop_database(postgres_endpoint: &str, db_name: &str, tls: T) -> Result<(), Error> +async fn drop_database( + postgres_endpoint: &str, default_db: &str, db_name: &str, tls: T, +) -> Result<(), Error> where T: MakeTlsConnect + Clone + Send + Sync + 'static, T::Stream: Send + Sync, T::TlsConnect: Send, <>::TlsConnect as TlsConnect>::Future: Send, { - let client = make_postgres_db_connection(&postgres_endpoint, tls).await?; + let client = make_db_connection(postgres_endpoint, default_db, tls).await?; let drop_database_statement = format!("{} {};", DROP_DB_CMD, db_name); let num_rows = client.execute(&drop_database_statement, &[]).await.map_err(|e| { @@ -133,15 +137,17 @@ where impl PostgresPlaintextBackend { /// Constructs a [`PostgresPlaintextBackend`] using `postgres_endpoint` for PostgreSQL connection information. - pub async fn new(postgres_endpoint: &str, db_name: &str) -> Result { - PostgresBackend::new_internal(postgres_endpoint, db_name, NoTls).await + pub async fn new( + postgres_endpoint: &str, default_db: &str, vss_db: &str, + ) -> Result { + PostgresBackend::new_internal(postgres_endpoint, default_db, vss_db, NoTls).await } } impl PostgresTlsBackend { /// Constructs a [`PostgresTlsBackend`] using `postgres_endpoint` for PostgreSQL connection information. pub async fn new( - postgres_endpoint: &str, db_name: &str, crt_pem: Option<&str>, + postgres_endpoint: &str, default_db: &str, vss_db: &str, crt_pem: Option<&str>, ) -> Result { let mut builder = TlsConnector::builder(); if let Some(pem) = crt_pem { @@ -156,8 +162,13 @@ impl PostgresTlsBackend { let connector = builder.build().map_err(|e| { Error::new(ErrorKind::Other, format!("Error building tls connector: {}", e)) })?; - PostgresBackend::new_internal(postgres_endpoint, db_name, MakeTlsConnector::new(connector)) - .await + PostgresBackend::new_internal( + postgres_endpoint, + default_db, + vss_db, + MakeTlsConnector::new(connector), + ) + .await } } @@ -168,9 +179,11 @@ where T::TlsConnect: Send, <>::TlsConnect as TlsConnect>::Future: Send, { - async fn new_internal(postgres_endpoint: &str, db_name: &str, tls: T) -> Result { - initialize_vss_database(postgres_endpoint, db_name, tls.clone()).await?; - let vss_dsn = format!("{}/{}", postgres_endpoint, db_name); + async fn new_internal( + postgres_endpoint: &str, default_db: &str, vss_db: &str, tls: T, + ) -> Result { + create_database(postgres_endpoint, default_db, vss_db, tls.clone()).await?; + let vss_dsn = format!("{}/{}", postgres_endpoint, vss_db); let manager = PostgresConnectionManager::new_from_stringlike(vss_dsn, tls).map_err(|e| { Error::new( @@ -655,24 +668,27 @@ mod tests { use tokio_postgres::NoTls; const POSTGRES_ENDPOINT: &str = "postgresql://postgres:postgres@localhost:5432"; + const DEFAULT_DB: &str = "postgres"; const MIGRATIONS_START: usize = 0; const MIGRATIONS_END: usize = MIGRATIONS.len(); static START: OnceCell<()> = OnceCell::const_new(); define_kv_store_tests!(PostgresKvStoreTest, PostgresPlaintextBackend, { - let db_name = "postgres_kv_store_tests"; + let vss_db = "postgres_kv_store_tests"; START .get_or_init(|| async { - let _ = drop_database(POSTGRES_ENDPOINT, db_name, NoTls).await; - let store = - PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let _ = drop_database(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db, NoTls).await; + let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db) + .await + .unwrap(); let (start, end) = store.migrate_vss_database(MIGRATIONS).await.unwrap(); assert_eq!(start, MIGRATIONS_START); assert_eq!(end, MIGRATIONS_END); }) .await; - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let (start, end) = store.migrate_vss_database(MIGRATIONS).await.unwrap(); assert_eq!(start, MIGRATIONS_END); assert_eq!(end, MIGRATIONS_END); @@ -684,28 +700,31 @@ mod tests { #[tokio::test] #[should_panic(expected = "We do not allow downgrades")] async fn panic_on_downgrade() { - let db_name = "panic_on_downgrade_test"; - let _ = drop_database(POSTGRES_ENDPOINT, db_name, NoTls).await; + let vss_db = "panic_on_downgrade_test"; + let _ = drop_database(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db, NoTls).await; { let mut migrations = MIGRATIONS.to_vec(); migrations.push(DUMMY_MIGRATION); - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let (start, end) = store.migrate_vss_database(&migrations).await.unwrap(); assert_eq!(start, MIGRATIONS_START); assert_eq!(end, MIGRATIONS_END + 1); }; { - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let _ = store.migrate_vss_database(MIGRATIONS).await.unwrap(); }; } #[tokio::test] async fn new_migrations_increments_upgrades() { - let db_name = "new_migrations_increments_upgrades_test"; - let _ = drop_database(POSTGRES_ENDPOINT, db_name, NoTls).await; + let vss_db = "new_migrations_increments_upgrades_test"; + let _ = drop_database(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db, NoTls).await; { - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let (start, end) = store.migrate_vss_database(MIGRATIONS).await.unwrap(); assert_eq!(start, MIGRATIONS_START); assert_eq!(end, MIGRATIONS_END); @@ -713,7 +732,8 @@ mod tests { assert_eq!(store.get_schema_version().await, MIGRATIONS_END); }; { - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let (start, end) = store.migrate_vss_database(MIGRATIONS).await.unwrap(); assert_eq!(start, MIGRATIONS_END); assert_eq!(end, MIGRATIONS_END); @@ -724,7 +744,8 @@ mod tests { let mut migrations = MIGRATIONS.to_vec(); migrations.push(DUMMY_MIGRATION); { - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let (start, end) = store.migrate_vss_database(&migrations).await.unwrap(); assert_eq!(start, MIGRATIONS_END); assert_eq!(end, MIGRATIONS_END + 1); @@ -735,7 +756,8 @@ mod tests { migrations.push(DUMMY_MIGRATION); migrations.push(DUMMY_MIGRATION); { - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let (start, end) = store.migrate_vss_database(&migrations).await.unwrap(); assert_eq!(start, MIGRATIONS_END + 1); assert_eq!(end, MIGRATIONS_END + 3); @@ -747,13 +769,14 @@ mod tests { }; { - let store = PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, db_name).await.unwrap(); + let store = + PostgresPlaintextBackend::new(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db).await.unwrap(); let list = store.get_upgrades_list().await; assert_eq!(list, [MIGRATIONS_START, MIGRATIONS_END, MIGRATIONS_END + 1]); let version = store.get_schema_version().await; assert_eq!(version, MIGRATIONS_END + 3); } - drop_database(POSTGRES_ENDPOINT, db_name, NoTls).await.unwrap(); + drop_database(POSTGRES_ENDPOINT, DEFAULT_DB, vss_db, NoTls).await.unwrap(); } } diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 219189e..55983dc 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -113,22 +113,27 @@ fn main() { let postgresql_config = postgresql_config.expect("PostgreSQLConfig must be defined in config file."); let endpoint = postgresql_config.to_postgresql_endpoint(); - let db_name = postgresql_config.database; + let default_db = postgresql_config.default_database; + let vss_db = postgresql_config.vss_database; let store: Arc = if let Some(tls_config) = postgresql_config.tls { - let postgres_tls_backend = - match PostgresTlsBackend::new(&endpoint, &db_name, tls_config.crt_pem.as_deref()) - .await - { - Ok(backend) => backend, - Err(e) => { - println!("Failed to start postgres tls backend: {}", e); - std::process::exit(-1); - }, - }; + let postgres_tls_backend = match PostgresTlsBackend::new( + &endpoint, + &default_db, + &vss_db, + tls_config.crt_pem.as_deref(), + ) + .await + { + Ok(backend) => backend, + Err(e) => { + println!("Failed to start postgres tls backend: {}", e); + std::process::exit(-1); + }, + }; Arc::new(postgres_tls_backend) } else { let postgres_plaintext_backend = - match PostgresPlaintextBackend::new(&endpoint, &db_name).await { + match PostgresPlaintextBackend::new(&endpoint, &default_db, &vss_db).await { Ok(backend) => backend, Err(e) => { println!("Failed to start postgres plaintext backend: {}", e); @@ -137,7 +142,7 @@ fn main() { }; Arc::new(postgres_plaintext_backend) }; - println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, db_name); + println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, vss_db); let rest_svc_listener = TcpListener::bind(&addr).await.expect("Failed to bind listening port"); diff --git a/rust/server/src/util/config.rs b/rust/server/src/util/config.rs index 8149a64..d80600d 100644 --- a/rust/server/src/util/config.rs +++ b/rust/server/src/util/config.rs @@ -24,7 +24,8 @@ pub(crate) struct PostgreSQLConfig { pub(crate) password: Option, // Optional in TOML, can be overridden by env pub(crate) host: String, pub(crate) port: u16, - pub(crate) database: String, + pub(crate) default_database: String, + pub(crate) vss_database: String, pub(crate) tls: Option, } diff --git a/rust/server/vss-server-config.toml b/rust/server/vss-server-config.toml index 322209e..0c2a486 100644 --- a/rust/server/vss-server-config.toml +++ b/rust/server/vss-server-config.toml @@ -15,7 +15,9 @@ username = "postgres" # Optional in TOML, can be overridden by env var `VSS_POS password = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_PASSWORD` host = "localhost" port = 5432 -database = "postgres" +# We first connect to `default_database` to create `vss_database` if it does not exist +default_database = "postgres" +vss_database = "vss" # [postgresql_config.tls] # Uncomment to make TLS connections to the postgres database # From f153b49ecb48f53ab4ba34a4ec8cb3cc3587f23e Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 6 Dec 2025 05:03:35 +0000 Subject: [PATCH 4/4] Add env var override to all configuration settings This allows users to configure VSS-Server entirely through environment variables, without a configuration file. We hence remove the requirement that a path to a configuration file always be passed as an argument. If a file is passed as an argument, and it does not exist, we now abort startup. Also consolidate host and port settings into single socket address setting. --- rust/server/src/main.rs | 103 +++++++--------- rust/server/src/util/config.rs | 181 ++++++++++++++++++++++------- rust/server/vss-server-config.toml | 18 ++- 3 files changed, 190 insertions(+), 112 deletions(-) diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 55983dc..9092ff2 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -9,7 +9,6 @@ #![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] -use std::net::SocketAddr; use std::sync::Arc; use tokio::net::TcpListener; @@ -25,7 +24,6 @@ use auth_impls::jwt::JWTAuthorizer; #[cfg(feature = "sigs")] use auth_impls::signature::SignatureValidatingAuthorizer; use impls::postgres_store::{PostgresPlaintextBackend, PostgresTlsBackend}; -use util::config::{Config, ServerConfig}; use vss_service::VssService; mod util; @@ -33,26 +31,12 @@ mod vss_service; fn main() { let args: Vec = std::env::args().collect(); - if args.len() != 2 { - eprintln!("Usage: {} ", args[0]); - std::process::exit(1); - } - 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!("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 config = + util::config::load_configuration(args.get(1).map(|s| s.as_str())).unwrap_or_else(|e| { + eprintln!("Failed to load configuration: {}", e); + std::process::exit(-1); + }); let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { Ok(runtime) => Arc::new(runtime), @@ -74,17 +58,8 @@ fn main() { let mut authorizer: Option> = None; #[cfg(feature = "jwt")] { - 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.and_then(|config| config.rsa_pem)); - if let Some(pem) = rsa_pem { - authorizer = match JWTAuthorizer::new(pem.as_str()).await { + if let Some(rsa_pem) = config.rsa_pem { + authorizer = match JWTAuthorizer::new(&rsa_pem).await { Ok(auth) => { println!("Configured JWT authorizer with RSA public key"); Some(Arc::new(auth)) @@ -110,43 +85,47 @@ fn main() { Arc::new(NoopAuthorizer {}) }; - let postgresql_config = - postgresql_config.expect("PostgreSQLConfig must be defined in config file."); - let endpoint = postgresql_config.to_postgresql_endpoint(); - let default_db = postgresql_config.default_database; - let vss_db = postgresql_config.vss_database; - let store: Arc = if let Some(tls_config) = postgresql_config.tls { - let postgres_tls_backend = match PostgresTlsBackend::new( - &endpoint, - &default_db, - &vss_db, - tls_config.crt_pem.as_deref(), + let store: Arc = if let Some(crt_pem) = config.tls_config { + let postgres_tls_backend = PostgresTlsBackend::new( + &config.postgresql_prefix, + &config.default_db, + &config.vss_db, + crt_pem.as_deref(), ) .await - { - Ok(backend) => backend, - Err(e) => { - println!("Failed to start postgres tls backend: {}", e); - std::process::exit(-1); - }, - }; + .unwrap_or_else(|e| { + println!("Failed to start postgres TLS backend: {}", e); + std::process::exit(-1); + }); + println!( + "Connected to PostgreSQL TLS backend with DSN: {}/{}", + config.postgresql_prefix, config.vss_db + ); Arc::new(postgres_tls_backend) } else { - let postgres_plaintext_backend = - match PostgresPlaintextBackend::new(&endpoint, &default_db, &vss_db).await { - Ok(backend) => backend, - Err(e) => { - println!("Failed to start postgres plaintext backend: {}", e); - std::process::exit(-1); - }, - }; + let postgres_plaintext_backend = PostgresPlaintextBackend::new( + &config.postgresql_prefix, + &config.default_db, + &config.vss_db, + ) + .await + .unwrap_or_else(|e| { + println!("Failed to start postgres plaintext backend: {}", e); + std::process::exit(-1); + }); + println!( + "Connected to PostgreSQL plaintext backend with DSN: {}/{}", + config.postgresql_prefix, config.vss_db + ); Arc::new(postgres_plaintext_backend) }; - println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, vss_db); - let rest_svc_listener = - TcpListener::bind(&addr).await.expect("Failed to bind listening port"); - println!("Listening for incoming connections on {}", addr); + let rest_svc_listener = TcpListener::bind(&config.bind_address).await.unwrap_or_else(|e| { + println!("Failed to bind listening port: {}", e); + std::process::exit(-1); + }); + println!("Listening for incoming connections on {}", config.bind_address); + loop { tokio::select! { res = rest_svc_listener.accept() => { diff --git a/rust/server/src/util/config.rs b/rust/server/src/util/config.rs index d80600d..3236941 100644 --- a/rust/server/src/util/config.rs +++ b/rust/server/src/util/config.rs @@ -1,58 +1,159 @@ use serde::Deserialize; +use std::net::SocketAddr; -#[derive(Deserialize)] -pub(crate) struct Config { - pub(crate) server_config: ServerConfig, - pub(crate) jwt_auth_config: Option, - pub(crate) postgresql_config: Option, +const BIND_ADDR_VAR: &str = "VSS_BIND_ADDRESS"; +const JWT_RSA_PEM_VAR: &str = "VSS_JWT_RSA_PEM"; +const PSQL_USER_VAR: &str = "VSS_PSQL_USERNAME"; +const PSQL_PASS_VAR: &str = "VSS_PSQL_PASSWORD"; +const PSQL_ADDR_VAR: &str = "VSS_PSQL_ADDRESS"; +const PSQL_DB_VAR: &str = "VSS_PSQL_DEFAULT_DB"; +const PSQL_VSS_DB_VAR: &str = "VSS_PSQL_VSS_DB"; +const PSQL_TLS_VAR: &str = "VSS_PSQL_TLS"; +const PSQL_CERT_PEM_VAR: &str = "VSS_PSQL_CRT_PEM"; + +// The structure of the toml config file. Any settings specified therein can be overriden by the corresponding +// environment variable. +#[derive(Deserialize, Default)] +struct TomlConfig { + server_config: Option, + jwt_auth_config: Option, + postgresql_config: Option, } #[derive(Deserialize)] -pub(crate) struct ServerConfig { - pub(crate) host: String, - pub(crate) port: u16, +struct ServerConfig { + bind_address: Option, } #[derive(Deserialize)] -pub(crate) struct JwtAuthConfig { - pub(crate) rsa_pem: Option, +struct JwtAuthConfig { + rsa_pem: Option, } #[derive(Deserialize)] -pub(crate) struct PostgreSQLConfig { - pub(crate) username: Option, // Optional in TOML, can be overridden by env - pub(crate) password: Option, // Optional in TOML, can be overridden by env - pub(crate) host: String, - pub(crate) port: u16, - pub(crate) default_database: String, - pub(crate) vss_database: String, - pub(crate) tls: Option, +struct PostgreSQLConfig { + username: Option, + password: Option, + address: Option, + default_database: Option, + vss_database: Option, + tls: Option, } #[derive(Deserialize)] -pub(crate) struct TlsConfig { - pub(crate) crt_pem: Option, -} - -impl PostgreSQLConfig { - pub(crate) fn to_postgresql_endpoint(&self) -> String { - let username_env = std::env::var("VSS_POSTGRESQL_USERNAME"); - let username = username_env.as_ref() - .ok() - .or_else(|| self.username.as_ref()) - .expect("PostgreSQL database username must be provided in config or env var VSS_POSTGRESQL_USERNAME must be set."); - let password_env = std::env::var("VSS_POSTGRESQL_PASSWORD"); - let password = password_env.as_ref() - .ok() - .or_else(|| self.password.as_ref()) - .expect("PostgreSQL database password must be provided in config or env var VSS_POSTGRESQL_PASSWORD must be set."); - - format!("postgresql://{}:{}@{}:{}", username, password, self.host, self.port) +struct TlsConfig { + crt_pem: Option, +} + +// Encapsulates the result of reading both the environment variables and the config file. +pub(crate) struct Configuration { + pub(crate) bind_address: SocketAddr, + pub(crate) rsa_pem: Option, + pub(crate) postgresql_prefix: String, + pub(crate) default_db: String, + pub(crate) vss_db: String, + pub(crate) tls_config: Option>, +} + +#[inline] +fn read_env(env_var: &str) -> Result, String> { + match std::env::var(env_var) { + Ok(env) => Ok(Some(env)), + Err(std::env::VarError::NotPresent) => Ok(None), + Err(e) => Err(format!("Failed to load the {} environment variable: {}", env_var, e)), } } -pub(crate) fn load_config(config_path: &str) -> Result> { - let config_str = std::fs::read_to_string(config_path)?; - let config: Config = toml::from_str(&config_str)?; - Ok(config) +#[inline] +fn read_config<'a, T: std::fmt::Display>( + env: Option, config: Option, item: &str, var_name: &str, +) -> Result { + env.or(config).ok_or(format!( + "{} must be provided in the configuration file or the environment variable {} must be set.", + item, var_name + )) +} + +pub(crate) fn load_configuration(config_file_path: Option<&str>) -> Result { + let TomlConfig { server_config, jwt_auth_config, postgresql_config } = match config_file_path { + Some(path) => { + let config_file = std::fs::read_to_string(path) + .map_err(|e| format!("Failed to read configuration file: {}", e))?; + toml::from_str(&config_file) + .map_err(|e| format!("Failed to parse configuration file: {}", e))? + }, + None => TomlConfig::default(), // All fields are set to `None` + }; + + let bind_address_env = read_env(BIND_ADDR_VAR)? + .map(|addr| { + addr.parse().map_err(|e| { + format!("Unable to parse the bind address environment variable: {}", e) + }) + }) + .transpose()?; + let bind_address = read_config( + bind_address_env, + server_config.and_then(|c| c.bind_address), + "VSS server bind address", + BIND_ADDR_VAR, + )?; + + let rsa_pem_env = read_env(JWT_RSA_PEM_VAR)?; + let rsa_pem = rsa_pem_env.or(jwt_auth_config.and_then(|config| config.rsa_pem)); + + let username_env = read_env(PSQL_USER_VAR)?; + let password_env = read_env(PSQL_PASS_VAR)?; + let address_env: Option = read_env(PSQL_ADDR_VAR)? + .map(|address| { + address.parse().map_err(|e| { + format!("Unable to parse the postgresql address environment variable: {}", e) + }) + }) + .transpose()?; + let default_db_env = read_env(PSQL_DB_VAR)?; + let vss_db_env = read_env(PSQL_VSS_DB_VAR)?; + let tls_config_env = read_env(PSQL_TLS_VAR)?; + let crt_pem_env = read_env(PSQL_CERT_PEM_VAR)?; + + let ( + username_config, + password_config, + address_config, + default_db_config, + vss_db_config, + tls_config, + ) = match postgresql_config { + Some(c) => ( + c.username, + c.password, + c.address, + c.default_database, + c.vss_database, + c.tls.map(|tls| tls.crt_pem), + ), + None => (None, None, None, None, None, None), + }; + + let username = + read_config(username_env, username_config, "PostgreSQL database username", PSQL_USER_VAR)?; + let password = + read_config(password_env, password_config, "PostgreSQL database password", PSQL_PASS_VAR)?; + let address = + read_config(address_env, address_config, "PostgreSQL service address", PSQL_ADDR_VAR)?; + let default_db = read_config( + default_db_env, + default_db_config, + "PostgreSQL default database name", + PSQL_DB_VAR, + )?; + let vss_db = + read_config(vss_db_env, vss_db_config, "PostgreSQL vss database name", PSQL_VSS_DB_VAR)?; + + let tls_config = + crt_pem_env.map(|pem| Some(pem)).or(tls_config_env.map(|_| None)).or(tls_config); + + let postgresql_prefix = format!("postgresql://{}:{}@{}", username, password, address); + + Ok(Configuration { bind_address, rsa_pem, postgresql_prefix, default_db, vss_db, tls_config }) } diff --git a/rust/server/vss-server-config.toml b/rust/server/vss-server-config.toml index 0c2a486..7a80dff 100644 --- a/rust/server/vss-server-config.toml +++ b/rust/server/vss-server-config.toml @@ -1,6 +1,5 @@ [server_config] -host = "127.0.0.1" -port = 8080 +bind_address = "127.0.0.1:8080" # Optional in TOML, can be overridden by env var `VSS_BIND_ADDRESS` # 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` @@ -11,17 +10,16 @@ port = 8080 # """ [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` -host = "localhost" -port = 5432 +username = "postgres" # Optional in TOML, can be overridden by env var `VSS_PSQL_USERNAME` +password = "postgres" # Optional in TOML, can be overridden by env var `VSS_PSQL_PASSWORD` +address = "127.0.0.1:5432" # Optional in TOML, can be overridden by env var `VSS_PSQL_ADDRESS` # We first connect to `default_database` to create `vss_database` if it does not exist -default_database = "postgres" -vss_database = "vss" +default_database = "postgres" # Optional in TOML, can be overridden by env var `VSS_PSQL_DEFAULT_DB` +vss_database = "vss" # Optional in TOML, can be overridden by env var `VSS_PSQL_VSS_DB` -# [postgresql_config.tls] # Uncomment to make TLS connections to the postgres database +# [postgresql_config.tls] # Uncomment, or set env var `VSS_PSQL_TLS` to make TLS connections to the postgres database # -# Uncomment the lines below to add a root certificate to your trusted root certificates +# Uncomment the lines below, or set `VSS_PSQL_CRT_PEM`, to add a root certificate to your trusted root certificates # # crt_pem = """ # -----BEGIN CERTIFICATE-----