From 82b92113ddfb83a76610d8b553cc4ad9faa13efa Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 22 Jan 2026 02:39:15 +0000 Subject: [PATCH 1/5] Add a logger for VSS This is a clone of the `ldk-server` logger --- rust/server/Cargo.toml | 2 + rust/server/src/main.rs | 25 ++++ rust/server/src/util/config.rs | 71 ++++++++++-- rust/server/src/util/logger.rs | 176 +++++++++++++++++++++++++++++ rust/server/src/util/mod.rs | 1 + rust/server/vss-server-config.toml | 6 +- 6 files changed, 270 insertions(+), 11 deletions(-) create mode 100644 rust/server/src/util/logger.rs diff --git a/rust/server/Cargo.toml b/rust/server/Cargo.toml index 686c154..81504f3 100644 --- a/rust/server/Cargo.toml +++ b/rust/server/Cargo.toml @@ -21,3 +21,5 @@ prost = { version = "0.11.6", default-features = false, features = ["std"] } bytes = "1.4.0" serde = { version = "1.0.203", default-features = false, features = ["derive"] } toml = { version = "0.8.9", default-features = false, features = ["parse"] } +log = { version = "0.4.29", default-features = false, features = ["std"] } +chrono = { version = "0.4", default-features = false, features = ["clock"] } diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index cfc529f..6c5128c 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -17,6 +17,8 @@ use tokio::signal::unix::SignalKind; use hyper::server::conn::http1; use hyper_util::rt::TokioIo; +use log::error; + use api::auth::{Authorizer, NoopAuthorizer}; use api::kv_store::KvStore; #[cfg(feature = "jwt")] @@ -24,6 +26,7 @@ use auth_impls::jwt::JWTAuthorizer; #[cfg(feature = "sigs")] use auth_impls::signature::SignatureValidatingAuthorizer; use impls::postgres_store::{PostgresPlaintextBackend, PostgresTlsBackend}; +use util::logger::ServerLogger; use vss_service::VssService; mod util; @@ -38,6 +41,14 @@ fn main() { std::process::exit(-1); }); + let logger = match ServerLogger::init(config.log_level, &config.log_file) { + Ok(logger) => logger, + Err(e) => { + eprintln!("Failed to initialize logger: {e}"); + std::process::exit(-1); + }, + }; + let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { Ok(runtime) => Arc::new(runtime), Err(e) => { @@ -47,6 +58,15 @@ fn main() { }; runtime.block_on(async { + // Register SIGHUP handler for log rotation + let mut sighup_stream = match tokio::signal::unix::signal(SignalKind::hangup()) { + Ok(stream) => stream, + Err(e) => { + eprintln!("Failed to register SIGHUP handler: {e}"); + std::process::exit(-1); + } + }; + let mut sigterm_stream = match tokio::signal::unix::signal(SignalKind::terminate()) { Ok(stream) => stream, Err(e) => { @@ -146,6 +166,11 @@ fn main() { println!("Received CTRL-C, shutting down.."); break; } + _ = sighup_stream.recv() => { + if let Err(e) = logger.reopen() { + error!("Failed to reopen log file on SIGHUP: {e}"); + } + } _ = sigterm_stream.recv() => { println!("Received SIGTERM, shutting down.."); break; diff --git a/rust/server/src/util/config.rs b/rust/server/src/util/config.rs index 3236941..2252653 100644 --- a/rust/server/src/util/config.rs +++ b/rust/server/src/util/config.rs @@ -1,7 +1,11 @@ +use log::LevelFilter; use serde::Deserialize; use std::net::SocketAddr; +use std::path::PathBuf; const BIND_ADDR_VAR: &str = "VSS_BIND_ADDRESS"; +const LOG_FILE_VAR: &str = "VSS_LOG_FILE"; +const LOG_LEVEL_VAR: &str = "VSS_LOG_LEVEL"; 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"; @@ -16,6 +20,7 @@ const PSQL_CERT_PEM_VAR: &str = "VSS_PSQL_CRT_PEM"; #[derive(Deserialize, Default)] struct TomlConfig { server_config: Option, + log_config: Option, jwt_auth_config: Option, postgresql_config: Option, } @@ -45,6 +50,12 @@ struct TlsConfig { crt_pem: Option, } +#[derive(Deserialize)] +struct LogConfig { + level: Option, + file: Option, +} + // Encapsulates the result of reading both the environment variables and the config file. pub(crate) struct Configuration { pub(crate) bind_address: SocketAddr, @@ -53,6 +64,8 @@ pub(crate) struct Configuration { pub(crate) default_db: String, pub(crate) vss_db: String, pub(crate) tls_config: Option>, + pub(crate) log_file: PathBuf, + pub(crate) log_level: LevelFilter, } #[inline] @@ -75,15 +88,16 @@ fn read_config<'a, T: std::fmt::Display>( } 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 TomlConfig { server_config, log_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| { @@ -99,6 +113,34 @@ pub(crate) fn load_configuration(config_file_path: Option<&str>) -> Result = read_env(LOG_LEVEL_VAR)? + .map(|level_str| { + level_str + .parse() + .map_err(|e| format!("Unable to parse the log level environment variable: {}", e)) + }) + .transpose()?; + let log_level_config: Option = log_config + .as_ref() + .and_then(|config| config.level.as_ref()) + .map(|level_str| { + level_str + .parse() + .map_err(|e| format!("Unable to parse the log level config variable: {}", e)) + }) + .transpose()?; + let log_level = log_level_env.or(log_level_config).unwrap_or(LevelFilter::Debug); + + let log_file_env: Option = read_env(LOG_FILE_VAR)? + .map(|file_str| { + file_str + .parse() + .map_err(|e| format!("Unable to parse the log file environment variable: {}", e)) + }) + .transpose()?; + let log_file_config: Option = log_config.and_then(|config| config.file); + let log_file = log_file_env.or(log_file_config).unwrap_or(PathBuf::from("vss.log")); + 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)); @@ -155,5 +197,14 @@ pub(crate) fn load_configuration(config_file_path: Option<&str>) -> Result or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use std::fs::{self, File, OpenOptions}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +use log::{Level, LevelFilter, Log, Metadata, Record}; + +/// A logger implementation that writes logs to both stderr and a file. +/// +/// The logger formats log messages with RFC3339 timestamps and writes them to: +/// - stdout/stderr for console output +/// - A file specified during initialization +/// +/// All log messages follow the format: +/// `[TIMESTAMP LEVEL TARGET FILE:LINE] MESSAGE` +/// +/// Example: `[2025-12-04T10:30:45Z INFO ldk_server:42] Starting up...` +/// +/// The logger handles SIGHUP for log rotation by reopening the file handle when signaled. +pub struct ServerLogger { + /// The maximum log level to display + level: LevelFilter, + /// The file to write logs to, protected by a mutex for thread-safe access + file: Mutex, + /// Path to the log file for reopening on SIGHUP + log_file_path: PathBuf, +} + +impl ServerLogger { + /// Initializes the global logger with the specified level and file path. + /// + /// Opens or creates the log file at the given path. If the file exists, logs are appended. + /// If the file doesn't exist, it will be created along with any necessary parent directories. + /// + /// This should be called once at application startup. Subsequent calls will fail. + /// + /// Returns an Arc to the logger for signal handling purposes. + pub fn init(level: LevelFilter, log_file_path: &Path) -> Result, io::Error> { + // Create parent directories if they don't exist + if let Some(parent) = log_file_path.parent() { + fs::create_dir_all(parent)?; + } + + let file = open_log_file(log_file_path)?; + + let logger = Arc::new(ServerLogger { + level, + file: Mutex::new(file), + log_file_path: log_file_path.to_path_buf(), + }); + + log::set_boxed_logger(Box::new(LoggerWrapper(Arc::clone(&logger)))) + .map_err(io::Error::other)?; + log::set_max_level(level); + Ok(logger) + } + + /// Reopens the log file. Called on SIGHUP for log rotation. + pub fn reopen(&self) -> Result<(), io::Error> { + let new_file = open_log_file(&self.log_file_path)?; + match self.file.lock() { + Ok(mut file) => { + // Flush the old buffer before replacing with the new file + file.flush()?; + *file = new_file; + Ok(()) + }, + Err(e) => Err(io::Error::other(format!("Failed to acquire lock: {e}"))), + } + } +} + +impl Log for ServerLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let level_str = format_level(record.level()); + let line = record.line().unwrap_or(0); + + // Log to console + let _ = match record.level() { + Level::Error => { + writeln!( + io::stderr(), + "[{} {} {}:{}] {}", + format_timestamp(), + level_str, + record.target(), + line, + record.args() + ) + }, + _ => { + writeln!( + io::stdout(), + "[{} {} {}:{}] {}", + format_timestamp(), + level_str, + record.target(), + line, + record.args() + ) + }, + }; + + // Log to file + if let Ok(mut file) = self.file.lock() { + let _ = writeln!( + file, + "[{} {} {}:{}] {}", + format_timestamp(), + level_str, + record.target(), + line, + record.args() + ); + } + } + } + + fn flush(&self) { + let _ = io::stdout().flush(); + let _ = io::stderr().flush(); + if let Ok(mut file) = self.file.lock() { + let _ = file.flush(); + } + } +} + +fn format_timestamp() -> String { + let now = chrono::Utc::now(); + now.to_rfc3339_opts(chrono::SecondsFormat::Millis, true) +} + +fn format_level(level: Level) -> &'static str { + match level { + Level::Error => "ERROR", + Level::Warn => "WARN ", + Level::Info => "INFO ", + Level::Debug => "DEBUG", + Level::Trace => "TRACE", + } +} + +fn open_log_file(log_file_path: &Path) -> Result { + OpenOptions::new().create(true).append(true).open(log_file_path) +} + +/// Wrapper to allow Arc to implement Log trait +struct LoggerWrapper(Arc); + +impl Log for LoggerWrapper { + fn enabled(&self, metadata: &Metadata) -> bool { + self.0.enabled(metadata) + } + + fn log(&self, record: &Record) { + self.0.log(record) + } + + fn flush(&self) { + self.0.flush() + } +} diff --git a/rust/server/src/util/mod.rs b/rust/server/src/util/mod.rs index 30e489d..794593c 100644 --- a/rust/server/src/util/mod.rs +++ b/rust/server/src/util/mod.rs @@ -1 +1,2 @@ pub(crate) mod config; +pub(crate) mod logger; diff --git a/rust/server/vss-server-config.toml b/rust/server/vss-server-config.toml index 7a80dff..3c8fc1d 100644 --- a/rust/server/vss-server-config.toml +++ b/rust/server/vss-server-config.toml @@ -19,9 +19,13 @@ vss_database = "vss" # Optional in TOML, can be overridden by env var # [postgresql_config.tls] # Uncomment, or set env var `VSS_PSQL_TLS` to make TLS connections to the postgres database # -# Uncomment the lines below, or set `VSS_PSQL_CRT_PEM`, 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----- # -----END CERTIFICATE----- # """ + +# [log_config] +# level = "debug" # Uncomment, or set env var `VSS_LOG_LEVEL` to set the log level, the default is "debug" +# file = "vss.log" # Uncomment, or set env var `VSS_LOG_FILE` to set the log file path, the default is "vss.log" From 7c840997440b171e228a29a0227c03d6ab759b68 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 22 Jan 2026 03:37:55 +0000 Subject: [PATCH 2/5] Move all `println`'s after logger setup to the log facade --- rust/impls/Cargo.toml | 1 + rust/impls/src/postgres_store.rs | 9 +++++--- rust/server/src/main.rs | 36 ++++++++++++++++---------------- rust/server/src/vss_service.rs | 30 ++++++++++++++++++++++---- 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/rust/impls/Cargo.toml b/rust/impls/Cargo.toml index fd8cece..bfc61e1 100644 --- a/rust/impls/Cargo.toml +++ b/rust/impls/Cargo.toml @@ -12,6 +12,7 @@ bytes = "1.4.0" tokio = { version = "1.38.0", default-features = false, features = ["rt", "macros"] } native-tls = { version = "0.2.14", default-features = false } postgres-native-tls = { version = "0.5.2", default-features = false, features = ["runtime"] } +log = "0.4.29" [dev-dependencies] tokio = { version = "1.38.0", default-features = false, features = ["rt-multi-thread", "macros"] } diff --git a/rust/impls/src/postgres_store.rs b/rust/impls/src/postgres_store.rs index 35363ef..b3fd714 100644 --- a/rust/impls/src/postgres_store.rs +++ b/rust/impls/src/postgres_store.rs @@ -17,6 +17,8 @@ use tokio::sync::Mutex; use tokio_postgres::tls::{MakeTlsConnect, TlsConnect}; use tokio_postgres::{error, Client, NoTls, Socket, Transaction}; +use log::{debug, error, info, warn}; + pub use native_tls::Certificate; pub(crate) struct VssDbRecord { @@ -104,6 +106,7 @@ where async fn ensure_connected(&self, client: &mut Client) -> Result<(), Error> { if client.is_closed() || client.check_connection().await.is_err() { + debug!("Rotating connection to the postgres database"); let new_client = make_db_connection(&self.endpoint, &self.db_name, self.tls.clone()).await?; *client = new_client; @@ -145,7 +148,7 @@ where // Connection must be driven on a separate task, and will resolve when the client is dropped tokio::spawn(async move { if let Err(e) = connection.await { - eprintln!("Connection error: {}", e); + warn!("Connection error: {}", e); } }); Ok(client) @@ -173,7 +176,7 @@ where client.execute(&stmt, &[]).await.map_err(|e| { Error::new(ErrorKind::Other, format!("Failed to create database {}: {}", db_name, e)) })?; - println!("Created database {}", db_name); + info!("Created database {}", db_name); } Ok(()) @@ -291,7 +294,7 @@ where panic!("We do not allow downgrades"); } - println!("Applying migration(s) {} through {}", migration_start, migrations.len() - 1); + info!("Applying migration(s) {} through {}", migration_start, migrations.len() - 1); for (idx, &stmt) in (&migrations[migration_start..]).iter().enumerate() { let _num_rows = tx.execute(stmt, &[]).await.map_err(|e| { diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 6c5128c..560522e 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -17,7 +17,7 @@ use tokio::signal::unix::SignalKind; use hyper::server::conn::http1; use hyper_util::rt::TokioIo; -use log::error; +use log::{error, info, warn}; use api::auth::{Authorizer, NoopAuthorizer}; use api::kv_store::KvStore; @@ -52,7 +52,7 @@ fn main() { let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { Ok(runtime) => Arc::new(runtime), Err(e) => { - eprintln!("Failed to setup tokio runtime: {}", e); + error!("Failed to setup tokio runtime: {}", e); std::process::exit(-1); }, }; @@ -62,7 +62,7 @@ fn main() { let mut sighup_stream = match tokio::signal::unix::signal(SignalKind::hangup()) { Ok(stream) => stream, Err(e) => { - eprintln!("Failed to register SIGHUP handler: {e}"); + error!("Failed to register SIGHUP handler: {e}"); std::process::exit(-1); } }; @@ -70,7 +70,7 @@ fn main() { let mut sigterm_stream = match tokio::signal::unix::signal(SignalKind::terminate()) { Ok(stream) => stream, Err(e) => { - println!("Failed to register for SIGTERM stream: {}", e); + error!("Failed to register for SIGTERM stream: {}", e); std::process::exit(-1); }, }; @@ -81,11 +81,11 @@ fn main() { 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"); + info!("Configured JWT authorizer with RSA public key"); Some(Arc::new(auth)) }, Err(e) => { - println!("Failed to configure JWT authorizer: {}", e); + error!("Failed to configure JWT authorizer: {}", e); std::process::exit(-1); }, }; @@ -94,14 +94,14 @@ fn main() { #[cfg(feature = "sigs")] { if authorizer.is_none() { - println!("Configured signature-validating authorizer"); + info!("Configured signature-validating authorizer"); authorizer = Some(Arc::new(SignatureValidatingAuthorizer)); } } let authorizer = if let Some(auth) = authorizer { auth } else { - println!("No authentication method configured, all storage with the same store id will be commingled."); + warn!("No authentication method configured, all storage with the same store id will be commingled."); Arc::new(NoopAuthorizer {}) }; @@ -114,10 +114,10 @@ fn main() { ) .await .unwrap_or_else(|e| { - println!("Failed to start postgres TLS backend: {}", e); + error!("Failed to start postgres TLS backend: {}", e); std::process::exit(-1); }); - println!( + info!( "Connected to PostgreSQL TLS backend with DSN: {}/{}", config.postgresql_prefix, config.vss_db ); @@ -130,10 +130,10 @@ fn main() { ) .await .unwrap_or_else(|e| { - println!("Failed to start postgres plaintext backend: {}", e); + error!("Failed to start postgres plaintext backend: {}", e); std::process::exit(-1); }); - println!( + info!( "Connected to PostgreSQL plaintext backend with DSN: {}/{}", config.postgresql_prefix, config.vss_db ); @@ -141,10 +141,10 @@ fn main() { }; let rest_svc_listener = TcpListener::bind(&config.bind_address).await.unwrap_or_else(|e| { - println!("Failed to bind listening port: {}", e); + error!("Failed to bind listening port: {}", e); std::process::exit(-1); }); - println!("Listening for incoming connections on {}{}", config.bind_address, crate::vss_service::BASE_PATH_PREFIX); + info!("Listening for incoming connections on {}{}", config.bind_address, crate::vss_service::BASE_PATH_PREFIX); loop { tokio::select! { @@ -155,15 +155,15 @@ fn main() { let vss_service = VssService::new(Arc::clone(&store), Arc::clone(&authorizer)); runtime.spawn(async move { if let Err(err) = http1::Builder::new().serve_connection(io_stream, vss_service).await { - eprintln!("Failed to serve connection: {}", err); + warn!("Failed to serve connection: {}", err); } }); }, - Err(e) => eprintln!("Failed to accept connection: {}", e), + Err(e) => warn!("Failed to accept connection: {}", e), } } _ = tokio::signal::ctrl_c() => { - println!("Received CTRL-C, shutting down.."); + info!("Received CTRL-C, shutting down.."); break; } _ = sighup_stream.recv() => { @@ -172,7 +172,7 @@ fn main() { } } _ = sigterm_stream.recv() => { - println!("Received SIGTERM, shutting down.."); + info!("Received SIGTERM, shutting down.."); break; } } diff --git a/rust/server/src/vss_service.rs b/rust/server/src/vss_service.rs index f641c0e..38c402c 100644 --- a/rust/server/src/vss_service.rs +++ b/rust/server/src/vss_service.rs @@ -18,6 +18,8 @@ use std::future::Future; use std::pin::Pin; use std::sync::Arc; +use log::{debug, trace}; + #[derive(Clone)] pub struct VssService { store: Arc, @@ -73,22 +75,42 @@ impl Service> for VssService { async fn handle_get_object_request( store: Arc, user_token: String, request: GetObjectRequest, ) -> Result { - store.get(user_token, request).await + trace!("handling GetObject request with store_id {}", request.store_id); + let result = store.get(user_token, request).await; + if let Err(ref e) = result { + debug!("GetObject request with store_id {} failed", e); + } + result } async fn handle_put_object_request( store: Arc, user_token: String, request: PutObjectRequest, ) -> Result { - store.put(user_token, request).await + trace!("handling PutObject request with store_id {}", request.store_id); + let result = store.put(user_token, request).await; + if let Err(ref e) = result { + debug!("PutObject request with store_id {} failed", e); + } + result } async fn handle_delete_object_request( store: Arc, user_token: String, request: DeleteObjectRequest, ) -> Result { - store.delete(user_token, request).await + trace!("handling DeleteObject request with store_id {}", request.store_id); + let result = store.delete(user_token, request).await; + if let Err(ref e) = result { + debug!("DeleteObject request with store_id {} failed", e); + } + result } async fn handle_list_object_request( store: Arc, user_token: String, request: ListKeyVersionsRequest, ) -> Result { - store.list_key_versions(user_token, request).await + trace!("handling ListKeyVersions request with store_id {}", request.store_id); + let result = store.list_key_versions(user_token, request).await; + if let Err(ref e) = result { + debug!("ListKeyVersions request with store_id {} failed", e); + } + result } async fn handle_request< T: Message + Default, From 3908ab85d95c6c7889e9c19593cb01dde6caf6bd Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 22 Jan 2026 22:38:20 +0000 Subject: [PATCH 3/5] fixup: specify no default features on the log dependency in the `impl`crate --- rust/impls/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/impls/Cargo.toml b/rust/impls/Cargo.toml index bfc61e1..7b2be11 100644 --- a/rust/impls/Cargo.toml +++ b/rust/impls/Cargo.toml @@ -12,7 +12,7 @@ bytes = "1.4.0" tokio = { version = "1.38.0", default-features = false, features = ["rt", "macros"] } native-tls = { version = "0.2.14", default-features = false } postgres-native-tls = { version = "0.5.2", default-features = false, features = ["runtime"] } -log = "0.4.29" +log = { version = "0.4.29", default-features = false } [dev-dependencies] tokio = { version = "1.38.0", default-features = false, features = ["rt-multi-thread", "macros"] } From d8b12959433603bba4627119e2cd56c3237888ff Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 23 Jan 2026 21:45:24 +0000 Subject: [PATCH 4/5] fixup: make logs consistent with vss-client --- rust/server/Cargo.toml | 1 + rust/server/src/util/mod.rs | 20 ++++++++++++++++++ rust/server/src/vss_service.rs | 37 ++++++++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/rust/server/Cargo.toml b/rust/server/Cargo.toml index 81504f3..007cc7a 100644 --- a/rust/server/Cargo.toml +++ b/rust/server/Cargo.toml @@ -23,3 +23,4 @@ serde = { version = "1.0.203", default-features = false, features = ["derive"] } toml = { version = "0.8.9", default-features = false, features = ["parse"] } log = { version = "0.4.29", default-features = false, features = ["std"] } chrono = { version = "0.4", default-features = false, features = ["clock"] } +rand = { version = "0.9.2", default-features = false } diff --git a/rust/server/src/util/mod.rs b/rust/server/src/util/mod.rs index 794593c..a4bb4c2 100644 --- a/rust/server/src/util/mod.rs +++ b/rust/server/src/util/mod.rs @@ -1,2 +1,22 @@ pub(crate) mod config; pub(crate) mod logger; + +use api::types::KeyValue; + +/// A copy of the same printer used in vss-client to keep logs consistent. +pub(crate) struct KeyValueVecKeyPrinter<'a>(pub(crate) &'a Vec); + +impl core::fmt::Display for KeyValueVecKeyPrinter<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + for (i, k) in self.0.iter().enumerate() { + if i == self.0.len() - 1 { + write!(f, "{}", &k.key)?; + } else { + write!(f, "{}, ", &k.key)?; + } + } + write!(f, "]")?; + Ok(()) + } +} diff --git a/rust/server/src/vss_service.rs b/rust/server/src/vss_service.rs index 38c402c..1dad13a 100644 --- a/rust/server/src/vss_service.rs +++ b/rust/server/src/vss_service.rs @@ -20,6 +20,8 @@ use std::sync::Arc; use log::{debug, trace}; +use crate::util::KeyValueVecKeyPrinter; + #[derive(Clone)] pub struct VssService { store: Arc, @@ -75,40 +77,59 @@ impl Service> for VssService { async fn handle_get_object_request( store: Arc, user_token: String, request: GetObjectRequest, ) -> Result { - trace!("handling GetObject request with store_id {}", request.store_id); + let request_id: u64 = rand::random(); + trace!("Handling GetObjectRequest {} for key {}.", request_id, request.key); let result = store.get(user_token, request).await; if let Err(ref e) = result { - debug!("GetObject request with store_id {} failed", e); + debug!("GetObjectRequest {} failed: {}", request_id, e); } result } async fn handle_put_object_request( store: Arc, user_token: String, request: PutObjectRequest, ) -> Result { - trace!("handling PutObject request with store_id {}", request.store_id); + let request_id: u64 = rand::random(); + trace!( + "Handling PutObjectRequest {} for transaction_items {} and delete_items {}.", + request_id, + KeyValueVecKeyPrinter(&request.transaction_items), + KeyValueVecKeyPrinter(&request.delete_items), + ); let result = store.put(user_token, request).await; if let Err(ref e) = result { - debug!("PutObject request with store_id {} failed", e); + debug!("PutObjectRequest {} failed: {}", request_id, e); } result } async fn handle_delete_object_request( store: Arc, user_token: String, request: DeleteObjectRequest, ) -> Result { - trace!("handling DeleteObject request with store_id {}", request.store_id); + let request_id: u64 = rand::random(); + trace!( + "Handling DeleteObjectRequest {} for key {:?}", + request_id, + request.key_value.as_ref().map(|t| &t.key) + ); let result = store.delete(user_token, request).await; if let Err(ref e) = result { - debug!("DeleteObject request with store_id {} failed", e); + trace!("DeleteObjectRequest {} failed: {}", request_id, e); } result } async fn handle_list_object_request( store: Arc, user_token: String, request: ListKeyVersionsRequest, ) -> Result { - trace!("handling ListKeyVersions request with store_id {}", request.store_id); + let request_id: u64 = rand::random(); + trace!( + "Handling ListKeyVersionsRequest {} for key_prefix {:?}, page_size {:?}, page_token {:?}", + request_id, + request.key_prefix, + request.page_size, + request.page_token + ); let result = store.list_key_versions(user_token, request).await; if let Err(ref e) = result { - debug!("ListKeyVersions request with store_id {} failed", e); + debug!("ListKeyVersionsRequest {} failed: {}", request_id, e); } result } From 06f748706408f749d0b04b3989816e4f1bb57650 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 22 Jan 2026 23:25:21 +0000 Subject: [PATCH 5/5] Gate `NoopAuthorizer` behind the `noop_authorizer` cfg flag `NoopAuthorizer` is now only accessible via `RUSTFLAGS="--cfg noop_authorizer" cargo run --no-default-features`. We want to discourage our users from ever using `NoopAuthorizer`. --- .github/workflows/build-and-deploy-rust.yml | 7 +++---- .github/workflows/ldk-node-integration.yml | 3 +-- rust/Cargo.toml | 4 ++++ rust/api/src/auth.rs | 4 +++- rust/server/Cargo.toml | 6 ++++++ rust/server/src/main.rs | 12 +++++++++++- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-deploy-rust.yml b/.github/workflows/build-and-deploy-rust.yml index 23399bd..4516640 100644 --- a/.github/workflows/build-and-deploy-rust.yml +++ b/.github/workflows/build-and-deploy-rust.yml @@ -33,8 +33,7 @@ jobs: - name: Build and Deploy VSS Server run: | cd rust - cargo build - cargo run server/vss-server-config.toml& + RUSTFLAGS="--cfg noop_authorizer" cargo run --no-default-features server/vss-server-config.toml& - name: Hit endpoint to verify service is up run: | @@ -42,8 +41,8 @@ jobs: # Put request with store='storeId' and key=k1 hex=0A0773746F726549641A150A026B3110FFFFFFFFFFFFFFFFFF011A046B317631 - curl --data-binary "$(echo "$hex" | xxd -r -p)" http://localhost:8080/vss/putObjects + curl -f --data-binary "$(echo "$hex" | xxd -r -p)" http://localhost:8080/vss/putObjects # Get request with store='storeId' and key=k1 hex=0A0773746F7265496412026B31 - curl --data-binary "$(echo "$hex" | xxd -r -p)" http://localhost:8080/vss/getObject + curl -f --data-binary "$(echo "$hex" | xxd -r -p)" http://localhost:8080/vss/getObject diff --git a/.github/workflows/ldk-node-integration.yml b/.github/workflows/ldk-node-integration.yml index e79d210..cd27eb4 100644 --- a/.github/workflows/ldk-node-integration.yml +++ b/.github/workflows/ldk-node-integration.yml @@ -39,8 +39,7 @@ jobs: - name: Build and Deploy VSS Server run: | cd vss-server/rust - cargo build - cargo run --no-default-features server/vss-server-config.toml& + RUSTFLAGS="--cfg noop_authorizer" cargo run --no-default-features server/vss-server-config.toml& - name: Run LDK Node Integration tests run: | cd ldk-node diff --git a/rust/Cargo.toml b/rust/Cargo.toml index f7d60c1..183f43e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,3 +10,7 @@ lto = true [profile.dev] panic = "abort" + +[workspace.lints.rust.unexpected_cfgs] +level = "forbid" +check-cfg = ['cfg(noop_authorizer)'] diff --git a/rust/api/src/auth.rs b/rust/api/src/auth.rs index 5b742ba..a8290ee 100644 --- a/rust/api/src/auth.rs +++ b/rust/api/src/auth.rs @@ -1,7 +1,6 @@ use crate::error::VssError; use async_trait::async_trait; use std::collections::HashMap; -use std::string::ToString; /// Response returned for [`Authorizer`] request if user is authenticated and authorized. #[derive(Debug, Clone)] @@ -21,10 +20,13 @@ pub trait Authorizer: Send + Sync { } /// A no-operation authorizer, which lets any user-request go through. +#[cfg(feature = "_test_utils")] pub struct NoopAuthorizer {} +#[cfg(feature = "_test_utils")] const UNAUTHENTICATED_USER: &str = "unauth-user"; +#[cfg(feature = "_test_utils")] #[async_trait] impl Authorizer for NoopAuthorizer { async fn verify( diff --git a/rust/server/Cargo.toml b/rust/server/Cargo.toml index 007cc7a..f0d67d5 100644 --- a/rust/server/Cargo.toml +++ b/rust/server/Cargo.toml @@ -24,3 +24,9 @@ toml = { version = "0.8.9", default-features = false, features = ["parse"] } log = { version = "0.4.29", default-features = false, features = ["std"] } chrono = { version = "0.4", default-features = false, features = ["clock"] } rand = { version = "0.9.2", default-features = false } + +[target.'cfg(noop_authorizer)'.dependencies] +api = { path = "../api", features = ["_test_utils"] } + +[lints] +workspace = true diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 560522e..16515c4 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -19,7 +19,9 @@ use hyper_util::rt::TokioIo; use log::{error, info, warn}; -use api::auth::{Authorizer, NoopAuthorizer}; +use api::auth::Authorizer; +#[cfg(noop_authorizer)] +use api::auth::NoopAuthorizer; use api::kv_store::KvStore; #[cfg(feature = "jwt")] use auth_impls::jwt::JWTAuthorizer; @@ -98,6 +100,8 @@ fn main() { authorizer = Some(Arc::new(SignatureValidatingAuthorizer)); } } + + #[cfg(noop_authorizer)] let authorizer = if let Some(auth) = authorizer { auth } else { @@ -105,6 +109,12 @@ fn main() { Arc::new(NoopAuthorizer {}) }; + #[cfg(not(noop_authorizer))] + let authorizer = authorizer.unwrap_or_else(|| { + error!("No authentication method configured, please configure either `JWTAuthorizer` or `SignatureValidatingAuthorizer`"); + std::process::exit(-1); + }); + let store: Arc = if let Some(crt_pem) = config.tls_config { let postgres_tls_backend = PostgresTlsBackend::new( &config.postgresql_prefix,