From b26e1098b2d9e5e040adddea803dd7b0cacc419b Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 5 Aug 2025 10:48:05 +0200 Subject: [PATCH 001/100] version crate, structs and tower layer --- Cargo.lock | 48 +++++++++ Cargo.toml | 1 + crates/defguard_core/Cargo.toml | 2 + crates/defguard_core/src/grpc/mod.rs | 8 +- crates/defguard_version/Cargo.toml | 16 +++ crates/defguard_version/src/lib.rs | 153 +++++++++++++++++++++++++++ 6 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 crates/defguard_version/Cargo.toml create mode 100644 crates/defguard_version/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 795ec0a755..22f082141c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1088,6 +1088,7 @@ dependencies = [ "chrono", "claims", "clap", + "defguard_version", "defguard_web_ui", "dotenvy", "ed25519-dalek", @@ -1137,6 +1138,7 @@ dependencies = [ "tonic-build", "tonic-health", "totp-lite", + "tower 0.5.2", "tower-http", "tracing", "tracing-subscriber", @@ -1179,6 +1181,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "defguard_version" +version = "0.0.0" +dependencies = [ + "http", + "os_info", + "semver", + "thiserror 2.0.12", + "tower 0.5.2", + "tracing", +] + [[package]] name = "defguard_web_ui" version = "0.0.0" @@ -3090,6 +3104,18 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "os_info" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +dependencies = [ + "log", + "plist", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "overload" version = "0.1.1" @@ -3458,6 +3484,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64 0.22.1", + "indexmap 2.10.0", + "quick-xml", + "serde", + "time", +] + [[package]] name = "polyval" version = "0.6.2" @@ -3627,6 +3666,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" +[[package]] +name = "quick-xml" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.8" diff --git a/Cargo.toml b/Cargo.toml index 3a5f0a6d97..b2c3d7d53f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ resolver = "2" defguard_core = { path = "./crates/defguard_core", version = "1.5.0" } defguard_event_logger = { path = "./crates/defguard_event_logger", version = "0.0.0" } defguard_event_router = { path = "./crates/defguard_event_router", version = "0.0.0" } +defguard_version = { path = "./crates/defguard_version", version = "0.0.0" } defguard_web_ui = { path = "./crates/defguard_web_ui", version = "0.0.0" } model_derive = { path = "./crates/model_derive", version = "0.0.0" } diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index c141334dba..b169112318 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -10,6 +10,7 @@ rust-version.workspace = true [dependencies] # internal crates defguard_web_ui = { workspace = true } +defguard_version = { workspace = true } model_derive = { workspace = true } # external dependencies @@ -84,6 +85,7 @@ strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } ed25519-dalek = "2.2.0" +tower = "0.5.2" # https://github.com/juhaku/utoipa/issues/1345 [dependencies.zip] diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index c75c21c0d8..1ff7961ccf 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -8,7 +8,7 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, }; - +use defguard_version::{DefguardVersionLayer, ComponentInfo}; use chrono::{NaiveDateTime, Utc}; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -893,7 +893,13 @@ pub async fn run_grpc_server( } else { Server::builder() }; + let version_layer = tower::ServiceBuilder::new() + .layer(DefguardVersionLayer { + component_info: ComponentInfo::parse("1.5.666").unwrap(), + }) + .into_inner(); let router = builder + .layer(version_layer) .http2_keepalive_interval(Some(TEN_SECS)) .tcp_keepalive(Some(TEN_SECS)) .add_service(health_service) diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml new file mode 100644 index 0000000000..88b973410b --- /dev/null +++ b/crates/defguard_version/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "defguard_version" +version = "0.0.0" +edition.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +http = "1.3.1" +os_info = "3.12.0" +semver.workspace = true +thiserror.workspace = true +tower = "0.5.2" +tracing.workspace = true diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs new file mode 100644 index 0000000000..b5e6fd5cb4 --- /dev/null +++ b/crates/defguard_version/src/lib.rs @@ -0,0 +1,153 @@ +use http::HeaderValue; +use std::{ + fmt::Display, + pin::Pin, + task::{Context, Poll}, +}; +use thiserror::Error; +use tower::{Layer, Service}; +use tracing::error; + +#[derive(Debug, Error)] +pub enum DefguardVersionError { + #[error(transparent)] + SemverError(#[from] semver::Error), +} + +#[derive(Clone, Debug)] +pub struct SemanticVersion { + pub major: u64, + pub minor: u64, + pub patch: u64, +} + +impl Display for SemanticVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + +#[derive(Debug, Clone)] +pub struct SystemInfo { + /// The operating system type (e.g., "Linux", "Windows", "macOS") + pub os_type: String, + /// The operating system version (e.g., "22.04", "11", "13.0") + pub os_version: String, + /// The operating system edition (e.g., "Server", "Pro", "Home") + pub os_edition: String, + /// The operating system codename (e.g., "jammy", "focal") + pub os_codename: String, + /// The system bitness (e.g., "64-bit", "32-bit") + pub bitness: String, + /// The system architecture (e.g., "x86_64", "aarch64", "arm") + pub architecture: String, +} + +impl From for SystemInfo { + fn from(info: os_info::Info) -> Self { + Self { + os_type: info.os_type().to_string(), + os_version: info.version().to_string(), + os_edition: info.edition().unwrap_or_else(|| "?").to_string(), + os_codename: info.codename().unwrap_or_else(|| "?").to_string(), + bitness: info.bitness().to_string(), + architecture: info.architecture().unwrap_or_else(|| "?").to_string(), + } + } +} + +#[derive(Debug, Clone)] +pub struct ComponentInfo { + pub version: SemanticVersion, + pub system: SystemInfo, +} + +impl ComponentInfo { + /// Automatically detects the current operating system and hardware information + /// using the `os_info` crate and combines it with the provided application version. + /// + /// # Arguments + /// * `version` - The application version string + /// + /// # Returns + /// A new `VersionInfo` instance with version and system information + pub fn parse(version: &str) -> Result { + let info = os_info::get(); + let version = semver::Version::parse(version)?; + Ok(Self { + version: SemanticVersion { + major: version.major, + minor: version.minor, + patch: version.patch, + }, + system: info.into(), + }) + } +} + +#[derive(Debug, Clone)] +pub struct DefguardVersionLayer { + pub component_info: ComponentInfo, +} + +impl Layer for DefguardVersionLayer { + type Service = DefguardVersionMiddleware; + + fn layer(&self, service: S) -> Self::Service { + DefguardVersionMiddleware { + inner: service, + component_info: self.component_info.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub struct DefguardVersionMiddleware { + inner: S, + component_info: ComponentInfo, +} + +type BoxFuture<'a, T> = Pin + Send + 'a>>; + +impl Service> for DefguardVersionMiddleware +where + S: Service, Response = http::Response> + Clone + Send + 'static, + S::Future: Send + 'static, + ReqBody: Send + 'static, + ResBody: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: http::Request) -> Self::Future { + // This is necessary because tonic internally uses `tower::buffer::Buffer`. + // See https://github.com/tower-rs/tower/issues/547#issuecomment-767629149 + // for details on why this is necessary + let clone = self.inner.clone(); + let mut inner = std::mem::replace(&mut self.inner, clone); + + let header_value = HeaderValue::from_str(&self.component_info.version.to_string()).unwrap(); + Box::pin(async move { + // copy request header map for later use + let req_header_map = &req.headers().clone(); + let version = req_header_map.get("DFG-version"); + error!( + "DFG-version: {}", + version + .map(|h| h.to_str().unwrap()) + .unwrap_or("missing header") + ); + let mut response = inner.call(req).await?; + response + .headers_mut() + .insert("DFG-version", header_value); + + Ok(response) + }) + } +} From 2e0a582dc4d7583410f3c0411bf7f812f66bc8e6 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 5 Aug 2025 13:04:53 +0200 Subject: [PATCH 002/100] DefguardVersionLayer::make_layer() --- crates/defguard_core/src/grpc/mod.rs | 7 +------ crates/defguard_version/src/lib.rs | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 1ff7961ccf..95bf38445e 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -893,13 +893,8 @@ pub async fn run_grpc_server( } else { Server::builder() }; - let version_layer = tower::ServiceBuilder::new() - .layer(DefguardVersionLayer { - component_info: ComponentInfo::parse("1.5.666").unwrap(), - }) - .into_inner(); let router = builder - .layer(version_layer) + .layer(DefguardVersionLayer::make_layer()) .http2_keepalive_interval(Some(TEN_SECS)) .tcp_keepalive(Some(TEN_SECS)) .add_service(health_service) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index b5e6fd5cb4..0dc7aec4fe 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -5,7 +5,7 @@ use std::{ task::{Context, Poll}, }; use thiserror::Error; -use tower::{Layer, Service}; +use tower::{layer::util::{Identity, Stack}, Layer, Service}; use tracing::error; #[derive(Debug, Error)] @@ -90,6 +90,15 @@ pub struct DefguardVersionLayer { pub component_info: ComponentInfo, } +impl DefguardVersionLayer { + pub fn make_layer() -> Stack { + tower::ServiceBuilder::new() + .layer(DefguardVersionLayer { + component_info: ComponentInfo::parse("1.5.666").unwrap(), + }) + .into_inner() + } +} impl Layer for DefguardVersionLayer { type Service = DefguardVersionMiddleware; @@ -135,17 +144,18 @@ where Box::pin(async move { // copy request header map for later use let req_header_map = &req.headers().clone(); - let version = req_header_map.get("DFG-version"); + let version = req_header_map.get("dfg-version"); + for key in req_header_map.keys() { + error!("key: {key}"); + } error!( - "DFG-version: {}", + "dfg-version: {}", version .map(|h| h.to_str().unwrap()) .unwrap_or("missing header") ); let mut response = inner.call(req).await?; - response - .headers_mut() - .insert("DFG-version", header_value); + response.headers_mut().insert("dfg-version", header_value); Ok(response) }) From c3af6f6ad64172da76f905779394f07779d8ba46 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 6 Aug 2025 09:46:57 +0200 Subject: [PATCH 003/100] working interceptor-based middleware --- Cargo.lock | 1 + crates/defguard_core/src/grpc/mod.rs | 53 ++++++--- crates/defguard_version/Cargo.toml | 1 + crates/defguard_version/src/lib.rs | 168 ++++++++++++++++----------- 4 files changed, 140 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22f082141c..23f61e1ae1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,6 +1189,7 @@ dependencies = [ "os_info", "semver", "thiserror 2.0.12", + "tonic", "tower 0.5.2", "tracing", ] diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 95bf38445e..bdf5408037 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,3 +1,10 @@ +use chrono::{NaiveDateTime, Utc}; +use defguard_version::{ComponentInfo, DefguardVersionInterceptor}; +use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; +use reqwest::Url; +use serde::Serialize; +#[cfg(feature = "worker")] +use sqlx::PgPool; use std::{ collections::hash_map::HashMap, fs::read_to_string, @@ -8,13 +15,6 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, }; -use defguard_version::{DefguardVersionLayer, ComponentInfo}; -use chrono::{NaiveDateTime, Utc}; -use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; -use reqwest::Url; -use serde::Serialize; -#[cfg(feature = "worker")] -use sqlx::PgPool; use thiserror::Error; use tokio::{ sync::{ @@ -25,7 +25,7 @@ use tokio::{ }; use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::sync::CancellationToken; -use tonic::{ +use tonic::{service::Interceptor, Code, Status, transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, }; @@ -869,10 +869,38 @@ pub async fn run_grpc_server( JwtInterceptor::new(ClaimsType::YubiBridge), ); #[cfg(feature = "wireguard")] - let gateway_service = GatewayServiceServer::with_interceptor( - GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), - JwtInterceptor::new(ClaimsType::Gateway), - ); + let gateway_service = { + // use tonic::service::interceptor::interceptor; + // use tower::ServiceBuilder; + + let jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); + let version_interceptor = + DefguardVersionInterceptor::new(ComponentInfo::parse("1.5.666").unwrap()); + + // let layered_service = ServiceBuilder::new() + // .layer(interceptor(version_interceptor)) + // .layer(interceptor(jwt_interceptor)) + // .service(GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx)); + + // layered_service + // GatewayServiceServer::new(layered_service) + + // Create a combined interceptor function that applies both + let combined_interceptor = + move |req: tonic::Request<()>| -> Result, tonic::Status> { + let mut version_interceptor = version_interceptor.clone(); + let mut jwt_interceptor = jwt_interceptor.clone(); + // Apply version interceptor first + let req = version_interceptor.call(req)?; + // Then apply JWT interceptor + jwt_interceptor.call(req) + }; + + GatewayServiceServer::with_interceptor( + GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), + combined_interceptor, + ) + }; let (mut health_reporter, health_service) = tonic_health::server::health_reporter(); health_reporter @@ -894,7 +922,6 @@ pub async fn run_grpc_server( Server::builder() }; let router = builder - .layer(DefguardVersionLayer::make_layer()) .http2_keepalive_interval(Some(TEN_SECS)) .tcp_keepalive(Some(TEN_SECS)) .add_service(health_service) diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml index 88b973410b..68eaa6deca 100644 --- a/crates/defguard_version/Cargo.toml +++ b/crates/defguard_version/Cargo.toml @@ -12,5 +12,6 @@ http = "1.3.1" os_info = "3.12.0" semver.workspace = true thiserror.workspace = true +tonic.workspace = true tower = "0.5.2" tracing.workspace = true diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 0dc7aec4fe..376946765f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,12 +1,12 @@ -use http::HeaderValue; use std::{ fmt::Display, pin::Pin, task::{Context, Poll}, }; use thiserror::Error; +use tonic::{service::Interceptor, Status}; use tower::{layer::util::{Identity, Stack}, Layer, Service}; -use tracing::error; +use tracing::{debug, error}; #[derive(Debug, Error)] pub enum DefguardVersionError { @@ -85,79 +85,107 @@ impl ComponentInfo { } } -#[derive(Debug, Clone)] -pub struct DefguardVersionLayer { - pub component_info: ComponentInfo, +// #[derive(Debug, Clone)] +// pub struct DefguardVersionLayer { +// pub component_info: ComponentInfo, +// } + +// impl DefguardVersionLayer { +// pub fn make_layer() -> Stack { +// tower::ServiceBuilder::new() +// .layer(DefguardVersionLayer { +// component_info: ComponentInfo::parse("1.5.666").unwrap(), +// }) +// .into_inner() +// } + +// pub fn make_interceptor() -> DefguardVersionInterceptor { +// DefguardVersionInterceptor::new( +// ComponentInfo::parse("1.5.666").unwrap() +// ) +// } +// } +// impl Layer for DefguardVersionLayer { +// type Service = DefguardVersionMiddleware; + +// fn layer(&self, service: S) -> Self::Service { +// DefguardVersionMiddleware { +// inner: service, +// component_info: self.component_info.clone(), +// } +// } +// } + +// #[derive(Debug, Clone)] +// pub struct DefguardVersionMiddleware { +// inner: S, +// component_info: ComponentInfo, +// } + +#[derive(Clone)] +pub struct DefguardVersionInterceptor { + component_info: ComponentInfo, } -impl DefguardVersionLayer { - pub fn make_layer() -> Stack { - tower::ServiceBuilder::new() - .layer(DefguardVersionLayer { - component_info: ComponentInfo::parse("1.5.666").unwrap(), - }) - .into_inner() +impl DefguardVersionInterceptor { + pub fn new(component_info: ComponentInfo) -> Self { + Self { component_info } } } -impl Layer for DefguardVersionLayer { - type Service = DefguardVersionMiddleware; - fn layer(&self, service: S) -> Self::Service { - DefguardVersionMiddleware { - inner: service, - component_info: self.component_info.clone(), - } +impl Interceptor for DefguardVersionInterceptor { + fn call(&mut self, mut req: tonic::Request<()>) -> Result, Status> { + // Read client version from metadata + let client_version = req + .metadata() + .get("dfg-version") + .map(|v| v.to_str().unwrap_or("unknown")) + .unwrap_or("missing"); + + let server_version = self.component_info.version.to_string(); + error!("Client version: {}", client_version); + error!("Server version: {}", server_version); + + // Add server version to response metadata + req.metadata_mut().insert( + "dfg-version", + server_version.parse() + .map_err(|_| Status::internal("Failed to set server version metadata"))? + ); + + Ok(req) } } -#[derive(Debug, Clone)] -pub struct DefguardVersionMiddleware { - inner: S, - component_info: ComponentInfo, -} - -type BoxFuture<'a, T> = Pin + Send + 'a>>; - -impl Service> for DefguardVersionMiddleware -where - S: Service, Response = http::Response> + Clone + Send + 'static, - S::Future: Send + 'static, - ReqBody: Send + 'static, - ResBody: Send + 'static, -{ - type Response = S::Response; - type Error = S::Error; - type Future = BoxFuture<'static, Result>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, req: http::Request) -> Self::Future { - // This is necessary because tonic internally uses `tower::buffer::Buffer`. - // See https://github.com/tower-rs/tower/issues/547#issuecomment-767629149 - // for details on why this is necessary - let clone = self.inner.clone(); - let mut inner = std::mem::replace(&mut self.inner, clone); - - let header_value = HeaderValue::from_str(&self.component_info.version.to_string()).unwrap(); - Box::pin(async move { - // copy request header map for later use - let req_header_map = &req.headers().clone(); - let version = req_header_map.get("dfg-version"); - for key in req_header_map.keys() { - error!("key: {key}"); - } - error!( - "dfg-version: {}", - version - .map(|h| h.to_str().unwrap()) - .unwrap_or("missing header") - ); - let mut response = inner.call(req).await?; - response.headers_mut().insert("dfg-version", header_value); - - Ok(response) - }) - } -} +// type BoxFuture<'a, T> = Pin + Send + 'a>>; + +// impl Service> for DefguardVersionMiddleware +// where +// S: Service, Response = http::Response> + Clone + Send + 'static, +// S::Future: Send + 'static, +// ReqBody: Send + 'static, +// ResBody: Send + 'static, +// { +// type Response = S::Response; +// type Error = S::Error; +// type Future = BoxFuture<'static, Result>; + +// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { +// self.inner.poll_ready(cx) +// } + +// fn call(&mut self, req: http::Request) -> Self::Future { +// // This is necessary because tonic internally uses `tower::buffer::Buffer`. +// // See https://github.com/tower-rs/tower/issues/547#issuecomment-767629149 +// // for details on why this is necessary +// let clone = self.inner.clone(); +// let mut inner = std::mem::replace(&mut self.inner, clone); + +// let version_string = self.component_info.version.to_string(); +// Box::pin(async move { +// // For gRPC requests, we don't modify HTTP headers but let the interceptor handle metadata +// let response = inner.call(req).await?; +// Ok(response) +// }) +// } +// } From 1e7c7dfd3b8c1cbec202e6d962e5719739bd1a34 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 6 Aug 2025 09:52:02 +0200 Subject: [PATCH 004/100] remove tower dependency --- Cargo.lock | 1 - crates/defguard_core/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23f61e1ae1..d3586b4ae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,7 +1138,6 @@ dependencies = [ "tonic-build", "tonic-health", "totp-lite", - "tower 0.5.2", "tower-http", "tracing", "tracing-subscriber", diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index b169112318..9c416b64c5 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -85,7 +85,6 @@ strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } ed25519-dalek = "2.2.0" -tower = "0.5.2" # https://github.com/juhaku/utoipa/issues/1345 [dependencies.zip] From 891ae49ca069a5af2a06cc2f7bfdf97bf7731614 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 6 Aug 2025 10:13:56 +0200 Subject: [PATCH 005/100] cleanup --- crates/defguard_core/src/grpc/mod.rs | 22 ++----- crates/defguard_version/src/lib.rs | 94 ++-------------------------- 2 files changed, 10 insertions(+), 106 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index bdf5408037..eac104177b 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -870,29 +870,15 @@ pub async fn run_grpc_server( ); #[cfg(feature = "wireguard")] let gateway_service = { - // use tonic::service::interceptor::interceptor; - // use tower::ServiceBuilder; - let jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); - let version_interceptor = - DefguardVersionInterceptor::new(ComponentInfo::parse("1.5.666").unwrap()); + let mut jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); + let mut version_interceptor = + DefguardVersionInterceptor::new(ComponentInfo::parse(VERSION).unwrap()); - // let layered_service = ServiceBuilder::new() - // .layer(interceptor(version_interceptor)) - // .layer(interceptor(jwt_interceptor)) - // .service(GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx)); - - // layered_service - // GatewayServiceServer::new(layered_service) - - // Create a combined interceptor function that applies both + // combine both interceptors let combined_interceptor = move |req: tonic::Request<()>| -> Result, tonic::Status> { - let mut version_interceptor = version_interceptor.clone(); - let mut jwt_interceptor = jwt_interceptor.clone(); - // Apply version interceptor first let req = version_interceptor.call(req)?; - // Then apply JWT interceptor jwt_interceptor.call(req) }; diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 376946765f..d7be50eff2 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,12 +1,7 @@ -use std::{ - fmt::Display, - pin::Pin, - task::{Context, Poll}, -}; +use std::fmt::Display; use thiserror::Error; -use tonic::{service::Interceptor, Status}; -use tower::{layer::util::{Identity, Stack}, Layer, Service}; -use tracing::{debug, error}; +use tonic::{Status, service::Interceptor}; +use tracing::error; #[derive(Debug, Error)] pub enum DefguardVersionError { @@ -63,14 +58,6 @@ pub struct ComponentInfo { } impl ComponentInfo { - /// Automatically detects the current operating system and hardware information - /// using the `os_info` crate and combines it with the provided application version. - /// - /// # Arguments - /// * `version` - The application version string - /// - /// # Returns - /// A new `VersionInfo` instance with version and system information pub fn parse(version: &str) -> Result { let info = os_info::get(); let version = semver::Version::parse(version)?; @@ -85,43 +72,6 @@ impl ComponentInfo { } } -// #[derive(Debug, Clone)] -// pub struct DefguardVersionLayer { -// pub component_info: ComponentInfo, -// } - -// impl DefguardVersionLayer { -// pub fn make_layer() -> Stack { -// tower::ServiceBuilder::new() -// .layer(DefguardVersionLayer { -// component_info: ComponentInfo::parse("1.5.666").unwrap(), -// }) -// .into_inner() -// } - -// pub fn make_interceptor() -> DefguardVersionInterceptor { -// DefguardVersionInterceptor::new( -// ComponentInfo::parse("1.5.666").unwrap() -// ) -// } -// } -// impl Layer for DefguardVersionLayer { -// type Service = DefguardVersionMiddleware; - -// fn layer(&self, service: S) -> Self::Service { -// DefguardVersionMiddleware { -// inner: service, -// component_info: self.component_info.clone(), -// } -// } -// } - -// #[derive(Debug, Clone)] -// pub struct DefguardVersionMiddleware { -// inner: S, -// component_info: ComponentInfo, -// } - #[derive(Clone)] pub struct DefguardVersionInterceptor { component_info: ComponentInfo, @@ -149,43 +99,11 @@ impl Interceptor for DefguardVersionInterceptor { // Add server version to response metadata req.metadata_mut().insert( "dfg-version", - server_version.parse() - .map_err(|_| Status::internal("Failed to set server version metadata"))? + server_version + .parse() + .map_err(|_| Status::internal("Failed to set server version metadata"))?, ); Ok(req) } } - -// type BoxFuture<'a, T> = Pin + Send + 'a>>; - -// impl Service> for DefguardVersionMiddleware -// where -// S: Service, Response = http::Response> + Clone + Send + 'static, -// S::Future: Send + 'static, -// ReqBody: Send + 'static, -// ResBody: Send + 'static, -// { -// type Response = S::Response; -// type Error = S::Error; -// type Future = BoxFuture<'static, Result>; - -// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { -// self.inner.poll_ready(cx) -// } - -// fn call(&mut self, req: http::Request) -> Self::Future { -// // This is necessary because tonic internally uses `tower::buffer::Buffer`. -// // See https://github.com/tower-rs/tower/issues/547#issuecomment-767629149 -// // for details on why this is necessary -// let clone = self.inner.clone(); -// let mut inner = std::mem::replace(&mut self.inner, clone); - -// let version_string = self.component_info.version.to_string(); -// Box::pin(async move { -// // For gRPC requests, we don't modify HTTP headers but let the interceptor handle metadata -// let response = inner.call(req).await?; -// Ok(response) -// }) -// } -// } From 5b5bb7466fe9547b722b63a9852c5c6a38a25949 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 6 Aug 2025 12:33:15 +0200 Subject: [PATCH 006/100] wip try to use shared version set, tonnic typing issues --- Cargo.lock | 1 + crates/defguard/Cargo.toml | 1 + crates/defguard/src/main.rs | 7 ++- crates/defguard_core/src/grpc/mod.rs | 40 ++++++++++------- crates/defguard_version/src/lib.rs | 64 ++++++++++++++++++++++------ 5 files changed, 83 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3586b4ae9..1a6b165727 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,6 +1066,7 @@ dependencies = [ "defguard_core", "defguard_event_logger", "defguard_event_router", + "defguard_version", "dotenvy", "secrecy", "tokio", diff --git a/crates/defguard/Cargo.toml b/crates/defguard/Cargo.toml index 5c453e15fe..5ba6ccc31f 100644 --- a/crates/defguard/Cargo.toml +++ b/crates/defguard/Cargo.toml @@ -12,6 +12,7 @@ rust-version.workspace = true defguard_core = { workspace = true } defguard_event_router = { workspace = true } defguard_event_logger = { workspace = true } +defguard_version = { workspace = true } # external dependencies anyhow = { workspace = true } diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 26c8904468..de407611a5 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -1,6 +1,6 @@ use std::{ fs::read_to_string, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, RwLock}, }; use bytes::Bytes; @@ -28,6 +28,7 @@ use defguard_core::{ }; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; +use defguard_version::DefguardVersionSet; use secrecy::ExposeSecret; use tokio::sync::{broadcast, mpsc::unbounded_channel}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -42,6 +43,8 @@ async fn main() -> Result<(), anyhow::Error> { } let config = DefGuardConfig::new(); SERVER_CONFIG.set(config.clone())?; + let version_set = Arc::new(RwLock::new(DefguardVersionSet::try_from(VERSION)?)); + // TODO: tracing with version-set // initialize tracing tracing_subscriber::registry() .with( @@ -145,7 +148,7 @@ async fn main() -> Result<(), anyhow::Error> { // run services tokio::select! { res = run_grpc_bidi_stream(pool.clone(), wireguard_tx.clone(), mail_tx.clone(), bidi_event_tx), if config.proxy_url.is_some() => error!("Proxy gRPC stream returned early: {res:?}"), - res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx) => error!("gRPC server returned early: {res:?}"), + res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx, Arc::clone(&version_set)) => error!("gRPC server returned early: {res:?}"), res = run_web_server(worker_state, gateway_state, webhook_tx, webhook_rx, wireguard_tx.clone(), mail_tx.clone(), pool.clone(), failed_logins, api_event_tx) => error!("Web server returned early: {res:?}"), res = run_mail_handler(mail_rx) => error!("Mail handler returned early: {res:?}"), res = run_periodic_peer_disconnect(pool.clone(), wireguard_tx.clone(), internal_event_tx.clone()) => error!("Periodic peer disconnect task returned early: {res:?}"), diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index eac104177b..6f33fd2f88 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,5 +1,5 @@ use chrono::{NaiveDateTime, Utc}; -use defguard_version::{ComponentInfo, DefguardVersionInterceptor}; +use defguard_version::{DefguardComponent, DefguardVersionInterceptor, DefguardVersionSet}; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; use serde::Serialize; @@ -8,6 +8,7 @@ use sqlx::PgPool; use std::{ collections::hash_map::HashMap, fs::read_to_string, + sync::RwLock, time::{Duration, Instant}, }; #[cfg(any(feature = "wireguard", feature = "worker"))] @@ -25,9 +26,8 @@ use tokio::{ }; use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::sync::CancellationToken; -use tonic::{service::Interceptor, - Code, Status, - transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, +use tonic::{ + service::{interceptor::InterceptedService, Interceptor}, transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, Code, Status }; use utoipa::ToSchema; use uuid::Uuid; @@ -860,6 +860,7 @@ pub async fn run_grpc_server( grpc_key: Option, failed_logins: Arc>, grpc_event_tx: UnboundedSender, + version_set: Arc>, ) -> Result<(), anyhow::Error> { // Build gRPC services let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); @@ -870,21 +871,28 @@ pub async fn run_grpc_server( ); #[cfg(feature = "wireguard")] let gateway_service = { + let jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); + let version_interceptor = + DefguardVersionInterceptor::new(DefguardComponent::Gateway, version_set); - let mut jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); - let mut version_interceptor = - DefguardVersionInterceptor::new(ComponentInfo::parse(VERSION).unwrap()); - - // combine both interceptors - let combined_interceptor = - move |req: tonic::Request<()>| -> Result, tonic::Status> { - let req = version_interceptor.call(req)?; - jwt_interceptor.call(req) - }; + let service_with_version = InterceptedService::new( + GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), + version_interceptor, + ); + // // combine both interceptors + // let combined_interceptor = + // move |req: tonic::Request<()>| -> Result, tonic::Status> { + // let req = version_interceptor.call(req)?; + // jwt_interceptor.call(req) + // }; + // GatewayServiceServer::with_interceptor( + // GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), + // combined_interceptor, + // ) GatewayServiceServer::with_interceptor( - GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), - combined_interceptor, + service_with_version, + jwt_interceptor, ) }; diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index d7be50eff2..76d9bff5ac 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,4 +1,7 @@ -use std::fmt::Display; +use std::{ + fmt::Display, + sync::{Arc, RwLock}, +}; use thiserror::Error; use tonic::{Status, service::Interceptor}; use tracing::error; @@ -9,6 +12,25 @@ pub enum DefguardVersionError { SemverError(#[from] semver::Error), } +#[derive(Clone, Debug)] +pub struct DefguardVersionSet { + pub own: ComponentInfo, + pub core: Arc>>, + pub proxy: Arc>>, + pub gateway: Arc>>, +} + +impl DefguardVersionSet { + pub fn try_from(version: &str) -> Result { + Ok(Self { + own: ComponentInfo::try_from(version)?, + core: Arc::new(RwLock::new(None)), + proxy: Arc::new(RwLock::new(None)), + gateway: Arc::new(RwLock::new(None)), + }) + } +} + #[derive(Clone, Debug)] pub struct SemanticVersion { pub major: u64, @@ -58,7 +80,7 @@ pub struct ComponentInfo { } impl ComponentInfo { - pub fn parse(version: &str) -> Result { + pub fn try_from(version: &str) -> Result { let info = os_info::get(); let version = semver::Version::parse(version)?; Ok(Self { @@ -72,34 +94,52 @@ impl ComponentInfo { } } +#[derive(Clone)] +pub enum DefguardComponent { + Core, + Proxy, + Gateway, +} + #[derive(Clone)] pub struct DefguardVersionInterceptor { - component_info: ComponentInfo, + component: DefguardComponent, + version_set: Arc>, } impl DefguardVersionInterceptor { - pub fn new(component_info: ComponentInfo) -> Self { - Self { component_info } + pub fn new(component: DefguardComponent, version_set: Arc>) -> Self { + Self { + component, + version_set, + } } } impl Interceptor for DefguardVersionInterceptor { fn call(&mut self, mut req: tonic::Request<()>) -> Result, Status> { - // Read client version from metadata + // read and set client version from metadata let client_version = req .metadata() .get("dfg-version") .map(|v| v.to_str().unwrap_or("unknown")) .unwrap_or("missing"); + // TODO set appropriate component version + // match self.component { + // DefguardComponent::Core => self.version_set.write().unwrap().core = + // } + for header in req.metadata().keys() { + error!("key: {:?}", header); + } + let own_version = &self.version_set.read().unwrap().own.version; + error!("Remote version: {}", client_version); + error!("Own version: {}", own_version); - let server_version = self.component_info.version.to_string(); - error!("Client version: {}", client_version); - error!("Server version: {}", server_version); - - // Add server version to response metadata + // add own version to response metadata req.metadata_mut().insert( "dfg-version", - server_version + own_version + .to_string() .parse() .map_err(|_| Status::internal("Failed to set server version metadata"))?, ); From 17af1718d8b5c00004999d986102210b5fc51586 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 07:25:18 +0200 Subject: [PATCH 007/100] initial server middleware implementation --- crates/defguard_core/src/grpc/mod.rs | 29 +++----- crates/defguard_version/src/lib.rs | 3 + crates/defguard_version/src/server.rs | 103 ++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 crates/defguard_version/src/server.rs diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 6f33fd2f88..1968bc60b2 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -871,28 +871,21 @@ pub async fn run_grpc_server( ); #[cfg(feature = "wireguard")] let gateway_service = { - let jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); - let version_interceptor = + let mut jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); + let mut version_interceptor = DefguardVersionInterceptor::new(DefguardComponent::Gateway, version_set); - let service_with_version = InterceptedService::new( - GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), - version_interceptor, - ); - // // combine both interceptors - // let combined_interceptor = - // move |req: tonic::Request<()>| -> Result, tonic::Status> { - // let req = version_interceptor.call(req)?; - // jwt_interceptor.call(req) - // }; + // combine both interceptors + let combined_interceptor = + move |req: tonic::Request<()>| -> Result, tonic::Status> { + let req = version_interceptor.call(req)?; + error!("BETWEEEN INTERCEPTORS"); + jwt_interceptor.call(req) + }; - // GatewayServiceServer::with_interceptor( - // GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), - // combined_interceptor, - // ) GatewayServiceServer::with_interceptor( - service_with_version, - jwt_interceptor, + GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), + combined_interceptor, ) }; diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 76d9bff5ac..76b2ba6d3e 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -6,6 +6,8 @@ use thiserror::Error; use tonic::{Status, service::Interceptor}; use tracing::error; +pub mod server; + #[derive(Debug, Error)] pub enum DefguardVersionError { #[error(transparent)] @@ -143,6 +145,7 @@ impl Interceptor for DefguardVersionInterceptor { .parse() .map_err(|_| Status::internal("Failed to set server version metadata"))?, ); + error!("STORED VERSION metadta"); Ok(req) } diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs new file mode 100644 index 0000000000..abf72ba821 --- /dev/null +++ b/crates/defguard_version/src/server.rs @@ -0,0 +1,103 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; +use tonic::{Request, Response, Status}; +use tower::{Layer, Service}; +use tracing::error; + +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +#[derive(Clone)] +pub struct MetadataInterceptor { + inner: S, +} + +impl Service> for MetadataInterceptor +where + S: Service> + Clone + Send + 'static, + S::Response: Send + 'static, + S::Future: Send + 'static, + B: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, request: Request) -> Self::Future { + // Clone the metadata for logging + let metadata = request.metadata().clone(); + + // Read what you need from request metadata + let auth = metadata + .get("authorization") + .and_then(|v| v.to_str().ok()) + .map(|s| s.to_string()); + + let mut inner = self.inner.clone(); + + Box::pin(async move { + println!("Request auth: {:?}", auth); + + // Call the actual service + let response = inner.call(request).await?; + + // Can't modify response here easily because we don't know the type + // But we've already read the request metadata + Ok(response) + }) + } +} + +// For response modification, we need a more specific approach +#[derive(Clone)] +pub struct ResponseMetadataLayer { + inner: S, +} + +impl Service> for ResponseMetadataLayer +where + S: Service, Response = Response> + Clone + Send + 'static, + S::Future: Send + 'static, + B: Send + 'static, +{ + type Response = Response; + type Error = S::Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, request: Request) -> Self::Future { + let mut inner = self.inner.clone(); + + Box::pin(async move { + // Read request metadata if needed + let client_version = request + .metadata() + .get("dfg-version") + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown") + .to_string(); + + error!("Remote version: {}", client_version); + error!("Own version: TODO"); + for header in request.metadata().keys() { + error!("key: {:?}", header); + } + // Call inner service + let mut response = inner.call(request).await?; + + response + .metadata_mut() + .insert("dfg-version", "1.555.555".parse().unwrap()); + + Ok(response) + }) + } +} From 689f8826ef124eaeb7135e26813da58538e97a1f Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 07:58:15 +0200 Subject: [PATCH 008/100] wip try to implement DefguardVersion Layer and Middleware, new typing issues... --- crates/defguard_core/src/grpc/mod.rs | 23 ++--- crates/defguard_version/src/server.rs | 138 +++++++++++++------------- 2 files changed, 75 insertions(+), 86 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 1968bc60b2..da9b88b643 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,5 +1,5 @@ use chrono::{NaiveDateTime, Utc}; -use defguard_version::{DefguardComponent, DefguardVersionInterceptor, DefguardVersionSet}; +use defguard_version::{DefguardComponent, DefguardVersionInterceptor, DefguardVersionSet, server::DefguardVersionLayer}; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; use serde::Serialize; @@ -27,7 +27,9 @@ use tokio::{ use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::sync::CancellationToken; use tonic::{ - service::{interceptor::InterceptedService, Interceptor}, transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, Code, Status + Code, Status, + service::{Interceptor, interceptor::InterceptedService}, + transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, }; use utoipa::ToSchema; use uuid::Uuid; @@ -871,21 +873,10 @@ pub async fn run_grpc_server( ); #[cfg(feature = "wireguard")] let gateway_service = { - let mut jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); - let mut version_interceptor = - DefguardVersionInterceptor::new(DefguardComponent::Gateway, version_set); - - // combine both interceptors - let combined_interceptor = - move |req: tonic::Request<()>| -> Result, tonic::Status> { - let req = version_interceptor.call(req)?; - error!("BETWEEEN INTERCEPTORS"); - jwt_interceptor.call(req) - }; - + let jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); GatewayServiceServer::with_interceptor( GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), - combined_interceptor, + jwt_interceptor, ) }; @@ -908,7 +899,9 @@ pub async fn run_grpc_server( } else { Server::builder() }; + let router = builder + .layer(DefguardVersionLayer) .http2_keepalive_interval(Some(TEN_SECS)) .tcp_keepalive(Some(TEN_SECS)) .add_service(health_service) diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index abf72ba821..7859bec907 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,4 +1,5 @@ use std::{ + future::Future, pin::Pin, task::{Context, Poll}, }; @@ -8,96 +9,91 @@ use tracing::error; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; +// For response modification, we need a more specific approach #[derive(Clone)] -pub struct MetadataInterceptor { - inner: S, -} +pub struct DefguardVersionLayer; -impl Service> for MetadataInterceptor -where - S: Service> + Clone + Send + 'static, - S::Response: Send + 'static, - S::Future: Send + 'static, - B: Send + 'static, -{ - type Response = S::Response; - type Error = S::Error; - type Future = BoxFuture<'static, Result>; +impl Layer for DefguardVersionLayer { + type Service = DefguardVersionMiddleware; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, request: Request) -> Self::Future { - // Clone the metadata for logging - let metadata = request.metadata().clone(); - - // Read what you need from request metadata - let auth = metadata - .get("authorization") - .and_then(|v| v.to_str().ok()) - .map(|s| s.to_string()); - - let mut inner = self.inner.clone(); - - Box::pin(async move { - println!("Request auth: {:?}", auth); - - // Call the actual service - let response = inner.call(request).await?; - - // Can't modify response here easily because we don't know the type - // But we've already read the request metadata - Ok(response) - }) + fn layer(&self, inner: S) -> Self::Service { + DefguardVersionMiddleware { inner } } } -// For response modification, we need a more specific approach #[derive(Clone)] -pub struct ResponseMetadataLayer { - inner: S, +pub struct DefguardVersionMiddleware { + inner: S, } -impl Service> for ResponseMetadataLayer +// impl Service> for DefguardVersionMiddleware +// where +// S: Service, Response = Response> + Clone + Send + 'static, +// S::Future: Send + 'static, +// B: Send + 'static, +// { +// type Response = Response; +// type Error = S::Error; +// type Future = BoxFuture<'static, Result>; + +// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { +// self.inner.poll_ready(cx) +// } + +// fn call(&mut self, request: Request) -> Self::Future { +// let mut inner = self.inner.clone(); + +// Box::pin(async move { +// // Read request metadata if needed +// let client_version = request +// .metadata() +// .get("dfg-version") +// .and_then(|v| v.to_str().ok()) +// .unwrap_or("unknown") +// .to_string(); + +// error!("Remote version: {}", client_version); +// error!("Own version: TODO"); +// for header in request.metadata().keys() { +// error!("key: {:?}", header); +// } +// // Call inner service +// let mut response = inner.call(request).await?; + +// response +// .metadata_mut() +// .insert("dfg-version", "1.555.555".parse().unwrap()); + +// Ok(response) +// }) +// } +// } + + +impl Service> for DefguardVersionMiddleware where - S: Service, Response = Response> + Clone + Send + 'static, + S: Service, Response = Response, Error = Status> + Send + 'static, S::Future: Send + 'static, - B: Send + 'static, + ReqBody: Send + 'static, { - type Response = Response; - type Error = S::Error; - type Future = BoxFuture<'static, Result>; + type Response = Response; + type Error = Status; + type Future = Pin> + Send>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.inner.poll_ready(cx) } - fn call(&mut self, request: Request) -> Self::Future { - let mut inner = self.inner.clone(); + fn call(&mut self, req: Request) -> Self::Future { + let fut = self.inner.call(req); Box::pin(async move { - // Read request metadata if needed - let client_version = request - .metadata() - .get("dfg-version") - .and_then(|v| v.to_str().ok()) - .unwrap_or("unknown") - .to_string(); - - error!("Remote version: {}", client_version); - error!("Own version: TODO"); - for header in request.metadata().keys() { - error!("key: {:?}", header); - } - // Call inner service - let mut response = inner.call(request).await?; - - response - .metadata_mut() - .insert("dfg-version", "1.555.555".parse().unwrap()); - - Ok(response) + let mut response = fut.await?; + + // 🔁 Modify the inner message + let modified = modify_response(response.into_inner()); + + Ok(Response::new(modified)) }) } } From 9ee0f2c4f0f31d1c3de7d55a6019358cb246a446 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 09:41:46 +0200 Subject: [PATCH 009/100] client versioning stack --- Cargo.lock | 14 ++ crates/defguard_core/Cargo.toml | 1 + crates/defguard_core/src/grpc/mod.rs | 10 +- crates/defguard_version/Cargo.toml | 1 + crates/defguard_version/src/client.rs | 93 ++++++++++++ crates/defguard_version/src/lib.rs | 2 + crates/defguard_version/src/middleware.rs | 44 ++++++ crates/defguard_version/src/server.rs | 172 +++++++++++----------- 8 files changed, 247 insertions(+), 90 deletions(-) create mode 100644 crates/defguard_version/src/client.rs create mode 100644 crates/defguard_version/src/middleware.rs diff --git a/Cargo.lock b/Cargo.lock index 1a6b165727..37e4a7d400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,6 +1138,7 @@ dependencies = [ "tonic", "tonic-build", "tonic-health", + "tonic-middleware", "totp-lite", "tower-http", "tracing", @@ -1190,6 +1191,7 @@ dependencies = [ "semver", "thiserror 2.0.12", "tonic", + "tonic-middleware", "tower 0.5.2", "tracing", ] @@ -5280,6 +5282,18 @@ dependencies = [ "tonic", ] +[[package]] +name = "tonic-middleware" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd11ca7918ee9f94e217285ace20caf6187476f399244ba8438cdc92ce665236" +dependencies = [ + "async-trait", + "futures-util", + "tonic", + "tower 0.4.13", +] + [[package]] name = "totp-lite" version = "2.0.1" diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index 9c416b64c5..bc362c2ad4 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -85,6 +85,7 @@ strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } ed25519-dalek = "2.2.0" +tonic-middleware = "0.2" # https://github.com/juhaku/utoipa/issues/1345 [dependencies.zip] diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index da9b88b643..3a177a2630 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,5 +1,5 @@ use chrono::{NaiveDateTime, Utc}; -use defguard_version::{DefguardComponent, DefguardVersionInterceptor, DefguardVersionSet, server::DefguardVersionLayer}; +use defguard_version::{DefguardVersionSet, middleware::DefguardVersionMiddleware}; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; use serde::Serialize; @@ -28,9 +28,9 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::sync::CancellationToken; use tonic::{ Code, Status, - service::{Interceptor, interceptor::InterceptedService}, transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, }; +use tonic_middleware::MiddlewareFor; use utoipa::ToSchema; use uuid::Uuid; @@ -901,13 +901,15 @@ pub async fn run_grpc_server( }; let router = builder - .layer(DefguardVersionLayer) .http2_keepalive_interval(Some(TEN_SECS)) .tcp_keepalive(Some(TEN_SECS)) .add_service(health_service) .add_service(auth_service); #[cfg(feature = "wireguard")] - let router = router.add_service(gateway_service); + let router = router.add_service(MiddlewareFor::new( + gateway_service, + DefguardVersionMiddleware::default(), + )); #[cfg(feature = "worker")] let router = router.add_service(worker_service); router.serve(addr).await?; diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml index 68eaa6deca..6d30a25219 100644 --- a/crates/defguard_version/Cargo.toml +++ b/crates/defguard_version/Cargo.toml @@ -13,5 +13,6 @@ os_info = "3.12.0" semver.workspace = true thiserror.workspace = true tonic.workspace = true +tonic-middleware = "0.2" tower = "0.5.2" tracing.workspace = true diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs new file mode 100644 index 0000000000..b7fa392f6f --- /dev/null +++ b/crates/defguard_version/src/client.rs @@ -0,0 +1,93 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use http::{Request, Response}; +use tonic::body::BoxBody; +use tower::{Layer, Service}; +use tracing::{debug, error}; + +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +/// Layer for adding version information to outgoing gRPC requests (client-side) +#[derive(Clone)] +pub struct DefguardVersionClientLayer { + version: String, +} + +impl DefguardVersionClientLayer { + pub fn new(version: String) -> Self { + Self { version } + } +} + +impl Layer for DefguardVersionClientLayer { + type Service = DefguardVersionClientService; + + fn layer(&self, inner: S) -> Self::Service { + DefguardVersionClientService { + inner, + version: self.version.clone(), + } + } +} + +/// Service that adds version metadata to outgoing requests and reads version info from responses +#[derive(Clone)] +pub struct DefguardVersionClientService { + inner: S, + version: String, +} + +impl Service> for DefguardVersionClientService +where + S: Service, Response = Response> + Clone + Send + 'static, + S::Future: Send + 'static, + S::Error: Into>, +{ + type Response = Response; + type Error = Box; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, mut request: Request) -> Self::Future { + // Add our version to the outgoing request metadata + request.headers_mut().insert( + "dfg-version", + self.version + .parse() + .expect("Version should be valid header value"), + ); + + debug!("Client: Sending dfg-version: {}", self.version); + + // Call the inner service directly (don't clone) + let future = self.inner.call(request); + let version = self.version.clone(); + + Box::pin(async move { + // Make the request + let response = future.await.map_err(Into::into)?; + + // Read server version from response metadata + let server_version = response + .headers() + .get("dfg-version") + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown"); + + error!("Client: Received server dfg-version: {}", server_version); + + Ok(response) + }) + } +} + +/// Convenience function to create a version layer for clients +pub fn version_layer(version: String) -> DefguardVersionClientLayer { + DefguardVersionClientLayer::new(version) +} diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 76b2ba6d3e..a5a7c80d34 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -6,6 +6,8 @@ use thiserror::Error; use tonic::{Status, service::Interceptor}; use tracing::error; +pub mod client; +pub mod middleware; pub mod server; #[derive(Debug, Error)] diff --git a/crates/defguard_version/src/middleware.rs b/crates/defguard_version/src/middleware.rs new file mode 100644 index 0000000000..07d3534874 --- /dev/null +++ b/crates/defguard_version/src/middleware.rs @@ -0,0 +1,44 @@ +use tonic::async_trait; +use tonic::body::BoxBody; +use tonic::codegen::http::Request; // Use this instead of tonic::Request in Middleware! +use tonic::codegen::http::Response; // Use this instead of tonic::Response in Middleware! +use tonic_middleware::Middleware; +use tonic_middleware::ServiceBound; +use tracing::error; + +#[derive(Default, Clone)] +pub struct DefguardVersionMiddleware; + +#[async_trait] +impl Middleware for DefguardVersionMiddleware +where + S: ServiceBound, + S::Future: Send, +{ + async fn call( + &self, + request: Request, + mut service: S, + ) -> Result, S::Error> { + let client_version = request + .headers() + .get("dfg-version") + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown") + .to_string(); + + error!("Remote version: {}", client_version); + error!("Own version: TODO"); + for header in request.headers().keys() { + error!("key: {:?}", header); + } + // Call inner service + let mut response = service.call(request).await?; + + response + .headers_mut() + .insert("dfg-version", "1.555.555".parse().unwrap()); + + Ok(response) + } +} diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 7859bec907..0b38538c69 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,99 +1,99 @@ -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; -use tonic::{Request, Response, Status}; -use tower::{Layer, Service}; -use tracing::error; - -pub type BoxFuture<'a, T> = Pin + Send + 'a>>; - -// For response modification, we need a more specific approach -#[derive(Clone)] -pub struct DefguardVersionLayer; - -impl Layer for DefguardVersionLayer { - type Service = DefguardVersionMiddleware; - - fn layer(&self, inner: S) -> Self::Service { - DefguardVersionMiddleware { inner } - } -} - -#[derive(Clone)] -pub struct DefguardVersionMiddleware { - inner: S, -} - -// impl Service> for DefguardVersionMiddleware +// use std::{ +// future::Future, +// pin::Pin, +// task::{Context, Poll}, +// }; +// use tonic::{Request, Response, Status}; +// use tower::{Layer, Service}; +// use tracing::error; + +// pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +// // For response modification, we need a more specific approach +// #[derive(Clone)] +// pub struct DefguardVersionLayer; + +// impl Layer for DefguardVersionLayer { +// type Service = DefguardVersionMiddleware; + +// fn layer(&self, inner: S) -> Self::Service { +// DefguardVersionMiddleware { inner } +// } +// } + +// #[derive(Clone)] +// pub struct DefguardVersionMiddleware { +// inner: S, +// } + +// // impl Service> for DefguardVersionMiddleware +// // where +// // S: Service, Response = Response> + Clone + Send + 'static, +// // S::Future: Send + 'static, +// // B: Send + 'static, +// // { +// // type Response = Response; +// // type Error = S::Error; +// // type Future = BoxFuture<'static, Result>; + +// // fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { +// // self.inner.poll_ready(cx) +// // } + +// // fn call(&mut self, request: Request) -> Self::Future { +// // let mut inner = self.inner.clone(); + +// // Box::pin(async move { +// // // Read request metadata if needed +// // let client_version = request +// // .metadata() +// // .get("dfg-version") +// // .and_then(|v| v.to_str().ok()) +// // .unwrap_or("unknown") +// // .to_string(); + +// // error!("Remote version: {}", client_version); +// // error!("Own version: TODO"); +// // for header in request.metadata().keys() { +// // error!("key: {:?}", header); +// // } +// // // Call inner service +// // let mut response = inner.call(request).await?; + +// // response +// // .metadata_mut() +// // .insert("dfg-version", "1.555.555".parse().unwrap()); + +// // Ok(response) +// // }) +// // } +// // } + + +// impl Service> for DefguardVersionMiddleware // where -// S: Service, Response = Response> + Clone + Send + 'static, +// S: Service, Response = Response, Error = Status> + Send + 'static, // S::Future: Send + 'static, -// B: Send + 'static, +// ReqBody: Send + 'static, // { -// type Response = Response; -// type Error = S::Error; -// type Future = BoxFuture<'static, Result>; +// type Response = Response; +// type Error = Status; +// type Future = Pin> + Send>>; // fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { // self.inner.poll_ready(cx) // } -// fn call(&mut self, request: Request) -> Self::Future { -// let mut inner = self.inner.clone(); +// fn call(&mut self, req: Request) -> Self::Future { +// let fut = self.inner.call(req); // Box::pin(async move { -// // Read request metadata if needed -// let client_version = request -// .metadata() -// .get("dfg-version") -// .and_then(|v| v.to_str().ok()) -// .unwrap_or("unknown") -// .to_string(); - -// error!("Remote version: {}", client_version); -// error!("Own version: TODO"); -// for header in request.metadata().keys() { -// error!("key: {:?}", header); -// } -// // Call inner service -// let mut response = inner.call(request).await?; - -// response -// .metadata_mut() -// .insert("dfg-version", "1.555.555".parse().unwrap()); - -// Ok(response) +// let mut response = fut.await?; + +// // 🔁 Modify the inner message +// let modified = modify_response(response.into_inner()); + +// Ok(Response::new(modified)) // }) // } // } - - -impl Service> for DefguardVersionMiddleware -where - S: Service, Response = Response, Error = Status> + Send + 'static, - S::Future: Send + 'static, - ReqBody: Send + 'static, -{ - type Response = Response; - type Error = Status; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, req: Request) -> Self::Future { - let fut = self.inner.call(req); - - Box::pin(async move { - let mut response = fut.await?; - - // 🔁 Modify the inner message - let modified = modify_response(response.into_inner()); - - Ok(Response::new(modified)) - }) - } -} From 8c72d2a9ce8f7609ea5f6806ba38281eeff57587 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 10:01:19 +0200 Subject: [PATCH 010/100] cargo fmt --- crates/defguard/src/main.rs | 4 +- crates/defguard_core/src/grpc/mod.rs | 2 +- crates/defguard_version/src/client.rs | 7 +- crates/defguard_version/src/lib.rs | 64 ++-------- crates/defguard_version/src/middleware.rs | 44 ------- crates/defguard_version/src/server.rs | 143 +++++++--------------- 6 files changed, 56 insertions(+), 208 deletions(-) delete mode 100644 crates/defguard_version/src/middleware.rs diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index de407611a5..f47955b592 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -43,8 +43,8 @@ async fn main() -> Result<(), anyhow::Error> { } let config = DefGuardConfig::new(); SERVER_CONFIG.set(config.clone())?; - let version_set = Arc::new(RwLock::new(DefguardVersionSet::try_from(VERSION)?)); - // TODO: tracing with version-set + let version_set = Arc::new(RwLock::new(DefguardVersionSet::try_from(VERSION)?)); + // TODO: tracing with version-set // initialize tracing tracing_subscriber::registry() .with( diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 3a177a2630..2a737fadd6 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,5 +1,5 @@ use chrono::{NaiveDateTime, Utc}; -use defguard_version::{DefguardVersionSet, middleware::DefguardVersionMiddleware}; +use defguard_version::{DefguardVersionSet, server::DefguardVersionMiddleware}; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; use serde::Serialize; diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index b7fa392f6f..c15a1ff8f8 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -1,12 +1,12 @@ +use http::{Request, Response}; use std::{ future::Future, pin::Pin, task::{Context, Poll}, }; -use http::{Request, Response}; use tonic::body::BoxBody; use tower::{Layer, Service}; -use tracing::{debug, error}; +use tracing::error; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; @@ -63,11 +63,8 @@ where .expect("Version should be valid header value"), ); - debug!("Client: Sending dfg-version: {}", self.version); - // Call the inner service directly (don't clone) let future = self.inner.call(request); - let version = self.version.clone(); Box::pin(async move { // Make the request diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index a5a7c80d34..5f8cf25b12 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -3,11 +3,9 @@ use std::{ sync::{Arc, RwLock}, }; use thiserror::Error; -use tonic::{Status, service::Interceptor}; use tracing::error; pub mod client; -pub mod middleware; pub mod server; #[derive(Debug, Error)] @@ -16,6 +14,13 @@ pub enum DefguardVersionError { SemverError(#[from] semver::Error), } +#[derive(Clone)] +pub enum DefguardComponent { + Core, + Proxy, + Gateway, +} + #[derive(Clone, Debug)] pub struct DefguardVersionSet { pub own: ComponentInfo, @@ -97,58 +102,3 @@ impl ComponentInfo { }) } } - -#[derive(Clone)] -pub enum DefguardComponent { - Core, - Proxy, - Gateway, -} - -#[derive(Clone)] -pub struct DefguardVersionInterceptor { - component: DefguardComponent, - version_set: Arc>, -} - -impl DefguardVersionInterceptor { - pub fn new(component: DefguardComponent, version_set: Arc>) -> Self { - Self { - component, - version_set, - } - } -} - -impl Interceptor for DefguardVersionInterceptor { - fn call(&mut self, mut req: tonic::Request<()>) -> Result, Status> { - // read and set client version from metadata - let client_version = req - .metadata() - .get("dfg-version") - .map(|v| v.to_str().unwrap_or("unknown")) - .unwrap_or("missing"); - // TODO set appropriate component version - // match self.component { - // DefguardComponent::Core => self.version_set.write().unwrap().core = - // } - for header in req.metadata().keys() { - error!("key: {:?}", header); - } - let own_version = &self.version_set.read().unwrap().own.version; - error!("Remote version: {}", client_version); - error!("Own version: {}", own_version); - - // add own version to response metadata - req.metadata_mut().insert( - "dfg-version", - own_version - .to_string() - .parse() - .map_err(|_| Status::internal("Failed to set server version metadata"))?, - ); - error!("STORED VERSION metadta"); - - Ok(req) - } -} diff --git a/crates/defguard_version/src/middleware.rs b/crates/defguard_version/src/middleware.rs deleted file mode 100644 index 07d3534874..0000000000 --- a/crates/defguard_version/src/middleware.rs +++ /dev/null @@ -1,44 +0,0 @@ -use tonic::async_trait; -use tonic::body::BoxBody; -use tonic::codegen::http::Request; // Use this instead of tonic::Request in Middleware! -use tonic::codegen::http::Response; // Use this instead of tonic::Response in Middleware! -use tonic_middleware::Middleware; -use tonic_middleware::ServiceBound; -use tracing::error; - -#[derive(Default, Clone)] -pub struct DefguardVersionMiddleware; - -#[async_trait] -impl Middleware for DefguardVersionMiddleware -where - S: ServiceBound, - S::Future: Send, -{ - async fn call( - &self, - request: Request, - mut service: S, - ) -> Result, S::Error> { - let client_version = request - .headers() - .get("dfg-version") - .and_then(|v| v.to_str().ok()) - .unwrap_or("unknown") - .to_string(); - - error!("Remote version: {}", client_version); - error!("Own version: TODO"); - for header in request.headers().keys() { - error!("key: {:?}", header); - } - // Call inner service - let mut response = service.call(request).await?; - - response - .headers_mut() - .insert("dfg-version", "1.555.555".parse().unwrap()); - - Ok(response) - } -} diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 0b38538c69..35c0a2fda2 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,99 +1,44 @@ -// use std::{ -// future::Future, -// pin::Pin, -// task::{Context, Poll}, -// }; -// use tonic::{Request, Response, Status}; -// use tower::{Layer, Service}; -// use tracing::error; - -// pub type BoxFuture<'a, T> = Pin + Send + 'a>>; - -// // For response modification, we need a more specific approach -// #[derive(Clone)] -// pub struct DefguardVersionLayer; - -// impl Layer for DefguardVersionLayer { -// type Service = DefguardVersionMiddleware; - -// fn layer(&self, inner: S) -> Self::Service { -// DefguardVersionMiddleware { inner } -// } -// } - -// #[derive(Clone)] -// pub struct DefguardVersionMiddleware { -// inner: S, -// } - -// // impl Service> for DefguardVersionMiddleware -// // where -// // S: Service, Response = Response> + Clone + Send + 'static, -// // S::Future: Send + 'static, -// // B: Send + 'static, -// // { -// // type Response = Response; -// // type Error = S::Error; -// // type Future = BoxFuture<'static, Result>; - -// // fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { -// // self.inner.poll_ready(cx) -// // } - -// // fn call(&mut self, request: Request) -> Self::Future { -// // let mut inner = self.inner.clone(); - -// // Box::pin(async move { -// // // Read request metadata if needed -// // let client_version = request -// // .metadata() -// // .get("dfg-version") -// // .and_then(|v| v.to_str().ok()) -// // .unwrap_or("unknown") -// // .to_string(); - -// // error!("Remote version: {}", client_version); -// // error!("Own version: TODO"); -// // for header in request.metadata().keys() { -// // error!("key: {:?}", header); -// // } -// // // Call inner service -// // let mut response = inner.call(request).await?; - -// // response -// // .metadata_mut() -// // .insert("dfg-version", "1.555.555".parse().unwrap()); - -// // Ok(response) -// // }) -// // } -// // } - - -// impl Service> for DefguardVersionMiddleware -// where -// S: Service, Response = Response, Error = Status> + Send + 'static, -// S::Future: Send + 'static, -// ReqBody: Send + 'static, -// { -// type Response = Response; -// type Error = Status; -// type Future = Pin> + Send>>; - -// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { -// self.inner.poll_ready(cx) -// } - -// fn call(&mut self, req: Request) -> Self::Future { -// let fut = self.inner.call(req); - -// Box::pin(async move { -// let mut response = fut.await?; - -// // 🔁 Modify the inner message -// let modified = modify_response(response.into_inner()); - -// Ok(Response::new(modified)) -// }) -// } -// } +use tonic::{ + async_trait, + body::BoxBody, + codegen::http::{Request, Response}, +}; +use tonic_middleware::{Middleware, ServiceBound}; +use tracing::error; + +#[derive(Default, Clone)] +pub struct DefguardVersionMiddleware; + +#[async_trait] +impl Middleware for DefguardVersionMiddleware +where + S: ServiceBound, + S::Future: Send, +{ + async fn call( + &self, + request: Request, + mut service: S, + ) -> Result, S::Error> { + let client_version = request + .headers() + .get("dfg-version") + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown") + .to_string(); + + error!("Remote version: {}", client_version); + error!("Own version: TODO"); + for header in request.headers().keys() { + error!("key: {:?}", header); + } + // Call inner service + let mut response = service.call(request).await?; + + response + .headers_mut() + .insert("dfg-version", "1.555.555".parse().unwrap()); + + Ok(response) + } +} From 581cdbc7fb002d45fb800b29aa0da70e9df46b73 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 11:10:41 +0200 Subject: [PATCH 011/100] DefguardVersionMiddleware holds and sets own and remote component infos --- crates/defguard_core/src/grpc/mod.rs | 5 +- crates/defguard_version/src/lib.rs | 22 +++++--- crates/defguard_version/src/server.rs | 75 ++++++++++++++++++++------- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 2a737fadd6..7164bb7dcb 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -908,7 +908,10 @@ pub async fn run_grpc_server( #[cfg(feature = "wireguard")] let router = router.add_service(MiddlewareFor::new( gateway_service, - DefguardVersionMiddleware::default(), + DefguardVersionMiddleware::new( + version_set.read().unwrap().own.clone(), + Arc::clone(&version_set.read().unwrap().gateway), + ), )); #[cfg(feature = "worker")] let router = router.add_service(worker_service); diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 5f8cf25b12..4a68cd26da 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -8,6 +8,9 @@ use tracing::error; pub mod client; pub mod server; +static VERSION_HEADER: &str = "dfg-version"; +static SYSTEM_INFO_HEADER: &str = "dfg-system-info"; + #[derive(Debug, Error)] pub enum DefguardVersionError { #[error(transparent)] @@ -47,6 +50,17 @@ pub struct SemanticVersion { pub patch: u64, } +impl SemanticVersion { + fn try_from(version: &str) -> Result { + let parsed = semver::Version::parse(version)?; + Ok(Self { + major: parsed.major, + minor: parsed.minor, + patch: parsed.patch, + }) + } +} + impl Display for SemanticVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) @@ -90,14 +104,10 @@ pub struct ComponentInfo { impl ComponentInfo { pub fn try_from(version: &str) -> Result { + let version = SemanticVersion::try_from(version)?; let info = os_info::get(); - let version = semver::Version::parse(version)?; Ok(Self { - version: SemanticVersion { - major: version.major, - minor: version.minor, - patch: version.patch, - }, + version, system: info.into(), }) } diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 35c0a2fda2..eac59e78de 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,13 +1,29 @@ +use std::sync::{Arc, RwLock}; + use tonic::{ async_trait, body::BoxBody, codegen::http::{Request, Response}, }; use tonic_middleware::{Middleware, ServiceBound}; -use tracing::error; +use tracing::{error, warn}; + +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SemanticVersion, SystemInfo, VERSION_HEADER}; + +#[derive(Clone)] +pub struct DefguardVersionMiddleware { + own_info: ComponentInfo, + remote_info: Arc>>, +} -#[derive(Default, Clone)] -pub struct DefguardVersionMiddleware; +impl DefguardVersionMiddleware { + pub fn new(own_info: ComponentInfo, remote_info: Arc>>) -> Self { + Self { + own_info, + remote_info, + } + } +} #[async_trait] impl Middleware for DefguardVersionMiddleware @@ -20,24 +36,45 @@ where request: Request, mut service: S, ) -> Result, S::Error> { - let client_version = request - .headers() - .get("dfg-version") - .and_then(|v| v.to_str().ok()) - .unwrap_or("unknown") - .to_string(); - - error!("Remote version: {}", client_version); - error!("Own version: TODO"); - for header in request.headers().keys() { - error!("key: {:?}", header); + let client_version = request.headers().get(VERSION_HEADER); + // .and_then(|v| v.to_str().ok()) + // .unwrap_or("unknown") + // .to_string(); + + let client_info = request.headers().get(SYSTEM_INFO_HEADER); + // .and_then(|v| v.to_str().ok()) + // .unwrap_or("unknown") + // .to_string(); + + // error!("Remote version: {}", client_version); + // error!("Remote system: {}", client_info); + + if let (Some(client_version), Some(_client_info)) = (client_version, client_info) { + if let Ok(version) = client_version.to_str() { + if let Ok(version) = SemanticVersion::try_from(version) { + error!("OWN VERSION: {}", self.own_info.version.to_string()); + error!("CLIENT VERSION: {}", version.to_string()); + // TODO + let system = SystemInfo { + os_type: "?".to_string(), + os_version: "?".to_string(), + os_edition: "?".to_string(), + os_codename: "?".to_string(), + bitness: "?".to_string(), + architecture: "?".to_string(), + }; + *self.remote_info.write().unwrap() = Some(ComponentInfo { version, system }); + } + } + } else { + warn!("Missing version and/or system info header"); } - // Call inner service - let mut response = service.call(request).await?; - response - .headers_mut() - .insert("dfg-version", "1.555.555".parse().unwrap()); + let mut response = service.call(request).await?; + response.headers_mut().insert( + VERSION_HEADER, + self.own_info.version.to_string().parse().unwrap(), + ); Ok(response) } From 10a0ffae9df8b38542dacd52a673aaacde38f320 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 12:23:12 +0200 Subject: [PATCH 012/100] cleanup server --- crates/defguard_version/src/server.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index eac59e78de..e69b8f05b7 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -37,19 +37,9 @@ where mut service: S, ) -> Result, S::Error> { let client_version = request.headers().get(VERSION_HEADER); - // .and_then(|v| v.to_str().ok()) - // .unwrap_or("unknown") - // .to_string(); - let client_info = request.headers().get(SYSTEM_INFO_HEADER); - // .and_then(|v| v.to_str().ok()) - // .unwrap_or("unknown") - // .to_string(); - - // error!("Remote version: {}", client_version); - // error!("Remote system: {}", client_info); - if let (Some(client_version), Some(_client_info)) = (client_version, client_info) { + if let (Some(client_version), _) = (client_version, client_info) { if let Ok(version) = client_version.to_str() { if let Ok(version) = SemanticVersion::try_from(version) { error!("OWN VERSION: {}", self.own_info.version.to_string()); @@ -75,7 +65,6 @@ where VERSION_HEADER, self.own_info.version.to_string().parse().unwrap(), ); - Ok(response) } } From c95ce47f15b823daec13b58905e79a31e8ccca79 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 12:24:15 +0200 Subject: [PATCH 013/100] DefguardVersionClientLayer holds and sets own and remote version --- crates/defguard_version/src/client.rs | 75 ++++++++++++++++++++------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index c15a1ff8f8..d5c7db578a 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -2,23 +2,30 @@ use http::{Request, Response}; use std::{ future::Future, pin::Pin, + sync::{Arc, RwLock}, task::{Context, Poll}, }; use tonic::body::BoxBody; use tower::{Layer, Service}; -use tracing::error; +use tracing::{error, warn}; + +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SemanticVersion, SystemInfo, VERSION_HEADER}; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; /// Layer for adding version information to outgoing gRPC requests (client-side) #[derive(Clone)] pub struct DefguardVersionClientLayer { - version: String, + own_info: ComponentInfo, + remote_info: Arc>>, } impl DefguardVersionClientLayer { - pub fn new(version: String) -> Self { - Self { version } + pub fn new(own_info: ComponentInfo, remote_info: Arc>>) -> Self { + Self { + own_info, + remote_info, + } } } @@ -28,7 +35,8 @@ impl Layer for DefguardVersionClientLayer { fn layer(&self, inner: S) -> Self::Service { DefguardVersionClientService { inner, - version: self.version.clone(), + own_info: self.own_info.clone(), + remote_info: Arc::clone(&self.remote_info), } } } @@ -37,7 +45,8 @@ impl Layer for DefguardVersionClientLayer { #[derive(Clone)] pub struct DefguardVersionClientService { inner: S, - version: String, + own_info: ComponentInfo, + remote_info: Arc>>, } impl Service> for DefguardVersionClientService @@ -57,34 +66,60 @@ where fn call(&mut self, mut request: Request) -> Self::Future { // Add our version to the outgoing request metadata request.headers_mut().insert( - "dfg-version", - self.version + VERSION_HEADER, + self.own_info + .version + .to_string() .parse() + // TODO .expect("Version should be valid header value"), ); + // TODO add system info header + // Call the inner service directly (don't clone) let future = self.inner.call(request); + let remote_info = Arc::clone(&self.remote_info); + let own_info = self.own_info.clone(); Box::pin(async move { // Make the request let response = future.await.map_err(Into::into)?; - // Read server version from response metadata - let server_version = response - .headers() - .get("dfg-version") - .and_then(|v| v.to_str().ok()) - .unwrap_or("unknown"); + let server_version = response.headers().get(VERSION_HEADER); + let server_info = response.headers().get(SYSTEM_INFO_HEADER); + + if let (Some(server_version), _) = (server_version, server_info) { + if let Ok(version) = server_version.to_str() { + if let Ok(version) = SemanticVersion::try_from(version) { + error!("OWN VERSION: {}", own_info.version.to_string()); + error!("SERVER VERSION: {}", version.to_string()); + // TODO + let system = SystemInfo { + os_type: "?".to_string(), + os_version: "?".to_string(), + os_edition: "?".to_string(), + os_codename: "?".to_string(), + bitness: "?".to_string(), + architecture: "?".to_string(), + }; + *remote_info.write().unwrap() = Some(ComponentInfo { version, system }); + } + } + } else { + warn!("Missing version and/or system info header"); + } - error!("Client: Received server dfg-version: {}", server_version); + // // Read server version from response metadata + // let server_version = response + // .headers() + // .get(VERSION_HEADER); + // // .and_then(|v| v.to_str().ok()) + // // .unwrap_or("unknown"); + + // error!("Client: Received server dfg-version: {}", server_version); Ok(response) }) } } - -/// Convenience function to create a version layer for clients -pub fn version_layer(version: String) -> DefguardVersionClientLayer { - DefguardVersionClientLayer::new(version) -} From fed8c47d269424de3319877481c4d5e10c5b9c7a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 13:13:22 +0200 Subject: [PATCH 014/100] also send / parse system info --- crates/defguard_version/src/client.rs | 61 +++++++++++++-------------- crates/defguard_version/src/lib.rs | 45 +++++++++++++++++--- crates/defguard_version/src/server.rs | 32 ++++++++------ 3 files changed, 86 insertions(+), 52 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index d5c7db578a..65fe2e55d0 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -64,61 +64,58 @@ where } fn call(&mut self, mut request: Request) -> Self::Future { - // Add our version to the outgoing request metadata + // add version and system info headers request.headers_mut().insert( VERSION_HEADER, self.own_info .version .to_string() .parse() - // TODO - .expect("Version should be valid header value"), + .expect("Failed to parse SemanticVersion as HeaderValue"), + ); + request.headers_mut().insert( + SYSTEM_INFO_HEADER, + self.own_info + .system + .as_header_value() + .parse() + .expect("Failed to parse SystemInfo as HeaderValue"), ); - // TODO add system info header - - // Call the inner service directly (don't clone) - let future = self.inner.call(request); + // send the request + let response_future = self.inner.call(request); + // handle response let remote_info = Arc::clone(&self.remote_info); let own_info = self.own_info.clone(); Box::pin(async move { - // Make the request - let response = future.await.map_err(Into::into)?; + let response = response_future.await.map_err(Into::into)?; + // extract version headers let server_version = response.headers().get(VERSION_HEADER); let server_info = response.headers().get(SYSTEM_INFO_HEADER); - if let (Some(server_version), _) = (server_version, server_info) { - if let Ok(version) = server_version.to_str() { - if let Ok(version) = SemanticVersion::try_from(version) { - error!("OWN VERSION: {}", own_info.version.to_string()); - error!("SERVER VERSION: {}", version.to_string()); - // TODO - let system = SystemInfo { - os_type: "?".to_string(), - os_version: "?".to_string(), - os_edition: "?".to_string(), - os_codename: "?".to_string(), - bitness: "?".to_string(), - architecture: "?".to_string(), - }; + if let (Some(version), Some(system)) = (server_version, server_info) { + if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { + if let (Ok(version), Ok(system)) = ( + SemanticVersion::try_from(version), + SystemInfo::try_from_header_value(system), + ) { + error!("OWN VERSION: {}", own_info.version); + error!("OWN SYSTEM: {}", own_info.system); + error!("SERVER VERSION: {}", version); + error!("SERVER SYSTEM: {}", system); *remote_info.write().unwrap() = Some(ComponentInfo { version, system }); + } else { + warn!("Failed to parse SemanticVersion or SystemInfo"); } + } else { + warn!("Failed to stringify HeaderValues"); } } else { warn!("Missing version and/or system info header"); } - // // Read server version from response metadata - // let server_version = response - // .headers() - // .get(VERSION_HEADER); - // // .and_then(|v| v.to_str().ok()) - // // .unwrap_or("unknown"); - - // error!("Client: Received server dfg-version: {}", server_version); - Ok(response) }) } diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 4a68cd26da..48bc2eb229 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,3 +1,4 @@ +use http::HeaderValue; use std::{ fmt::Display, sync::{Arc, RwLock}, @@ -15,6 +16,9 @@ static SYSTEM_INFO_HEADER: &str = "dfg-system-info"; pub enum DefguardVersionError { #[error(transparent)] SemverError(#[from] semver::Error), + + #[error("Failed to parse SystemInfo header: {0}")] + SystemInfoParseError(String), } #[derive(Clone)] @@ -73,23 +77,52 @@ pub struct SystemInfo { pub os_type: String, /// The operating system version (e.g., "22.04", "11", "13.0") pub os_version: String, - /// The operating system edition (e.g., "Server", "Pro", "Home") - pub os_edition: String, - /// The operating system codename (e.g., "jammy", "focal") - pub os_codename: String, /// The system bitness (e.g., "64-bit", "32-bit") pub bitness: String, /// The system architecture (e.g., "x86_64", "aarch64", "arm") pub architecture: String, } +impl Display for SystemInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} {} {} {}", + self.os_type, self.os_version, self.bitness, self.architecture + ) + } +} + +impl SystemInfo { + fn as_header_value(&self) -> String { + format!( + "{};{};{};{}", + self.os_type, self.os_version, self.bitness, self.architecture + ) + } + + fn try_from_header_value(header_value: &str) -> Result { + let parts: Vec<&str> = header_value.split(';').collect(); + if parts.len() != 4 { + return Err(DefguardVersionError::SystemInfoParseError( + header_value.to_string(), + )); + } + + Ok(Self { + os_type: parts[0].to_string(), + os_version: parts[1].to_string(), + bitness: parts[2].to_string(), + architecture: parts[3].to_string(), + }) + } +} + impl From for SystemInfo { fn from(info: os_info::Info) -> Self { Self { os_type: info.os_type().to_string(), os_version: info.version().to_string(), - os_edition: info.edition().unwrap_or_else(|| "?").to_string(), - os_codename: info.codename().unwrap_or_else(|| "?").to_string(), bitness: info.bitness().to_string(), architecture: info.architecture().unwrap_or_else(|| "?").to_string(), } diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index e69b8f05b7..c420b52b89 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -39,22 +39,22 @@ where let client_version = request.headers().get(VERSION_HEADER); let client_info = request.headers().get(SYSTEM_INFO_HEADER); - if let (Some(client_version), _) = (client_version, client_info) { - if let Ok(version) = client_version.to_str() { - if let Ok(version) = SemanticVersion::try_from(version) { - error!("OWN VERSION: {}", self.own_info.version.to_string()); - error!("CLIENT VERSION: {}", version.to_string()); - // TODO - let system = SystemInfo { - os_type: "?".to_string(), - os_version: "?".to_string(), - os_edition: "?".to_string(), - os_codename: "?".to_string(), - bitness: "?".to_string(), - architecture: "?".to_string(), - }; + if let (Some(version), Some(system)) = (client_version, client_info) { + if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { + if let (Ok(version), Ok(system)) = ( + SemanticVersion::try_from(version), + SystemInfo::try_from_header_value(system), + ) { + error!("OWN VERSION: {}", self.own_info.version); + error!("OWN SYSTEM: {}", self.own_info.system); + error!("CLIENT VERSION: {}", version); + error!("CLIENT SYSTEM: {}", system); *self.remote_info.write().unwrap() = Some(ComponentInfo { version, system }); + } else { + warn!("Failed to parse SemanticVersion or SystemInfo"); } + } else { + warn!("Failed to stringify HeaderValues"); } } else { warn!("Missing version and/or system info header"); @@ -65,6 +65,10 @@ where VERSION_HEADER, self.own_info.version.to_string().parse().unwrap(), ); + response.headers_mut().insert( + SYSTEM_INFO_HEADER, + self.own_info.system.as_header_value().parse().unwrap(), + ); Ok(response) } } From c8a3d0fddc5446df44047bf98654408b81774a6a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 13:15:20 +0200 Subject: [PATCH 015/100] remove unused import --- crates/defguard_version/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 48bc2eb229..70b1676f66 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,4 +1,3 @@ -use http::HeaderValue; use std::{ fmt::Display, sync::{Arc, RwLock}, From 85f1a247a4e299314cbff190fdfcae1080e05931 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 7 Aug 2025 13:40:47 +0200 Subject: [PATCH 016/100] tracing-version integration --- Cargo.lock | 1 + crates/defguard/src/main.rs | 19 +-- crates/defguard_version/Cargo.toml | 1 + crates/defguard_version/src/lib.rs | 3 +- crates/defguard_version/src/tracing.rs | 170 +++++++++++++++++++++++++ 5 files changed, 181 insertions(+), 13 deletions(-) create mode 100644 crates/defguard_version/src/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index 37e4a7d400..0cb7dadc5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1194,6 +1194,7 @@ dependencies = [ "tonic-middleware", "tower 0.5.2", "tracing", + "tracing-subscriber", ] [[package]] diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index f47955b592..af523c44c5 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -1,6 +1,6 @@ use std::{ fs::read_to_string, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex}, }; use bytes::Bytes; @@ -28,10 +28,8 @@ use defguard_core::{ }; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; -use defguard_version::DefguardVersionSet; use secrecy::ExposeSecret; use tokio::sync::{broadcast, mpsc::unbounded_channel}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[macro_use] extern crate tracing; @@ -43,16 +41,13 @@ async fn main() -> Result<(), anyhow::Error> { } let config = DefGuardConfig::new(); SERVER_CONFIG.set(config.clone())?; - let version_set = Arc::new(RwLock::new(DefguardVersionSet::try_from(VERSION)?)); - // TODO: tracing with version-set + // initialize tracing - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| format!("{},h2=info", config.log_level).into()), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); + let version_set = defguard_version::tracing::init( + VERSION, + &config.log_level, + &["run_grpc_bidi_stream", "run_grpc_server"], + ); info!("Starting ... version v{}", VERSION); debug!("Using config: {config:?}"); diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml index 6d30a25219..05685b8693 100644 --- a/crates/defguard_version/Cargo.toml +++ b/crates/defguard_version/Cargo.toml @@ -16,3 +16,4 @@ tonic.workspace = true tonic-middleware = "0.2" tower = "0.5.2" tracing.workspace = true +tracing-subscriber.workspace = true diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 70b1676f66..2efa0f9714 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -3,10 +3,11 @@ use std::{ sync::{Arc, RwLock}, }; use thiserror::Error; -use tracing::error; +use ::tracing::error; pub mod client; pub mod server; +pub mod tracing; static VERSION_HEADER: &str = "dfg-version"; static SYSTEM_INFO_HEADER: &str = "dfg-system-info"; diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs new file mode 100644 index 0000000000..dfae5845f5 --- /dev/null +++ b/crates/defguard_version/src/tracing.rs @@ -0,0 +1,170 @@ +use std::{ + collections::HashSet, + sync::{Arc, RwLock}, +}; + +use tracing_subscriber::{ + fmt::{FormatEvent, FormatFields, format::Writer}, + {layer::SubscriberExt, util::SubscriberInitExt}, +}; + +use crate::{ComponentInfo, DefguardVersionSet}; + +/// Custom tracing formatter that conditionally includes version information in log messages. +/// +/// This formatter wraps the default tracing formatter and adds version prefixes to log messages +/// under specific conditions: +/// - Always includes version info for ERROR level logs +/// - Always includes version info for logs within specified span names (regardless of log level) +/// +/// The version information includes details about the application and connected services, +/// along with comprehensive system information for debugging purposes. +struct VersionPrefixFormat { + /// The underlying tracing formatter + inner: tracing_subscriber::fmt::format::Format, + /// Shared version information of all components + version_set: Arc>, + /// Set of span names that should always include version info in their logs + always_version_spans: HashSet, +} + +impl FormatEvent for VersionPrefixFormat +where + S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + /// Formats a tracing event, conditionally adding version information as a prefix. + /// + /// This method checks if version information should be included based on: + /// 1. Log level (always include for ERROR level) + /// 2. Span context (include if any span in the current context matches configured span names) + /// + /// The version prefix format includes comprehensive system information: + /// `[v{version}|{os_type}|{os_version}|{bitness}|{architecture}]` + /// + /// Additional prefixes are added for connected services: + /// - Core service: `[C:v{version}|...]` + /// - Proxy service: `[PX:v{version}|...]` + /// - Gateway service: `[GW:v{version}|...]` + fn format_event( + &self, + ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &tracing::Event<'_>, + ) -> std::fmt::Result { + let is_error_level = *event.metadata().level() == tracing::Level::ERROR; + + // Check if we're within any of the configured spans + let is_in_version_span = ctx.lookup_current().map_or(false, |span_ref| { + let mut current_span = Some(span_ref); + while let Some(span) = current_span { + let span_name = span.metadata().name(); + if self.always_version_spans.contains(span_name) { + return true; + } + current_span = span.parent(); + } + false + }); + + let should_log_version = is_error_level || is_in_version_span; + if should_log_version { + let version_set = self.version_set.read().unwrap(); + write!( + writer, + "[v{}|{}|{}|{}|{}] ", + version_set.own.version, + version_set.own.system.os_type, + version_set.own.system.os_version, + version_set.own.system.bitness, + version_set.own.system.architecture, + )?; + + if let Some(ref core) = *version_set.core.read().unwrap() { + write!( + writer, + "[C:v{}|{}|{}|{}|{}] ", + core.version, + core.system.os_type, + core.system.os_version, + core.system.bitness, + core.system.architecture, + )?; + } + if let Some(ref proxy) = *version_set.proxy.read().unwrap() { + write!( + writer, + "[PX:v{}|{}|{}|{}|{}] ", + proxy.version, + proxy.system.os_type, + proxy.system.os_version, + proxy.system.bitness, + proxy.system.architecture, + )?; + } + + if let Some(ref gateway) = *version_set.gateway.read().unwrap() { + write!( + writer, + "[GW:v{}|{}|{}|{}|{}] ", + gateway.version, + gateway.system.os_type, + gateway.system.os_version, + gateway.system.bitness, + gateway.system.architecture, + )?; + } + } + + self.inner.format_event(ctx, writer, event) + } +} + +/// Initializes tracing with custom formatter displaying own and connected services version. +/// Returns shared VersionInfo object that will be used to display services versions. +/// +/// # Arguments +/// * `version` - The application version +/// * `log_level` - The log level to use +/// * `always_version_spans` - Span names that should always include version info in logs +/// +/// # Examples +/// ``` +/// let version_set = defguard_version::init_tracing( +/// "1.5.0", +/// "info", +/// &["run_grpc_bidi_stream", "enrollment_process"] +/// ); +/// ``` +pub fn init( + version: &str, + log_level: &str, + always_version_spans: &[&str], +) -> Arc> { + let version_set = Arc::new(RwLock::new(DefguardVersionSet { + own: ComponentInfo::try_from(version).expect("Failed to parse version: {version}"), + core: Arc::new(RwLock::new(None)), + proxy: Arc::new(RwLock::new(None)), + gateway: Arc::new(RwLock::new(None)), + })); + + let spans: HashSet = always_version_spans + .iter() + .map(|&s| s.to_string()) + .collect(); + + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| format!("{},h2=info", log_level).into()), + ) + .with( + tracing_subscriber::fmt::layer().event_format(VersionPrefixFormat { + inner: tracing_subscriber::fmt::format::Format::default(), + version_set: Arc::clone(&version_set), + always_version_spans: spans, + }), + ) + .init(); + version_set +} From 961f20637ffa6dd80bb3114afa71b4c2a03fe47e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 08:54:27 +0200 Subject: [PATCH 017/100] format --- crates/defguard_core/src/grpc/mod.rs | 11 ++++------- crates/defguard_version/Cargo.toml | 8 ++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 7164bb7dcb..908288870b 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -872,13 +872,10 @@ pub async fn run_grpc_server( JwtInterceptor::new(ClaimsType::YubiBridge), ); #[cfg(feature = "wireguard")] - let gateway_service = { - let jwt_interceptor = JwtInterceptor::new(ClaimsType::Gateway); - GatewayServiceServer::with_interceptor( - GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), - jwt_interceptor, - ) - }; + let gateway_service = GatewayServiceServer::with_interceptor( + GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), + JwtInterceptor::new(ClaimsType::Gateway), + ); let (mut health_reporter, health_service) = tonic_health::server::health_reporter(); health_reporter diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml index 05685b8693..97790d9d1f 100644 --- a/crates/defguard_version/Cargo.toml +++ b/crates/defguard_version/Cargo.toml @@ -8,12 +8,12 @@ repository.workspace = true rust-version.workspace = true [dependencies] -http = "1.3.1" -os_info = "3.12.0" semver.workspace = true thiserror.workspace = true tonic.workspace = true -tonic-middleware = "0.2" -tower = "0.5.2" tracing.workspace = true tracing-subscriber.workspace = true +tonic-middleware = "0.2" +http = "1.3.1" +os_info = "3.12.0" +tower = "0.5.2" From e16032f4292a147e5bf0c0f28365f9727bd507bc Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 08:57:55 +0200 Subject: [PATCH 018/100] remove unused enum --- crates/defguard_version/src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 2efa0f9714..3c7f5e5592 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -21,13 +21,6 @@ pub enum DefguardVersionError { SystemInfoParseError(String), } -#[derive(Clone)] -pub enum DefguardComponent { - Core, - Proxy, - Gateway, -} - #[derive(Clone, Debug)] pub struct DefguardVersionSet { pub own: ComponentInfo, From 18bbd751f42d1fd037c2af1f40c5419e632c5e3a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 09:18:35 +0200 Subject: [PATCH 019/100] don't RwLock the whole DefguardVersionSet struct --- crates/defguard_core/src/grpc/mod.rs | 7 +++--- crates/defguard_version/src/tracing.rs | 33 +++++++++++++------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 908288870b..58947d8b64 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -8,7 +8,6 @@ use sqlx::PgPool; use std::{ collections::hash_map::HashMap, fs::read_to_string, - sync::RwLock, time::{Duration, Instant}, }; #[cfg(any(feature = "wireguard", feature = "worker"))] @@ -862,7 +861,7 @@ pub async fn run_grpc_server( grpc_key: Option, failed_logins: Arc>, grpc_event_tx: UnboundedSender, - version_set: Arc>, + version_set: Arc, ) -> Result<(), anyhow::Error> { // Build gRPC services let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); @@ -906,8 +905,8 @@ pub async fn run_grpc_server( let router = router.add_service(MiddlewareFor::new( gateway_service, DefguardVersionMiddleware::new( - version_set.read().unwrap().own.clone(), - Arc::clone(&version_set.read().unwrap().gateway), + version_set.own.clone(), + Arc::clone(&version_set.gateway), ), )); #[cfg(feature = "worker")] diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index dfae5845f5..df0e34b87e 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -23,7 +23,7 @@ struct VersionPrefixFormat { /// The underlying tracing formatter inner: tracing_subscriber::fmt::format::Format, /// Shared version information of all components - version_set: Arc>, + version_set: Arc, /// Set of span names that should always include version info in their logs always_version_spans: HashSet, } @@ -69,18 +69,17 @@ where let should_log_version = is_error_level || is_in_version_span; if should_log_version { - let version_set = self.version_set.read().unwrap(); write!( writer, "[v{}|{}|{}|{}|{}] ", - version_set.own.version, - version_set.own.system.os_type, - version_set.own.system.os_version, - version_set.own.system.bitness, - version_set.own.system.architecture, + self.version_set.own.version, + self.version_set.own.system.os_type, + self.version_set.own.system.os_version, + self.version_set.own.system.bitness, + self.version_set.own.system.architecture, )?; - if let Some(ref core) = *version_set.core.read().unwrap() { + if let Some(ref core) = *self.version_set.core.read().unwrap() { write!( writer, "[C:v{}|{}|{}|{}|{}] ", @@ -91,7 +90,7 @@ where core.system.architecture, )?; } - if let Some(ref proxy) = *version_set.proxy.read().unwrap() { + if let Some(ref proxy) = *self.version_set.proxy.read().unwrap() { write!( writer, "[PX:v{}|{}|{}|{}|{}] ", @@ -103,7 +102,7 @@ where )?; } - if let Some(ref gateway) = *version_set.gateway.read().unwrap() { + if let Some(ref gateway) = *self.version_set.gateway.read().unwrap() { write!( writer, "[GW:v{}|{}|{}|{}|{}] ", @@ -140,13 +139,13 @@ pub fn init( version: &str, log_level: &str, always_version_spans: &[&str], -) -> Arc> { - let version_set = Arc::new(RwLock::new(DefguardVersionSet { - own: ComponentInfo::try_from(version).expect("Failed to parse version: {version}"), - core: Arc::new(RwLock::new(None)), - proxy: Arc::new(RwLock::new(None)), - gateway: Arc::new(RwLock::new(None)), - })); +) -> Arc { + let version_set = Arc::new(DefguardVersionSet { + own: ComponentInfo::try_from(version).expect("Failed to parse version: {version}"), + core: Arc::new(RwLock::new(None)), + proxy: Arc::new(RwLock::new(None)), + gateway: Arc::new(RwLock::new(None)), + }); let spans: HashSet = always_version_spans .iter() From 9b42618eff55770d115e038de3d5a3f283b04488 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 09:29:52 +0200 Subject: [PATCH 020/100] remove SemanticVersion struct, use semver::Version instead --- crates/defguard_version/src/client.rs | 6 +++-- crates/defguard_version/src/lib.rs | 33 +++++---------------------- crates/defguard_version/src/server.rs | 10 +++++--- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 65fe2e55d0..a4ece3b259 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -1,7 +1,9 @@ use http::{Request, Response}; +use semver::Version; use std::{ future::Future, pin::Pin, + str::FromStr, sync::{Arc, RwLock}, task::{Context, Poll}, }; @@ -9,7 +11,7 @@ use tonic::body::BoxBody; use tower::{Layer, Service}; use tracing::{error, warn}; -use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SemanticVersion, SystemInfo, VERSION_HEADER}; +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SystemInfo, VERSION_HEADER}; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; @@ -98,7 +100,7 @@ where if let (Some(version), Some(system)) = (server_version, server_info) { if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { if let (Ok(version), Ok(system)) = ( - SemanticVersion::try_from(version), + Version::from_str(version), SystemInfo::try_from_header_value(system), ) { error!("OWN VERSION: {}", own_info.version); diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 3c7f5e5592..5df411b852 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,9 +1,12 @@ +use ::tracing::error; use std::{ fmt::Display, + str::FromStr, sync::{Arc, RwLock}, }; use thiserror::Error; -use ::tracing::error; +use semver::Version; + pub mod client; pub mod server; @@ -40,30 +43,6 @@ impl DefguardVersionSet { } } -#[derive(Clone, Debug)] -pub struct SemanticVersion { - pub major: u64, - pub minor: u64, - pub patch: u64, -} - -impl SemanticVersion { - fn try_from(version: &str) -> Result { - let parsed = semver::Version::parse(version)?; - Ok(Self { - major: parsed.major, - minor: parsed.minor, - patch: parsed.patch, - }) - } -} - -impl Display for SemanticVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}.{}.{}", self.major, self.minor, self.patch) - } -} - #[derive(Debug, Clone)] pub struct SystemInfo { /// The operating system type (e.g., "Linux", "Windows", "macOS") @@ -124,13 +103,13 @@ impl From for SystemInfo { #[derive(Debug, Clone)] pub struct ComponentInfo { - pub version: SemanticVersion, + pub version: Version, pub system: SystemInfo, } impl ComponentInfo { pub fn try_from(version: &str) -> Result { - let version = SemanticVersion::try_from(version)?; + let version = Version::from_str(version)?; let info = os_info::get(); Ok(Self { version, diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index c420b52b89..9551188b4e 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,5 +1,9 @@ -use std::sync::{Arc, RwLock}; +use std::{ + str::FromStr, + sync::{Arc, RwLock}, +}; +use semver::Version; use tonic::{ async_trait, body::BoxBody, @@ -8,7 +12,7 @@ use tonic::{ use tonic_middleware::{Middleware, ServiceBound}; use tracing::{error, warn}; -use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SemanticVersion, SystemInfo, VERSION_HEADER}; +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SystemInfo, VERSION_HEADER}; #[derive(Clone)] pub struct DefguardVersionMiddleware { @@ -42,7 +46,7 @@ where if let (Some(version), Some(system)) = (client_version, client_info) { if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { if let (Ok(version), Ok(system)) = ( - SemanticVersion::try_from(version), + Version::from_str(version), SystemInfo::try_from_header_value(system), ) { error!("OWN VERSION: {}", self.own_info.version); From 286b8175bc39b5dd66e24f8d26112092bc839cee Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 09:40:40 +0200 Subject: [PATCH 021/100] parse_version_headers util fn --- crates/defguard_version/src/client.rs | 36 ++++++++------------------- crates/defguard_version/src/lib.rs | 29 ++++++++++++++++++--- crates/defguard_version/src/server.rs | 35 ++++++++------------------ 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index a4ece3b259..39bbe25316 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -1,17 +1,15 @@ use http::{Request, Response}; -use semver::Version; use std::{ future::Future, pin::Pin, - str::FromStr, sync::{Arc, RwLock}, task::{Context, Poll}, }; use tonic::body::BoxBody; use tower::{Layer, Service}; -use tracing::{error, warn}; +use tracing::{error}; -use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SystemInfo, VERSION_HEADER}; +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER, parse_version_headers}; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; @@ -94,30 +92,16 @@ where let response = response_future.await.map_err(Into::into)?; // extract version headers - let server_version = response.headers().get(VERSION_HEADER); - let server_info = response.headers().get(SYSTEM_INFO_HEADER); + let version = response.headers().get(VERSION_HEADER); + let info = response.headers().get(SYSTEM_INFO_HEADER); - if let (Some(version), Some(system)) = (server_version, server_info) { - if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { - if let (Ok(version), Ok(system)) = ( - Version::from_str(version), - SystemInfo::try_from_header_value(system), - ) { - error!("OWN VERSION: {}", own_info.version); - error!("OWN SYSTEM: {}", own_info.system); - error!("SERVER VERSION: {}", version); - error!("SERVER SYSTEM: {}", system); - *remote_info.write().unwrap() = Some(ComponentInfo { version, system }); - } else { - warn!("Failed to parse SemanticVersion or SystemInfo"); - } - } else { - warn!("Failed to stringify HeaderValues"); - } - } else { - warn!("Missing version and/or system info header"); + if let Some((version, system)) = parse_version_headers(version, info) { + error!("OWN VERSION: {}", own_info.version); + error!("OWN SYSTEM: {}", own_info.system); + error!("SERVER VERSION: {}", version); + error!("SERVER SYSTEM: {}", system); + *remote_info.write().unwrap() = Some(ComponentInfo { version, system }); } - Ok(response) }) } diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 5df411b852..4c4624a72a 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,12 +1,12 @@ -use ::tracing::error; +use ::tracing::{error, warn}; +use http::HeaderValue; +use semver::Version; use std::{ fmt::Display, str::FromStr, sync::{Arc, RwLock}, }; use thiserror::Error; -use semver::Version; - pub mod client; pub mod server; @@ -117,3 +117,26 @@ impl ComponentInfo { }) } } + +pub(crate) fn parse_version_headers( + version: Option<&HeaderValue>, + info: Option<&HeaderValue>, +) -> Option<(Version, SystemInfo)> { + if let (Some(version), Some(system)) = (version, info) { + if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { + if let (Ok(version), Ok(system)) = ( + Version::from_str(version), + SystemInfo::try_from_header_value(system), + ) { + return Some((version, system)); + } else { + warn!("Failed to parse SemanticVersion or SystemInfo"); + } + } else { + warn!("Failed to stringify HeaderValues"); + } + } else { + warn!("Missing version and/or system info header"); + } + None +} diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 9551188b4e..5c03dec947 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,18 +1,16 @@ use std::{ - str::FromStr, sync::{Arc, RwLock}, }; -use semver::Version; use tonic::{ async_trait, body::BoxBody, codegen::http::{Request, Response}, }; use tonic_middleware::{Middleware, ServiceBound}; -use tracing::{error, warn}; +use tracing::error; -use crate::{ComponentInfo, SYSTEM_INFO_HEADER, SystemInfo, VERSION_HEADER}; +use crate::{parse_version_headers, ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; #[derive(Clone)] pub struct DefguardVersionMiddleware { @@ -40,28 +38,15 @@ where request: Request, mut service: S, ) -> Result, S::Error> { - let client_version = request.headers().get(VERSION_HEADER); - let client_info = request.headers().get(SYSTEM_INFO_HEADER); + let version = request.headers().get(VERSION_HEADER); + let info = request.headers().get(SYSTEM_INFO_HEADER); - if let (Some(version), Some(system)) = (client_version, client_info) { - if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { - if let (Ok(version), Ok(system)) = ( - Version::from_str(version), - SystemInfo::try_from_header_value(system), - ) { - error!("OWN VERSION: {}", self.own_info.version); - error!("OWN SYSTEM: {}", self.own_info.system); - error!("CLIENT VERSION: {}", version); - error!("CLIENT SYSTEM: {}", system); - *self.remote_info.write().unwrap() = Some(ComponentInfo { version, system }); - } else { - warn!("Failed to parse SemanticVersion or SystemInfo"); - } - } else { - warn!("Failed to stringify HeaderValues"); - } - } else { - warn!("Missing version and/or system info header"); + if let Some((version, system)) = parse_version_headers(version, info) { + error!("OWN VERSION: {}", self.own_info.version); + error!("OWN SYSTEM: {}", self.own_info.system); + error!("CLIENT VERSION: {}", version); + error!("CLIENT SYSTEM: {}", system); + *self.remote_info.write().unwrap() = Some(ComponentInfo { version, system }); } let mut response = service.call(request).await?; From 307f98a8ecc8720d93d8a3dbbdda6461e8b12f21 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 10:02:05 +0200 Subject: [PATCH 022/100] refactor parse_version_headers function --- crates/defguard_version/src/lib.rs | 42 ++++++++++++++++++------------ 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 4c4624a72a..15a1e54f93 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -122,21 +122,29 @@ pub(crate) fn parse_version_headers( version: Option<&HeaderValue>, info: Option<&HeaderValue>, ) -> Option<(Version, SystemInfo)> { - if let (Some(version), Some(system)) = (version, info) { - if let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) { - if let (Ok(version), Ok(system)) = ( - Version::from_str(version), - SystemInfo::try_from_header_value(system), - ) { - return Some((version, system)); - } else { - warn!("Failed to parse SemanticVersion or SystemInfo"); - } - } else { - warn!("Failed to stringify HeaderValues"); - } - } else { - warn!("Missing version and/or system info header"); - } - None + let Some(version) = version else { + warn!("Missing version header"); + return None; + }; + let Some(info) = info else { + warn!("Missing system info header"); + return None; + }; + + let (Ok(version), Ok(info)) = (version.to_str(), info.to_str()) else { + warn!("Failed to stringify version or system info header value"); + return None; + }; + + let Ok(version) = Version::from_str(version) else { + warn!("Failed to parse version: {version}"); + return None; + }; + + let Ok(info) = SystemInfo::try_from_header_value(info) else { + warn!("Failed to parse system info: {info}"); + return None; + }; + + Some((version, info)) } From 766a73a01e6761e6281cc4399efde7588e6fd461 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 10:10:42 +0200 Subject: [PATCH 023/100] rename DefguardVersionMiddleware -> DefguardVersionServerMiddleware --- crates/defguard_core/src/grpc/mod.rs | 4 ++-- crates/defguard_version/src/client.rs | 2 +- crates/defguard_version/src/server.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 58947d8b64..08eaec4012 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,5 +1,5 @@ use chrono::{NaiveDateTime, Utc}; -use defguard_version::{DefguardVersionSet, server::DefguardVersionMiddleware}; +use defguard_version::{DefguardVersionSet, server::DefguardVersionServerMiddleware}; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; use serde::Serialize; @@ -904,7 +904,7 @@ pub async fn run_grpc_server( #[cfg(feature = "wireguard")] let router = router.add_service(MiddlewareFor::new( gateway_service, - DefguardVersionMiddleware::new( + DefguardVersionServerMiddleware::new( version_set.own.clone(), Arc::clone(&version_set.gateway), ), diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 39bbe25316..ef380421d4 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -7,7 +7,7 @@ use std::{ }; use tonic::body::BoxBody; use tower::{Layer, Service}; -use tracing::{error}; +use tracing::error; use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER, parse_version_headers}; diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 5c03dec947..e7d7ef9d88 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -13,12 +13,12 @@ use tracing::error; use crate::{parse_version_headers, ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; #[derive(Clone)] -pub struct DefguardVersionMiddleware { +pub struct DefguardVersionServerMiddleware { own_info: ComponentInfo, remote_info: Arc>>, } -impl DefguardVersionMiddleware { +impl DefguardVersionServerMiddleware { pub fn new(own_info: ComponentInfo, remote_info: Arc>>) -> Self { Self { own_info, @@ -28,7 +28,7 @@ impl DefguardVersionMiddleware { } #[async_trait] -impl Middleware for DefguardVersionMiddleware +impl Middleware for DefguardVersionServerMiddleware where S: ServiceBound, S::Future: Send, From c9fce4b3a683f54b60f6f3e4d0ae9a309f942ad9 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 10:56:19 +0200 Subject: [PATCH 024/100] versioned proxy communication --- Cargo.lock | 1 + crates/defguard/src/main.rs | 4 ++-- crates/defguard_core/Cargo.toml | 1 + crates/defguard_core/src/grpc/mod.rs | 15 +++++++++++++-- crates/defguard_version/src/server.rs | 6 ++---- crates/defguard_version/src/tracing.rs | 12 ++++++------ 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cb7dadc5f..42c8a01ff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1140,6 +1140,7 @@ dependencies = [ "tonic-health", "tonic-middleware", "totp-lite", + "tower 0.5.2", "tower-http", "tracing", "tracing-subscriber", diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index af523c44c5..7f4ac7e550 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -142,8 +142,8 @@ async fn main() -> Result<(), anyhow::Error> { // run services tokio::select! { - res = run_grpc_bidi_stream(pool.clone(), wireguard_tx.clone(), mail_tx.clone(), bidi_event_tx), if config.proxy_url.is_some() => error!("Proxy gRPC stream returned early: {res:?}"), - res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx, Arc::clone(&version_set)) => error!("gRPC server returned early: {res:?}"), + res = run_grpc_bidi_stream(pool.clone(), wireguard_tx.clone(), mail_tx.clone(), bidi_event_tx, Arc::clone(&version_set)), if config.proxy_url.is_some() => error!("Proxy gRPC stream returned early: {res:?}"), + res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx, version_set) => error!("gRPC server returned early: {res:?}"), res = run_web_server(worker_state, gateway_state, webhook_tx, webhook_rx, wireguard_tx.clone(), mail_tx.clone(), pool.clone(), failed_logins, api_event_tx) => error!("Web server returned early: {res:?}"), res = run_mail_handler(mail_rx) => error!("Mail handler returned early: {res:?}"), res = run_periodic_peer_disconnect(pool.clone(), wireguard_tx.clone(), internal_event_tx.clone()) => error!("Periodic peer disconnect task returned early: {res:?}"), diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index bc362c2ad4..81f3c814b4 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -86,6 +86,7 @@ strum_macros = { workspace = true } bytes = { workspace = true } ed25519-dalek = "2.2.0" tonic-middleware = "0.2" +tower = "0.5.2" # https://github.com/juhaku/utoipa/issues/1345 [dependencies.zip] diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 08eaec4012..9ee321692a 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,5 +1,7 @@ use chrono::{NaiveDateTime, Utc}; -use defguard_version::{DefguardVersionSet, server::DefguardVersionServerMiddleware}; +use defguard_version::{ + DefguardVersionSet, client::DefguardVersionClientLayer, server::DefguardVersionServerMiddleware, +}; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; use serde::Serialize; @@ -30,6 +32,7 @@ use tonic::{ transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, }; use tonic_middleware::MiddlewareFor; +use tower::ServiceBuilder; use utoipa::ToSchema; use uuid::Uuid; @@ -476,6 +479,7 @@ pub async fn run_grpc_bidi_stream( wireguard_tx: Sender, mail_tx: UnboundedSender, bidi_event_tx: UnboundedSender, + version_set: Arc, ) -> Result<(), anyhow::Error> { let config = server_config(); @@ -507,7 +511,14 @@ pub async fn run_grpc_bidi_stream( loop { debug!("Connecting to proxy at {}", endpoint.uri()); - let mut client = ProxyClient::new(endpoint.connect_lazy()); + let version_layer = DefguardVersionClientLayer::new( + version_set.own.clone(), + Arc::clone(&version_set.proxy), + ); + let channel = ServiceBuilder::new() + .layer(version_layer) + .service(endpoint.connect_lazy()); + let mut client = ProxyClient::new(channel); let (tx, rx) = mpsc::unbounded_channel(); let Ok(response) = client.bidi(UnboundedReceiverStream::new(rx)).await else { error!( diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index e7d7ef9d88..e5d55847e1 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,6 +1,4 @@ -use std::{ - sync::{Arc, RwLock}, -}; +use std::sync::{Arc, RwLock}; use tonic::{ async_trait, @@ -10,7 +8,7 @@ use tonic::{ use tonic_middleware::{Middleware, ServiceBound}; use tracing::error; -use crate::{parse_version_headers, ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER, parse_version_headers}; #[derive(Clone)] pub struct DefguardVersionServerMiddleware { diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index df0e34b87e..432ed3e7d8 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -140,12 +140,12 @@ pub fn init( log_level: &str, always_version_spans: &[&str], ) -> Arc { - let version_set = Arc::new(DefguardVersionSet { - own: ComponentInfo::try_from(version).expect("Failed to parse version: {version}"), - core: Arc::new(RwLock::new(None)), - proxy: Arc::new(RwLock::new(None)), - gateway: Arc::new(RwLock::new(None)), - }); + let version_set = Arc::new(DefguardVersionSet { + own: ComponentInfo::try_from(version).expect("Failed to parse version: {version}"), + core: Arc::new(RwLock::new(None)), + proxy: Arc::new(RwLock::new(None)), + gateway: Arc::new(RwLock::new(None)), + }); let spans: HashSet = always_version_spans .iter() From 771dfe62c69bc96b0c290edd36de41e6ae6f7ada Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 11:21:53 +0200 Subject: [PATCH 025/100] don't version defguard binary crate --- Cargo.lock | 2 +- crates/defguard/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42c8a01ff8..4f6f8d87c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1059,7 +1059,7 @@ dependencies = [ [[package]] name = "defguard" -version = "1.5.0" +version = "0.0.0" dependencies = [ "anyhow", "bytes", diff --git a/crates/defguard/Cargo.toml b/crates/defguard/Cargo.toml index 5ba6ccc31f..87a445f7f9 100644 --- a/crates/defguard/Cargo.toml +++ b/crates/defguard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard" -version = "1.5.0" +version = "0.0.0" edition.workspace = true license-file.workspace = true homepage.workspace = true From 05adc84aee2f18216c8f346d6e797effdccaddea Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 13:15:25 +0200 Subject: [PATCH 026/100] remove version-set struct, remove tracing --- crates/defguard/src/main.rs | 26 +- crates/defguard_core/src/grpc/mod.rs | 19 +- crates/defguard_version/src/client.rs | 40 +--- crates/defguard_version/src/lib.rs | 39 ++- crates/defguard_version/src/server.rs | 36 +-- crates/defguard_version/src/tracing.rs | 314 ++++++++++++------------- 6 files changed, 218 insertions(+), 256 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 7f4ac7e550..349c48005d 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -1,8 +1,3 @@ -use std::{ - fs::read_to_string, - sync::{Arc, Mutex}, -}; - use bytes::Bytes; use defguard_core::{ SERVER_CONFIG, VERSION, @@ -29,7 +24,12 @@ use defguard_core::{ use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; use secrecy::ExposeSecret; +use std::{ + fs::read_to_string, + sync::{Arc, Mutex}, +}; use tokio::sync::{broadcast, mpsc::unbounded_channel}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[macro_use] extern crate tracing; @@ -43,11 +43,13 @@ async fn main() -> Result<(), anyhow::Error> { SERVER_CONFIG.set(config.clone())?; // initialize tracing - let version_set = defguard_version::tracing::init( - VERSION, - &config.log_level, - &["run_grpc_bidi_stream", "run_grpc_server"], - ); + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| format!("{},h2=info", config.log_level).into()), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); info!("Starting ... version v{}", VERSION); debug!("Using config: {config:?}"); @@ -142,8 +144,8 @@ async fn main() -> Result<(), anyhow::Error> { // run services tokio::select! { - res = run_grpc_bidi_stream(pool.clone(), wireguard_tx.clone(), mail_tx.clone(), bidi_event_tx, Arc::clone(&version_set)), if config.proxy_url.is_some() => error!("Proxy gRPC stream returned early: {res:?}"), - res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx, version_set) => error!("gRPC server returned early: {res:?}"), + res = run_grpc_bidi_stream(pool.clone(), wireguard_tx.clone(), mail_tx.clone(), bidi_event_tx), if config.proxy_url.is_some() => error!("Proxy gRPC stream returned early: {res:?}"), + res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx) => error!("gRPC server returned early: {res:?}"), res = run_web_server(worker_state, gateway_state, webhook_tx, webhook_rx, wireguard_tx.clone(), mail_tx.clone(), pool.clone(), failed_logins, api_event_tx) => error!("Web server returned early: {res:?}"), res = run_mail_handler(mail_rx) => error!("Mail handler returned early: {res:?}"), res = run_periodic_peer_disconnect(pool.clone(), wireguard_tx.clone(), internal_event_tx.clone()) => error!("Periodic peer disconnect task returned early: {res:?}"), diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 9ee321692a..146e66199f 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,6 +1,6 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - DefguardVersionSet, client::DefguardVersionClientLayer, server::DefguardVersionServerMiddleware, + client::DefguardVersionClientLayer, server::DefguardVersionServerMiddleware, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -50,9 +50,8 @@ use self::{ interceptor::JwtInterceptor, proto::worker::worker_service_server::WorkerServiceServer, worker::WorkerServer, }; -#[cfg(feature = "worker")] -use crate::{auth::ClaimsType, db::GatewayEvent}; use crate::{ + VERSION, auth::failed_login::FailedLoginMap, db::{ AppEvent, Id, Settings, @@ -71,6 +70,8 @@ use crate::{ mail::Mail, server_config, }; +#[cfg(feature = "worker")] +use crate::{auth::ClaimsType, db::GatewayEvent}; mod auth; pub(crate) mod client_mfa; @@ -479,7 +480,6 @@ pub async fn run_grpc_bidi_stream( wireguard_tx: Sender, mail_tx: UnboundedSender, bidi_event_tx: UnboundedSender, - version_set: Arc, ) -> Result<(), anyhow::Error> { let config = server_config(); @@ -511,10 +511,7 @@ pub async fn run_grpc_bidi_stream( loop { debug!("Connecting to proxy at {}", endpoint.uri()); - let version_layer = DefguardVersionClientLayer::new( - version_set.own.clone(), - Arc::clone(&version_set.proxy), - ); + let version_layer = DefguardVersionClientLayer::from_str(VERSION)?; let channel = ServiceBuilder::new() .layer(version_layer) .service(endpoint.connect_lazy()); @@ -872,7 +869,6 @@ pub async fn run_grpc_server( grpc_key: Option, failed_logins: Arc>, grpc_event_tx: UnboundedSender, - version_set: Arc, ) -> Result<(), anyhow::Error> { // Build gRPC services let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); @@ -915,10 +911,7 @@ pub async fn run_grpc_server( #[cfg(feature = "wireguard")] let router = router.add_service(MiddlewareFor::new( gateway_service, - DefguardVersionServerMiddleware::new( - version_set.own.clone(), - Arc::clone(&version_set.gateway), - ), + DefguardVersionServerMiddleware::from_str(VERSION)?, )); #[cfg(feature = "worker")] let router = router.add_service(worker_service); diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index ef380421d4..f9698a0da0 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -9,23 +9,21 @@ use tonic::body::BoxBody; use tower::{Layer, Service}; use tracing::error; -use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER, parse_version_headers}; +use crate::{parse_version_headers, ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; /// Layer for adding version information to outgoing gRPC requests (client-side) #[derive(Clone)] pub struct DefguardVersionClientLayer { - own_info: ComponentInfo, - remote_info: Arc>>, + component_info: ComponentInfo, } impl DefguardVersionClientLayer { - pub fn new(own_info: ComponentInfo, remote_info: Arc>>) -> Self { - Self { - own_info, - remote_info, - } + pub fn from_str(version: &str) -> Result { + Ok(Self { + component_info: ComponentInfo::from_str(version)?, + }) } } @@ -35,8 +33,7 @@ impl Layer for DefguardVersionClientLayer { fn layer(&self, inner: S) -> Self::Service { DefguardVersionClientService { inner, - own_info: self.own_info.clone(), - remote_info: Arc::clone(&self.remote_info), + component_info: self.component_info.clone(), } } } @@ -45,8 +42,7 @@ impl Layer for DefguardVersionClientLayer { #[derive(Clone)] pub struct DefguardVersionClientService { inner: S, - own_info: ComponentInfo, - remote_info: Arc>>, + component_info: ComponentInfo, } impl Service> for DefguardVersionClientService @@ -67,7 +63,7 @@ where // add version and system info headers request.headers_mut().insert( VERSION_HEADER, - self.own_info + self.component_info .version .to_string() .parse() @@ -75,7 +71,7 @@ where ); request.headers_mut().insert( SYSTEM_INFO_HEADER, - self.own_info + self.component_info .system .as_header_value() .parse() @@ -84,24 +80,8 @@ where // send the request let response_future = self.inner.call(request); - - // handle response - let remote_info = Arc::clone(&self.remote_info); - let own_info = self.own_info.clone(); Box::pin(async move { let response = response_future.await.map_err(Into::into)?; - - // extract version headers - let version = response.headers().get(VERSION_HEADER); - let info = response.headers().get(SYSTEM_INFO_HEADER); - - if let Some((version, system)) = parse_version_headers(version, info) { - error!("OWN VERSION: {}", own_info.version); - error!("OWN SYSTEM: {}", own_info.system); - error!("SERVER VERSION: {}", version); - error!("SERVER SYSTEM: {}", system); - *remote_info.write().unwrap() = Some(ComponentInfo { version, system }); - } Ok(response) }) } diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 15a1e54f93..2698f4298a 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -4,7 +4,6 @@ use semver::Version; use std::{ fmt::Display, str::FromStr, - sync::{Arc, RwLock}, }; use thiserror::Error; @@ -24,24 +23,24 @@ pub enum DefguardVersionError { SystemInfoParseError(String), } -#[derive(Clone, Debug)] -pub struct DefguardVersionSet { - pub own: ComponentInfo, - pub core: Arc>>, - pub proxy: Arc>>, - pub gateway: Arc>>, -} - -impl DefguardVersionSet { - pub fn try_from(version: &str) -> Result { - Ok(Self { - own: ComponentInfo::try_from(version)?, - core: Arc::new(RwLock::new(None)), - proxy: Arc::new(RwLock::new(None)), - gateway: Arc::new(RwLock::new(None)), - }) - } -} +// #[derive(Clone, Debug)] +// pub struct DefguardVersionSet { +// pub own: ComponentInfo, +// pub core: Arc>>, +// pub proxy: Arc>>, +// pub gateway: Arc>>, +// } + +// impl DefguardVersionSet { +// pub fn try_from(version: &str) -> Result { +// Ok(Self { +// own: ComponentInfo::from_str(version)?, +// core: Arc::new(RwLock::new(None)), +// proxy: Arc::new(RwLock::new(None)), +// gateway: Arc::new(RwLock::new(None)), +// }) +// } +// } #[derive(Debug, Clone)] pub struct SystemInfo { @@ -108,7 +107,7 @@ pub struct ComponentInfo { } impl ComponentInfo { - pub fn try_from(version: &str) -> Result { + pub fn from_str(version: &str) -> Result { let version = Version::from_str(version)?; let info = os_info::get(); Ok(Self { diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index e5d55847e1..10ac241a45 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,27 +1,22 @@ -use std::sync::{Arc, RwLock}; - use tonic::{ async_trait, body::BoxBody, codegen::http::{Request, Response}, }; use tonic_middleware::{Middleware, ServiceBound}; -use tracing::error; -use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER, parse_version_headers}; +use crate::{ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; #[derive(Clone)] pub struct DefguardVersionServerMiddleware { - own_info: ComponentInfo, - remote_info: Arc>>, + component_info: ComponentInfo, } impl DefguardVersionServerMiddleware { - pub fn new(own_info: ComponentInfo, remote_info: Arc>>) -> Self { - Self { - own_info, - remote_info, - } + pub fn from_str(version: &str) -> Result { + Ok(Self { + component_info: ComponentInfo::from_str(version)?, + }) } } @@ -36,25 +31,18 @@ where request: Request, mut service: S, ) -> Result, S::Error> { - let version = request.headers().get(VERSION_HEADER); - let info = request.headers().get(SYSTEM_INFO_HEADER); - - if let Some((version, system)) = parse_version_headers(version, info) { - error!("OWN VERSION: {}", self.own_info.version); - error!("OWN SYSTEM: {}", self.own_info.system); - error!("CLIENT VERSION: {}", version); - error!("CLIENT SYSTEM: {}", system); - *self.remote_info.write().unwrap() = Some(ComponentInfo { version, system }); - } - let mut response = service.call(request).await?; response.headers_mut().insert( VERSION_HEADER, - self.own_info.version.to_string().parse().unwrap(), + self.component_info.version.to_string().parse().unwrap(), ); response.headers_mut().insert( SYSTEM_INFO_HEADER, - self.own_info.system.as_header_value().parse().unwrap(), + self.component_info + .system + .as_header_value() + .parse() + .unwrap(), ); Ok(response) } diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 432ed3e7d8..13a149ebbb 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,169 +1,169 @@ -use std::{ - collections::HashSet, - sync::{Arc, RwLock}, -}; +// use std::{ +// collections::HashSet, +// sync::{Arc, RwLock}, +// }; -use tracing_subscriber::{ - fmt::{FormatEvent, FormatFields, format::Writer}, - {layer::SubscriberExt, util::SubscriberInitExt}, -}; +// use tracing_subscriber::{ +// fmt::{FormatEvent, FormatFields, format::Writer}, +// {layer::SubscriberExt, util::SubscriberInitExt}, +// }; -use crate::{ComponentInfo, DefguardVersionSet}; +// use crate::{ComponentInfo, DefguardVersionSet}; -/// Custom tracing formatter that conditionally includes version information in log messages. -/// -/// This formatter wraps the default tracing formatter and adds version prefixes to log messages -/// under specific conditions: -/// - Always includes version info for ERROR level logs -/// - Always includes version info for logs within specified span names (regardless of log level) -/// -/// The version information includes details about the application and connected services, -/// along with comprehensive system information for debugging purposes. -struct VersionPrefixFormat { - /// The underlying tracing formatter - inner: tracing_subscriber::fmt::format::Format, - /// Shared version information of all components - version_set: Arc, - /// Set of span names that should always include version info in their logs - always_version_spans: HashSet, -} +// /// Custom tracing formatter that conditionally includes version information in log messages. +// /// +// /// This formatter wraps the default tracing formatter and adds version prefixes to log messages +// /// under specific conditions: +// /// - Always includes version info for ERROR level logs +// /// - Always includes version info for logs within specified span names (regardless of log level) +// /// +// /// The version information includes details about the application and connected services, +// /// along with comprehensive system information for debugging purposes. +// struct VersionPrefixFormat { +// /// The underlying tracing formatter +// inner: tracing_subscriber::fmt::format::Format, +// /// Shared version information of all components +// version_set: Arc, +// /// Set of span names that should always include version info in their logs +// always_version_spans: HashSet, +// } -impl FormatEvent for VersionPrefixFormat -where - S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, - N: for<'a> FormatFields<'a> + 'static, -{ - /// Formats a tracing event, conditionally adding version information as a prefix. - /// - /// This method checks if version information should be included based on: - /// 1. Log level (always include for ERROR level) - /// 2. Span context (include if any span in the current context matches configured span names) - /// - /// The version prefix format includes comprehensive system information: - /// `[v{version}|{os_type}|{os_version}|{bitness}|{architecture}]` - /// - /// Additional prefixes are added for connected services: - /// - Core service: `[C:v{version}|...]` - /// - Proxy service: `[PX:v{version}|...]` - /// - Gateway service: `[GW:v{version}|...]` - fn format_event( - &self, - ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, - mut writer: Writer<'_>, - event: &tracing::Event<'_>, - ) -> std::fmt::Result { - let is_error_level = *event.metadata().level() == tracing::Level::ERROR; +// impl FormatEvent for VersionPrefixFormat +// where +// S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, +// N: for<'a> FormatFields<'a> + 'static, +// { +// /// Formats a tracing event, conditionally adding version information as a prefix. +// /// +// /// This method checks if version information should be included based on: +// /// 1. Log level (always include for ERROR level) +// /// 2. Span context (include if any span in the current context matches configured span names) +// /// +// /// The version prefix format includes comprehensive system information: +// /// `[v{version}|{os_type}|{os_version}|{bitness}|{architecture}]` +// /// +// /// Additional prefixes are added for connected services: +// /// - Core service: `[C:v{version}|...]` +// /// - Proxy service: `[PX:v{version}|...]` +// /// - Gateway service: `[GW:v{version}|...]` +// fn format_event( +// &self, +// ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, +// mut writer: Writer<'_>, +// event: &tracing::Event<'_>, +// ) -> std::fmt::Result { +// let is_error_level = *event.metadata().level() == tracing::Level::ERROR; - // Check if we're within any of the configured spans - let is_in_version_span = ctx.lookup_current().map_or(false, |span_ref| { - let mut current_span = Some(span_ref); - while let Some(span) = current_span { - let span_name = span.metadata().name(); - if self.always_version_spans.contains(span_name) { - return true; - } - current_span = span.parent(); - } - false - }); +// // Check if we're within any of the configured spans +// let is_in_version_span = ctx.lookup_current().map_or(false, |span_ref| { +// let mut current_span = Some(span_ref); +// while let Some(span) = current_span { +// let span_name = span.metadata().name(); +// if self.always_version_spans.contains(span_name) { +// return true; +// } +// current_span = span.parent(); +// } +// false +// }); - let should_log_version = is_error_level || is_in_version_span; - if should_log_version { - write!( - writer, - "[v{}|{}|{}|{}|{}] ", - self.version_set.own.version, - self.version_set.own.system.os_type, - self.version_set.own.system.os_version, - self.version_set.own.system.bitness, - self.version_set.own.system.architecture, - )?; +// let should_log_version = is_error_level || is_in_version_span; +// if should_log_version { +// write!( +// writer, +// "[v{}|{}|{}|{}|{}] ", +// self.version_set.own.version, +// self.version_set.own.system.os_type, +// self.version_set.own.system.os_version, +// self.version_set.own.system.bitness, +// self.version_set.own.system.architecture, +// )?; - if let Some(ref core) = *self.version_set.core.read().unwrap() { - write!( - writer, - "[C:v{}|{}|{}|{}|{}] ", - core.version, - core.system.os_type, - core.system.os_version, - core.system.bitness, - core.system.architecture, - )?; - } - if let Some(ref proxy) = *self.version_set.proxy.read().unwrap() { - write!( - writer, - "[PX:v{}|{}|{}|{}|{}] ", - proxy.version, - proxy.system.os_type, - proxy.system.os_version, - proxy.system.bitness, - proxy.system.architecture, - )?; - } +// if let Some(ref core) = *self.version_set.core.read().unwrap() { +// write!( +// writer, +// "[C:v{}|{}|{}|{}|{}] ", +// core.version, +// core.system.os_type, +// core.system.os_version, +// core.system.bitness, +// core.system.architecture, +// )?; +// } +// if let Some(ref proxy) = *self.version_set.proxy.read().unwrap() { +// write!( +// writer, +// "[PX:v{}|{}|{}|{}|{}] ", +// proxy.version, +// proxy.system.os_type, +// proxy.system.os_version, +// proxy.system.bitness, +// proxy.system.architecture, +// )?; +// } - if let Some(ref gateway) = *self.version_set.gateway.read().unwrap() { - write!( - writer, - "[GW:v{}|{}|{}|{}|{}] ", - gateway.version, - gateway.system.os_type, - gateway.system.os_version, - gateway.system.bitness, - gateway.system.architecture, - )?; - } - } +// if let Some(ref gateway) = *self.version_set.gateway.read().unwrap() { +// write!( +// writer, +// "[GW:v{}|{}|{}|{}|{}] ", +// gateway.version, +// gateway.system.os_type, +// gateway.system.os_version, +// gateway.system.bitness, +// gateway.system.architecture, +// )?; +// } +// } - self.inner.format_event(ctx, writer, event) - } -} +// self.inner.format_event(ctx, writer, event) +// } +// } -/// Initializes tracing with custom formatter displaying own and connected services version. -/// Returns shared VersionInfo object that will be used to display services versions. -/// -/// # Arguments -/// * `version` - The application version -/// * `log_level` - The log level to use -/// * `always_version_spans` - Span names that should always include version info in logs -/// -/// # Examples -/// ``` -/// let version_set = defguard_version::init_tracing( -/// "1.5.0", -/// "info", -/// &["run_grpc_bidi_stream", "enrollment_process"] -/// ); -/// ``` -pub fn init( - version: &str, - log_level: &str, - always_version_spans: &[&str], -) -> Arc { - let version_set = Arc::new(DefguardVersionSet { - own: ComponentInfo::try_from(version).expect("Failed to parse version: {version}"), - core: Arc::new(RwLock::new(None)), - proxy: Arc::new(RwLock::new(None)), - gateway: Arc::new(RwLock::new(None)), - }); +// /// Initializes tracing with custom formatter displaying own and connected services version. +// /// Returns shared VersionInfo object that will be used to display services versions. +// /// +// /// # Arguments +// /// * `version` - The application version +// /// * `log_level` - The log level to use +// /// * `always_version_spans` - Span names that should always include version info in logs +// /// +// /// # Examples +// /// ``` +// /// let version_set = defguard_version::init_tracing( +// /// "1.5.0", +// /// "info", +// /// &["run_grpc_bidi_stream", "enrollment_process"] +// /// ); +// /// ``` +// pub fn init( +// version: &str, +// log_level: &str, +// always_version_spans: &[&str], +// ) -> Arc { +// let version_set = Arc::new(DefguardVersionSet { +// own: ComponentInfo::from_str(version).expect("Failed to parse version: {version}"), +// core: Arc::new(RwLock::new(None)), +// proxy: Arc::new(RwLock::new(None)), +// gateway: Arc::new(RwLock::new(None)), +// }); - let spans: HashSet = always_version_spans - .iter() - .map(|&s| s.to_string()) - .collect(); +// let spans: HashSet = always_version_spans +// .iter() +// .map(|&s| s.to_string()) +// .collect(); - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| format!("{},h2=info", log_level).into()), - ) - .with( - tracing_subscriber::fmt::layer().event_format(VersionPrefixFormat { - inner: tracing_subscriber::fmt::format::Format::default(), - version_set: Arc::clone(&version_set), - always_version_spans: spans, - }), - ) - .init(); - version_set -} +// tracing_subscriber::registry() +// .with( +// tracing_subscriber::EnvFilter::try_from_default_env() +// .unwrap_or_else(|_| format!("{},h2=info", log_level).into()), +// ) +// .with( +// tracing_subscriber::fmt::layer().event_format(VersionPrefixFormat { +// inner: tracing_subscriber::fmt::format::Format::default(), +// version_set: Arc::clone(&version_set), +// always_version_spans: spans, +// }), +// ) +// .init(); +// version_set +// } From 242a6ea32fe097c7b7b59c4f0440f23bcb2981fe Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 8 Aug 2025 13:32:45 +0200 Subject: [PATCH 027/100] version metadata parsing --- crates/defguard_core/src/grpc/mod.rs | 3 ++- crates/defguard_version/src/lib.rs | 31 ++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 146e66199f..ad61b9bfb3 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,6 +1,6 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - client::DefguardVersionClientLayer, server::DefguardVersionServerMiddleware, + parse_metadata, client::DefguardVersionClientLayer, server::DefguardVersionServerMiddleware, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -525,6 +525,7 @@ pub async fn run_grpc_bidi_stream( sleep(TEN_SECS).await; continue; }; + let (version, info) = parse_metadata(response.metadata()).unwrap(); info!("Connected to proxy at {}", endpoint.uri()); let mut resp_stream = response.into_inner(); 'message: loop { diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 2698f4298a..a08a661e8f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,11 +1,9 @@ use ::tracing::{error, warn}; use http::HeaderValue; use semver::Version; -use std::{ - fmt::Display, - str::FromStr, -}; +use std::{fmt::Display, str::FromStr}; use thiserror::Error; +use tonic::metadata::MetadataMap; pub mod client; pub mod server; @@ -147,3 +145,28 @@ pub(crate) fn parse_version_headers( Some((version, info)) } + +pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { + let Some(version) = metadata.get(VERSION_HEADER) else { + warn!("Missing version header"); + return None; + }; + let Some(info) = metadata.get(SYSTEM_INFO_HEADER) else { + warn!("Missing system info header"); + return None; + }; + let (Ok(version), Ok(info)) = (version.to_str(), info.to_str()) else { + warn!("Failed to stringify version or system info header value"); + return None; + }; + let Ok(version) = Version::from_str(version) else { + warn!("Failed to parse version: {version}"); + return None; + }; + let Ok(info) = SystemInfo::try_from_header_value(info) else { + warn!("Failed to parse system info: {info}"); + return None; + }; + + Some((version, info)) +} From 7d399f99d4aaf1f83c9151f7a6f33e1c40fe4972 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 08:21:58 +0200 Subject: [PATCH 028/100] span-based version logging --- crates/defguard_core/src/grpc/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index ad61b9bfb3..ae54ba22ab 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -526,6 +526,14 @@ pub async fn run_grpc_bidi_stream( continue; }; let (version, info) = parse_metadata(response.metadata()).unwrap(); + + let span = tracing::error_span!( + "proxy_connection", + core_version = %VERSION, + proxy_version = %version, + ); + let _guard = span.enter(); + info!("Connected to proxy at {}", endpoint.uri()); let mut resp_stream = response.into_inner(); 'message: loop { From a4d6f91ec6aa7619c5cbbf25f50741ec7a2e8315 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 09:38:24 +0200 Subject: [PATCH 029/100] move proxy message loop to separate, instrumented function --- crates/defguard_core/src/grpc/mod.rs | 712 ++++++++++++++------------- 1 file changed, 372 insertions(+), 340 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index ae54ba22ab..bc691239d1 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,9 +1,10 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - parse_metadata, client::DefguardVersionClientLayer, server::DefguardVersionServerMiddleware, + client::DefguardVersionClientLayer, parse_metadata, server::DefguardVersionServerMiddleware, SystemInfo, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; +use semver::Version; use serde::Serialize; #[cfg(feature = "worker")] use sqlx::PgPool; @@ -28,7 +29,7 @@ use tokio::{ use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::sync::CancellationToken; use tonic::{ - Code, Status, + Code, Status, Streaming, transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, }; use tonic_middleware::MiddlewareFor; @@ -109,7 +110,7 @@ pub mod proto { } use proto::proxy::{ - AuthCallbackResponse, AuthInfoResponse, CoreError, CoreResponse, core_request, + AuthCallbackResponse, AuthInfoResponse, CoreError, CoreRequest, CoreResponse, core_request, proxy_client::ProxyClient, }; @@ -473,6 +474,358 @@ impl From for CoreError { } } +#[instrument( + name = "proxy_message_loop", + skip_all, + fields( + core_version = %VERSION, + proxy_version = %proxy_version, + proxy_info = %proxy_info, + ) +)] +async fn handle_message_loop( + proxy_version: &Version, + proxy_info: &SystemInfo, + pool: PgPool, + tx: UnboundedSender, + wireguard_tx: Sender, + resp_stream: &mut Streaming, + enrollment_server: &mut EnrollmentServer, + password_reset_server: &mut PasswordResetServer, + client_mfa_server: &mut ClientMfaServer, + polling_server: &mut PollingServer, +) -> Result<(), anyhow::Error> { + 'message: loop { + let pool = pool.clone(); + match resp_stream.message().await { + Ok(None) => { + info!("stream was closed by the sender"); + break 'message; + } + Ok(Some(received)) => { + info!("Received message from proxy."); + debug!("Received the following message from proxy: {received:?}"); + let payload = match received.payload { + // rpc RegisterMobileAuth (RegisterMobileAuthRequest) return (google.protobuf.Empty) + Some(core_request::Payload::RegisterMobileAuth(request)) => { + match enrollment_server.register_mobile_auth(request).await { + Ok(()) => Some(core_response::Payload::Empty(())), + Err(err) => { + error!("Register mobile auth error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc StartEnrollment (EnrollmentStartRequest) returns (EnrollmentStartResponse) + Some(core_request::Payload::EnrollmentStart(request)) => { + match enrollment_server + .start_enrollment(request, received.device_info) + .await + { + Ok(response_payload) => { + Some(core_response::Payload::EnrollmentStart(response_payload)) + } + Err(err) => { + error!("start enrollment error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc ActivateUser (ActivateUserRequest) returns (google.protobuf.Empty) + Some(core_request::Payload::ActivateUser(request)) => { + match enrollment_server + .activate_user(request, received.device_info) + .await + { + Ok(()) => Some(core_response::Payload::Empty(())), + Err(err) => { + error!("activate user error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc CreateDevice (NewDevice) returns (DeviceConfigResponse) + Some(core_request::Payload::NewDevice(request)) => { + match enrollment_server + .create_device(request, received.device_info) + .await + { + Ok(response_payload) => { + Some(core_response::Payload::DeviceConfig(response_payload)) + } + Err(err) => { + error!("create device error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc GetNetworkInfo (ExistingDevice) returns (DeviceConfigResponse) + Some(core_request::Payload::ExistingDevice(request)) => { + match enrollment_server.get_network_info(request).await { + Ok(response_payload) => { + Some(core_response::Payload::DeviceConfig(response_payload)) + } + Err(err) => { + error!("get network info error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc RequestPasswordReset (PasswordResetInitializeRequest) returns (google.protobuf.Empty) + Some(core_request::Payload::PasswordResetInit(request)) => { + match password_reset_server + .request_password_reset(request, received.device_info) + .await + { + Ok(()) => Some(core_response::Payload::Empty(())), + Err(err) => { + error!("password reset init error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc StartPasswordReset (PasswordResetStartRequest) returns (PasswordResetStartResponse) + Some(core_request::Payload::PasswordResetStart(request)) => { + match password_reset_server + .start_password_reset(request, received.device_info) + .await + { + Ok(response_payload) => { + Some(core_response::Payload::PasswordResetStart(response_payload)) + } + Err(err) => { + error!("password reset start error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc ResetPassword (PasswordResetRequest) returns (google.protobuf.Empty) + Some(core_request::Payload::PasswordReset(request)) => { + match password_reset_server + .reset_password(request, received.device_info) + .await + { + Ok(()) => Some(core_response::Payload::Empty(())), + Err(err) => { + error!("password reset error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc ClientMfaStart (ClientMfaStartRequest) returns (ClientMfaStartResponse) + Some(core_request::Payload::ClientMfaStart(request)) => { + match client_mfa_server.start_client_mfa_login(request).await { + Ok(response_payload) => { + Some(core_response::Payload::ClientMfaStart(response_payload)) + } + Err(err) => { + error!("client MFA start error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc ClientMfaFinish (ClientMfaFinishRequest) returns (ClientMfaFinishResponse) + Some(core_request::Payload::ClientMfaFinish(request)) => { + match client_mfa_server + .finish_client_mfa_login(request, received.device_info) + .await + { + Ok(response_payload) => { + Some(core_response::Payload::ClientMfaFinish(response_payload)) + } + Err(err) => { + match err.code() { + Code::FailedPrecondition => { + // User not yet done with OIDC authentication. Don't log it as an error. + debug!("Client MFA finish error: {err}"); + } + _ => { + // Log other errors as errors. + error!("Client MFA finish error: {err}"); + } + } + Some(core_response::Payload::CoreError(err.into())) + } + } + } + Some(core_request::Payload::ClientMfaOidcAuthenticate(request)) => { + match client_mfa_server + .auth_mfa_session_with_oidc(request, received.device_info) + .await + { + Ok(()) => Some(core_response::Payload::Empty(())), + Err(err) => { + error!("client MFA OIDC authenticate error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + // rpc LocationInfo (LocationInfoRequest) returns (LocationInfoResponse) + Some(core_request::Payload::InstanceInfo(request)) => { + match polling_server.info(request).await { + Ok(response_payload) => { + Some(core_response::Payload::InstanceInfo(response_payload)) + } + Err(err) => { + if Code::FailedPrecondition == err.code() { + // Ignore the case when we are not enterprise but the client is trying to fetch the instance config, + // to avoid spamming the logs with misleading errors. + + debug!( + "A client tried to fetch the instance config, but we are not enterprise." + ); + Some(core_response::Payload::CoreError(err.into())) + } else { + error!("Instance info error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } + } + Some(core_request::Payload::AuthInfo(request)) => { + if !is_enterprise_enabled() { + warn!("Enterprise license required"); + Some(core_response::Payload::CoreError(CoreError { + status_code: Code::FailedPrecondition as i32, + message: "no valid license".into(), + })) + } else if let Ok(redirect_url) = Url::parse(&request.redirect_url) { + if let Some(provider) = OpenIdProvider::get_current(&pool).await? { + if let Ok((_client_id, client)) = + make_oidc_client(redirect_url, &provider).await + { + let (url, csrf_token, nonce) = client + .authorize_url( + CoreAuthenticationFlow::AuthorizationCode, + || build_state(request.state), + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("profile".to_string())) + .url(); + Some(core_response::Payload::AuthInfo(AuthInfoResponse { + url: url.into(), + csrf_token: csrf_token.secret().to_owned(), + nonce: nonce.secret().to_owned(), + button_display_name: provider.display_name, + })) + } else { + Some(core_response::Payload::CoreError(CoreError { + status_code: Code::Internal as i32, + message: "failed to build OIDC client".into(), + })) + } + } else { + error!("Failed to get current OpenID provider"); + Some(core_response::Payload::CoreError(CoreError { + status_code: Code::Internal as i32, + message: "failed to get current OpenID provider".into(), + })) + } + } else { + Some(core_response::Payload::CoreError(CoreError { + status_code: Code::Internal as i32, + message: "invalid redirect URL".into(), + })) + } + } + Some(core_request::Payload::AuthCallback(request)) => { + match Url::parse(&request.callback_url) { + Ok(callback_url) => { + let code = AuthorizationCode::new(request.code); + match user_from_claims( + &pool, + Nonce::new(request.nonce), + code, + callback_url, + ) + .await + { + Ok(mut user) => { + user.clear_unused_enrollment_tokens(&pool).await?; + if let Err(err) = sync_user_groups_if_configured( + &user, + &pool, + &wireguard_tx, + ) + .await + { + error!( + "Failed to sync user groups for user {} with the directory while the user was logging in through an external provider: {err:?}", + user.username, + ); + } else { + ldap_update_user_state(&mut user, &pool).await; + } + debug!("Cleared unused tokens for {}.", user.username); + debug!( + "Creating a new desktop activation token for user {} as a result of proxy OpenID auth callback.", + user.username + ); + let config = server_config(); + let desktop_configuration = Token::new( + user.id, + Some(user.id), + Some(user.email), + config.enrollment_token_timeout.as_secs(), + Some(ENROLLMENT_TOKEN_TYPE.to_string()), + ); + debug!("Saving a new desktop configuration token..."); + desktop_configuration.save(&pool).await?; + debug!( + "Saved desktop configuration token. Responding to proxy with the token." + ); + + Some(core_response::Payload::AuthCallback( + AuthCallbackResponse { + url: config.enrollment_url.clone().into(), + token: desktop_configuration.id, + }, + )) + } + Err(err) => { + let message = format!("OpenID auth error {err}"); + error!(message); + Some(core_response::Payload::CoreError(CoreError { + status_code: Code::Internal as i32, + message, + })) + } + } + } + Err(err) => { + error!( + "Proxy requested an OpenID authentication info for a callback URL ({}) that couldn't be parsed. Details: {err}", + request.callback_url + ); + Some(core_response::Payload::CoreError(CoreError { + status_code: Code::Internal as i32, + message: "invalid callback URL".into(), + })) + } + } + } + // Reply without payload. + None => None, + }; + let req = CoreResponse { + id: received.id, + payload, + }; + tx.send(req).unwrap(); + } + Err(err) => { + // error!("Disconnected from proxy at {}", endpoint.uri()); + error!("stream error: {err}"); + debug!("waiting 10s to re-establish the connection"); + sleep(TEN_SECS).await; + break 'message; + } + } + } + Ok(()) +} + /// Bi-directional gRPC stream for communication with Defguard proxy. #[instrument(skip_all)] pub async fn run_grpc_bidi_stream( @@ -484,17 +837,17 @@ pub async fn run_grpc_bidi_stream( let config = server_config(); // TODO: merge the two - let enrollment_server = EnrollmentServer::new( + let mut enrollment_server = EnrollmentServer::new( pool.clone(), wireguard_tx.clone(), mail_tx.clone(), bidi_event_tx.clone(), ); - let password_reset_server = + let mut password_reset_server = PasswordResetServer::new(pool.clone(), mail_tx.clone(), bidi_event_tx.clone()); let mut client_mfa_server = ClientMfaServer::new(pool.clone(), mail_tx, wireguard_tx.clone(), bidi_event_tx); - let polling_server = PollingServer::new(pool.clone()); + let mut polling_server = PollingServer::new(pool.clone()); let endpoint = Endpoint::from_shared(config.proxy_url.as_deref().unwrap())?; let endpoint = endpoint @@ -527,342 +880,21 @@ pub async fn run_grpc_bidi_stream( }; let (version, info) = parse_metadata(response.metadata()).unwrap(); - let span = tracing::error_span!( - "proxy_connection", - core_version = %VERSION, - proxy_version = %version, - ); - let _guard = span.enter(); - info!("Connected to proxy at {}", endpoint.uri()); let mut resp_stream = response.into_inner(); - 'message: loop { - match resp_stream.message().await { - Ok(None) => { - info!("stream was closed by the sender"); - break 'message; - } - Ok(Some(received)) => { - info!("Received message from proxy."); - debug!("Received the following message from proxy: {received:?}"); - let payload = match received.payload { - // rpc RegisterMobileAuth (RegisterMobileAuthRequest) return (google.protobuf.Empty) - Some(core_request::Payload::RegisterMobileAuth(request)) => { - match enrollment_server.register_mobile_auth(request).await { - Ok(()) => Some(core_response::Payload::Empty(())), - Err(err) => { - error!("Register mobile auth error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc StartEnrollment (EnrollmentStartRequest) returns (EnrollmentStartResponse) - Some(core_request::Payload::EnrollmentStart(request)) => { - match enrollment_server - .start_enrollment(request, received.device_info) - .await - { - Ok(response_payload) => { - Some(core_response::Payload::EnrollmentStart(response_payload)) - } - Err(err) => { - error!("start enrollment error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc ActivateUser (ActivateUserRequest) returns (google.protobuf.Empty) - Some(core_request::Payload::ActivateUser(request)) => { - match enrollment_server - .activate_user(request, received.device_info) - .await - { - Ok(()) => Some(core_response::Payload::Empty(())), - Err(err) => { - error!("activate user error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc CreateDevice (NewDevice) returns (DeviceConfigResponse) - Some(core_request::Payload::NewDevice(request)) => { - match enrollment_server - .create_device(request, received.device_info) - .await - { - Ok(response_payload) => { - Some(core_response::Payload::DeviceConfig(response_payload)) - } - Err(err) => { - error!("create device error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc GetNetworkInfo (ExistingDevice) returns (DeviceConfigResponse) - Some(core_request::Payload::ExistingDevice(request)) => { - match enrollment_server.get_network_info(request).await { - Ok(response_payload) => { - Some(core_response::Payload::DeviceConfig(response_payload)) - } - Err(err) => { - error!("get network info error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc RequestPasswordReset (PasswordResetInitializeRequest) returns (google.protobuf.Empty) - Some(core_request::Payload::PasswordResetInit(request)) => { - match password_reset_server - .request_password_reset(request, received.device_info) - .await - { - Ok(()) => Some(core_response::Payload::Empty(())), - Err(err) => { - error!("password reset init error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc StartPasswordReset (PasswordResetStartRequest) returns (PasswordResetStartResponse) - Some(core_request::Payload::PasswordResetStart(request)) => { - match password_reset_server - .start_password_reset(request, received.device_info) - .await - { - Ok(response_payload) => Some( - core_response::Payload::PasswordResetStart(response_payload), - ), - Err(err) => { - error!("password reset start error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc ResetPassword (PasswordResetRequest) returns (google.protobuf.Empty) - Some(core_request::Payload::PasswordReset(request)) => { - match password_reset_server - .reset_password(request, received.device_info) - .await - { - Ok(()) => Some(core_response::Payload::Empty(())), - Err(err) => { - error!("password reset error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc ClientMfaStart (ClientMfaStartRequest) returns (ClientMfaStartResponse) - Some(core_request::Payload::ClientMfaStart(request)) => { - match client_mfa_server.start_client_mfa_login(request).await { - Ok(response_payload) => { - Some(core_response::Payload::ClientMfaStart(response_payload)) - } - Err(err) => { - error!("client MFA start error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc ClientMfaFinish (ClientMfaFinishRequest) returns (ClientMfaFinishResponse) - Some(core_request::Payload::ClientMfaFinish(request)) => { - match client_mfa_server - .finish_client_mfa_login(request, received.device_info) - .await - { - Ok(response_payload) => { - Some(core_response::Payload::ClientMfaFinish(response_payload)) - } - Err(err) => { - match err.code() { - Code::FailedPrecondition => { - // User not yet done with OIDC authentication. Don't log it as an error. - debug!("Client MFA finish error: {err}"); - } - _ => { - // Log other errors as errors. - error!("Client MFA finish error: {err}"); - } - } - Some(core_response::Payload::CoreError(err.into())) - } - } - } - Some(core_request::Payload::ClientMfaOidcAuthenticate(request)) => { - match client_mfa_server - .auth_mfa_session_with_oidc(request, received.device_info) - .await - { - Ok(()) => Some(core_response::Payload::Empty(())), - Err(err) => { - error!("client MFA OIDC authenticate error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - // rpc LocationInfo (LocationInfoRequest) returns (LocationInfoResponse) - Some(core_request::Payload::InstanceInfo(request)) => { - match polling_server.info(request).await { - Ok(response_payload) => { - Some(core_response::Payload::InstanceInfo(response_payload)) - } - Err(err) => { - if Code::FailedPrecondition == err.code() { - // Ignore the case when we are not enterprise but the client is trying to fetch the instance config, - // to avoid spamming the logs with misleading errors. - - debug!( - "A client tried to fetch the instance config, but we are not enterprise." - ); - Some(core_response::Payload::CoreError(err.into())) - } else { - error!("Instance info error {err}"); - Some(core_response::Payload::CoreError(err.into())) - } - } - } - } - Some(core_request::Payload::AuthInfo(request)) => { - if !is_enterprise_enabled() { - warn!("Enterprise license required"); - Some(core_response::Payload::CoreError(CoreError { - status_code: Code::FailedPrecondition as i32, - message: "no valid license".into(), - })) - } else if let Ok(redirect_url) = Url::parse(&request.redirect_url) { - if let Some(provider) = OpenIdProvider::get_current(&pool).await? { - if let Ok((_client_id, client)) = - make_oidc_client(redirect_url, &provider).await - { - let (url, csrf_token, nonce) = client - .authorize_url( - CoreAuthenticationFlow::AuthorizationCode, - || build_state(request.state), - Nonce::new_random, - ) - .add_scope(Scope::new("email".to_string())) - .add_scope(Scope::new("profile".to_string())) - .url(); - Some(core_response::Payload::AuthInfo(AuthInfoResponse { - url: url.into(), - csrf_token: csrf_token.secret().to_owned(), - nonce: nonce.secret().to_owned(), - button_display_name: provider.display_name, - })) - } else { - Some(core_response::Payload::CoreError(CoreError { - status_code: Code::Internal as i32, - message: "failed to build OIDC client".into(), - })) - } - } else { - error!("Failed to get current OpenID provider"); - Some(core_response::Payload::CoreError(CoreError { - status_code: Code::Internal as i32, - message: "failed to get current OpenID provider".into(), - })) - } - } else { - Some(core_response::Payload::CoreError(CoreError { - status_code: Code::Internal as i32, - message: "invalid redirect URL".into(), - })) - } - } - Some(core_request::Payload::AuthCallback(request)) => { - match Url::parse(&request.callback_url) { - Ok(callback_url) => { - let code = AuthorizationCode::new(request.code); - match user_from_claims( - &pool, - Nonce::new(request.nonce), - code, - callback_url, - ) - .await - { - Ok(mut user) => { - user.clear_unused_enrollment_tokens(&pool).await?; - if let Err(err) = sync_user_groups_if_configured( - &user, - &pool, - &wireguard_tx, - ) - .await - { - error!( - "Failed to sync user groups for user {} with the directory while the user was logging in through an external provider: {err:?}", - user.username, - ); - } else { - ldap_update_user_state(&mut user, &pool).await; - } - debug!("Cleared unused tokens for {}.", user.username); - debug!( - "Creating a new desktop activation token for user {} as a result of proxy OpenID auth callback.", - user.username - ); - let config = server_config(); - let desktop_configuration = Token::new( - user.id, - Some(user.id), - Some(user.email), - config.enrollment_token_timeout.as_secs(), - Some(ENROLLMENT_TOKEN_TYPE.to_string()), - ); - debug!("Saving a new desktop configuration token..."); - desktop_configuration.save(&pool).await?; - debug!( - "Saved desktop configuration token. Responding to proxy with the token." - ); - - Some(core_response::Payload::AuthCallback( - AuthCallbackResponse { - url: config.enrollment_url.clone().into(), - token: desktop_configuration.id, - }, - )) - } - Err(err) => { - let message = format!("OpenID auth error {err}"); - error!(message); - Some(core_response::Payload::CoreError(CoreError { - status_code: Code::Internal as i32, - message, - })) - } - } - } - Err(err) => { - error!( - "Proxy requested an OpenID authentication info for a callback URL ({}) that couldn't be parsed. Details: {err}", - request.callback_url - ); - Some(core_response::Payload::CoreError(CoreError { - status_code: Code::Internal as i32, - message: "invalid callback URL".into(), - })) - } - } - } - // Reply without payload. - None => None, - }; - let req = CoreResponse { - id: received.id, - payload, - }; - tx.send(req).unwrap(); - } - Err(err) => { - error!("Disconnected from proxy at {}", endpoint.uri()); - error!("stream error: {err}"); - debug!("waiting 10s to re-establish the connection"); - sleep(TEN_SECS).await; - break 'message; - } - } - } + handle_message_loop( + &version, + &info, + pool.clone(), + tx, + wireguard_tx.clone(), + &mut resp_stream, + &mut enrollment_server, + &mut password_reset_server, + &mut client_mfa_server, + &mut polling_server, + ) + .await?; } } From 9aeb381dff2becac01f163edcd388bc1b60543c8 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 09:45:18 +0200 Subject: [PATCH 030/100] ProxyMessageLoopContext struct --- crates/defguard_core/src/grpc/mod.rs | 91 +++++++++++++++++----------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index bc691239d1..cb9c0ef0a5 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,6 +1,7 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - client::DefguardVersionClientLayer, parse_metadata, server::DefguardVersionServerMiddleware, SystemInfo, + SystemInfo, client::DefguardVersionClientLayer, parse_metadata, + server::DefguardVersionServerMiddleware, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -474,6 +475,17 @@ impl From for CoreError { } } +struct ProxyMessageLoopContext<'a> { + pool: PgPool, + tx: UnboundedSender, + wireguard_tx: Sender, + resp_stream: &'a mut Streaming, + enrollment_server: &'a mut EnrollmentServer, + password_reset_server: &'a mut PasswordResetServer, + client_mfa_server: &'a mut ClientMfaServer, + polling_server: &'a mut PollingServer, +} + #[instrument( name = "proxy_message_loop", skip_all, @@ -486,18 +498,11 @@ impl From for CoreError { async fn handle_message_loop( proxy_version: &Version, proxy_info: &SystemInfo, - pool: PgPool, - tx: UnboundedSender, - wireguard_tx: Sender, - resp_stream: &mut Streaming, - enrollment_server: &mut EnrollmentServer, - password_reset_server: &mut PasswordResetServer, - client_mfa_server: &mut ClientMfaServer, - polling_server: &mut PollingServer, + context: ProxyMessageLoopContext<'_>, ) -> Result<(), anyhow::Error> { 'message: loop { - let pool = pool.clone(); - match resp_stream.message().await { + let pool = context.pool.clone(); + match context.resp_stream.message().await { Ok(None) => { info!("stream was closed by the sender"); break 'message; @@ -508,7 +513,11 @@ async fn handle_message_loop( let payload = match received.payload { // rpc RegisterMobileAuth (RegisterMobileAuthRequest) return (google.protobuf.Empty) Some(core_request::Payload::RegisterMobileAuth(request)) => { - match enrollment_server.register_mobile_auth(request).await { + match context + .enrollment_server + .register_mobile_auth(request) + .await + { Ok(()) => Some(core_response::Payload::Empty(())), Err(err) => { error!("Register mobile auth error {err}"); @@ -518,7 +527,8 @@ async fn handle_message_loop( } // rpc StartEnrollment (EnrollmentStartRequest) returns (EnrollmentStartResponse) Some(core_request::Payload::EnrollmentStart(request)) => { - match enrollment_server + match context + .enrollment_server .start_enrollment(request, received.device_info) .await { @@ -533,7 +543,8 @@ async fn handle_message_loop( } // rpc ActivateUser (ActivateUserRequest) returns (google.protobuf.Empty) Some(core_request::Payload::ActivateUser(request)) => { - match enrollment_server + match context + .enrollment_server .activate_user(request, received.device_info) .await { @@ -546,7 +557,8 @@ async fn handle_message_loop( } // rpc CreateDevice (NewDevice) returns (DeviceConfigResponse) Some(core_request::Payload::NewDevice(request)) => { - match enrollment_server + match context + .enrollment_server .create_device(request, received.device_info) .await { @@ -561,7 +573,7 @@ async fn handle_message_loop( } // rpc GetNetworkInfo (ExistingDevice) returns (DeviceConfigResponse) Some(core_request::Payload::ExistingDevice(request)) => { - match enrollment_server.get_network_info(request).await { + match context.enrollment_server.get_network_info(request).await { Ok(response_payload) => { Some(core_response::Payload::DeviceConfig(response_payload)) } @@ -573,7 +585,8 @@ async fn handle_message_loop( } // rpc RequestPasswordReset (PasswordResetInitializeRequest) returns (google.protobuf.Empty) Some(core_request::Payload::PasswordResetInit(request)) => { - match password_reset_server + match context + .password_reset_server .request_password_reset(request, received.device_info) .await { @@ -586,7 +599,8 @@ async fn handle_message_loop( } // rpc StartPasswordReset (PasswordResetStartRequest) returns (PasswordResetStartResponse) Some(core_request::Payload::PasswordResetStart(request)) => { - match password_reset_server + match context + .password_reset_server .start_password_reset(request, received.device_info) .await { @@ -601,7 +615,8 @@ async fn handle_message_loop( } // rpc ResetPassword (PasswordResetRequest) returns (google.protobuf.Empty) Some(core_request::Payload::PasswordReset(request)) => { - match password_reset_server + match context + .password_reset_server .reset_password(request, received.device_info) .await { @@ -614,7 +629,11 @@ async fn handle_message_loop( } // rpc ClientMfaStart (ClientMfaStartRequest) returns (ClientMfaStartResponse) Some(core_request::Payload::ClientMfaStart(request)) => { - match client_mfa_server.start_client_mfa_login(request).await { + match context + .client_mfa_server + .start_client_mfa_login(request) + .await + { Ok(response_payload) => { Some(core_response::Payload::ClientMfaStart(response_payload)) } @@ -626,7 +645,8 @@ async fn handle_message_loop( } // rpc ClientMfaFinish (ClientMfaFinishRequest) returns (ClientMfaFinishResponse) Some(core_request::Payload::ClientMfaFinish(request)) => { - match client_mfa_server + match context + .client_mfa_server .finish_client_mfa_login(request, received.device_info) .await { @@ -649,7 +669,8 @@ async fn handle_message_loop( } } Some(core_request::Payload::ClientMfaOidcAuthenticate(request)) => { - match client_mfa_server + match context + .client_mfa_server .auth_mfa_session_with_oidc(request, received.device_info) .await { @@ -662,7 +683,7 @@ async fn handle_message_loop( } // rpc LocationInfo (LocationInfoRequest) returns (LocationInfoResponse) Some(core_request::Payload::InstanceInfo(request)) => { - match polling_server.info(request).await { + match context.polling_server.info(request).await { Ok(response_payload) => { Some(core_response::Payload::InstanceInfo(response_payload)) } @@ -746,7 +767,7 @@ async fn handle_message_loop( if let Err(err) = sync_user_groups_if_configured( &user, &pool, - &wireguard_tx, + &context.wireguard_tx, ) .await { @@ -812,7 +833,7 @@ async fn handle_message_loop( id: received.id, payload, }; - tx.send(req).unwrap(); + context.tx.send(req).unwrap(); } Err(err) => { // error!("Disconnected from proxy at {}", endpoint.uri()); @@ -884,15 +905,17 @@ pub async fn run_grpc_bidi_stream( let mut resp_stream = response.into_inner(); handle_message_loop( &version, - &info, - pool.clone(), - tx, - wireguard_tx.clone(), - &mut resp_stream, - &mut enrollment_server, - &mut password_reset_server, - &mut client_mfa_server, - &mut polling_server, + &info, + ProxyMessageLoopContext { + pool: pool.clone(), + tx: tx, + wireguard_tx: wireguard_tx.clone(), + resp_stream: &mut resp_stream, + enrollment_server: &mut enrollment_server, + password_reset_server: &mut password_reset_server, + client_mfa_server: &mut client_mfa_server, + polling_server: &mut polling_server, + }, ) .await?; } From a06433d785b21cde2e44ca2364d827413c2b7c3e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 09:59:10 +0200 Subject: [PATCH 031/100] custom formatter: - log versions always if available - log info in errors only --- crates/defguard/src/main.rs | 12 +- crates/defguard_version/src/tracing.rs | 335 +++++++++++++------------ 2 files changed, 179 insertions(+), 168 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 349c48005d..49ddbd83ad 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -21,6 +21,7 @@ use defguard_core::{ wireguard_peer_disconnect::run_periodic_peer_disconnect, wireguard_stats_purge::run_periodic_stats_purge, }; +use defguard_version; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; use secrecy::ExposeSecret; @@ -29,7 +30,6 @@ use std::{ sync::{Arc, Mutex}, }; use tokio::sync::{broadcast, mpsc::unbounded_channel}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[macro_use] extern crate tracing; @@ -42,14 +42,8 @@ async fn main() -> Result<(), anyhow::Error> { let config = DefGuardConfig::new(); SERVER_CONFIG.set(config.clone())?; - // initialize tracing - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| format!("{},h2=info", config.log_level).into()), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); + // initialize tracing with custom version formatter + defguard_version::tracing::init(VERSION, &config.log_level); info!("Starting ... version v{}", VERSION); debug!("Using config: {config:?}"); diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 13a149ebbb..ae69448d6d 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,169 +1,186 @@ -// use std::{ -// collections::HashSet, -// sync::{Arc, RwLock}, -// }; +use std::sync::OnceLock; +use tracing_subscriber::{ + fmt::{FormatEvent, FormatFields, format::Writer}, + layer::{Context, SubscriberExt}, + util::SubscriberInitExt, + Layer, +}; +use tracing::{Level, Subscriber}; -// use tracing_subscriber::{ -// fmt::{FormatEvent, FormatFields, format::Writer}, -// {layer::SubscriberExt, util::SubscriberInitExt}, -// }; +// Static storage for version information that will be available globally +static CORE_VERSION: OnceLock = OnceLock::new(); -// use crate::{ComponentInfo, DefguardVersionSet}; +/// Sets the core version that will be displayed in logs +pub fn set_core_version(version: impl Into) { + CORE_VERSION.set(version.into()).ok(); +} -// /// Custom tracing formatter that conditionally includes version information in log messages. -// /// -// /// This formatter wraps the default tracing formatter and adds version prefixes to log messages -// /// under specific conditions: -// /// - Always includes version info for ERROR level logs -// /// - Always includes version info for logs within specified span names (regardless of log level) -// /// -// /// The version information includes details about the application and connected services, -// /// along with comprehensive system information for debugging purposes. -// struct VersionPrefixFormat { -// /// The underlying tracing formatter -// inner: tracing_subscriber::fmt::format::Format, -// /// Shared version information of all components -// version_set: Arc, -// /// Set of span names that should always include version info in their logs -// always_version_spans: HashSet, -// } +/// Custom tracing formatter that conditionally includes version information in log messages. +/// +/// This formatter wraps the default tracing formatter and adds version prefixes to log messages: +/// - For ERROR level logs: includes core_version, proxy_version, and proxy_info (if available) +/// - For other levels: includes only core_version and proxy_version (if available) +/// +/// The version information is extracted from tracing span fields. +struct VersionPrefixFormat { + /// The underlying tracing formatter + inner: tracing_subscriber::fmt::format::Format, +} -// impl FormatEvent for VersionPrefixFormat -// where -// S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, -// N: for<'a> FormatFields<'a> + 'static, -// { -// /// Formats a tracing event, conditionally adding version information as a prefix. -// /// -// /// This method checks if version information should be included based on: -// /// 1. Log level (always include for ERROR level) -// /// 2. Span context (include if any span in the current context matches configured span names) -// /// -// /// The version prefix format includes comprehensive system information: -// /// `[v{version}|{os_type}|{os_version}|{bitness}|{architecture}]` -// /// -// /// Additional prefixes are added for connected services: -// /// - Core service: `[C:v{version}|...]` -// /// - Proxy service: `[PX:v{version}|...]` -// /// - Gateway service: `[GW:v{version}|...]` -// fn format_event( -// &self, -// ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, -// mut writer: Writer<'_>, -// event: &tracing::Event<'_>, -// ) -> std::fmt::Result { -// let is_error_level = *event.metadata().level() == tracing::Level::ERROR; +/// A layer that captures version fields from spans and stores them for use by the formatter +struct VersionFieldLayer; -// // Check if we're within any of the configured spans -// let is_in_version_span = ctx.lookup_current().map_or(false, |span_ref| { -// let mut current_span = Some(span_ref); -// while let Some(span) = current_span { -// let span_name = span.metadata().name(); -// if self.always_version_spans.contains(span_name) { -// return true; -// } -// current_span = span.parent(); -// } -// false -// }); +impl Layer for VersionFieldLayer +where + S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, +{ + fn on_new_span( + &self, + attrs: &tracing::span::Attributes<'_>, + id: &tracing::span::Id, + ctx: Context<'_, S>, + ) { + if let Some(span) = ctx.span(id) { + let mut visitor = SpanFieldVisitor::default(); + attrs.record(&mut visitor); + span.extensions_mut().insert(visitor); + } + } +} -// let should_log_version = is_error_level || is_in_version_span; -// if should_log_version { -// write!( -// writer, -// "[v{}|{}|{}|{}|{}] ", -// self.version_set.own.version, -// self.version_set.own.system.os_type, -// self.version_set.own.system.os_version, -// self.version_set.own.system.bitness, -// self.version_set.own.system.architecture, -// )?; +impl FormatEvent for VersionPrefixFormat +where + S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + /// Formats a tracing event, conditionally adding version information as a prefix. + /// + /// This method includes version information based on: + /// - For ERROR level logs: includes core_version, proxy_version, and proxy_info (if available in span) + /// - For other levels: includes only core_version and proxy_version (if available in span) + fn format_event( + &self, + ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &tracing::Event<'_>, + ) -> std::fmt::Result { + let is_error_level = *event.metadata().level() == Level::ERROR; + + // Extract version information from current span context + let mut core_version = None; + let mut proxy_version = None; + let mut proxy_info = None; + + if let Some(span_ref) = ctx.lookup_current() { + let mut current_span = Some(span_ref); + while let Some(span) = current_span { + let extensions = span.extensions(); + + // Try to get stored visitor from span extensions + if let Some(stored_visitor) = extensions.get::() { + if core_version.is_none() && stored_visitor.core_version.is_some() { + core_version = stored_visitor.core_version.clone(); + } + if proxy_version.is_none() && stored_visitor.proxy_version.is_some() { + proxy_version = stored_visitor.proxy_version.clone(); + } + if proxy_info.is_none() && stored_visitor.proxy_info.is_some() { + proxy_info = stored_visitor.proxy_info.clone(); + } + } + + current_span = span.parent(); + } + } + + // Fallback to global core version if not found in spans + if core_version.is_none() { + if let Some(global_version) = CORE_VERSION.get() { + core_version = Some(global_version.clone()); + } + } + + // Format version prefix based on log level and available information + if core_version.is_some() || proxy_version.is_some() { + if let Some(ref cv) = core_version { + write!(writer, "[C:{}] ", cv)?; + } + if let Some(ref pv) = proxy_version { + write!(writer, "[P:{}] ", pv)?; + } + // Only include proxy_info for ERROR level logs + if is_error_level { + if let Some(ref pi) = proxy_info { + write!(writer, "[PI:{}] ", pi)?; + } + } + } -// if let Some(ref core) = *self.version_set.core.read().unwrap() { -// write!( -// writer, -// "[C:v{}|{}|{}|{}|{}] ", -// core.version, -// core.system.os_type, -// core.system.os_version, -// core.system.bitness, -// core.system.architecture, -// )?; -// } -// if let Some(ref proxy) = *self.version_set.proxy.read().unwrap() { -// write!( -// writer, -// "[PX:v{}|{}|{}|{}|{}] ", -// proxy.version, -// proxy.system.os_type, -// proxy.system.os_version, -// proxy.system.bitness, -// proxy.system.architecture, -// )?; -// } + self.inner.format_event(ctx, writer, event) + } +} -// if let Some(ref gateway) = *self.version_set.gateway.read().unwrap() { -// write!( -// writer, -// "[GW:v{}|{}|{}|{}|{}] ", -// gateway.version, -// gateway.system.os_type, -// gateway.system.os_version, -// gateway.system.bitness, -// gateway.system.architecture, -// )?; -// } -// } +/// A visitor that extracts version fields from spans +#[derive(Default, Clone)] +struct SpanFieldVisitor { + core_version: Option, + proxy_version: Option, + proxy_info: Option, +} -// self.inner.format_event(ctx, writer, event) -// } -// } +impl tracing::field::Visit for SpanFieldVisitor { + fn record_str(&mut self, field: &tracing::field::Field, value: &str) { + match field.name() { + "core_version" => self.core_version = Some(value.to_string()), + "proxy_version" => self.proxy_version = Some(value.to_string()), + "proxy_info" => self.proxy_info = Some(value.to_string()), + _ => {} + } + } + + fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { + match field.name() { + "core_version" => self.core_version = Some(format!("{:?}", value)), + "proxy_version" => self.proxy_version = Some(format!("{:?}", value)), + "proxy_info" => self.proxy_info = Some(format!("{:?}", value)), + _ => {} + } + } +} -// /// Initializes tracing with custom formatter displaying own and connected services version. -// /// Returns shared VersionInfo object that will be used to display services versions. -// /// -// /// # Arguments -// /// * `version` - The application version -// /// * `log_level` - The log level to use -// /// * `always_version_spans` - Span names that should always include version info in logs -// /// -// /// # Examples -// /// ``` -// /// let version_set = defguard_version::init_tracing( -// /// "1.5.0", -// /// "info", -// /// &["run_grpc_bidi_stream", "enrollment_process"] -// /// ); -// /// ``` -// pub fn init( -// version: &str, -// log_level: &str, -// always_version_spans: &[&str], -// ) -> Arc { -// let version_set = Arc::new(DefguardVersionSet { -// own: ComponentInfo::from_str(version).expect("Failed to parse version: {version}"), -// core: Arc::new(RwLock::new(None)), -// proxy: Arc::new(RwLock::new(None)), -// gateway: Arc::new(RwLock::new(None)), -// }); - -// let spans: HashSet = always_version_spans -// .iter() -// .map(|&s| s.to_string()) -// .collect(); - -// tracing_subscriber::registry() -// .with( -// tracing_subscriber::EnvFilter::try_from_default_env() -// .unwrap_or_else(|_| format!("{},h2=info", log_level).into()), -// ) -// .with( -// tracing_subscriber::fmt::layer().event_format(VersionPrefixFormat { -// inner: tracing_subscriber::fmt::format::Format::default(), -// version_set: Arc::clone(&version_set), -// always_version_spans: spans, -// }), -// ) -// .init(); -// version_set -// } +/// Initializes tracing with custom formatter that conditionally displays version information. +/// +/// The formatter will: +/// - For ERROR level logs: display core_version, proxy_version, and proxy_info (if available) +/// - For other log levels: display only core_version and proxy_version (if available) +/// +/// Version information is extracted from tracing span fields with names: +/// - `core_version`: The core application version +/// - `proxy_version`: The connected proxy version +/// - `proxy_info`: Additional proxy system information +/// +/// # Arguments +/// * `core_version` - The core application version to display globally +/// * `log_level` - The log level filter to use +/// +/// # Examples +/// ``` +/// defguard_version::tracing::init("1.5.0", "info"); +/// ``` +pub fn init(core_version: &str, log_level: &str) { + // Set the global core version + set_core_version(core_version); + + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| format!("{},h2=info", log_level).into()), + ) + .with(VersionFieldLayer) // Add our custom layer to capture span fields + .with( + tracing_subscriber::fmt::layer().event_format(VersionPrefixFormat { + inner: tracing_subscriber::fmt::format::Format::default(), + }), + ) + .init(); +} From 2c4a10232ee70e97ad08b95b87207cda0a2f4b80 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 11:00:33 +0200 Subject: [PATCH 032/100] don't store own version in OnceLock, don't require own version in span context --- crates/defguard_core/src/grpc/mod.rs | 5 +- crates/defguard_version/src/client.rs | 4 +- crates/defguard_version/src/lib.rs | 4 ++ crates/defguard_version/src/tracing.rs | 89 +++++++++++--------------- 4 files changed, 45 insertions(+), 57 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index cb9c0ef0a5..6658422d1d 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -490,12 +490,11 @@ struct ProxyMessageLoopContext<'a> { name = "proxy_message_loop", skip_all, fields( - core_version = %VERSION, proxy_version = %proxy_version, proxy_info = %proxy_info, ) )] -async fn handle_message_loop( +async fn handle_proxy_message_loop( proxy_version: &Version, proxy_info: &SystemInfo, context: ProxyMessageLoopContext<'_>, @@ -903,7 +902,7 @@ pub async fn run_grpc_bidi_stream( info!("Connected to proxy at {}", endpoint.uri()); let mut resp_stream = response.into_inner(); - handle_message_loop( + handle_proxy_message_loop( &version, &info, ProxyMessageLoopContext { diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index f9698a0da0..6ce664dc04 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -2,14 +2,12 @@ use http::{Request, Response}; use std::{ future::Future, pin::Pin, - sync::{Arc, RwLock}, task::{Context, Poll}, }; use tonic::body::BoxBody; use tower::{Layer, Service}; -use tracing::error; -use crate::{parse_version_headers, ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; +use crate::{ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index a08a661e8f..1192c3794d 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -63,6 +63,10 @@ impl Display for SystemInfo { } impl SystemInfo { + fn get() -> Self { + os_info::get().into() + } + fn as_header_value(&self) -> String { format!( "{};{};{};{}", diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index ae69448d6d..402aedb044 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,30 +1,26 @@ -use std::sync::OnceLock; +use tracing::{Level, Subscriber}; use tracing_subscriber::{ + Layer, fmt::{FormatEvent, FormatFields, format::Writer}, layer::{Context, SubscriberExt}, util::SubscriberInitExt, - Layer, }; -use tracing::{Level, Subscriber}; -// Static storage for version information that will be available globally -static CORE_VERSION: OnceLock = OnceLock::new(); - -/// Sets the core version that will be displayed in logs -pub fn set_core_version(version: impl Into) { - CORE_VERSION.set(version.into()).ok(); -} +use crate::SystemInfo; /// Custom tracing formatter that conditionally includes version information in log messages. /// /// This formatter wraps the default tracing formatter and adds version prefixes to log messages: /// - For ERROR level logs: includes core_version, proxy_version, and proxy_info (if available) /// - For other levels: includes only core_version and proxy_version (if available) -/// +/// /// The version information is extracted from tracing span fields. struct VersionPrefixFormat { /// The underlying tracing formatter inner: tracing_subscriber::fmt::format::Format, + /// The core application version to display as fallback + own_version: String, + own_info: SystemInfo, } /// A layer that captures version fields from spans and stores them for use by the formatter @@ -64,23 +60,17 @@ where mut writer: Writer<'_>, event: &tracing::Event<'_>, ) -> std::fmt::Result { - let is_error_level = *event.metadata().level() == Level::ERROR; - // Extract version information from current span context - let mut core_version = None; let mut proxy_version = None; let mut proxy_info = None; - + if let Some(span_ref) = ctx.lookup_current() { let mut current_span = Some(span_ref); while let Some(span) = current_span { let extensions = span.extensions(); - - // Try to get stored visitor from span extensions + + // Try to get stored visitor from span extensions if let Some(stored_visitor) = extensions.get::() { - if core_version.is_none() && stored_visitor.core_version.is_some() { - core_version = stored_visitor.core_version.clone(); - } if proxy_version.is_none() && stored_visitor.proxy_version.is_some() { proxy_version = stored_visitor.proxy_version.clone(); } @@ -88,32 +78,33 @@ where proxy_info = stored_visitor.proxy_info.clone(); } } - + current_span = span.parent(); } } - - // Fallback to global core version if not found in spans - if core_version.is_none() { - if let Some(global_version) = CORE_VERSION.get() { - core_version = Some(global_version.clone()); - } - } - + // Format version prefix based on log level and available information - if core_version.is_some() || proxy_version.is_some() { - if let Some(ref cv) = core_version { - write!(writer, "[C:{}] ", cv)?; - } - if let Some(ref pv) = proxy_version { - write!(writer, "[P:{}] ", pv)?; + let is_versioned_span = proxy_version.is_some(); + let is_error = *event.metadata().level() == Level::ERROR; + if is_versioned_span || is_error { + // Own version + let mut own_version_str = format!("[{}", self.own_version); + if is_error { + own_version_str = format!("{own_version_str} {}", self.own_info); } - // Only include proxy_info for ERROR level logs - if is_error_level { - if let Some(ref pi) = proxy_info { - write!(writer, "[PI:{}] ", pi)?; + own_version_str = format!("{own_version_str}]"); + write!(writer, "{}", own_version_str)?; + } + // Proxy version + if let Some(ref proxy_version) = proxy_version { + let mut proxy_version_str = format!("[PX:{}", proxy_version); + if is_error { + if let Some(ref proxy_info) = proxy_info { + proxy_version_str = format!("{proxy_version_str} {}", proxy_info); } } + proxy_version_str = format!("{proxy_version_str}]"); + write!(writer, "{}", proxy_version_str)?; } self.inner.format_event(ctx, writer, event) @@ -123,24 +114,21 @@ where /// A visitor that extracts version fields from spans #[derive(Default, Clone)] struct SpanFieldVisitor { - core_version: Option, - proxy_version: Option, + proxy_version: Option, proxy_info: Option, } impl tracing::field::Visit for SpanFieldVisitor { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { - "core_version" => self.core_version = Some(value.to_string()), "proxy_version" => self.proxy_version = Some(value.to_string()), "proxy_info" => self.proxy_info = Some(value.to_string()), _ => {} } } - + fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { match field.name() { - "core_version" => self.core_version = Some(format!("{:?}", value)), "proxy_version" => self.proxy_version = Some(format!("{:?}", value)), "proxy_info" => self.proxy_info = Some(format!("{:?}", value)), _ => {} @@ -151,26 +139,23 @@ impl tracing::field::Visit for SpanFieldVisitor { /// Initializes tracing with custom formatter that conditionally displays version information. /// /// The formatter will: -/// - For ERROR level logs: display core_version, proxy_version, and proxy_info (if available) +/// - For ERROR level logs: display core_version, proxy_version, and proxy_info (if available) /// - For other log levels: display only core_version and proxy_version (if available) /// /// Version information is extracted from tracing span fields with names: /// - `core_version`: The core application version -/// - `proxy_version`: The connected proxy version +/// - `proxy_version`: The connected proxy version /// - `proxy_info`: Additional proxy system information /// /// # Arguments -/// * `core_version` - The core application version to display globally +/// * `core_version` - The core application version to use as fallback when not found in spans /// * `log_level` - The log level filter to use /// /// # Examples /// ``` /// defguard_version::tracing::init("1.5.0", "info"); /// ``` -pub fn init(core_version: &str, log_level: &str) { - // Set the global core version - set_core_version(core_version); - +pub fn init(own_version: &str, log_level: &str) { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() @@ -180,6 +165,8 @@ pub fn init(core_version: &str, log_level: &str) { .with( tracing_subscriber::fmt::layer().event_format(VersionPrefixFormat { inner: tracing_subscriber::fmt::format::Format::default(), + own_version: own_version.to_string(), + own_info: SystemInfo::get(), }), ) .init(); From 237701af3c2b3a250b6a3c9458c057f7c238c754 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 12:25:13 +0200 Subject: [PATCH 033/100] extract and log gateway version info, apply to stats endpoint --- crates/defguard_core/src/grpc/gateway/mod.rs | 13 +++++++-- crates/defguard_core/src/grpc/mod.rs | 2 +- crates/defguard_version/src/tracing.rs | 29 +++++++++++++++++++- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index dd5293e575..52fe2f0e22 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -8,6 +8,7 @@ use std::{ use chrono::{DateTime, TimeDelta, Utc}; use client_state::ClientMap; +use defguard_version::parse_metadata; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query}; use thiserror::Error; use tokio::{ @@ -721,11 +722,19 @@ impl gateway_service_server::GatewayService for GatewayServer { &self, request: Request>, ) -> Result, Status> { - let network_id = Self::get_network_id(request.metadata())?; - let gateway_hostname = Self::get_gateway_hostname(request.metadata())?; + let metadata = request.metadata(); + let network_id = Self::get_network_id(metadata)?; + let (version, info) = parse_metadata(metadata).unwrap(); + let gateway_hostname = Self::get_gateway_hostname(metadata)?; let mut stream = request.into_inner(); let mut disconnect_timer = interval(Duration::from_secs(PEER_DISCONNECT_INTERVAL)); + let span = tracing::info_span!( + "gateway_stats", + gateway_version = %version, + gateway_info = %info, + ); + let _guard = span.enter(); loop { // wait for a message or update client map at least once a mninute if no messages are received let stats_update = tokio::select! { diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 6658422d1d..38b87e6f45 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -907,7 +907,7 @@ pub async fn run_grpc_bidi_stream( &info, ProxyMessageLoopContext { pool: pool.clone(), - tx: tx, + tx, wireguard_tx: wireguard_tx.clone(), resp_stream: &mut resp_stream, enrollment_server: &mut enrollment_server, diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 402aedb044..ae5534a6eb 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -63,6 +63,8 @@ where // Extract version information from current span context let mut proxy_version = None; let mut proxy_info = None; + let mut gateway_version = None; + let mut gateway_info = None; if let Some(span_ref) = ctx.lookup_current() { let mut current_span = Some(span_ref); @@ -77,6 +79,12 @@ where if proxy_info.is_none() && stored_visitor.proxy_info.is_some() { proxy_info = stored_visitor.proxy_info.clone(); } + if gateway_version.is_none() && stored_visitor.gateway_version.is_some() { + gateway_version = stored_visitor.gateway_version.clone(); + } + if gateway_info.is_none() && stored_visitor.gateway_info.is_some() { + gateway_info = stored_visitor.gateway_info.clone(); + } } current_span = span.parent(); @@ -84,7 +92,7 @@ where } // Format version prefix based on log level and available information - let is_versioned_span = proxy_version.is_some(); + let is_versioned_span = proxy_version.is_some() || gateway_version.is_some(); let is_error = *event.metadata().level() == Level::ERROR; if is_versioned_span || is_error { // Own version @@ -95,6 +103,7 @@ where own_version_str = format!("{own_version_str}]"); write!(writer, "{}", own_version_str)?; } + // Proxy version if let Some(ref proxy_version) = proxy_version { let mut proxy_version_str = format!("[PX:{}", proxy_version); @@ -107,6 +116,18 @@ where write!(writer, "{}", proxy_version_str)?; } + // Gateway version + if let Some(ref gateway_version) = gateway_version { + let mut gateway_version_str = format!("[GW:{}", gateway_version); + if is_error { + if let Some(ref gateway_info) = gateway_info { + gateway_version_str = format!("{gateway_version_str} {}", gateway_info); + } + } + gateway_version_str = format!("{gateway_version_str}]"); + write!(writer, "{}", gateway_version_str)?; + } + self.inner.format_event(ctx, writer, event) } } @@ -116,6 +137,8 @@ where struct SpanFieldVisitor { proxy_version: Option, proxy_info: Option, + gateway_version: Option, + gateway_info: Option, } impl tracing::field::Visit for SpanFieldVisitor { @@ -123,6 +146,8 @@ impl tracing::field::Visit for SpanFieldVisitor { match field.name() { "proxy_version" => self.proxy_version = Some(value.to_string()), "proxy_info" => self.proxy_info = Some(value.to_string()), + "gateway_version" => self.gateway_version = Some(value.to_string()), + "gateway_info" => self.gateway_info = Some(value.to_string()), _ => {} } } @@ -131,6 +156,8 @@ impl tracing::field::Visit for SpanFieldVisitor { match field.name() { "proxy_version" => self.proxy_version = Some(format!("{:?}", value)), "proxy_info" => self.proxy_info = Some(format!("{:?}", value)), + "gateway_version" => self.gateway_version = Some(format!("{:?}", value)), + "gateway_info" => self.gateway_info = Some(format!("{:?}", value)), _ => {} } } From bddcc6af4b2142cf86dd6f336d8bc6a1944aa85f Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 12:32:04 +0200 Subject: [PATCH 034/100] version span for gateway config and updates endpoints --- crates/defguard_core/src/grpc/gateway/mod.rs | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 52fe2f0e22..ea809c2e00 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -893,8 +893,16 @@ impl gateway_service_server::GatewayService for GatewayServer { request: Request, ) -> Result, Status> { debug!("Sending configuration to gateway client."); - let network_id = Self::get_network_id(request.metadata())?; - let hostname = Self::get_gateway_hostname(request.metadata())?; + let metadata = request.metadata(); + let network_id = Self::get_network_id(metadata)?; + let hostname = Self::get_gateway_hostname(metadata)?; + let (version, info) = parse_metadata(metadata).unwrap(); + let span = tracing::info_span!( + "gateway_config", + gateway_version = %version, + gateway_info = %info, + ); + let _guard = span.enter(); let mut conn = self.pool.acquire().await.map_err(|e| { error!("Failed to acquire DB connection: {e}"); @@ -965,8 +973,16 @@ impl gateway_service_server::GatewayService for GatewayServer { } async fn updates(&self, request: Request<()>) -> Result, Status> { - let gateway_network_id = Self::get_network_id(request.metadata())?; - let hostname = Self::get_gateway_hostname(request.metadata())?; + let metadata = request.metadata(); + let gateway_network_id = Self::get_network_id(metadata)?; + let hostname = Self::get_gateway_hostname(metadata)?; + let (version, info) = parse_metadata(metadata).unwrap(); + let span = tracing::info_span!( + "gateway_updates", + gateway_version = %version, + gateway_info = %info, + ); + let _guard = span.enter(); let Some(network) = WireguardNetwork::find_by_id(&self.pool, gateway_network_id) .await From d214a8e6441dd435530bdb78fc1ed1cec3171c36 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 12:43:31 +0200 Subject: [PATCH 035/100] extract and log core version --- crates/defguard_version/src/tracing.rs | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index ae5534a6eb..69da1f44d1 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -61,6 +61,8 @@ where event: &tracing::Event<'_>, ) -> std::fmt::Result { // Extract version information from current span context + let mut core_version = None; + let mut core_info = None; let mut proxy_version = None; let mut proxy_info = None; let mut gateway_version = None; @@ -73,6 +75,12 @@ where // Try to get stored visitor from span extensions if let Some(stored_visitor) = extensions.get::() { + if core_version.is_none() && stored_visitor.core_version.is_some() { + core_version = stored_visitor.core_version.clone(); + } + if core_info.is_none() && stored_visitor.core_info.is_some() { + core_info = stored_visitor.core_info.clone(); + } if proxy_version.is_none() && stored_visitor.proxy_version.is_some() { proxy_version = stored_visitor.proxy_version.clone(); } @@ -92,7 +100,8 @@ where } // Format version prefix based on log level and available information - let is_versioned_span = proxy_version.is_some() || gateway_version.is_some(); + let is_versioned_span = + core_version.is_some() || proxy_version.is_some() || gateway_version.is_some(); let is_error = *event.metadata().level() == Level::ERROR; if is_versioned_span || is_error { // Own version @@ -104,6 +113,17 @@ where write!(writer, "{}", own_version_str)?; } + // Core version + if let Some(ref core_version) = core_version { + let mut core_version_str = format!("[C:{}", core_version); + if is_error { + if let Some(ref core_info) = core_info { + core_version_str = format!("{core_version_str} {}", core_info); + } + } + core_version_str = format!("{core_version_str}]"); + write!(writer, "{}", core_version_str)?; + } // Proxy version if let Some(ref proxy_version) = proxy_version { let mut proxy_version_str = format!("[PX:{}", proxy_version); @@ -135,6 +155,8 @@ where /// A visitor that extracts version fields from spans #[derive(Default, Clone)] struct SpanFieldVisitor { + core_version: Option, + core_info: Option, proxy_version: Option, proxy_info: Option, gateway_version: Option, @@ -144,6 +166,8 @@ struct SpanFieldVisitor { impl tracing::field::Visit for SpanFieldVisitor { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { + "core_version" => self.core_version = Some(value.to_string()), + "core_info" => self.core_info = Some(value.to_string()), "proxy_version" => self.proxy_version = Some(value.to_string()), "proxy_info" => self.proxy_info = Some(value.to_string()), "gateway_version" => self.gateway_version = Some(value.to_string()), @@ -154,6 +178,8 @@ impl tracing::field::Visit for SpanFieldVisitor { fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { match field.name() { + "core_version" => self.core_version = Some(format!("{:?}", value)), + "core_info" => self.core_info = Some(format!("{:?}", value)), "proxy_version" => self.proxy_version = Some(format!("{:?}", value)), "proxy_info" => self.proxy_info = Some(format!("{:?}", value)), "gateway_version" => self.gateway_version = Some(format!("{:?}", value)), From 9b16408a7194fbbdc1f56d0b08f5bce650144dc1 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 14:02:33 +0200 Subject: [PATCH 036/100] cleanup --- crates/defguard_version/src/lib.rs | 19 ------------------- crates/defguard_version/src/tracing.rs | 1 + 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 1192c3794d..dee237f782 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -21,25 +21,6 @@ pub enum DefguardVersionError { SystemInfoParseError(String), } -// #[derive(Clone, Debug)] -// pub struct DefguardVersionSet { -// pub own: ComponentInfo, -// pub core: Arc>>, -// pub proxy: Arc>>, -// pub gateway: Arc>>, -// } - -// impl DefguardVersionSet { -// pub fn try_from(version: &str) -> Result { -// Ok(Self { -// own: ComponentInfo::from_str(version)?, -// core: Arc::new(RwLock::new(None)), -// proxy: Arc::new(RwLock::new(None)), -// gateway: Arc::new(RwLock::new(None)), -// }) -// } -// } - #[derive(Debug, Clone)] pub struct SystemInfo { /// The operating system type (e.g., "Linux", "Windows", "macOS") diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 69da1f44d1..4d8fad9e1f 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -124,6 +124,7 @@ where core_version_str = format!("{core_version_str}]"); write!(writer, "{}", core_version_str)?; } + // Proxy version if let Some(ref proxy_version) = proxy_version { let mut proxy_version_str = format!("[PX:{}", proxy_version); From b0b62334b0cf92f8e5a9c062170381d6b47ad2cc Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 11 Aug 2025 14:08:59 +0200 Subject: [PATCH 037/100] clippy fixes --- crates/defguard/src/main.rs | 1 - crates/defguard_core/src/grpc/mod.rs | 4 +-- crates/defguard_version/src/client.rs | 4 +-- crates/defguard_version/src/lib.rs | 36 ++------------------------ crates/defguard_version/src/server.rs | 4 +-- crates/defguard_version/src/tracing.rs | 34 ++++++++++++------------ 6 files changed, 25 insertions(+), 58 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 49ddbd83ad..a0845aaae1 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -21,7 +21,6 @@ use defguard_core::{ wireguard_peer_disconnect::run_periodic_peer_disconnect, wireguard_stats_purge::run_periodic_stats_purge, }; -use defguard_version; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; use secrecy::ExposeSecret; diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 38b87e6f45..1b486a7d44 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -884,7 +884,7 @@ pub async fn run_grpc_bidi_stream( loop { debug!("Connecting to proxy at {}", endpoint.uri()); - let version_layer = DefguardVersionClientLayer::from_str(VERSION)?; + let version_layer = DefguardVersionClientLayer::new(VERSION)?; let channel = ServiceBuilder::new() .layer(version_layer) .service(endpoint.connect_lazy()); @@ -974,7 +974,7 @@ pub async fn run_grpc_server( #[cfg(feature = "wireguard")] let router = router.add_service(MiddlewareFor::new( gateway_service, - DefguardVersionServerMiddleware::from_str(VERSION)?, + DefguardVersionServerMiddleware::new(VERSION)?, )); #[cfg(feature = "worker")] let router = router.add_service(worker_service); diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 6ce664dc04..15b6d15faf 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -18,9 +18,9 @@ pub struct DefguardVersionClientLayer { } impl DefguardVersionClientLayer { - pub fn from_str(version: &str) -> Result { + pub fn new(version: &str) -> Result { Ok(Self { - component_info: ComponentInfo::from_str(version)?, + component_info: ComponentInfo::new(version)?, }) } } diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index dee237f782..67d51569d5 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,5 +1,4 @@ use ::tracing::{error, warn}; -use http::HeaderValue; use semver::Version; use std::{fmt::Display, str::FromStr}; use thiserror::Error; @@ -78,7 +77,7 @@ impl From for SystemInfo { os_type: info.os_type().to_string(), os_version: info.version().to_string(), bitness: info.bitness().to_string(), - architecture: info.architecture().unwrap_or_else(|| "?").to_string(), + architecture: info.architecture().unwrap_or("?").to_string(), } } } @@ -90,7 +89,7 @@ pub struct ComponentInfo { } impl ComponentInfo { - pub fn from_str(version: &str) -> Result { + pub fn new(version: &str) -> Result { let version = Version::from_str(version)?; let info = os_info::get(); Ok(Self { @@ -100,37 +99,6 @@ impl ComponentInfo { } } -pub(crate) fn parse_version_headers( - version: Option<&HeaderValue>, - info: Option<&HeaderValue>, -) -> Option<(Version, SystemInfo)> { - let Some(version) = version else { - warn!("Missing version header"); - return None; - }; - let Some(info) = info else { - warn!("Missing system info header"); - return None; - }; - - let (Ok(version), Ok(info)) = (version.to_str(), info.to_str()) else { - warn!("Failed to stringify version or system info header value"); - return None; - }; - - let Ok(version) = Version::from_str(version) else { - warn!("Failed to parse version: {version}"); - return None; - }; - - let Ok(info) = SystemInfo::try_from_header_value(info) else { - warn!("Failed to parse system info: {info}"); - return None; - }; - - Some((version, info)) -} - pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { let Some(version) = metadata.get(VERSION_HEADER) else { warn!("Missing version header"); diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 10ac241a45..d4e0c18cbe 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -13,9 +13,9 @@ pub struct DefguardVersionServerMiddleware { } impl DefguardVersionServerMiddleware { - pub fn from_str(version: &str) -> Result { + pub fn new(version: &str) -> Result { Ok(Self { - component_info: ComponentInfo::from_str(version)?, + component_info: ComponentInfo::new(version)?, }) } } diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 4d8fad9e1f..a51d6945c9 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -110,43 +110,43 @@ where own_version_str = format!("{own_version_str} {}", self.own_info); } own_version_str = format!("{own_version_str}]"); - write!(writer, "{}", own_version_str)?; + write!(writer, "{own_version_str}")?; } // Core version if let Some(ref core_version) = core_version { - let mut core_version_str = format!("[C:{}", core_version); + let mut core_version_str = format!("[C:{core_version}"); if is_error { if let Some(ref core_info) = core_info { - core_version_str = format!("{core_version_str} {}", core_info); + core_version_str = format!("{core_version_str} {core_info}"); } } core_version_str = format!("{core_version_str}]"); - write!(writer, "{}", core_version_str)?; + write!(writer, "{core_version_str}")?; } // Proxy version if let Some(ref proxy_version) = proxy_version { - let mut proxy_version_str = format!("[PX:{}", proxy_version); + let mut proxy_version_str = format!("[PX:{proxy_version}"); if is_error { if let Some(ref proxy_info) = proxy_info { - proxy_version_str = format!("{proxy_version_str} {}", proxy_info); + proxy_version_str = format!("{proxy_version_str} {proxy_info}"); } } proxy_version_str = format!("{proxy_version_str}]"); - write!(writer, "{}", proxy_version_str)?; + write!(writer, "{proxy_version_str}")?; } // Gateway version if let Some(ref gateway_version) = gateway_version { - let mut gateway_version_str = format!("[GW:{}", gateway_version); + let mut gateway_version_str = format!("[GW:{gateway_version}"); if is_error { if let Some(ref gateway_info) = gateway_info { - gateway_version_str = format!("{gateway_version_str} {}", gateway_info); + gateway_version_str = format!("{gateway_version_str} {gateway_info}"); } } gateway_version_str = format!("{gateway_version_str}]"); - write!(writer, "{}", gateway_version_str)?; + write!(writer, "{gateway_version_str}")?; } self.inner.format_event(ctx, writer, event) @@ -179,12 +179,12 @@ impl tracing::field::Visit for SpanFieldVisitor { fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { match field.name() { - "core_version" => self.core_version = Some(format!("{:?}", value)), - "core_info" => self.core_info = Some(format!("{:?}", value)), - "proxy_version" => self.proxy_version = Some(format!("{:?}", value)), - "proxy_info" => self.proxy_info = Some(format!("{:?}", value)), - "gateway_version" => self.gateway_version = Some(format!("{:?}", value)), - "gateway_info" => self.gateway_info = Some(format!("{:?}", value)), + "core_version" => self.core_version = Some(format!("{value:?}")), + "core_info" => self.core_info = Some(format!("{value:?}")), + "proxy_version" => self.proxy_version = Some(format!("{value:?}")), + "proxy_info" => self.proxy_info = Some(format!("{value:?}")), + "gateway_version" => self.gateway_version = Some(format!("{value:?}")), + "gateway_info" => self.gateway_info = Some(format!("{value:?}")), _ => {} } } @@ -213,7 +213,7 @@ pub fn init(own_version: &str, log_level: &str) { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| format!("{},h2=info", log_level).into()), + .unwrap_or_else(|_| format!("{log_level},h2=info").into()), ) .with(VersionFieldLayer) // Add our custom layer to capture span fields .with( From b6b15432633cc1391d03327c4f3eac85052df2a1 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 12 Aug 2025 09:12:01 +0200 Subject: [PATCH 038/100] skip logging version fields to avoid duplication --- crates/defguard_core/src/grpc/gateway/mod.rs | 10 +-- crates/defguard_version/src/tracing.rs | 72 ++++++++++++++++++-- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index ea809c2e00..416e9fb145 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -730,7 +730,7 @@ impl gateway_service_server::GatewayService for GatewayServer { let mut disconnect_timer = interval(Duration::from_secs(PEER_DISCONNECT_INTERVAL)); let span = tracing::info_span!( - "gateway_stats", + "gateway_stats", gateway_version = %version, gateway_info = %info, ); @@ -893,12 +893,12 @@ impl gateway_service_server::GatewayService for GatewayServer { request: Request, ) -> Result, Status> { debug!("Sending configuration to gateway client."); - let metadata = request.metadata(); + let metadata = request.metadata(); let network_id = Self::get_network_id(metadata)?; let hostname = Self::get_gateway_hostname(metadata)?; let (version, info) = parse_metadata(metadata).unwrap(); let span = tracing::info_span!( - "gateway_config", + "gateway_config", gateway_version = %version, gateway_info = %info, ); @@ -973,12 +973,12 @@ impl gateway_service_server::GatewayService for GatewayServer { } async fn updates(&self, request: Request<()>) -> Result, Status> { - let metadata = request.metadata(); + let metadata = request.metadata(); let gateway_network_id = Self::get_network_id(metadata)?; let hostname = Self::get_gateway_hostname(metadata)?; let (version, info) = parse_metadata(metadata).unwrap(); let span = tracing::info_span!( - "gateway_updates", + "gateway_updates", gateway_version = %version, gateway_info = %info, ); diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index a51d6945c9..055af96ee2 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -4,6 +4,7 @@ use tracing_subscriber::{ fmt::{FormatEvent, FormatFields, format::Writer}, layer::{Context, SubscriberExt}, util::SubscriberInitExt, + field::RecordFields, }; use crate::SystemInfo; @@ -190,6 +191,65 @@ impl tracing::field::Visit for SpanFieldVisitor { } } +/// Custom field formatter that filters out version fields to prevent duplication +struct VersionFilteredFields; + +impl<'writer> FormatFields<'writer> for VersionFilteredFields { + fn format_fields( + &self, + writer: Writer<'writer>, + fields: R, + ) -> std::fmt::Result { + let mut visitor = FieldFilterVisitor::new(writer); + fields.record(&mut visitor); + Ok(()) + } +} + +/// Field visitor that skips version-related fields +struct FieldFilterVisitor<'writer> { + writer: Writer<'writer>, + first: bool, +} + +impl<'writer> FieldFilterVisitor<'writer> { + fn new(writer: Writer<'writer>) -> Self { + Self { writer, first: true } + } +} + +impl<'writer> tracing::field::Visit for FieldFilterVisitor<'writer> { + fn record_str(&mut self, field: &tracing::field::Field, value: &str) { + match field.name() { + "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" | "gateway_info" => { + // Skip version fields to prevent duplication + } + _ => { + if !self.first { + let _ = write!(self.writer, " "); + } + let _ = write!(self.writer, "{}={}", field.name(), value); + self.first = false; + } + } + } + + fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { + match field.name() { + "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" | "gateway_info" => { + // Skip version fields to prevent duplication + } + _ => { + if !self.first { + let _ = write!(self.writer, " "); + } + let _ = write!(self.writer, "{}={:?}", field.name(), value); + self.first = false; + } + } + } +} + /// Initializes tracing with custom formatter that conditionally displays version information. /// /// The formatter will: @@ -217,11 +277,13 @@ pub fn init(own_version: &str, log_level: &str) { ) .with(VersionFieldLayer) // Add our custom layer to capture span fields .with( - tracing_subscriber::fmt::layer().event_format(VersionPrefixFormat { - inner: tracing_subscriber::fmt::format::Format::default(), - own_version: own_version.to_string(), - own_info: SystemInfo::get(), - }), + tracing_subscriber::fmt::layer() + .event_format(VersionPrefixFormat { + inner: tracing_subscriber::fmt::format::Format::default(), + own_version: own_version.to_string(), + own_info: SystemInfo::get(), + }) + .fmt_fields(VersionFilteredFields), ) .init(); } From 771d3600b8f6cfa6c0e9a56eebdc2528230c1c28 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 12 Aug 2025 09:52:14 +0200 Subject: [PATCH 039/100] version info at the end of the log line --- crates/defguard_version/src/tracing.rs | 28 +++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 055af96ee2..e3fe7a6319 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -100,18 +100,21 @@ where } } - // Format version prefix based on log level and available information + + // Build version suffix + let mut version_suffix = String::new(); let is_versioned_span = core_version.is_some() || proxy_version.is_some() || gateway_version.is_some(); let is_error = *event.metadata().level() == Level::ERROR; + if is_versioned_span || is_error { // Own version - let mut own_version_str = format!("[{}", self.own_version); + let mut own_version_str = format!(" [{}", self.own_version); if is_error { own_version_str = format!("{own_version_str} {}", self.own_info); } own_version_str = format!("{own_version_str}]"); - write!(writer, "{own_version_str}")?; + version_suffix.push_str(&own_version_str); } // Core version @@ -123,7 +126,7 @@ where } } core_version_str = format!("{core_version_str}]"); - write!(writer, "{core_version_str}")?; + version_suffix.push_str(&core_version_str); } // Proxy version @@ -135,7 +138,7 @@ where } } proxy_version_str = format!("{proxy_version_str}]"); - write!(writer, "{proxy_version_str}")?; + version_suffix.push_str(&proxy_version_str); } // Gateway version @@ -147,10 +150,21 @@ where } } gateway_version_str = format!("{gateway_version_str}]"); - write!(writer, "{gateway_version_str}")?; + version_suffix.push_str(&gateway_version_str); } - self.inner.format_event(ctx, writer, event) + // Format the regular event to a string first + let mut buffer = String::new(); + let temp_writer = Writer::new(&mut buffer); + self.inner.format_event(ctx, temp_writer, event)?; + + // Remove trailing newline if present + if buffer.ends_with('\n') { + buffer.pop(); + } + + // Write the complete line with version suffix + write!(writer, "{}{}\n", buffer, version_suffix) } } From 5925f5ca2ee5eb44e583e7b19d11ba63c7c62a3d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 12 Aug 2025 09:55:40 +0200 Subject: [PATCH 040/100] restore colored output --- crates/defguard_version/src/tracing.rs | 45 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index e3fe7a6319..f02626158d 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,7 +1,8 @@ +use std::io::{self, Write as IoWrite}; use tracing::{Level, Subscriber}; use tracing_subscriber::{ Layer, - fmt::{FormatEvent, FormatFields, format::Writer}, + fmt::{FormatEvent, FormatFields, format::Writer, MakeWriter}, layer::{Context, SubscriberExt}, util::SubscriberInitExt, field::RecordFields, @@ -153,18 +154,34 @@ where version_suffix.push_str(&gateway_version_str); } - // Format the regular event to a string first - let mut buffer = String::new(); - let temp_writer = Writer::new(&mut buffer); - self.inner.format_event(ctx, temp_writer, event)?; - - // Remove trailing newline if present - if buffer.ends_with('\n') { - buffer.pop(); + // Create a wrapper writer that will append version info before newlines + let mut wrapper = VersionSuffixWriter::new(writer, version_suffix); + self.inner.format_event(ctx, Writer::new(&mut wrapper), event) + } +} + +/// A wrapper writer that appends version suffix before newlines +struct VersionSuffixWriter<'a> { + inner: Writer<'a>, + version_suffix: String, +} + +impl<'a> VersionSuffixWriter<'a> { + fn new(inner: Writer<'a>, version_suffix: String) -> Self { + Self { inner, version_suffix } + } +} + +impl<'a> std::fmt::Write for VersionSuffixWriter<'a> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + if s.ends_with('\n') { + // Remove the newline, add version suffix, then add newline back + let content = &s[..s.len() - 1]; + write!(self.inner, "{}{}\n", content, self.version_suffix) + } else { + // No newline at end, just pass through + write!(self.inner, "{}", s) } - - // Write the complete line with version suffix - write!(writer, "{}{}\n", buffer, version_suffix) } } @@ -292,8 +309,10 @@ pub fn init(own_version: &str, log_level: &str) { .with(VersionFieldLayer) // Add our custom layer to capture span fields .with( tracing_subscriber::fmt::layer() + .with_ansi(true) // Enable ANSI colors at layer level .event_format(VersionPrefixFormat { - inner: tracing_subscriber::fmt::format::Format::default(), + inner: tracing_subscriber::fmt::format::Format::default() + .with_ansi(true), // Enable ANSI colors at format level own_version: own_version.to_string(), own_info: SystemInfo::get(), }) From a2b4b7c69e772706d5f97764ea18ee78b2a4da23 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 12 Aug 2025 09:59:44 +0200 Subject: [PATCH 041/100] clippy fixes --- crates/defguard/src/main.rs | 2 +- crates/defguard_version/src/tracing.rs | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index a0845aaae1..e6feffd28f 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -41,7 +41,7 @@ async fn main() -> Result<(), anyhow::Error> { let config = DefGuardConfig::new(); SERVER_CONFIG.set(config.clone())?; - // initialize tracing with custom version formatter + // initialize tracing with version formatter defguard_version::tracing::init(VERSION, &config.log_level); info!("Starting ... version v{}", VERSION); diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index f02626158d..877f772638 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,8 +1,7 @@ -use std::io::{self, Write as IoWrite}; use tracing::{Level, Subscriber}; use tracing_subscriber::{ Layer, - fmt::{FormatEvent, FormatFields, format::Writer, MakeWriter}, + fmt::{FormatEvent, FormatFields, format::Writer}, layer::{Context, SubscriberExt}, util::SubscriberInitExt, field::RecordFields, @@ -59,7 +58,7 @@ where fn format_event( &self, ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, - mut writer: Writer<'_>, + writer: Writer<'_>, event: &tracing::Event<'_>, ) -> std::fmt::Result { // Extract version information from current span context @@ -107,7 +106,7 @@ where let is_versioned_span = core_version.is_some() || proxy_version.is_some() || gateway_version.is_some(); let is_error = *event.metadata().level() == Level::ERROR; - + if is_versioned_span || is_error { // Own version let mut own_version_str = format!(" [{}", self.own_version); @@ -174,13 +173,12 @@ impl<'a> VersionSuffixWriter<'a> { impl<'a> std::fmt::Write for VersionSuffixWriter<'a> { fn write_str(&mut self, s: &str) -> std::fmt::Result { - if s.ends_with('\n') { + if let Some(content) = s.strip_suffix('\n') { // Remove the newline, add version suffix, then add newline back - let content = &s[..s.len() - 1]; - write!(self.inner, "{}{}\n", content, self.version_suffix) + writeln!(self.inner, "{}{}", content, self.version_suffix) } else { // No newline at end, just pass through - write!(self.inner, "{}", s) + write!(self.inner, "{s}") } } } From cd90229fd06e4d1661ce098eb591df5dc2e9b0ab Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 12 Aug 2025 10:07:42 +0200 Subject: [PATCH 042/100] remove unused dependency --- Cargo.lock | 1 - crates/defguard/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f6f8d87c0..d1ad901611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1071,7 +1071,6 @@ dependencies = [ "secrecy", "tokio", "tracing", - "tracing-subscriber", ] [[package]] diff --git a/crates/defguard/Cargo.toml b/crates/defguard/Cargo.toml index 87a445f7f9..d99b793b97 100644 --- a/crates/defguard/Cargo.toml +++ b/crates/defguard/Cargo.toml @@ -21,4 +21,3 @@ secrecy = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } bytes = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } From 8c342d68d55ce26fea2b975fc8cd595259bbdd29 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 13 Aug 2025 07:26:16 +0200 Subject: [PATCH 043/100] rename VersionPrefixFormat -> VersionSuffixFormat --- crates/defguard_version/src/tracing.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 877f772638..c06e5636d4 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -11,12 +11,12 @@ use crate::SystemInfo; /// Custom tracing formatter that conditionally includes version information in log messages. /// -/// This formatter wraps the default tracing formatter and adds version prefixes to log messages: -/// - For ERROR level logs: includes core_version, proxy_version, and proxy_info (if available) -/// - For other levels: includes only core_version and proxy_version (if available) +/// This formatter wraps the default tracing formatter and adds version suffix to log messages: +/// - For ERROR level logs: includes own_version, own_info and components version and info +/// - For other levels: includes only own_version and proxy_version (if available) /// /// The version information is extracted from tracing span fields. -struct VersionPrefixFormat { +struct VersionSuffixFormat { /// The underlying tracing formatter inner: tracing_subscriber::fmt::format::Format, /// The core application version to display as fallback @@ -45,7 +45,7 @@ where } } -impl FormatEvent for VersionPrefixFormat +impl FormatEvent for VersionSuffixFormat where S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, N: for<'a> FormatFields<'a> + 'static, @@ -307,10 +307,10 @@ pub fn init(own_version: &str, log_level: &str) { .with(VersionFieldLayer) // Add our custom layer to capture span fields .with( tracing_subscriber::fmt::layer() - .with_ansi(true) // Enable ANSI colors at layer level - .event_format(VersionPrefixFormat { + .with_ansi(true) + .event_format(VersionSuffixFormat { inner: tracing_subscriber::fmt::format::Format::default() - .with_ansi(true), // Enable ANSI colors at format level + .with_ansi(true), own_version: own_version.to_string(), own_info: SystemInfo::get(), }) From 6432ccdda30b1ac7666e4c9ac3765959a81f50be Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 13 Aug 2025 08:17:40 +0200 Subject: [PATCH 044/100] use interceptor instead of tower service for client-side --- Cargo.lock | 1 - crates/defguard_core/Cargo.toml | 1 - crates/defguard_core/src/grpc/mod.rs | 11 +-- crates/defguard_version/src/client.rs | 130 +++++++++----------------- 4 files changed, 50 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1ad901611..944305fdc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,7 +1139,6 @@ dependencies = [ "tonic-health", "tonic-middleware", "totp-lite", - "tower 0.5.2", "tower-http", "tracing", "tracing-subscriber", diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index 81f3c814b4..bc362c2ad4 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -86,7 +86,6 @@ strum_macros = { workspace = true } bytes = { workspace = true } ed25519-dalek = "2.2.0" tonic-middleware = "0.2" -tower = "0.5.2" # https://github.com/juhaku/utoipa/issues/1345 [dependencies.zip] diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 1b486a7d44..b268152ddf 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,6 +1,7 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - SystemInfo, client::DefguardVersionClientLayer, parse_metadata, + SystemInfo, parse_metadata, + client::version_interceptor, server::DefguardVersionServerMiddleware, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; @@ -34,7 +35,6 @@ use tonic::{ transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, }; use tonic_middleware::MiddlewareFor; -use tower::ServiceBuilder; use utoipa::ToSchema; use uuid::Uuid; @@ -884,11 +884,8 @@ pub async fn run_grpc_bidi_stream( loop { debug!("Connecting to proxy at {}", endpoint.uri()); - let version_layer = DefguardVersionClientLayer::new(VERSION)?; - let channel = ServiceBuilder::new() - .layer(version_layer) - .service(endpoint.connect_lazy()); - let mut client = ProxyClient::new(channel); + let interceptor = version_interceptor(VERSION); + let mut client = ProxyClient::with_interceptor(endpoint.connect_lazy(), interceptor); let (tx, rx) = mpsc::unbounded_channel(); let Ok(response) = client.bidi(UnboundedReceiverStream::new(rx)).await else { error!( diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 15b6d15faf..eddf8950f0 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -1,86 +1,48 @@ -use http::{Request, Response}; -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; -use tonic::body::BoxBody; -use tower::{Layer, Service}; - -use crate::{ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; - -pub type BoxFuture<'a, T> = Pin + Send + 'a>>; - -/// Layer for adding version information to outgoing gRPC requests (client-side) -#[derive(Clone)] -pub struct DefguardVersionClientLayer { - component_info: ComponentInfo, -} - -impl DefguardVersionClientLayer { - pub fn new(version: &str) -> Result { - Ok(Self { - component_info: ComponentInfo::new(version)?, - }) - } -} - -impl Layer for DefguardVersionClientLayer { - type Service = DefguardVersionClientService; - - fn layer(&self, inner: S) -> Self::Service { - DefguardVersionClientService { - inner, - component_info: self.component_info.clone(), - } - } -} - -/// Service that adds version metadata to outgoing requests and reads version info from responses -#[derive(Clone)] -pub struct DefguardVersionClientService { - inner: S, - component_info: ComponentInfo, -} - -impl Service> for DefguardVersionClientService -where - S: Service, Response = Response> + Clone + Send + 'static, - S::Future: Send + 'static, - S::Error: Into>, -{ - type Response = Response; - type Error = Box; - type Future = BoxFuture<'static, Result>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx).map_err(Into::into) - } - - fn call(&mut self, mut request: Request) -> Self::Future { - // add version and system info headers - request.headers_mut().insert( - VERSION_HEADER, - self.component_info - .version - .to_string() - .parse() - .expect("Failed to parse SemanticVersion as HeaderValue"), - ); - request.headers_mut().insert( - SYSTEM_INFO_HEADER, - self.component_info - .system - .as_header_value() - .parse() - .expect("Failed to parse SystemInfo as HeaderValue"), - ); - - // send the request - let response_future = self.inner.call(request); - Box::pin(async move { - let response = response_future.await.map_err(Into::into)?; - Ok(response) - }) +use tonic::{Request, Status}; +use tracing::warn; + +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; + +/// Adds version and system-info headers to outgoing requests +/// +/// # Examples +/// ```rust +/// use defguard_version::client::version_interceptor; +/// let interceptor = version_interceptor("1.0.0"); +/// let client = MyClient::with_interceptor(channel, interceptor); +/// ``` +pub fn version_interceptor( + version: &str, +) -> impl Fn(Request<()>) -> Result, Status> + Clone { + let component_info = ComponentInfo::new(version); + if let Err(ref err) = component_info { + warn!("Failed to get component info: {err}"); + }; + let component_info = component_info.ok(); + + move |mut request: Request<()>| -> Result, Status> { + let Some(component_info) = &component_info else { + return Ok(request); + }; + + let metadata = request.metadata_mut(); + + // Add version header + let version_value = component_info + .version + .to_string() + .parse() + .map_err(|_| Status::internal("Failed to parse version as metadata value"))?; + metadata.insert(VERSION_HEADER, version_value); + + // Add system info header + let system_info_value = component_info + .system + .as_header_value() + .parse() + .map_err(|_| Status::internal("Failed to parse system info as metadata value"))?; + metadata.insert(SYSTEM_INFO_HEADER, system_info_value); + + Ok(request) } } From dc7e5d403ec55b4179690cd47048f580fc020c37 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 13 Aug 2025 08:24:21 +0200 Subject: [PATCH 045/100] don't fail on version parsing issues --- crates/defguard_version/src/client.rs | 28 +++++++++++---------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index eddf8950f0..6db4a61ea7 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -14,11 +14,9 @@ use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; pub fn version_interceptor( version: &str, ) -> impl Fn(Request<()>) -> Result, Status> + Clone { - let component_info = ComponentInfo::new(version); - if let Err(ref err) = component_info { - warn!("Failed to get component info: {err}"); - }; - let component_info = component_info.ok(); + let component_info = ComponentInfo::new(version) + .inspect_err(|err| warn!("Failed to get component info: {err}")) + .ok(); move |mut request: Request<()>| -> Result, Status> { let Some(component_info) = &component_info else { @@ -28,20 +26,16 @@ pub fn version_interceptor( let metadata = request.metadata_mut(); // Add version header - let version_value = component_info - .version - .to_string() - .parse() - .map_err(|_| Status::internal("Failed to parse version as metadata value"))?; - metadata.insert(VERSION_HEADER, version_value); + match component_info.version.to_string().parse() { + Ok(version_value) => { metadata.insert(VERSION_HEADER, version_value); } + Err(err) => warn!("Failed to parse version: {err}"), + } // Add system info header - let system_info_value = component_info - .system - .as_header_value() - .parse() - .map_err(|_| Status::internal("Failed to parse system info as metadata value"))?; - metadata.insert(SYSTEM_INFO_HEADER, system_info_value); + match component_info.system.as_header_value().parse() { + Ok(system_info_value) => { metadata.insert(SYSTEM_INFO_HEADER, system_info_value); } + Err(err) => warn!("Failed to parse system info: {err}"), + } Ok(request) } From 53d55920f4448be3c05871c970a5783bf4f20ec2 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 13 Aug 2025 10:22:27 +0200 Subject: [PATCH 046/100] make structs and methods accessible from external crates --- crates/defguard_version/src/lib.rs | 6 ++--- crates/defguard_version/src/tracing.rs | 36 +++++++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 67d51569d5..7533566b2f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -8,8 +8,8 @@ pub mod client; pub mod server; pub mod tracing; -static VERSION_HEADER: &str = "dfg-version"; -static SYSTEM_INFO_HEADER: &str = "dfg-system-info"; +pub static VERSION_HEADER: &str = "dfg-version"; +pub static SYSTEM_INFO_HEADER: &str = "dfg-system-info"; #[derive(Debug, Error)] pub enum DefguardVersionError { @@ -43,7 +43,7 @@ impl Display for SystemInfo { } impl SystemInfo { - fn get() -> Self { + pub fn get() -> Self { os_info::get().into() } diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index c06e5636d4..b0b2b6241a 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -16,16 +16,16 @@ use crate::SystemInfo; /// - For other levels: includes only own_version and proxy_version (if available) /// /// The version information is extracted from tracing span fields. -struct VersionSuffixFormat { +pub struct VersionSuffixFormat { /// The underlying tracing formatter - inner: tracing_subscriber::fmt::format::Format, + pub inner: tracing_subscriber::fmt::format::Format, /// The core application version to display as fallback - own_version: String, - own_info: SystemInfo, + pub own_version: String, + pub own_info: SystemInfo, } /// A layer that captures version fields from spans and stores them for use by the formatter -struct VersionFieldLayer; +pub struct VersionFieldLayer; impl Layer for VersionFieldLayer where @@ -160,13 +160,13 @@ where } /// A wrapper writer that appends version suffix before newlines -struct VersionSuffixWriter<'a> { +pub struct VersionSuffixWriter<'a> { inner: Writer<'a>, version_suffix: String, } impl<'a> VersionSuffixWriter<'a> { - fn new(inner: Writer<'a>, version_suffix: String) -> Self { + pub fn new(inner: Writer<'a>, version_suffix: String) -> Self { Self { inner, version_suffix } } } @@ -185,13 +185,13 @@ impl<'a> std::fmt::Write for VersionSuffixWriter<'a> { /// A visitor that extracts version fields from spans #[derive(Default, Clone)] -struct SpanFieldVisitor { - core_version: Option, - core_info: Option, - proxy_version: Option, - proxy_info: Option, - gateway_version: Option, - gateway_info: Option, +pub struct SpanFieldVisitor { + pub core_version: Option, + pub core_info: Option, + pub proxy_version: Option, + pub proxy_info: Option, + pub gateway_version: Option, + pub gateway_info: Option, } impl tracing::field::Visit for SpanFieldVisitor { @@ -221,7 +221,7 @@ impl tracing::field::Visit for SpanFieldVisitor { } /// Custom field formatter that filters out version fields to prevent duplication -struct VersionFilteredFields; +pub struct VersionFilteredFields; impl<'writer> FormatFields<'writer> for VersionFilteredFields { fn format_fields( @@ -236,13 +236,13 @@ impl<'writer> FormatFields<'writer> for VersionFilteredFields { } /// Field visitor that skips version-related fields -struct FieldFilterVisitor<'writer> { +pub struct FieldFilterVisitor<'writer> { writer: Writer<'writer>, first: bool, } impl<'writer> FieldFilterVisitor<'writer> { - fn new(writer: Writer<'writer>) -> Self { + pub fn new(writer: Writer<'writer>) -> Self { Self { writer, first: true } } } @@ -304,7 +304,7 @@ pub fn init(own_version: &str, log_level: &str) { tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| format!("{log_level},h2=info").into()), ) - .with(VersionFieldLayer) // Add our custom layer to capture span fields + .with(VersionFieldLayer) // Add custom layer to capture span fields .with( tracing_subscriber::fmt::layer() .with_ansi(true) From 0d8e6d5878331d16eafbce70dbedcae62773c162 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 13 Aug 2025 10:40:14 +0200 Subject: [PATCH 047/100] expose commonly used functions to avoid code duplication --- crates/defguard_version/src/tracing.rs | 230 +++++++++++++++---------- 1 file changed, 139 insertions(+), 91 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index b0b2b6241a..545cb4f9f9 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,14 +1,147 @@ use tracing::{Level, Subscriber}; use tracing_subscriber::{ Layer, - fmt::{FormatEvent, FormatFields, format::Writer}, + fmt::{FormatEvent, FormatFields, format::Writer, FmtContext}, layer::{Context, SubscriberExt}, util::SubscriberInitExt, field::RecordFields, + registry::LookupSpan, }; use crate::SystemInfo; +/// Extracted version information from span context +#[derive(Debug, Default, Clone)] +pub struct ExtractedVersionInfo { + pub core_version: Option, + pub core_info: Option, + pub proxy_version: Option, + pub proxy_info: Option, + pub gateway_version: Option, + pub gateway_info: Option, +} + +impl ExtractedVersionInfo { + /// Check if any version information is present + pub fn has_version_info(&self) -> bool { + self.core_version.is_some() || self.proxy_version.is_some() || self.gateway_version.is_some() + } +} + +/// Extract version information from current span context +/// +/// This function walks up the span hierarchy and extracts version information +/// from span extensions that were stored by VersionFieldLayer. +/// +/// # Arguments +/// * `ctx` - The format context from the tracing formatter +/// +/// # Returns +/// An `ExtractedVersionInfo` struct containing all version information found in the span hierarchy +pub fn extract_version_info_from_context( + ctx: &FmtContext<'_, S, N>, +) -> ExtractedVersionInfo +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + let mut extracted = ExtractedVersionInfo::default(); + + if let Some(span_ref) = ctx.lookup_current() { + let mut current_span = Some(span_ref); + while let Some(span) = current_span { + let extensions = span.extensions(); + + if let Some(stored_visitor) = extensions.get::() { + if extracted.core_version.is_none() && stored_visitor.core_version.is_some() { + extracted.core_version = stored_visitor.core_version.clone(); + extracted.core_info = stored_visitor.core_info.clone(); + } + if extracted.proxy_version.is_none() && stored_visitor.proxy_version.is_some() { + extracted.proxy_version = stored_visitor.proxy_version.clone(); + extracted.proxy_info = stored_visitor.proxy_info.clone(); + } + if extracted.gateway_version.is_none() && stored_visitor.gateway_version.is_some() { + extracted.gateway_version = stored_visitor.gateway_version.clone(); + extracted.gateway_info = stored_visitor.gateway_info.clone(); + } + } + + current_span = span.parent(); + } + } + + extracted +} + +/// Build a version suffix string based on extracted version info +/// +/// # Arguments +/// * `extracted` - The extracted version information +/// * `own_version` - The application's own version +/// * `own_info` - The application's own system info +/// * `is_error` - Whether this is for an ERROR level log +/// +/// # Returns +/// A formatted string containing version information suitable for appending to log lines +pub fn build_version_suffix( + extracted: &ExtractedVersionInfo, + own_version: &str, + own_info: &SystemInfo, + is_error: bool, +) -> String { + let mut version_suffix = String::new(); + let is_versioned_span = extracted.has_version_info(); + + if is_versioned_span || is_error { + // Own version + let mut own_version_str = format!(" [{own_version}"); + if is_error { + own_version_str = format!("{own_version_str} {own_info}"); + } + own_version_str = format!("{own_version_str}]"); + version_suffix.push_str(&own_version_str); + } + + // Core version + if let Some(ref core_version) = extracted.core_version { + let mut core_version_str = format!("[C:{core_version}"); + if is_error { + if let Some(ref core_info) = extracted.core_info { + core_version_str = format!("{core_version_str} {core_info}"); + } + } + core_version_str = format!("{core_version_str}]"); + version_suffix.push_str(&core_version_str); + } + + // Proxy version + if let Some(ref proxy_version) = extracted.proxy_version { + let mut proxy_version_str = format!("[PX:{proxy_version}"); + if is_error { + if let Some(ref proxy_info) = extracted.proxy_info { + proxy_version_str = format!("{proxy_version_str} {proxy_info}"); + } + } + proxy_version_str = format!("{proxy_version_str}]"); + version_suffix.push_str(&proxy_version_str); + } + + // Gateway version + if let Some(ref gateway_version) = extracted.gateway_version { + let mut gateway_version_str = format!("[GW:{gateway_version}"); + if is_error { + if let Some(ref gateway_info) = extracted.gateway_info { + gateway_version_str = format!("{gateway_version_str} {gateway_info}"); + } + } + gateway_version_str = format!("{gateway_version_str}]"); + version_suffix.push_str(&gateway_version_str); + } + + version_suffix +} + /// Custom tracing formatter that conditionally includes version information in log messages. /// /// This formatter wraps the default tracing formatter and adds version suffix to log messages: @@ -61,97 +194,12 @@ where writer: Writer<'_>, event: &tracing::Event<'_>, ) -> std::fmt::Result { - // Extract version information from current span context - let mut core_version = None; - let mut core_info = None; - let mut proxy_version = None; - let mut proxy_info = None; - let mut gateway_version = None; - let mut gateway_info = None; - - if let Some(span_ref) = ctx.lookup_current() { - let mut current_span = Some(span_ref); - while let Some(span) = current_span { - let extensions = span.extensions(); - - // Try to get stored visitor from span extensions - if let Some(stored_visitor) = extensions.get::() { - if core_version.is_none() && stored_visitor.core_version.is_some() { - core_version = stored_visitor.core_version.clone(); - } - if core_info.is_none() && stored_visitor.core_info.is_some() { - core_info = stored_visitor.core_info.clone(); - } - if proxy_version.is_none() && stored_visitor.proxy_version.is_some() { - proxy_version = stored_visitor.proxy_version.clone(); - } - if proxy_info.is_none() && stored_visitor.proxy_info.is_some() { - proxy_info = stored_visitor.proxy_info.clone(); - } - if gateway_version.is_none() && stored_visitor.gateway_version.is_some() { - gateway_version = stored_visitor.gateway_version.clone(); - } - if gateway_info.is_none() && stored_visitor.gateway_info.is_some() { - gateway_info = stored_visitor.gateway_info.clone(); - } - } - - current_span = span.parent(); - } - } - - - // Build version suffix - let mut version_suffix = String::new(); - let is_versioned_span = - core_version.is_some() || proxy_version.is_some() || gateway_version.is_some(); + // Extract version information from current span context using utility function + let extracted = extract_version_info_from_context(ctx); + + // Build version suffix using utility function let is_error = *event.metadata().level() == Level::ERROR; - - if is_versioned_span || is_error { - // Own version - let mut own_version_str = format!(" [{}", self.own_version); - if is_error { - own_version_str = format!("{own_version_str} {}", self.own_info); - } - own_version_str = format!("{own_version_str}]"); - version_suffix.push_str(&own_version_str); - } - - // Core version - if let Some(ref core_version) = core_version { - let mut core_version_str = format!("[C:{core_version}"); - if is_error { - if let Some(ref core_info) = core_info { - core_version_str = format!("{core_version_str} {core_info}"); - } - } - core_version_str = format!("{core_version_str}]"); - version_suffix.push_str(&core_version_str); - } - - // Proxy version - if let Some(ref proxy_version) = proxy_version { - let mut proxy_version_str = format!("[PX:{proxy_version}"); - if is_error { - if let Some(ref proxy_info) = proxy_info { - proxy_version_str = format!("{proxy_version_str} {proxy_info}"); - } - } - proxy_version_str = format!("{proxy_version_str}]"); - version_suffix.push_str(&proxy_version_str); - } - - // Gateway version - if let Some(ref gateway_version) = gateway_version { - let mut gateway_version_str = format!("[GW:{gateway_version}"); - if is_error { - if let Some(ref gateway_info) = gateway_info { - gateway_version_str = format!("{gateway_version_str} {gateway_info}"); - } - } - gateway_version_str = format!("{gateway_version_str}]"); - version_suffix.push_str(&gateway_version_str); - } + let version_suffix = build_version_suffix(&extracted, &self.own_version, &self.own_info, is_error); // Create a wrapper writer that will append version info before newlines let mut wrapper = VersionSuffixWriter::new(writer, version_suffix); From 6bbeb80a2273636e8b90f6419eb24dfd761ff269 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 13 Aug 2025 11:08:09 +0200 Subject: [PATCH 048/100] fix biometric_auth_down migration --- migrations/20250731063659_biometric_auth.down.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/migrations/20250731063659_biometric_auth.down.sql b/migrations/20250731063659_biometric_auth.down.sql index dfb981387f..a722c24c32 100644 --- a/migrations/20250731063659_biometric_auth.down.sql +++ b/migrations/20250731063659_biometric_auth.down.sql @@ -1,2 +1 @@ DROP TABLE IF EXISTS biometric_auth; -DROP CONSTRAINT biometric_auth_device; From b671562dd67133409ff8d1699dbaec26da31d2cf Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 13 Aug 2025 11:31:54 +0200 Subject: [PATCH 049/100] remove unwrap --- crates/defguard_core/src/grpc/mod.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index b268152ddf..b570c83211 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,12 +1,10 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - SystemInfo, parse_metadata, - client::version_interceptor, + client::version_interceptor, parse_metadata, server::DefguardVersionServerMiddleware, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; -use semver::Version; use serde::Serialize; #[cfg(feature = "worker")] use sqlx::PgPool; @@ -490,13 +488,13 @@ struct ProxyMessageLoopContext<'a> { name = "proxy_message_loop", skip_all, fields( - proxy_version = %proxy_version, - proxy_info = %proxy_info, + proxy_version = proxy_version, + proxy_info = proxy_info, ) )] async fn handle_proxy_message_loop( - proxy_version: &Version, - proxy_info: &SystemInfo, + proxy_version: &str, + proxy_info: &str, context: ProxyMessageLoopContext<'_>, ) -> Result<(), anyhow::Error> { 'message: loop { @@ -895,7 +893,10 @@ pub async fn run_grpc_bidi_stream( sleep(TEN_SECS).await; continue; }; - let (version, info) = parse_metadata(response.metadata()).unwrap(); + let (version, info) = parse_metadata(response.metadata()).map_or( + ("unknown".to_string(), "unknown".to_string()), + |(version, info)| (version.to_string(), info.to_string()), + ); info!("Connected to proxy at {}", endpoint.uri()); let mut resp_stream = response.into_inner(); From 317154b6a787f14a3be49bfa5a61503d99b3c106 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 14 Aug 2025 08:19:46 +0200 Subject: [PATCH 050/100] version info from metadata function --- crates/defguard_core/src/grpc/gateway/mod.rs | 20 ++++++++++---------- crates/defguard_core/src/grpc/mod.rs | 8 ++------ crates/defguard_version/src/lib.rs | 6 ++++++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 416e9fb145..f570e5760e 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -8,7 +8,7 @@ use std::{ use chrono::{DateTime, TimeDelta, Utc}; use client_state::ClientMap; -use defguard_version::parse_metadata; +use defguard_version::version_info_from_metadata; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query}; use thiserror::Error; use tokio::{ @@ -724,15 +724,15 @@ impl gateway_service_server::GatewayService for GatewayServer { ) -> Result, Status> { let metadata = request.metadata(); let network_id = Self::get_network_id(metadata)?; - let (version, info) = parse_metadata(metadata).unwrap(); + let (version, info) = version_info_from_metadata(metadata); let gateway_hostname = Self::get_gateway_hostname(metadata)?; let mut stream = request.into_inner(); let mut disconnect_timer = interval(Duration::from_secs(PEER_DISCONNECT_INTERVAL)); let span = tracing::info_span!( "gateway_stats", - gateway_version = %version, - gateway_info = %info, + gateway_version = version, + gateway_info = info, ); let _guard = span.enter(); loop { @@ -896,11 +896,11 @@ impl gateway_service_server::GatewayService for GatewayServer { let metadata = request.metadata(); let network_id = Self::get_network_id(metadata)?; let hostname = Self::get_gateway_hostname(metadata)?; - let (version, info) = parse_metadata(metadata).unwrap(); + let (version, info) = version_info_from_metadata(metadata); let span = tracing::info_span!( "gateway_config", - gateway_version = %version, - gateway_info = %info, + gateway_version = version, + gateway_info = info, ); let _guard = span.enter(); @@ -976,11 +976,11 @@ impl gateway_service_server::GatewayService for GatewayServer { let metadata = request.metadata(); let gateway_network_id = Self::get_network_id(metadata)?; let hostname = Self::get_gateway_hostname(metadata)?; - let (version, info) = parse_metadata(metadata).unwrap(); + let (version, info) = version_info_from_metadata(metadata); let span = tracing::info_span!( "gateway_updates", - gateway_version = %version, - gateway_info = %info, + gateway_version = version, + gateway_info = info, ); let _guard = span.enter(); diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index b570c83211..1216817af0 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,7 +1,6 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - client::version_interceptor, parse_metadata, - server::DefguardVersionServerMiddleware, + client::version_interceptor, server::DefguardVersionServerMiddleware, version_info_from_metadata, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -893,10 +892,7 @@ pub async fn run_grpc_bidi_stream( sleep(TEN_SECS).await; continue; }; - let (version, info) = parse_metadata(response.metadata()).map_or( - ("unknown".to_string(), "unknown".to_string()), - |(version, info)| (version.to_string(), info.to_string()), - ); + let (version, info) = version_info_from_metadata(response.metadata()); info!("Connected to proxy at {}", endpoint.uri()); let mut resp_stream = response.into_inner(); diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 7533566b2f..4cf931f53e 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -123,3 +123,9 @@ pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { Some((version, info)) } + +pub fn version_info_from_metadata(metadata: &MetadataMap) -> (String, String) { + parse_metadata(metadata).map_or(("?".to_string(), "?".to_string()), |(version, info)| { + (version.to_string(), info.to_string()) + }) +} From a39390fb8501dc224ba34667e82d077e5db638a5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 14 Aug 2025 08:51:31 +0200 Subject: [PATCH 051/100] fmt --- crates/defguard_core/src/grpc/mod.rs | 3 +- crates/defguard_version/src/client.rs | 8 +++- crates/defguard_version/src/tracing.rs | 53 +++++++++++++++----------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 1216817af0..9ff64f0fa4 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,6 +1,7 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - client::version_interceptor, server::DefguardVersionServerMiddleware, version_info_from_metadata, + client::version_interceptor, server::DefguardVersionServerMiddleware, + version_info_from_metadata, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 6db4a61ea7..ea5ecaa3c4 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -27,13 +27,17 @@ pub fn version_interceptor( // Add version header match component_info.version.to_string().parse() { - Ok(version_value) => { metadata.insert(VERSION_HEADER, version_value); } + Ok(version_value) => { + metadata.insert(VERSION_HEADER, version_value); + } Err(err) => warn!("Failed to parse version: {err}"), } // Add system info header match component_info.system.as_header_value().parse() { - Ok(system_info_value) => { metadata.insert(SYSTEM_INFO_HEADER, system_info_value); } + Ok(system_info_value) => { + metadata.insert(SYSTEM_INFO_HEADER, system_info_value); + } Err(err) => warn!("Failed to parse system info: {err}"), } diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 545cb4f9f9..26d203061d 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,11 +1,11 @@ use tracing::{Level, Subscriber}; use tracing_subscriber::{ Layer, - fmt::{FormatEvent, FormatFields, format::Writer, FmtContext}, - layer::{Context, SubscriberExt}, - util::SubscriberInitExt, field::RecordFields, + fmt::{FmtContext, FormatEvent, FormatFields, format::Writer}, + layer::{Context, SubscriberExt}, registry::LookupSpan, + util::SubscriberInitExt, }; use crate::SystemInfo; @@ -24,23 +24,23 @@ pub struct ExtractedVersionInfo { impl ExtractedVersionInfo { /// Check if any version information is present pub fn has_version_info(&self) -> bool { - self.core_version.is_some() || self.proxy_version.is_some() || self.gateway_version.is_some() + self.core_version.is_some() + || self.proxy_version.is_some() + || self.gateway_version.is_some() } } /// Extract version information from current span context -/// -/// This function walks up the span hierarchy and extracts version information +/// +/// This function walks up the span hierarchy and extracts version information /// from span extensions that were stored by VersionFieldLayer. -/// +/// /// # Arguments /// * `ctx` - The format context from the tracing formatter -/// +/// /// # Returns /// An `ExtractedVersionInfo` struct containing all version information found in the span hierarchy -pub fn extract_version_info_from_context( - ctx: &FmtContext<'_, S, N>, -) -> ExtractedVersionInfo +pub fn extract_version_info_from_context(ctx: &FmtContext<'_, S, N>) -> ExtractedVersionInfo where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'a> FormatFields<'a> + 'static, @@ -75,13 +75,13 @@ where } /// Build a version suffix string based on extracted version info -/// +/// /// # Arguments /// * `extracted` - The extracted version information /// * `own_version` - The application's own version /// * `own_info` - The application's own system info /// * `is_error` - Whether this is for an ERROR level log -/// +/// /// # Returns /// A formatted string containing version information suitable for appending to log lines pub fn build_version_suffix( @@ -196,14 +196,16 @@ where ) -> std::fmt::Result { // Extract version information from current span context using utility function let extracted = extract_version_info_from_context(ctx); - + // Build version suffix using utility function let is_error = *event.metadata().level() == Level::ERROR; - let version_suffix = build_version_suffix(&extracted, &self.own_version, &self.own_info, is_error); + let version_suffix = + build_version_suffix(&extracted, &self.own_version, &self.own_info, is_error); // Create a wrapper writer that will append version info before newlines let mut wrapper = VersionSuffixWriter::new(writer, version_suffix); - self.inner.format_event(ctx, Writer::new(&mut wrapper), event) + self.inner + .format_event(ctx, Writer::new(&mut wrapper), event) } } @@ -215,7 +217,10 @@ pub struct VersionSuffixWriter<'a> { impl<'a> VersionSuffixWriter<'a> { pub fn new(inner: Writer<'a>, version_suffix: String) -> Self { - Self { inner, version_suffix } + Self { + inner, + version_suffix, + } } } @@ -291,14 +296,18 @@ pub struct FieldFilterVisitor<'writer> { impl<'writer> FieldFilterVisitor<'writer> { pub fn new(writer: Writer<'writer>) -> Self { - Self { writer, first: true } + Self { + writer, + first: true, + } } } impl<'writer> tracing::field::Visit for FieldFilterVisitor<'writer> { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { - "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" | "gateway_info" => { + "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" + | "gateway_info" => { // Skip version fields to prevent duplication } _ => { @@ -313,7 +322,8 @@ impl<'writer> tracing::field::Visit for FieldFilterVisitor<'writer> { fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { match field.name() { - "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" | "gateway_info" => { + "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" + | "gateway_info" => { // Skip version fields to prevent duplication } _ => { @@ -357,8 +367,7 @@ pub fn init(own_version: &str, log_level: &str) { tracing_subscriber::fmt::layer() .with_ansi(true) .event_format(VersionSuffixFormat { - inner: tracing_subscriber::fmt::format::Format::default() - .with_ansi(true), + inner: tracing_subscriber::fmt::format::Format::default().with_ansi(true), own_version: own_version.to_string(), own_info: SystemInfo::get(), }) From cdba82fa9b84919da0193acf5046749fffea8d84 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 10:26:36 +0200 Subject: [PATCH 052/100] bump tonic-middleware to 0.4 (compatible with tonic 0.14) --- Cargo.lock | 168 +++++--------------------- Cargo.toml | 1 + crates/defguard_core/Cargo.toml | 2 +- crates/defguard_version/Cargo.toml | 2 +- crates/defguard_version/src/server.rs | 7 +- 5 files changed, 34 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b18919d7a..b48d225c77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,40 +274,13 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core 0.4.5", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper", - "tower 0.5.2", - "tower-layer", - "tower-service", -] - [[package]] name = "axum" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "axum-core 0.5.2", + "axum-core", "bytes", "form_urlencoded", "futures-util", @@ -317,7 +290,7 @@ dependencies = [ "hyper", "hyper-util", "itoa", - "matchit 0.8.4", + "matchit", "memchr", "mime", "percent-encoding", @@ -329,7 +302,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", "tracing", @@ -341,31 +314,11 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff8ee1869817523c8f91c20bf17fd932707f66c2e7e0b0f811b29a227289562" dependencies = [ - "axum 0.8.4", + "axum", "forwarded-header-value", "serde", ] -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.5.2" @@ -392,8 +345,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" dependencies = [ - "axum 0.8.4", - "axum-core 0.5.2", + "axum", + "axum-core", "bytes", "cookie", "form_urlencoded", @@ -408,7 +361,7 @@ dependencies = [ "serde", "serde_html_form", "serde_path_to_error", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", ] @@ -1080,7 +1033,7 @@ version = "1.5.0" dependencies = [ "anyhow", "argon2", - "axum 0.8.4", + "axum", "axum-client-ip", "axum-extra", "base32", @@ -1106,7 +1059,7 @@ dependencies = [ "parse_link_header", "paste", "pgp", - "prost 0.14.1", + "prost", "pulldown-cmark", "rand 0.8.5", "rand_core 0.6.4", @@ -1134,7 +1087,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tonic 0.14.1", + "tonic", "tonic-health", "tonic-middleware", "tonic-prost", @@ -1190,9 +1143,9 @@ dependencies = [ "os_info", "semver", "thiserror 2.0.15", - "tonic 0.14.1", + "tonic", "tonic-middleware", - "tower 0.5.2", + "tower", "tracing", "tracing-subscriber", ] @@ -1201,7 +1154,7 @@ dependencies = [ name = "defguard_web_ui" version = "0.0.0" dependencies = [ - "axum 0.8.4", + "axum", "mime_guess", "rust-embed", ] @@ -2707,12 +2660,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "matchit" version = "0.8.4" @@ -3586,15 +3533,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" -dependencies = [ - "bytes", -] - [[package]] name = "prost" version = "0.14.1" @@ -3618,7 +3556,7 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.14.1", + "prost", "prost-types", "pulldown-cmark", "pulldown-cmark-to-cmark", @@ -3646,7 +3584,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" dependencies = [ - "prost 0.14.1", + "prost", ] [[package]] @@ -3958,7 +3896,7 @@ dependencies = [ "tokio-native-tls", "tokio-rustls", "tokio-util", - "tower 0.5.2", + "tower", "tower-http", "tower-service", "url", @@ -5245,36 +5183,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tonic" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.7.9", - "base64 0.22.1", - "bytes", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "prost 0.13.5", - "socket2 0.5.10", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tonic" version = "0.14.1" @@ -5282,7 +5190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" dependencies = [ "async-trait", - "axum 0.8.4", + "axum", "base64 0.22.1", "bytes", "flate2", @@ -5301,7 +5209,7 @@ dependencies = [ "tokio", "tokio-rustls", "tokio-stream", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", "tracing", @@ -5325,23 +5233,23 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8aa31379f0851de4f223496a469ffcaae24ad4ce736493179c3e42135e2af5c" dependencies = [ - "prost 0.14.1", + "prost", "tokio", "tokio-stream", - "tonic 0.14.1", + "tonic", "tonic-prost", ] [[package]] name = "tonic-middleware" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd11ca7918ee9f94e217285ace20caf6187476f399244ba8438cdc92ce665236" +checksum = "bd592855b0a0f96103af076c7523c5c51b75090ee31ff178cc7c024c06ac3566" dependencies = [ "async-trait", "futures-util", - "tonic 0.12.3", - "tower 0.4.13", + "tonic", + "tower", ] [[package]] @@ -5351,8 +5259,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9c511b9a96d40cb12b7d5d00464446acf3b9105fd3ce25437cfe41c92b1c87d" dependencies = [ "bytes", - "prost 0.14.1", - "tonic 0.14.1", + "prost", + "tonic", ] [[package]] @@ -5383,26 +5291,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.2" @@ -5444,7 +5332,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", "tracing", @@ -5754,7 +5642,7 @@ version = "9.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" dependencies = [ - "axum 0.8.4", + "axum", "base64 0.22.1", "mime_guess", "regex", diff --git a/Cargo.toml b/Cargo.toml index dba283fb33..a67bbcc336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ tokio-stream = "0.1" tokio-util = "0.7" tonic = { version = "0.14", features = ["gzip", "tls-native-roots"] } tonic-health = "0.14" +tonic-middleware = "0.4" totp-lite = { version = "2.0" } tower-http = { version = "0.6", features = ["fs", "trace"] } tracing = "0.1" diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index bccd63d9e5..c5817635b9 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -85,7 +85,7 @@ x25519-dalek = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } -tonic-middleware = "0.2" +tonic-middleware = { workspace = true } ed25519-dalek = { version = "2.2.0", features = ["rand_core"] } # https://github.com/juhaku/utoipa/issues/1345 diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml index 97790d9d1f..5f44bef5c2 100644 --- a/crates/defguard_version/Cargo.toml +++ b/crates/defguard_version/Cargo.toml @@ -13,7 +13,7 @@ thiserror.workspace = true tonic.workspace = true tracing.workspace = true tracing-subscriber.workspace = true -tonic-middleware = "0.2" +tonic-middleware.workspace = true http = "1.3.1" os_info = "3.12.0" tower = "0.5.2" diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index d4e0c18cbe..b806835657 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,6 +1,5 @@ use tonic::{ - async_trait, - body::BoxBody, + async_trait, body::Body, codegen::http::{Request, Response}, }; use tonic_middleware::{Middleware, ServiceBound}; @@ -28,9 +27,9 @@ where { async fn call( &self, - request: Request, + request: Request, mut service: S, - ) -> Result, S::Error> { + ) -> Result, S::Error> { let mut response = service.call(request).await?; response.headers_mut().insert( VERSION_HEADER, From 1289791f729dd7149903361e070f3bd8b36dcf29 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 11:30:56 +0200 Subject: [PATCH 053/100] remove tonic-middleware - does not work with our interceptors --- Cargo.lock | 14 -------------- Cargo.toml | 1 - crates/defguard_core/Cargo.toml | 1 - crates/defguard_version/Cargo.toml | 1 - 4 files changed, 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b48d225c77..8749b56e22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1089,7 +1089,6 @@ dependencies = [ "tokio-util", "tonic", "tonic-health", - "tonic-middleware", "tonic-prost", "tonic-prost-build", "totp-lite", @@ -1144,7 +1143,6 @@ dependencies = [ "semver", "thiserror 2.0.15", "tonic", - "tonic-middleware", "tower", "tracing", "tracing-subscriber", @@ -5240,18 +5238,6 @@ dependencies = [ "tonic-prost", ] -[[package]] -name = "tonic-middleware" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd592855b0a0f96103af076c7523c5c51b75090ee31ff178cc7c024c06ac3566" -dependencies = [ - "async-trait", - "futures-util", - "tonic", - "tower", -] - [[package]] name = "tonic-prost" version = "0.14.1" diff --git a/Cargo.toml b/Cargo.toml index a67bbcc336..dba283fb33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,6 @@ tokio-stream = "0.1" tokio-util = "0.7" tonic = { version = "0.14", features = ["gzip", "tls-native-roots"] } tonic-health = "0.14" -tonic-middleware = "0.4" totp-lite = { version = "2.0" } tower-http = { version = "0.6", features = ["fs", "trace"] } tracing = "0.1" diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index c5817635b9..cc1e4b544a 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -85,7 +85,6 @@ x25519-dalek = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } -tonic-middleware = { workspace = true } ed25519-dalek = { version = "2.2.0", features = ["rand_core"] } # https://github.com/juhaku/utoipa/issues/1345 diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml index 5f44bef5c2..a64c143789 100644 --- a/crates/defguard_version/Cargo.toml +++ b/crates/defguard_version/Cargo.toml @@ -13,7 +13,6 @@ thiserror.workspace = true tonic.workspace = true tracing.workspace = true tracing-subscriber.workspace = true -tonic-middleware.workspace = true http = "1.3.1" os_info = "3.12.0" tower = "0.5.2" From 4c42d09fdaafa11e18151b8a617afff076d1ff9f Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 12:32:51 +0200 Subject: [PATCH 054/100] rewrite server components - don't use tonic-middleware --- Cargo.lock | 1 + crates/defguard_core/Cargo.toml | 1 + crates/defguard_core/src/grpc/mod.rs | 13 ++-- crates/defguard_version/src/server.rs | 95 +++++++++++++++++++-------- 4 files changed, 77 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8749b56e22..72d53ac2e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1092,6 +1092,7 @@ dependencies = [ "tonic-prost", "tonic-prost-build", "totp-lite", + "tower", "tower-http", "tracing", "tracing-subscriber", diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index cc1e4b544a..62d908fb6d 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -86,6 +86,7 @@ strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } ed25519-dalek = { version = "2.2.0", features = ["rand_core"] } +tower = "0.5.2" # https://github.com/juhaku/utoipa/issues/1345 [dependencies.zip] diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 644afec75a..2d3ec05d6d 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,6 +1,6 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - client::version_interceptor, server::DefguardVersionServerMiddleware, + client::version_interceptor, server::DefguardVersionLayer, version_info_from_metadata, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; @@ -32,7 +32,7 @@ use tonic::{ Code, Status, Streaming, transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, }; -use tonic_middleware::MiddlewareFor; +use tower::ServiceBuilder; use utoipa::ToSchema; use uuid::Uuid; @@ -968,10 +968,11 @@ pub async fn run_grpc_server( .add_service(health_service) .add_service(auth_service); #[cfg(feature = "wireguard")] - let router = router.add_service(MiddlewareFor::new( - gateway_service, - DefguardVersionServerMiddleware::new(VERSION)?, - )); + let router = router.add_service( + ServiceBuilder::new() + .layer(DefguardVersionLayer::new(VERSION)?) + .service(gateway_service) + ); #[cfg(feature = "worker")] let router = router.add_service(worker_service); router.serve(addr).await?; diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index b806835657..85e50cec51 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,17 +1,23 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; use tonic::{ - async_trait, body::Body, + body::Body, codegen::http::{Request, Response}, + server::NamedService, }; -use tonic_middleware::{Middleware, ServiceBound}; +use tower::{Layer, Service}; use crate::{ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; #[derive(Clone)] -pub struct DefguardVersionServerMiddleware { +pub struct DefguardVersionLayer { component_info: ComponentInfo, } -impl DefguardVersionServerMiddleware { +impl DefguardVersionLayer { pub fn new(version: &str) -> Result { Ok(Self { component_info: ComponentInfo::new(version)?, @@ -19,30 +25,65 @@ impl DefguardVersionServerMiddleware { } } -#[async_trait] -impl Middleware for DefguardVersionServerMiddleware +impl Layer for DefguardVersionLayer { + type Service = DefguardVersionService; + + fn layer(&self, inner: S) -> Self::Service { + DefguardVersionService { + inner, + component_info: self.component_info.clone(), + } + } +} + +#[derive(Clone)] +pub struct DefguardVersionService { + inner: S, + component_info: ComponentInfo, +} + +impl Service> for DefguardVersionService where - S: ServiceBound, - S::Future: Send, + S: Service, Response = Response> + Clone + Send + 'static, + S::Future: Send + 'static, + S::Error: Send + 'static, { - async fn call( - &self, - request: Request, - mut service: S, - ) -> Result, S::Error> { - let mut response = service.call(request).await?; - response.headers_mut().insert( - VERSION_HEADER, - self.component_info.version.to_string().parse().unwrap(), - ); - response.headers_mut().insert( - SYSTEM_INFO_HEADER, - self.component_info - .system - .as_header_value() - .parse() - .unwrap(), - ); - Ok(response) + type Response = Response; + type Error = S::Error; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, request: Request) -> Self::Future { + let mut inner = self.inner.clone(); + let component_info = self.component_info.clone(); + + Box::pin(async move { + let mut response = inner.call(request).await?; + + response.headers_mut().insert( + VERSION_HEADER, + component_info.version.to_string().parse().unwrap(), + ); + response.headers_mut().insert( + SYSTEM_INFO_HEADER, + component_info + .system + .as_header_value() + .parse() + .unwrap(), + ); + + Ok(response) + }) } } + +impl NamedService for DefguardVersionService +where + S: NamedService, +{ + const NAME: &'static str = S::NAME; +} \ No newline at end of file From c1cc9e41e972167c9bcdfceb79105771f5e6361b Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 13:19:36 +0200 Subject: [PATCH 055/100] fix tls issue --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dba283fb33..e36f724714 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ tokio = { version = "1", features = [ ] } tokio-stream = "0.1" tokio-util = "0.7" -tonic = { version = "0.14", features = ["gzip", "tls-native-roots"] } +tonic = { version = "0.14", features = ["gzip", "tls-native-roots", "tls-ring"] } tonic-health = "0.14" totp-lite = { version = "2.0" } tower-http = { version = "0.6", features = ["fs", "trace"] } From 3ad01388a368b7144d8fb4d699f66d44b59553a1 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 13:31:04 +0200 Subject: [PATCH 056/100] fmt --- crates/defguard_core/src/grpc/mod.rs | 5 ++--- crates/defguard_version/src/server.rs | 12 ++++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 2d3ec05d6d..46e095ff6f 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,7 +1,6 @@ use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - client::version_interceptor, server::DefguardVersionLayer, - version_info_from_metadata, + client::version_interceptor, server::DefguardVersionLayer, version_info_from_metadata, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -971,7 +970,7 @@ pub async fn run_grpc_server( let router = router.add_service( ServiceBuilder::new() .layer(DefguardVersionLayer::new(VERSION)?) - .service(gateway_service) + .service(gateway_service), ); #[cfg(feature = "worker")] let router = router.add_service(worker_service); diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 85e50cec51..0b02921c15 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -62,20 +62,16 @@ where Box::pin(async move { let mut response = inner.call(request).await?; - + response.headers_mut().insert( VERSION_HEADER, component_info.version.to_string().parse().unwrap(), ); response.headers_mut().insert( SYSTEM_INFO_HEADER, - component_info - .system - .as_header_value() - .parse() - .unwrap(), + component_info.system.as_header_value().parse().unwrap(), ); - + Ok(response) }) } @@ -86,4 +82,4 @@ where S: NamedService, { const NAME: &'static str = S::NAME; -} \ No newline at end of file +} From 4d1be6e9415a9fc00a626a17a48ea3c7c1803a86 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 13:42:41 +0200 Subject: [PATCH 057/100] parse server version headers once --- crates/defguard_version/src/server.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 0b02921c15..df8d81e0dd 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,3 +1,4 @@ +use http::HeaderValue; use std::{ future::Future, pin::Pin, @@ -58,19 +59,25 @@ where fn call(&mut self, request: Request) -> Self::Future { let mut inner = self.inner.clone(); - let component_info = self.component_info.clone(); - + let parsed_info = ( + self.component_info + .version + .to_string() + .parse::() + .ok(), + self.component_info + .system + .as_header_value() + .parse::() + .ok(), + ); Box::pin(async move { let mut response = inner.call(request).await?; - response.headers_mut().insert( - VERSION_HEADER, - component_info.version.to_string().parse().unwrap(), - ); - response.headers_mut().insert( - SYSTEM_INFO_HEADER, - component_info.system.as_header_value().parse().unwrap(), - ); + if let (Some(version), Some(system)) = parsed_info { + response.headers_mut().insert(VERSION_HEADER, version); + response.headers_mut().insert(SYSTEM_INFO_HEADER, system); + } Ok(response) }) From d139c8295ed99f11c1a9026cec9d508c46668cd7 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 13:54:06 +0200 Subject: [PATCH 058/100] server comments --- crates/defguard_version/src/server.rs | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index df8d81e0dd..eeca5733c3 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -1,3 +1,27 @@ +//! Server-side middleware for adding Defguard version information to gRPC responses. +//! +//! This module provides a tower-based middleware layer that automatically adds version +//! and system information headers to all gRPC responses. The middleware is designed to +//! work with tonic's interceptor system and maintains compatibility with both regular +//! and intercepted services. +//! +//! # Headers Added +//! +//! - `dfg-version`: The semantic version of the Defguard component +//! - `dfg-system-info`: System information including OS type, version, bitness, and architecture +//! +//! # Usage +//! +//! ```rust,no_run +//! use tower::ServiceBuilder; +//! use defguard_version::server::DefguardVersionLayer; +//! +//! let version_layer = DefguardVersionLayer::new("1.0.0")?; +//! let service = ServiceBuilder::new() +//! .layer(version_layer) +//! .service(my_grpc_service); +//! ``` + use http::HeaderValue; use std::{ future::Future, @@ -13,12 +37,32 @@ use tower::{Layer, Service}; use crate::{ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; +/// A tower `Layer` that adds Defguard version and system information headers to gRPC responses. +/// +/// This layer wraps any service and ensures that all responses include version metadata +/// in HTTP headers. The layer is designed to be composable with other tower layers and +/// maintains the original service's `NamedService` implementation for tonic compatibility. +/// +/// # Fields +/// +/// * `component_info` - Contains version and system information that will be added to response +/// headers. #[derive(Clone)] pub struct DefguardVersionLayer { component_info: ComponentInfo, } impl DefguardVersionLayer { + /// Creates a new version layer with the specified version string. + /// + /// # Arguments + /// + /// * `version` - A semantic version string (e.g., "1.0.0") + /// + /// # Returns + /// + /// * `Ok(DefguardVersionLayer)` - A new layer instance + /// * `Err(DefguardVersionError)` - If the version string cannot be parsed pub fn new(version: &str) -> Result { Ok(Self { component_info: ComponentInfo::new(version)?, @@ -37,6 +81,20 @@ impl Layer for DefguardVersionLayer { } } +/// A tower `Service` that wraps another service and adds version headers to responses. +/// +/// This service is created by the `DefguardVersionLayer` and implements the actual +/// header injection logic. It maintains full compatibility with the wrapped service's +/// interface while adding the version metadata functionality. +/// +/// # Type Parameters +/// +/// * `S` - The inner service type being wrapped +/// +/// # Fields +/// +/// * `inner` - The wrapped service that handles the actual request processing +/// * `component_info` - Version and system information to be added to response headers #[derive(Clone)] pub struct DefguardVersionService { inner: S, @@ -54,11 +112,14 @@ where type Future = Pin> + Send>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + // Delegate readiness polling to the inner service self.inner.poll_ready(cx) } fn call(&mut self, request: Request) -> Self::Future { let mut inner = self.inner.clone(); + + // Pre-parse header values let parsed_info = ( self.component_info .version @@ -71,9 +132,13 @@ where .parse::() .ok(), ); + Box::pin(async move { + // Process the request with the inner service first let mut response = inner.call(request).await?; + // Add version headers only if both parsed successfully + // Prevents partial header injection in case of parsing errors if let (Some(version), Some(system)) = parsed_info { response.headers_mut().insert(VERSION_HEADER, version); response.headers_mut().insert(SYSTEM_INFO_HEADER, system); @@ -84,6 +149,11 @@ where } } +/// Implementation of `NamedService` that delegates to the inner service. +/// +/// This ensures that the wrapped service maintains its original service name +/// for tonic's service discovery and routing mechanisms. The version middleware +/// is transparent from the perspective of service identification. impl NamedService for DefguardVersionService where S: NamedService, From 979b982317c745419337d6d2aed8b83b7dd05a4d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 14:03:34 +0200 Subject: [PATCH 059/100] avoid redefining of tracing fields --- crates/defguard_core/src/grpc/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 46e095ff6f..3429a4b985 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -486,11 +486,7 @@ struct ProxyMessageLoopContext<'a> { #[instrument( name = "proxy_message_loop", - skip_all, - fields( - proxy_version = proxy_version, - proxy_info = proxy_info, - ) + skip(context), )] async fn handle_proxy_message_loop( proxy_version: &str, From d0d18788367983a5290c95dbffae324a8f213528 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 14:24:50 +0200 Subject: [PATCH 060/100] client docs --- crates/defguard_version/src/client.rs | 5 +++++ crates/defguard_version/src/server.rs | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index ea5ecaa3c4..37205901b4 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -5,6 +5,11 @@ use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; /// Adds version and system-info headers to outgoing requests /// +/// # Headers Added +/// +/// - `dfg-version`: The semantic version of the Defguard component +/// - `dfg-system-info`: System information including OS type, version, bitness, and architecture +/// /// # Examples /// ```rust /// use defguard_version::client::version_interceptor; diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index eeca5733c3..10026d01a6 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -137,8 +137,7 @@ where // Process the request with the inner service first let mut response = inner.call(request).await?; - // Add version headers only if both parsed successfully - // Prevents partial header injection in case of parsing errors + // Add version headers if let (Some(version), Some(system)) = parsed_info { response.headers_mut().insert(VERSION_HEADER, version); response.headers_mut().insert(SYSTEM_INFO_HEADER, system); From eccb395020203e59c49c97d2a6531be9f56fb581 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 14:49:34 +0200 Subject: [PATCH 061/100] lib comments --- crates/defguard_version/src/lib.rs | 165 +++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 4cf931f53e..59323a5b6f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -1,3 +1,59 @@ +//! Defguard version information handling for gRPC communications. +//! +//! This crate provides utilities for embedding and extracting version and system information +//! in gRPC communications between Defguard components. It supports both client-side and +//! server-side middleware for automatic version header management. +//! +//! # Headers +//! +//! The crate defines two standard headers used across all Defguard gRPC communications: +//! +//! - `dfg-version`: Semantic version string (e.g., "1.2.3") +//! - `dfg-system-info`: Semicolon-separated system information (OS;version;bitness;arch) +//! +//! # Usage +//! +//! ## Server-side middleware +//! +//! ```rust,no_run +//! use tower::ServiceBuilder; +//! use defguard_version::server::DefguardVersionLayer; +//! +//! let layer = DefguardVersionLayer::new("1.0.0")?; +//! let service = ServiceBuilder::new() +//! .layer(layer) +//! .service(my_grpc_service); +//! ``` +//! +//! ## Client-side interceptor +//! +//! ```rust,no_run +//! use defguard_version::client::version_interceptor; +//! use tonic::transport::Channel; +//! +//! let channel = Channel::from_static("http://localhost:50051").connect().await?; +//! let client = MyServiceClient::with_interceptor( +//! channel, +//! version_interceptor("1.0.0")? +//! ); +//! ``` +//! +//! ## Parsing version information +//! +//! ```rust,no_run +//! use defguard_version::{parse_metadata, version_info_from_metadata}; +//! use tonic::metadata::MetadataMap; +//! +//! // Extract parsed version and system info +//! if let Some((version, system_info)) = parse_metadata(&metadata) { +//! println!("Client version: {}", version); +//! println!("Client system: {}", system_info); +//! } +//! +//! // Get version info as strings (with fallback) +//! let (version_str, system_str) = version_info_from_metadata(&metadata); +//! ``` + use ::tracing::{error, warn}; use semver::Version; use std::{fmt::Display, str::FromStr}; @@ -8,7 +64,10 @@ pub mod client; pub mod server; pub mod tracing; +/// HTTP header name for the Defguard component version. pub static VERSION_HEADER: &str = "dfg-version"; + +/// HTTP header name for the Defguard system information. pub static SYSTEM_INFO_HEADER: &str = "dfg-system-info"; #[derive(Debug, Error)] @@ -20,6 +79,26 @@ pub enum DefguardVersionError { SystemInfoParseError(String), } +/// System information about the host running a Defguard component. +/// +/// This struct captures key system characteristics that are useful for +/// debugging, compatibility checking, and system analytics. The information +/// is automatically detected from the host system and can be serialized +/// into HTTP headers for transmission over gRPC. +/// +/// # Examples +/// +/// ```rust +/// use defguard_version::SystemInfo; +/// +/// // Get current system information +/// let info = SystemInfo::get(); +/// println!("Running on: {}", info); +/// +/// // Access individual fields +/// println!("OS: {} {}", info.os_type, info.os_version); +/// println!("Architecture: {} ({})", info.architecture, info.bitness); +/// ``` #[derive(Debug, Clone)] pub struct SystemInfo { /// The operating system type (e.g., "Linux", "Windows", "macOS") @@ -43,6 +122,12 @@ impl Display for SystemInfo { } impl SystemInfo { + /// Automatically detects the operating system type, version, architecture + /// and bitness using the `os_info` crate. + /// + /// # Returns + /// + /// A `SystemInfo` struct populated with the current system's characteristics. pub fn get() -> Self { os_info::get().into() } @@ -82,13 +167,40 @@ impl From for SystemInfo { } } +/// Combined version and system information for a Defguard component. +/// +/// This struct bundles together both the semantic version of a component +/// and the system information of the host it's running on. It's used by +/// middleware to generate the appropriate headers for gRPC communication. #[derive(Debug, Clone)] pub struct ComponentInfo { + /// The semantic version of the component pub version: Version, + /// System information about the host pub system: SystemInfo, } impl ComponentInfo { + /// This method parses the provided version string and automatically detects + /// the current system information. + /// + /// # Arguments + /// + /// * `version` - A semantic version string (e.g., "1.2.3") + /// + /// # Returns + /// + /// * `Ok(ComponentInfo)` - Successfully created component info + /// * `Err(DefguardVersionError)` - If version parsing fails + /// + /// # Examples + /// + /// ```rust + /// use defguard_version::ComponentInfo; + /// + /// let info = ComponentInfo::new("1.0.0")?; + /// assert_eq!(info.version.major, 1); + /// ``` pub fn new(version: &str) -> Result { let version = Version::from_str(version)?; let info = os_info::get(); @@ -99,6 +211,32 @@ impl ComponentInfo { } } +/// Parses version and system information from gRPC metadata headers. +/// +/// This function extracts and parses the Defguard version headers from +/// gRPC metadata, returning structured version and system information. +/// If any parsing step fails, warnings are logged and `None` is returned. +/// +/// # Arguments +/// +/// * `metadata` - The gRPC metadata map containing headers +/// +/// # Returns +/// +/// * `Some((Version, SystemInfo))` - Successfully parsed version information +/// * `None` - If headers are missing or parsing fails +/// +/// # Examples +/// +/// ```rust,no_run +/// use defguard_version::parse_metadata; +/// use tonic::metadata::MetadataMap; +/// +/// if let Some((version, system)) = parse_metadata(&metadata) { +/// println!("Peer version: {}", version); +/// println!("Peer system: {}", system); +/// } +/// ``` pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { let Some(version) = metadata.get(VERSION_HEADER) else { warn!("Missing version header"); @@ -124,6 +262,33 @@ pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { Some((version, info)) } +/// Extracts version information from metadata as formatted strings with fallback. +/// +/// This is a convenience function that calls `parse_metadata` internally and +/// returns the version and system information as strings. If parsing fails, +/// it returns "?" for both values instead of `None`. +/// +/// # Arguments +/// +/// * `metadata` - The gRPC metadata map containing headers +/// +/// # Returns +/// +/// A tuple containing: +/// * Version string (or "?" if parsing failed) +/// * System info string (or "?" if parsing failed) +/// +/// # Examples +/// +/// ```rust,no_run +/// use defguard_version::version_info_from_metadata; +/// use tonic::metadata::MetadataMap; +/// +/// let (version, system) = version_info_from_metadata(&metadata); +/// println!("Client: {} running on {}", version, system); +/// // Output might be: "Client: 1.2.3 running on Linux 22.04 64-bit x86_64" +/// // Or if headers missing: "Client: ? running on ?" +/// ``` pub fn version_info_from_metadata(metadata: &MetadataMap) -> (String, String) { parse_metadata(metadata).map_or(("?".to_string(), "?".to_string()), |(version, info)| { (version.to_string(), info.to_string()) From cfc3622b0d6bf7e8de2cf5f5161ca694a89f6d96 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 18 Aug 2025 14:57:40 +0200 Subject: [PATCH 062/100] cleanup tracing comments --- crates/defguard_version/src/tracing.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 26d203061d..dafd188af3 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -10,7 +10,6 @@ use tracing_subscriber::{ use crate::SystemInfo; -/// Extracted version information from span context #[derive(Debug, Default, Clone)] pub struct ExtractedVersionInfo { pub core_version: Option, @@ -22,7 +21,6 @@ pub struct ExtractedVersionInfo { } impl ExtractedVersionInfo { - /// Check if any version information is present pub fn has_version_info(&self) -> bool { self.core_version.is_some() || self.proxy_version.is_some() @@ -146,13 +144,12 @@ pub fn build_version_suffix( /// /// This formatter wraps the default tracing formatter and adds version suffix to log messages: /// - For ERROR level logs: includes own_version, own_info and components version and info -/// - For other levels: includes only own_version and proxy_version (if available) +/// - For other levels: includes only own_version and component version if available /// /// The version information is extracted from tracing span fields. pub struct VersionSuffixFormat { /// The underlying tracing formatter pub inner: tracing_subscriber::fmt::format::Format, - /// The core application version to display as fallback pub own_version: String, pub own_info: SystemInfo, } @@ -362,7 +359,7 @@ pub fn init(own_version: &str, log_level: &str) { tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| format!("{log_level},h2=info").into()), ) - .with(VersionFieldLayer) // Add custom layer to capture span fields + .with(VersionFieldLayer) .with( tracing_subscriber::fmt::layer() .with_ansi(true) From 6373859cbb2fb0aaa97cc1633df934f62ebd8dd7 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 08:35:32 +0200 Subject: [PATCH 063/100] cargo fmt --- crates/defguard_core/src/grpc/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 3429a4b985..090611a361 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -484,10 +484,7 @@ struct ProxyMessageLoopContext<'a> { polling_server: &'a mut PollingServer, } -#[instrument( - name = "proxy_message_loop", - skip(context), -)] +#[instrument(name = "proxy_message_loop", skip(context))] async fn handle_proxy_message_loop( proxy_version: &str, proxy_info: &str, From 2e98f952e8ed613191d8ade545bdd1432dc3b5fd Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 09:01:56 +0200 Subject: [PATCH 064/100] tracing module comments --- crates/defguard_version/src/tracing.rs | 85 ++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index dafd188af3..5ae44e055f 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -1,3 +1,76 @@ +//! Tracing integration with version-aware log formatting. +//! +//! This module provides a custom tracing formatter and layer system that automatically +//! includes version and system information in log messages. It's designed to make +//! debugging and monitoring easier in distributed Defguard deployments by providing +//! component version context in logs. +//! +//! # Features +//! +//! - **Version-aware formatting**: Automatically extracts and displays version information +//! - **Component differentiation**: Distinguishes between Core (C:), Proxy (PX:), and Gateway (GW:) components +//! - **Error-level enhancement**: Includes detailed system information for ERROR-level logs +//! +//! # Log Format +//! +//! The formatter adds version suffixes to log messages: +//! +//! - **Regular logs**: `[own_version][C:core_version][PX:proxy_version][GW:gateway_version]` +//! - **Error logs**: `[own_version own_system_info][C:core_version core_info][PX:proxy_version proxy_info][GW:gateway_version gateway_info]` +//! +//! # Span Fields +//! +//! The following span fields are automatically captured and used for version display: +//! +//! - `core_version`, `core_info` - Core component version and system information +//! - `proxy_version`, `proxy_info` - Proxy component version and system information +//! - `gateway_version`, `gateway_info` - Gateway component version and system information +//! +//! # Usage +//! +//! ## Basic Setup +//! +//! ```rust +//! // Initialize tracing with version-aware formatting +//! defguard_version::tracing::init("1.5.0", "info"); +//! ``` +//! +//! ## Creating Version-Aware Spans +//! +//! ```rust +//! use tracing::info_span; +//! +//! // Create a span with proxy version information +//! let _span = info_span!( +//! "proxy_communication", +//! proxy_version = "1.4.2", +//! proxy_info = "Linux 22.04 64-bit x86_64" +//! ).entered(); +//! +//! // This log will include the proxy version information +//! tracing::info!("Processing proxy request"); +//! // Output: 2024-01-01T12:00:00Z INFO proxy_communication: Processing proxy request [1.5.0][PX:1.4.2] +//! ``` +//! +//! ## Error Logs with Full Context +//! +//! ```rust +//! use tracing::error; +//! +//! // Error logs automatically include system information +//! tracing::error!("Failed to connect to gateway"); +//! // Output: 2024-01-01T12:00:00Z ERROR: Failed to connect to gateway [1.5.0 Linux 22.04 64-bit x86_64][GW:1.3.1 Windows 11 64-bit x86_64] +//! ``` +//! +//! # Architecture +//! +//! The module implements a layered architecture: +//! +//! 1. **`VersionFieldLayer`** - Captures version fields from spans and stores them in extensions +//! 2. **`VersionSuffixFormat`** - Custom formatter that adds version suffixes to log messages +//! 3. **`VersionFilteredFields`** - Field formatter that excludes version fields from normal output +//! 4. **Utility functions** - Extract and format version information from span hierarchy + use tracing::{Level, Subscriber}; use tracing_subscriber::{ Layer, @@ -10,6 +83,10 @@ use tracing_subscriber::{ use crate::SystemInfo; +/// Container for version information extracted from tracing span hierarchy. +/// +/// Aggregates version and system information for different Defguard components +/// (core, proxy, gateway) found while traversing up the span tree. #[derive(Debug, Default, Clone)] pub struct ExtractedVersionInfo { pub core_version: Option, @@ -101,7 +178,7 @@ pub fn build_version_suffix( version_suffix.push_str(&own_version_str); } - // Core version + // Core if let Some(ref core_version) = extracted.core_version { let mut core_version_str = format!("[C:{core_version}"); if is_error { @@ -113,7 +190,7 @@ pub fn build_version_suffix( version_suffix.push_str(&core_version_str); } - // Proxy version + // Proxy if let Some(ref proxy_version) = extracted.proxy_version { let mut proxy_version_str = format!("[PX:{proxy_version}"); if is_error { @@ -125,7 +202,7 @@ pub fn build_version_suffix( version_suffix.push_str(&proxy_version_str); } - // Gateway version + // Gateway if let Some(ref gateway_version) = extracted.gateway_version { let mut gateway_version_str = format!("[GW:{gateway_version}"); if is_error { @@ -285,7 +362,7 @@ impl<'writer> FormatFields<'writer> for VersionFilteredFields { } } -/// Field visitor that skips version-related fields +/// Field visitor that skips version-related fields to avoid duplication pub struct FieldFilterVisitor<'writer> { writer: Writer<'writer>, first: bool, From 2d9192daa8a51d97624b36c3b385bd03e989b7a4 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 09:27:49 +0200 Subject: [PATCH 065/100] strong typing for VersionSuffixFormat version fields --- crates/defguard/src/main.rs | 2 +- crates/defguard_version/src/tracing.rs | 47 +++++++++++++++++++------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index e6feffd28f..8ea1215f20 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -42,7 +42,7 @@ async fn main() -> Result<(), anyhow::Error> { SERVER_CONFIG.set(config.clone())?; // initialize tracing with version formatter - defguard_version::tracing::init(VERSION, &config.log_level); + defguard_version::tracing::init(VERSION, &config.log_level)?; info!("Starting ... version v{}", VERSION); debug!("Using config: {config:?}"); diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 5ae44e055f..43324f66f3 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -71,17 +71,22 @@ //! 3. **`VersionFilteredFields`** - Field formatter that excludes version fields from normal output //! 4. **Utility functions** - Extract and format version information from span hierarchy +use semver::Version; use tracing::{Level, Subscriber}; use tracing_subscriber::{ Layer, field::RecordFields, - fmt::{FmtContext, FormatEvent, FormatFields, format::Writer}, + fmt::{ + FmtContext, FormatEvent, FormatFields, + format::{Format, Full, Writer}, + time::SystemTime, + }, layer::{Context, SubscriberExt}, registry::LookupSpan, util::SubscriberInitExt, }; -use crate::SystemInfo; +use crate::{ComponentInfo, DefguardVersionError, SystemInfo}; /// Container for version information extracted from tracing span hierarchy. /// @@ -161,7 +166,7 @@ where /// A formatted string containing version information suitable for appending to log lines pub fn build_version_suffix( extracted: &ExtractedVersionInfo, - own_version: &str, + own_version: &Version, own_info: &SystemInfo, is_error: bool, ) -> String { @@ -227,8 +232,19 @@ pub fn build_version_suffix( pub struct VersionSuffixFormat { /// The underlying tracing formatter pub inner: tracing_subscriber::fmt::format::Format, - pub own_version: String, - pub own_info: SystemInfo, + pub component_info: ComponentInfo, +} + +impl VersionSuffixFormat { + pub fn new( + own_version: &str, + inner: Format, + ) -> Result { + Ok(Self { + inner, + component_info: ComponentInfo::new(own_version)?, + }) + } } /// A layer that captures version fields from spans and stores them for use by the formatter @@ -273,8 +289,12 @@ where // Build version suffix using utility function let is_error = *event.metadata().level() == Level::ERROR; - let version_suffix = - build_version_suffix(&extracted, &self.own_version, &self.own_info, is_error); + let version_suffix = build_version_suffix( + &extracted, + &self.component_info.version, + &self.component_info.system, + is_error, + ); // Create a wrapper writer that will append version info before newlines let mut wrapper = VersionSuffixWriter::new(writer, version_suffix); @@ -430,7 +450,7 @@ impl<'writer> tracing::field::Visit for FieldFilterVisitor<'writer> { /// ``` /// defguard_version::tracing::init("1.5.0", "info"); /// ``` -pub fn init(own_version: &str, log_level: &str) { +pub fn init(own_version: &str, log_level: &str) -> Result<(), DefguardVersionError> { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() @@ -440,12 +460,13 @@ pub fn init(own_version: &str, log_level: &str) { .with( tracing_subscriber::fmt::layer() .with_ansi(true) - .event_format(VersionSuffixFormat { - inner: tracing_subscriber::fmt::format::Format::default().with_ansi(true), - own_version: own_version.to_string(), - own_info: SystemInfo::get(), - }) + .event_format(VersionSuffixFormat::new( + own_version, + tracing_subscriber::fmt::format::Format::default().with_ansi(true), + )?) .fmt_fields(VersionFilteredFields), ) .init(); + + Ok(()) } From e41b087ebd4b3b8884ddf6bbe830263a3452758f Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 10:12:19 +0200 Subject: [PATCH 066/100] review: rename headers, remove bitness --- crates/defguard_version/src/client.rs | 4 ++-- crates/defguard_version/src/lib.rs | 26 +++++++++++--------------- crates/defguard_version/src/server.rs | 4 ++-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 37205901b4..4904f77a79 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -7,8 +7,8 @@ use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; /// /// # Headers Added /// -/// - `dfg-version`: The semantic version of the Defguard component -/// - `dfg-system-info`: System information including OS type, version, bitness, and architecture +/// - `defguard-version`: The semantic version of the Defguard component +/// - `defguard-system`: System information including OS type, version, and architecture /// /// # Examples /// ```rust diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 59323a5b6f..69f6bd151f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -8,8 +8,8 @@ //! //! The crate defines two standard headers used across all Defguard gRPC communications: //! -//! - `dfg-version`: Semantic version string (e.g., "1.2.3") -//! - `dfg-system-info`: Semicolon-separated system information (OS;version;bitness;arch) +//! - `defguard-version`: Semantic version string (e.g., "1.2.3") +//! - `defguard-system`: Semicolon-separated system information (OS;version;arch) //! //! # Usage //! @@ -65,10 +65,10 @@ pub mod server; pub mod tracing; /// HTTP header name for the Defguard component version. -pub static VERSION_HEADER: &str = "dfg-version"; +pub static VERSION_HEADER: &str = "defguard-version"; /// HTTP header name for the Defguard system information. -pub static SYSTEM_INFO_HEADER: &str = "dfg-system-info"; +pub static SYSTEM_INFO_HEADER: &str = "defguard-system"; #[derive(Debug, Error)] pub enum DefguardVersionError { @@ -97,7 +97,7 @@ pub enum DefguardVersionError { /// /// // Access individual fields /// println!("OS: {} {}", info.os_type, info.os_version); -/// println!("Architecture: {} ({})", info.architecture, info.bitness); +/// println!("Architecture: {}", info.architecture); /// ``` #[derive(Debug, Clone)] pub struct SystemInfo { @@ -105,8 +105,6 @@ pub struct SystemInfo { pub os_type: String, /// The operating system version (e.g., "22.04", "11", "13.0") pub os_version: String, - /// The system bitness (e.g., "64-bit", "32-bit") - pub bitness: String, /// The system architecture (e.g., "x86_64", "aarch64", "arm") pub architecture: String, } @@ -115,15 +113,15 @@ impl Display for SystemInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{} {} {} {}", - self.os_type, self.os_version, self.bitness, self.architecture + "{} {} {}", + self.os_type, self.os_version, self.architecture ) } } impl SystemInfo { - /// Automatically detects the operating system type, version, architecture - /// and bitness using the `os_info` crate. + /// Automatically detects the operating system type, version and architecture + /// using the `os_info` crate. /// /// # Returns /// @@ -134,8 +132,8 @@ impl SystemInfo { fn as_header_value(&self) -> String { format!( - "{};{};{};{}", - self.os_type, self.os_version, self.bitness, self.architecture + "{};{};{}", + self.os_type, self.os_version, self.architecture ) } @@ -150,7 +148,6 @@ impl SystemInfo { Ok(Self { os_type: parts[0].to_string(), os_version: parts[1].to_string(), - bitness: parts[2].to_string(), architecture: parts[3].to_string(), }) } @@ -161,7 +158,6 @@ impl From for SystemInfo { Self { os_type: info.os_type().to_string(), os_version: info.version().to_string(), - bitness: info.bitness().to_string(), architecture: info.architecture().unwrap_or("?").to_string(), } } diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 10026d01a6..b39abc7514 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -7,8 +7,8 @@ //! //! # Headers Added //! -//! - `dfg-version`: The semantic version of the Defguard component -//! - `dfg-system-info`: System information including OS type, version, bitness, and architecture +//! - `defguard-version`: The semantic version of the Defguard component +//! - `defguard-system`: System information including OS type, version, and architecture //! //! # Usage //! From e18a3ed637d64be6ed8b59cfae0f5e24030b0bbe Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 10:14:51 +0200 Subject: [PATCH 067/100] review: use std::fmt --- crates/defguard_version/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 69f6bd151f..f84f76c1e7 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -56,7 +56,7 @@ use ::tracing::{error, warn}; use semver::Version; -use std::{fmt::Display, str::FromStr}; +use std::{fmt, str::FromStr}; use thiserror::Error; use tonic::metadata::MetadataMap; @@ -109,8 +109,8 @@ pub struct SystemInfo { pub architecture: String, } -impl Display for SystemInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SystemInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} {} {}", From d3080be9f1888be22a91cc2d52f56c610627baec Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 10:16:43 +0200 Subject: [PATCH 068/100] inline string formatting --- crates/defguard_version/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index f84f76c1e7..6bb60657f3 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -46,8 +46,8 @@ //! //! // Extract parsed version and system info //! if let Some((version, system_info)) = parse_metadata(&metadata) { -//! println!("Client version: {}", version); -//! println!("Client system: {}", system_info); +//! println!("Client version: {version}"); +//! println!("Client system: {system_info}"); //! } //! //! // Get version info as strings (with fallback) From b1d5f1113d71146b1325d5466a331d5fe0423085 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 10:23:53 +0200 Subject: [PATCH 069/100] cargo fmt --- crates/defguard_version/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 6bb60657f3..e208d95f50 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -131,10 +131,7 @@ impl SystemInfo { } fn as_header_value(&self) -> String { - format!( - "{};{};{}", - self.os_type, self.os_version, self.architecture - ) + format!("{};{};{}", self.os_type, self.os_version, self.architecture) } fn try_from_header_value(header_value: &str) -> Result { From 450d631b20acdd29d816f7e19f5de03396a6337d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 10:37:05 +0200 Subject: [PATCH 070/100] fix try_from_header after bitness removal --- crates/defguard_version/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index e208d95f50..da87ac54b4 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -136,7 +136,7 @@ impl SystemInfo { fn try_from_header_value(header_value: &str) -> Result { let parts: Vec<&str> = header_value.split(';').collect(); - if parts.len() != 4 { + if parts.len() != 3 { return Err(DefguardVersionError::SystemInfoParseError( header_value.to_string(), )); @@ -145,7 +145,7 @@ impl SystemInfo { Ok(Self { os_type: parts[0].to_string(), os_version: parts[1].to_string(), - architecture: parts[3].to_string(), + architecture: parts[2].to_string(), }) } } From 03f04dbfab0f6c44893cb5943a06d9c0ffbb47bf Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 11:46:45 +0200 Subject: [PATCH 071/100] use String::push_str instead of format! macro in tracing module --- crates/defguard_version/src/tracing.rs | 36 ++++++++++++++------------ 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 43324f66f3..c750238158 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -175,48 +175,52 @@ pub fn build_version_suffix( if is_versioned_span || is_error { // Own version - let mut own_version_str = format!(" [{own_version}"); + version_suffix.push_str(" ["); + version_suffix.push_str(&own_version.to_string()); if is_error { - own_version_str = format!("{own_version_str} {own_info}"); + version_suffix.push(' '); + version_suffix.push_str(&own_info.to_string()); } - own_version_str = format!("{own_version_str}]"); - version_suffix.push_str(&own_version_str); + version_suffix.push(']'); } // Core if let Some(ref core_version) = extracted.core_version { - let mut core_version_str = format!("[C:{core_version}"); + version_suffix.push_str("[C:"); + version_suffix.push_str(core_version); if is_error { if let Some(ref core_info) = extracted.core_info { - core_version_str = format!("{core_version_str} {core_info}"); + version_suffix.push(' '); + version_suffix.push_str(core_info); } } - core_version_str = format!("{core_version_str}]"); - version_suffix.push_str(&core_version_str); + version_suffix.push(']'); } // Proxy if let Some(ref proxy_version) = extracted.proxy_version { - let mut proxy_version_str = format!("[PX:{proxy_version}"); + version_suffix.push_str("[PX:"); + version_suffix.push_str(proxy_version); if is_error { if let Some(ref proxy_info) = extracted.proxy_info { - proxy_version_str = format!("{proxy_version_str} {proxy_info}"); + version_suffix.push(' '); + version_suffix.push_str(proxy_info); } } - proxy_version_str = format!("{proxy_version_str}]"); - version_suffix.push_str(&proxy_version_str); + version_suffix.push(']'); } // Gateway if let Some(ref gateway_version) = extracted.gateway_version { - let mut gateway_version_str = format!("[GW:{gateway_version}"); + version_suffix.push_str("[GW:"); + version_suffix.push_str(gateway_version); if is_error { if let Some(ref gateway_info) = extracted.gateway_info { - gateway_version_str = format!("{gateway_version_str} {gateway_info}"); + version_suffix.push(' '); + version_suffix.push_str(gateway_info); } } - gateway_version_str = format!("{gateway_version_str}]"); - version_suffix.push_str(&gateway_version_str); + version_suffix.push(']'); } version_suffix From 04976c2927995b0389006bb6191fb2967ac2043e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 11:53:54 +0200 Subject: [PATCH 072/100] build_version_info unit tests --- crates/defguard_version/src/tracing.rs | 178 +++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index c750238158..ca28b6c2e8 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -474,3 +474,181 @@ pub fn init(own_version: &str, log_level: &str) -> Result<(), DefguardVersionErr Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use semver::Version; + + fn create_version() -> Version { + Version::parse("1.2.3").unwrap() + } + + fn create_system_info() -> SystemInfo { + SystemInfo { + os_type: "Linux".to_string(), + os_version: "22.04".to_string(), + architecture: "x86_64".to_string(), + } + } + + #[test] + fn test_build_version_suffix_empty_extracted_no_error() { + let extracted = ExtractedVersionInfo::default(); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, false); + + assert_eq!(result, ""); + } + + #[test] + fn test_build_version_suffix_empty_extracted_with_error() { + let extracted = ExtractedVersionInfo::default(); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, true); + + assert_eq!(result, " [1.2.3 Linux 22.04 x86_64]"); + } + + #[test] + fn test_build_version_suffix_core_version_only_no_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.core_version = Some("2.0.0".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, false); + + assert_eq!(result, " [1.2.3][C:2.0.0]"); + } + + #[test] + fn test_build_version_suffix_core_version_with_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.core_version = Some("2.0.0".to_string()); + extracted.core_info = Some("Windows 11 64-bit arm64".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, true); + + assert_eq!(result, " [1.2.3 Linux 22.04 x86_64][C:2.0.0 Windows 11 64-bit arm64]"); + } + + #[test] + fn test_build_version_suffix_proxy_version_only_no_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.proxy_version = Some("1.4.2".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, false); + + assert_eq!(result, " [1.2.3][PX:1.4.2]"); + } + + #[test] + fn test_build_version_suffix_proxy_version_with_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.proxy_version = Some("1.4.2".to_string()); + extracted.proxy_info = Some("macOS 13.0 arm64".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, true); + + assert_eq!(result, " [1.2.3 Linux 22.04 x86_64][PX:1.4.2 macOS 13.0 arm64]"); + } + + #[test] + fn test_build_version_suffix_gateway_version_only_no_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.gateway_version = Some("1.1.0".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, false); + + assert_eq!(result, " [1.2.3][GW:1.1.0]"); + } + + #[test] + fn test_build_version_suffix_gateway_version_with_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.gateway_version = Some("1.1.0".to_string()); + extracted.gateway_info = Some("FreeBSD 13.2 amd64".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, true); + + assert_eq!(result, " [1.2.3 Linux 22.04 x86_64][GW:1.1.0 FreeBSD 13.2 amd64]"); + } + + #[test] + fn test_build_version_suffix_all_components_no_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.core_version = Some("2.0.0".to_string()); + extracted.proxy_version = Some("1.4.2".to_string()); + extracted.gateway_version = Some("1.1.0".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, false); + + assert_eq!(result, " [1.2.3][C:2.0.0][PX:1.4.2][GW:1.1.0]"); + } + + #[test] + fn test_build_version_suffix_all_components_with_error() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.core_version = Some("2.0.0".to_string()); + extracted.core_info = Some("Windows 11 x86_64".to_string()); + extracted.proxy_version = Some("1.4.2".to_string()); + extracted.proxy_info = Some("macOS 13.0 arm64".to_string()); + extracted.gateway_version = Some("1.1.0".to_string()); + extracted.gateway_info = Some("FreeBSD 13.2 amd64".to_string()); + let version = create_version(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, true); + + assert_eq!( + result, + " [1.2.3 Linux 22.04 x86_64][C:2.0.0 Windows 11 x86_64][PX:1.4.2 macOS 13.0 arm64][GW:1.1.0 FreeBSD 13.2 amd64]" + ); + } + + #[test] + fn test_build_version_suffix_version_with_pre_release() { + let mut extracted = ExtractedVersionInfo::default(); + extracted.core_version = Some("2.0.0-alpha.1".to_string()); + let version = Version::parse("1.2.3-beta.2").unwrap(); + let system_info = create_system_info(); + + let result = build_version_suffix(&extracted, &version, &system_info, false); + + assert_eq!(result, " [1.2.3-beta.2][C:2.0.0-alpha.1]"); + } + + #[test] + fn test_extracted_version_info_has_version_info() { + let mut extracted = ExtractedVersionInfo::default(); + assert!(!extracted.has_version_info()); + + extracted.core_version = Some("2.0.0".to_string()); + assert!(extracted.has_version_info()); + + extracted.core_version = None; + extracted.proxy_version = Some("1.4.2".to_string()); + assert!(extracted.has_version_info()); + + extracted.proxy_version = None; + extracted.gateway_version = Some("1.1.0".to_string()); + assert!(extracted.has_version_info()); + } +} From 5a802f5e376ff5f6aee2111d5824c58fdde88cd0 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 12:18:44 +0200 Subject: [PATCH 073/100] nix flake update --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 5b1565932a..f650d8e58d 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755027561, - "narHash": "sha256-IVft239Bc8p8Dtvf7UAACMG5P3ZV+3/aO28gXpGtMXI=", + "lastModified": 1755186698, + "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "005433b926e16227259a1843015b5b2b7f7d1fc3", + "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1755139244, - "narHash": "sha256-SN1BFA00m+siVAQiGLtTwjv9LV9TH5n8tQcSziV6Nv4=", + "lastModified": 1755571033, + "narHash": "sha256-V8gmZBfMiFGCyGJQx/yO81LFJ4d/I5Jxs2id96rLxrM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "aeae248beb2a419e39d483dd9b7fec924aba8d4d", + "rev": "95487740bb7ac11553445e9249041a6fa4b5eccf", "type": "github" }, "original": { From b425c151be1a531f0455644d2718052d342311b5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 13:03:37 +0200 Subject: [PATCH 074/100] fix doctests --- crates/defguard_version/src/client.rs | 5 ++++- crates/defguard_version/src/lib.rs | 27 ++++++++++++++++----------- crates/defguard_version/src/server.rs | 5 +++-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 4904f77a79..610d782d37 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -11,9 +11,12 @@ use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; /// - `defguard-system`: System information including OS type, version, and architecture /// /// # Examples -/// ```rust +/// ```ignore +/// use tonic::transport::Channel; +/// /// use defguard_version::client::version_interceptor; /// let interceptor = version_interceptor("1.0.0"); +/// let channel = Channel::from_static("http://localhost:50051").connect().await.unwrap(); /// let client = MyClient::with_interceptor(channel, interceptor); /// ``` pub fn version_interceptor( diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index da87ac54b4..aac3af0db6 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -15,11 +15,12 @@ //! //! ## Server-side middleware //! -//! ```rust,no_run +//! ``` //! use tower::ServiceBuilder; //! use defguard_version::server::DefguardVersionLayer; //! -//! let layer = DefguardVersionLayer::new("1.0.0")?; +//! let my_grpc_service = ServiceBuilder::new(); +//! let layer = DefguardVersionLayer::new("1.0.0").unwrap(); //! let service = ServiceBuilder::new() //! .layer(layer) //! .service(my_grpc_service); @@ -27,23 +28,25 @@ //! //! ## Client-side interceptor //! -//! ```rust,no_run +//! ```ignore //! use defguard_version::client::version_interceptor; //! use tonic::transport::Channel; //! -//! let channel = Channel::from_static("http://localhost:50051").connect().await?; +//! let channel = Channel::from_static("http://localhost:50051").connect().await.unwrap(); //! let client = MyServiceClient::with_interceptor( //! channel, -//! version_interceptor("1.0.0")? +//! version_interceptor("1.0.0").unwrap() //! ); //! ``` //! //! ## Parsing version information //! -//! ```rust,no_run +//! ``` //! use defguard_version::{parse_metadata, version_info_from_metadata}; //! use tonic::metadata::MetadataMap; //! +//! let metadata = MetadataMap::new(); +//! //! // Extract parsed version and system info //! if let Some((version, system_info)) = parse_metadata(&metadata) { //! println!("Client version: {version}"); @@ -88,7 +91,7 @@ pub enum DefguardVersionError { /// /// # Examples /// -/// ```rust +/// ``` /// use defguard_version::SystemInfo; /// /// // Get current system information @@ -188,10 +191,10 @@ impl ComponentInfo { /// /// # Examples /// - /// ```rust + /// ``` /// use defguard_version::ComponentInfo; /// - /// let info = ComponentInfo::new("1.0.0")?; + /// let info = ComponentInfo::new("1.0.0").unwrap(); /// assert_eq!(info.version.major, 1); /// ``` pub fn new(version: &str) -> Result { @@ -221,10 +224,11 @@ impl ComponentInfo { /// /// # Examples /// -/// ```rust,no_run +/// ``` /// use defguard_version::parse_metadata; /// use tonic::metadata::MetadataMap; /// +/// let metadata = MetadataMap::new(); /// if let Some((version, system)) = parse_metadata(&metadata) { /// println!("Peer version: {}", version); /// println!("Peer system: {}", system); @@ -273,10 +277,11 @@ pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { /// /// # Examples /// -/// ```rust,no_run +/// ``` /// use defguard_version::version_info_from_metadata; /// use tonic::metadata::MetadataMap; /// +/// let metadata = MetadataMap::new(); /// let (version, system) = version_info_from_metadata(&metadata); /// println!("Client: {} running on {}", version, system); /// // Output might be: "Client: 1.2.3 running on Linux 22.04 64-bit x86_64" diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index b39abc7514..0e24fcc7fb 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -12,11 +12,12 @@ //! //! # Usage //! -//! ```rust,no_run +//! ``` //! use tower::ServiceBuilder; //! use defguard_version::server::DefguardVersionLayer; //! -//! let version_layer = DefguardVersionLayer::new("1.0.0")?; +//! let my_grpc_service = ServiceBuilder::new(); +//! let version_layer = DefguardVersionLayer::new("1.0.0").unwrap(); //! let service = ServiceBuilder::new() //! .layer(version_layer) //! .service(my_grpc_service); From b209b628d9e069ea265795fee186c555f7f63b70 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 13:04:33 +0200 Subject: [PATCH 075/100] cargo fmt --- crates/defguard_version/src/tracing.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index ca28b6c2e8..6c2ea14e73 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -536,7 +536,10 @@ mod tests { let result = build_version_suffix(&extracted, &version, &system_info, true); - assert_eq!(result, " [1.2.3 Linux 22.04 x86_64][C:2.0.0 Windows 11 64-bit arm64]"); + assert_eq!( + result, + " [1.2.3 Linux 22.04 x86_64][C:2.0.0 Windows 11 64-bit arm64]" + ); } #[test] @@ -561,7 +564,10 @@ mod tests { let result = build_version_suffix(&extracted, &version, &system_info, true); - assert_eq!(result, " [1.2.3 Linux 22.04 x86_64][PX:1.4.2 macOS 13.0 arm64]"); + assert_eq!( + result, + " [1.2.3 Linux 22.04 x86_64][PX:1.4.2 macOS 13.0 arm64]" + ); } #[test] @@ -586,7 +592,10 @@ mod tests { let result = build_version_suffix(&extracted, &version, &system_info, true); - assert_eq!(result, " [1.2.3 Linux 22.04 x86_64][GW:1.1.0 FreeBSD 13.2 amd64]"); + assert_eq!( + result, + " [1.2.3 Linux 22.04 x86_64][GW:1.1.0 FreeBSD 13.2 amd64]" + ); } #[test] From 8288cf8e1405f64428c6b8133f7e5819167ce587 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 19 Aug 2025 13:34:47 +0200 Subject: [PATCH 076/100] extract all span info from current span, don't traverse parents --- crates/defguard_version/src/tracing.rs | 34 +++++++++----------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 6c2ea14e73..929f76e004 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -112,14 +112,14 @@ impl ExtractedVersionInfo { /// Extract version information from current span context /// -/// This function walks up the span hierarchy and extracts version information -/// from span extensions that were stored by VersionFieldLayer. +/// This function extracts version information from the current span's extensions +/// that were stored by VersionFieldLayer. /// /// # Arguments /// * `ctx` - The format context from the tracing formatter /// /// # Returns -/// An `ExtractedVersionInfo` struct containing all version information found in the span hierarchy +/// An `ExtractedVersionInfo` struct containing all version information found in the current span pub fn extract_version_info_from_context(ctx: &FmtContext<'_, S, N>) -> ExtractedVersionInfo where S: Subscriber + for<'a> LookupSpan<'a>, @@ -128,26 +128,14 @@ where let mut extracted = ExtractedVersionInfo::default(); if let Some(span_ref) = ctx.lookup_current() { - let mut current_span = Some(span_ref); - while let Some(span) = current_span { - let extensions = span.extensions(); - - if let Some(stored_visitor) = extensions.get::() { - if extracted.core_version.is_none() && stored_visitor.core_version.is_some() { - extracted.core_version = stored_visitor.core_version.clone(); - extracted.core_info = stored_visitor.core_info.clone(); - } - if extracted.proxy_version.is_none() && stored_visitor.proxy_version.is_some() { - extracted.proxy_version = stored_visitor.proxy_version.clone(); - extracted.proxy_info = stored_visitor.proxy_info.clone(); - } - if extracted.gateway_version.is_none() && stored_visitor.gateway_version.is_some() { - extracted.gateway_version = stored_visitor.gateway_version.clone(); - extracted.gateway_info = stored_visitor.gateway_info.clone(); - } - } - - current_span = span.parent(); + let extensions = span_ref.extensions(); + if let Some(stored_visitor) = extensions.get::() { + extracted.core_version = stored_visitor.core_version.clone(); + extracted.core_info = stored_visitor.core_info.clone(); + extracted.proxy_version = stored_visitor.proxy_version.clone(); + extracted.proxy_info = stored_visitor.proxy_info.clone(); + extracted.gateway_version = stored_visitor.gateway_version.clone(); + extracted.gateway_info = stored_visitor.gateway_info.clone(); } } From f8a5b570a8c652dbf7b68b2f0c25c8b536695ad4 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 09:11:48 +0200 Subject: [PATCH 077/100] GatewayServer::extract_metadata utility function --- crates/defguard_core/src/grpc/gateway/mod.rs | 78 ++++++++++++-------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 58fc310a9c..b593a889a6 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -128,6 +128,14 @@ impl WireguardNetwork { } } +/// Utility struct encapsulating commonly extracted metadata fields during gRPC communication. +struct GatewayMetadata { + network_id: Id, + hostname: String, + version: String, + info: String, +} + impl GatewayServer { /// Create new gateway server instance #[must_use] @@ -159,10 +167,10 @@ impl GatewayServer { } // parse network id from gateway request metadata from intercepted information from JWT token - fn get_network_id_from_metadata(metadata: &MetadataMap) -> Option { + fn get_network_id_from_metadata(metadata: &MetadataMap) -> Option { if let Some(ascii_value) = metadata.get("gateway_network_id") { if let Ok(slice) = ascii_value.clone().to_str() { - if let Ok(id) = slice.parse::() { + if let Ok(id) = slice.parse::() { return Some(id); } } @@ -277,6 +285,17 @@ impl GatewayServer { Ok(user) } + + /// Utility function extracting metadata fields during gRPC communication. + fn extract_metadata(metadata: &MetadataMap) -> Result { + let (version, info) = version_info_from_metadata(metadata); + Ok(GatewayMetadata { + network_id: Self::get_network_id(metadata)?, + hostname: Self::get_gateway_hostname(metadata)?, + version, + info, + }) + } } fn gen_config( @@ -722,10 +741,12 @@ impl gateway_service_server::GatewayService for GatewayServer { &self, request: Request>, ) -> Result, Status> { - let metadata = request.metadata(); - let network_id = Self::get_network_id(metadata)?; - let (version, info) = version_info_from_metadata(metadata); - let gateway_hostname = Self::get_gateway_hostname(metadata)?; + let GatewayMetadata { + network_id, + hostname, + version, + info, + } = Self::extract_metadata(request.metadata())?; let mut stream = request.into_inner(); let mut disconnect_timer = interval(Duration::from_secs(PEER_DISCONNECT_INTERVAL)); @@ -830,7 +851,7 @@ impl gateway_service_server::GatewayService for GatewayServer { // mark new VPN client as connected client_map.connect_vpn_client( network_id, - &gateway_hostname, + &hostname, &public_key, &device, &user, @@ -893,10 +914,12 @@ impl gateway_service_server::GatewayService for GatewayServer { request: Request, ) -> Result, Status> { debug!("Sending configuration to gateway client."); - let metadata = request.metadata(); - let network_id = Self::get_network_id(metadata)?; - let hostname = Self::get_gateway_hostname(metadata)?; - let (version, info) = version_info_from_metadata(metadata); + let GatewayMetadata { + network_id, + hostname, + version, + info, + } = Self::extract_metadata(request.metadata())?; let span = tracing::info_span!( "gateway_config", gateway_version = version, @@ -973,10 +996,12 @@ impl gateway_service_server::GatewayService for GatewayServer { } async fn updates(&self, request: Request<()>) -> Result, Status> { - let metadata = request.metadata(); - let gateway_network_id = Self::get_network_id(metadata)?; - let hostname = Self::get_gateway_hostname(metadata)?; - let (version, info) = version_info_from_metadata(metadata); + let GatewayMetadata { + network_id, + hostname, + version, + info, + } = Self::extract_metadata(request.metadata())?; let span = tracing::info_span!( "gateway_updates", gateway_version = version, @@ -984,19 +1009,19 @@ impl gateway_service_server::GatewayService for GatewayServer { ); let _guard = span.enter(); - let Some(network) = WireguardNetwork::find_by_id(&self.pool, gateway_network_id) + let Some(network) = WireguardNetwork::find_by_id(&self.pool, network_id) .await .map_err(|_| { - error!("Failed to fetch network {gateway_network_id} from the database"); + error!("Failed to fetch network {network_id} from the database"); Status::new( Code::Internal, - format!("Failed to retrieve network {gateway_network_id} from the database"), + format!("Failed to retrieve network {network_id} from the database"), ) })? else { return Err(Status::new( Code::Internal, - format!("Network with id {gateway_network_id} not found"), + format!("Network with id {network_id} not found"), )); }; @@ -1006,9 +1031,9 @@ impl gateway_service_server::GatewayService for GatewayServer { let events_rx = self.wireguard_tx.subscribe(); let mut state = self.gateway_state.lock().unwrap(); state - .connect_gateway(gateway_network_id, &hostname, &self.pool) + .connect_gateway(network_id, &hostname, &self.pool) .map_err(|err| { - error!("Failed to connect gateway on network {gateway_network_id}: {err}"); + error!("Failed to connect gateway on network {network_id}: {err}"); Status::new( Code::Internal, "Failed to connect gateway on network {gateway_network_id}", @@ -1018,20 +1043,15 @@ impl gateway_service_server::GatewayService for GatewayServer { // clone here before moving into a closure let gateway_hostname = hostname.clone(); let handle = tokio::spawn(async move { - let mut update_handler = GatewayUpdatesHandler::new( - gateway_network_id, - network, - gateway_hostname, - events_rx, - tx, - ); + let mut update_handler = + GatewayUpdatesHandler::new(network_id, network, gateway_hostname, events_rx, tx); update_handler.run().await; }); Ok(Response::new(GatewayUpdatesStream::new( handle, rx, - gateway_network_id, + network_id, hostname, Arc::clone(&self.gateway_state), self.pool.clone(), From d3343f85bdea54a81a9e2097996f22871dc4b43c Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 09:24:56 +0200 Subject: [PATCH 078/100] log disconnection error message --- crates/defguard_core/src/grpc/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 090611a361..b118355bba 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,3 +1,4 @@ +use axum::http::Uri; use chrono::{NaiveDateTime, Utc}; use defguard_version::{ client::version_interceptor, server::DefguardVersionLayer, version_info_from_metadata, @@ -482,6 +483,7 @@ struct ProxyMessageLoopContext<'a> { password_reset_server: &'a mut PasswordResetServer, client_mfa_server: &'a mut ClientMfaServer, polling_server: &'a mut PollingServer, + endpoint_uri: &'a Uri, } #[instrument(name = "proxy_message_loop", skip(context))] @@ -826,8 +828,7 @@ async fn handle_proxy_message_loop( context.tx.send(req).unwrap(); } Err(err) => { - // error!("Disconnected from proxy at {}", endpoint.uri()); - error!("stream error: {err}"); + error!("Disconnected from proxy at {}: {err}", context.endpoint_uri); debug!("waiting 10s to re-establish the connection"); sleep(TEN_SECS).await; break 'message; @@ -902,6 +903,7 @@ pub async fn run_grpc_bidi_stream( password_reset_server: &mut password_reset_server, client_mfa_server: &mut client_mfa_server, polling_server: &mut polling_server, + endpoint_uri: endpoint.uri(), }, ) .await?; From b911a1a8f5bdc844332b425ed91899e316765769 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 09:29:10 +0200 Subject: [PATCH 079/100] clippy fixes --- crates/defguard_version/src/lib.rs | 2 ++ crates/defguard_version/src/tracing.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index aac3af0db6..7290783a28 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -129,6 +129,7 @@ impl SystemInfo { /// # Returns /// /// A `SystemInfo` struct populated with the current system's characteristics. + #[must_use] pub fn get() -> Self { os_info::get().into() } @@ -287,6 +288,7 @@ pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { /// // Output might be: "Client: 1.2.3 running on Linux 22.04 64-bit x86_64" /// // Or if headers missing: "Client: ? running on ?" /// ``` +#[must_use] pub fn version_info_from_metadata(metadata: &MetadataMap) -> (String, String) { parse_metadata(metadata).map_or(("?".to_string(), "?".to_string()), |(version, info)| { (version.to_string(), info.to_string()) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 929f76e004..f2eb7b2295 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -103,6 +103,7 @@ pub struct ExtractedVersionInfo { } impl ExtractedVersionInfo { + #[must_use] pub fn has_version_info(&self) -> bool { self.core_version.is_some() || self.proxy_version.is_some() @@ -120,6 +121,7 @@ impl ExtractedVersionInfo { /// /// # Returns /// An `ExtractedVersionInfo` struct containing all version information found in the current span +#[must_use] pub fn extract_version_info_from_context(ctx: &FmtContext<'_, S, N>) -> ExtractedVersionInfo where S: Subscriber + for<'a> LookupSpan<'a>, @@ -152,6 +154,7 @@ where /// /// # Returns /// A formatted string containing version information suitable for appending to log lines +#[must_use] pub fn build_version_suffix( extracted: &ExtractedVersionInfo, own_version: &Version, @@ -302,6 +305,7 @@ pub struct VersionSuffixWriter<'a> { } impl<'a> VersionSuffixWriter<'a> { + #[must_use] pub fn new(inner: Writer<'a>, version_suffix: String) -> Self { Self { inner, @@ -310,7 +314,7 @@ impl<'a> VersionSuffixWriter<'a> { } } -impl<'a> std::fmt::Write for VersionSuffixWriter<'a> { +impl std::fmt::Write for VersionSuffixWriter<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { if let Some(content) = s.strip_suffix('\n') { // Remove the newline, add version suffix, then add newline back @@ -381,6 +385,7 @@ pub struct FieldFilterVisitor<'writer> { } impl<'writer> FieldFilterVisitor<'writer> { + #[must_use] pub fn new(writer: Writer<'writer>) -> Self { Self { writer, @@ -389,7 +394,7 @@ impl<'writer> FieldFilterVisitor<'writer> { } } -impl<'writer> tracing::field::Visit for FieldFilterVisitor<'writer> { +impl tracing::field::Visit for FieldFilterVisitor<'_> { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" From cb496a9623a59372b18cbfe3103574b3cc6c433d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 11:00:44 +0200 Subject: [PATCH 080/100] clone_from() instead of reassignment --- crates/defguard_version/src/tracing.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index f2eb7b2295..56e098bfd2 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -132,12 +132,12 @@ where if let Some(span_ref) = ctx.lookup_current() { let extensions = span_ref.extensions(); if let Some(stored_visitor) = extensions.get::() { - extracted.core_version = stored_visitor.core_version.clone(); - extracted.core_info = stored_visitor.core_info.clone(); - extracted.proxy_version = stored_visitor.proxy_version.clone(); - extracted.proxy_info = stored_visitor.proxy_info.clone(); - extracted.gateway_version = stored_visitor.gateway_version.clone(); - extracted.gateway_info = stored_visitor.gateway_info.clone(); + extracted.core_version.clone_from(&stored_visitor.core_version); + extracted.core_info.clone_from(&stored_visitor.core_info); + extracted.proxy_version.clone_from(&stored_visitor.proxy_version); + extracted.proxy_info.clone_from(&stored_visitor.proxy_info); + extracted.gateway_version.clone_from(&stored_visitor.gateway_version); + extracted.gateway_info.clone_from(&stored_visitor.gateway_info); } } From edf8d42661e39768ee48036b0f146b55eed434e8 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 11:39:49 +0200 Subject: [PATCH 081/100] store single component info in ExtractedVersionInfo struct --- crates/defguard_core/src/grpc/mod.rs | 2 +- crates/defguard_version/src/tracing.rs | 282 +++---------------------- 2 files changed, 32 insertions(+), 252 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index b118355bba..fced5d2fde 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -903,7 +903,7 @@ pub async fn run_grpc_bidi_stream( password_reset_server: &mut password_reset_server, client_mfa_server: &mut client_mfa_server, polling_server: &mut polling_server, - endpoint_uri: endpoint.uri(), + endpoint_uri: endpoint.uri(), }, ) .await?; diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 56e098bfd2..7e8dd55339 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -94,20 +94,15 @@ use crate::{ComponentInfo, DefguardVersionError, SystemInfo}; /// (core, proxy, gateway) found while traversing up the span tree. #[derive(Debug, Default, Clone)] pub struct ExtractedVersionInfo { - pub core_version: Option, - pub core_info: Option, - pub proxy_version: Option, - pub proxy_info: Option, - pub gateway_version: Option, - pub gateway_info: Option, + pub component: Option, + pub info: Option, + pub version: Option, } impl ExtractedVersionInfo { #[must_use] pub fn has_version_info(&self) -> bool { - self.core_version.is_some() - || self.proxy_version.is_some() - || self.gateway_version.is_some() + self.component.is_some() || self.info.is_some() || self.version.is_some() } } @@ -132,12 +127,13 @@ where if let Some(span_ref) = ctx.lookup_current() { let extensions = span_ref.extensions(); if let Some(stored_visitor) = extensions.get::() { - extracted.core_version.clone_from(&stored_visitor.core_version); - extracted.core_info.clone_from(&stored_visitor.core_info); - extracted.proxy_version.clone_from(&stored_visitor.proxy_version); - extracted.proxy_info.clone_from(&stored_visitor.proxy_info); - extracted.gateway_version.clone_from(&stored_visitor.gateway_version); - extracted.gateway_info.clone_from(&stored_visitor.gateway_info); + extracted + .component + .clone_from(&stored_visitor.component); + extracted.version.clone_from(&stored_visitor.version); + extracted + .info + .clone_from(&stored_visitor.info); } } @@ -175,40 +171,20 @@ pub fn build_version_suffix( version_suffix.push(']'); } - // Core - if let Some(ref core_version) = extracted.core_version { - version_suffix.push_str("[C:"); - version_suffix.push_str(core_version); - if is_error { - if let Some(ref core_info) = extracted.core_info { - version_suffix.push(' '); - version_suffix.push_str(core_info); - } - } - version_suffix.push(']'); - } - - // Proxy - if let Some(ref proxy_version) = extracted.proxy_version { - version_suffix.push_str("[PX:"); - version_suffix.push_str(proxy_version); - if is_error { - if let Some(ref proxy_info) = extracted.proxy_info { - version_suffix.push(' '); - version_suffix.push_str(proxy_info); - } + if let Some(ref component) = extracted.component { + // TODO enum & match + let component = component.to_lowercase(); + if component == "core" { + version_suffix.push_str("[C:"); + } else if component == "proxy" { + version_suffix.push_str("[PX:"); + } else if component == "gateway" { + version_suffix.push_str("[GW:"); } - version_suffix.push(']'); - } - - // Gateway - if let Some(ref gateway_version) = extracted.gateway_version { - version_suffix.push_str("[GW:"); - version_suffix.push_str(gateway_version); if is_error { - if let Some(ref gateway_info) = extracted.gateway_info { + if let Some(ref info) = extracted.info { version_suffix.push(' '); - version_suffix.push_str(gateway_info); + version_suffix.push_str(&info); } } version_suffix.push(']'); @@ -329,35 +305,26 @@ impl std::fmt::Write for VersionSuffixWriter<'_> { /// A visitor that extracts version fields from spans #[derive(Default, Clone)] pub struct SpanFieldVisitor { - pub core_version: Option, - pub core_info: Option, - pub proxy_version: Option, - pub proxy_info: Option, - pub gateway_version: Option, - pub gateway_info: Option, + pub component: Option, + pub info: Option, + pub version: Option, } impl tracing::field::Visit for SpanFieldVisitor { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { - "core_version" => self.core_version = Some(value.to_string()), - "core_info" => self.core_info = Some(value.to_string()), - "proxy_version" => self.proxy_version = Some(value.to_string()), - "proxy_info" => self.proxy_info = Some(value.to_string()), - "gateway_version" => self.gateway_version = Some(value.to_string()), - "gateway_info" => self.gateway_info = Some(value.to_string()), + "component" => self.component = Some(value.to_string()), + "version" => self.version = Some(value.to_string()), + "info" => self.info = Some(value.to_string()), _ => {} } } fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { match field.name() { - "core_version" => self.core_version = Some(format!("{value:?}")), - "core_info" => self.core_info = Some(format!("{value:?}")), - "proxy_version" => self.proxy_version = Some(format!("{value:?}")), - "proxy_info" => self.proxy_info = Some(format!("{value:?}")), - "gateway_version" => self.gateway_version = Some(format!("{value:?}")), - "gateway_info" => self.gateway_info = Some(format!("{value:?}")), + "component" => self.component = Some(format!("{value:?}")), + "version" => self.version = Some(format!("{value:?}")), + "info" => self.info = Some(format!("{value:?}")), _ => {} } } @@ -467,190 +434,3 @@ pub fn init(own_version: &str, log_level: &str) -> Result<(), DefguardVersionErr Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - use semver::Version; - - fn create_version() -> Version { - Version::parse("1.2.3").unwrap() - } - - fn create_system_info() -> SystemInfo { - SystemInfo { - os_type: "Linux".to_string(), - os_version: "22.04".to_string(), - architecture: "x86_64".to_string(), - } - } - - #[test] - fn test_build_version_suffix_empty_extracted_no_error() { - let extracted = ExtractedVersionInfo::default(); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, false); - - assert_eq!(result, ""); - } - - #[test] - fn test_build_version_suffix_empty_extracted_with_error() { - let extracted = ExtractedVersionInfo::default(); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, true); - - assert_eq!(result, " [1.2.3 Linux 22.04 x86_64]"); - } - - #[test] - fn test_build_version_suffix_core_version_only_no_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.core_version = Some("2.0.0".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, false); - - assert_eq!(result, " [1.2.3][C:2.0.0]"); - } - - #[test] - fn test_build_version_suffix_core_version_with_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.core_version = Some("2.0.0".to_string()); - extracted.core_info = Some("Windows 11 64-bit arm64".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, true); - - assert_eq!( - result, - " [1.2.3 Linux 22.04 x86_64][C:2.0.0 Windows 11 64-bit arm64]" - ); - } - - #[test] - fn test_build_version_suffix_proxy_version_only_no_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.proxy_version = Some("1.4.2".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, false); - - assert_eq!(result, " [1.2.3][PX:1.4.2]"); - } - - #[test] - fn test_build_version_suffix_proxy_version_with_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.proxy_version = Some("1.4.2".to_string()); - extracted.proxy_info = Some("macOS 13.0 arm64".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, true); - - assert_eq!( - result, - " [1.2.3 Linux 22.04 x86_64][PX:1.4.2 macOS 13.0 arm64]" - ); - } - - #[test] - fn test_build_version_suffix_gateway_version_only_no_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.gateway_version = Some("1.1.0".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, false); - - assert_eq!(result, " [1.2.3][GW:1.1.0]"); - } - - #[test] - fn test_build_version_suffix_gateway_version_with_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.gateway_version = Some("1.1.0".to_string()); - extracted.gateway_info = Some("FreeBSD 13.2 amd64".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, true); - - assert_eq!( - result, - " [1.2.3 Linux 22.04 x86_64][GW:1.1.0 FreeBSD 13.2 amd64]" - ); - } - - #[test] - fn test_build_version_suffix_all_components_no_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.core_version = Some("2.0.0".to_string()); - extracted.proxy_version = Some("1.4.2".to_string()); - extracted.gateway_version = Some("1.1.0".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, false); - - assert_eq!(result, " [1.2.3][C:2.0.0][PX:1.4.2][GW:1.1.0]"); - } - - #[test] - fn test_build_version_suffix_all_components_with_error() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.core_version = Some("2.0.0".to_string()); - extracted.core_info = Some("Windows 11 x86_64".to_string()); - extracted.proxy_version = Some("1.4.2".to_string()); - extracted.proxy_info = Some("macOS 13.0 arm64".to_string()); - extracted.gateway_version = Some("1.1.0".to_string()); - extracted.gateway_info = Some("FreeBSD 13.2 amd64".to_string()); - let version = create_version(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, true); - - assert_eq!( - result, - " [1.2.3 Linux 22.04 x86_64][C:2.0.0 Windows 11 x86_64][PX:1.4.2 macOS 13.0 arm64][GW:1.1.0 FreeBSD 13.2 amd64]" - ); - } - - #[test] - fn test_build_version_suffix_version_with_pre_release() { - let mut extracted = ExtractedVersionInfo::default(); - extracted.core_version = Some("2.0.0-alpha.1".to_string()); - let version = Version::parse("1.2.3-beta.2").unwrap(); - let system_info = create_system_info(); - - let result = build_version_suffix(&extracted, &version, &system_info, false); - - assert_eq!(result, " [1.2.3-beta.2][C:2.0.0-alpha.1]"); - } - - #[test] - fn test_extracted_version_info_has_version_info() { - let mut extracted = ExtractedVersionInfo::default(); - assert!(!extracted.has_version_info()); - - extracted.core_version = Some("2.0.0".to_string()); - assert!(extracted.has_version_info()); - - extracted.core_version = None; - extracted.proxy_version = Some("1.4.2".to_string()); - assert!(extracted.has_version_info()); - - extracted.proxy_version = None; - extracted.gateway_version = Some("1.1.0".to_string()); - assert!(extracted.has_version_info()); - } -} From 81d0ddbb31486cdccd5821a102fe172cd76dc6ee Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 12:57:35 +0200 Subject: [PATCH 082/100] rewrite tracing methods to use the new ExtractedVersionInfo struct --- crates/defguard_core/src/grpc/gateway/mod.rs | 18 +++--------------- crates/defguard_core/src/grpc/mod.rs | 6 +++--- crates/defguard_version/src/tracing.rs | 11 ++++------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index b593a889a6..01d02ad62e 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -750,11 +750,7 @@ impl gateway_service_server::GatewayService for GatewayServer { let mut stream = request.into_inner(); let mut disconnect_timer = interval(Duration::from_secs(PEER_DISCONNECT_INTERVAL)); - let span = tracing::info_span!( - "gateway_stats", - gateway_version = version, - gateway_info = info, - ); + let span = tracing::info_span!("gateway_stats", component = "gateway", version, info); let _guard = span.enter(); loop { // wait for a message or update client map at least once a mninute if no messages are received @@ -920,11 +916,7 @@ impl gateway_service_server::GatewayService for GatewayServer { version, info, } = Self::extract_metadata(request.metadata())?; - let span = tracing::info_span!( - "gateway_config", - gateway_version = version, - gateway_info = info, - ); + let span = tracing::info_span!("gateway_config", component = "gateway", version, info); let _guard = span.enter(); let mut conn = self.pool.acquire().await.map_err(|e| { @@ -1002,11 +994,7 @@ impl gateway_service_server::GatewayService for GatewayServer { version, info, } = Self::extract_metadata(request.metadata())?; - let span = tracing::info_span!( - "gateway_updates", - gateway_version = version, - gateway_info = info, - ); + let span = tracing::info_span!("gateway_updates", component = "gateway", version, info); let _guard = span.enter(); let Some(network) = WireguardNetwork::find_by_id(&self.pool, network_id) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index fced5d2fde..ff8e46903b 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -486,10 +486,10 @@ struct ProxyMessageLoopContext<'a> { endpoint_uri: &'a Uri, } -#[instrument(name = "proxy_message_loop", skip(context))] +#[instrument(name = "proxy_message_loop", skip(context), fields(component="proxy"))] async fn handle_proxy_message_loop( - proxy_version: &str, - proxy_info: &str, + version: &str, + info: &str, context: ProxyMessageLoopContext<'_>, ) -> Result<(), anyhow::Error> { 'message: loop { diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 7e8dd55339..c3eefa2343 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -127,13 +127,9 @@ where if let Some(span_ref) = ctx.lookup_current() { let extensions = span_ref.extensions(); if let Some(stored_visitor) = extensions.get::() { - extracted - .component - .clone_from(&stored_visitor.component); + extracted.component.clone_from(&stored_visitor.component); extracted.version.clone_from(&stored_visitor.version); - extracted - .info - .clone_from(&stored_visitor.info); + extracted.info.clone_from(&stored_visitor.info); } } @@ -171,7 +167,7 @@ pub fn build_version_suffix( version_suffix.push(']'); } - if let Some(ref component) = extracted.component { + if let (Some(component), Some(version)) = (&extracted.component, &extracted.version) { // TODO enum & match let component = component.to_lowercase(); if component == "core" { @@ -181,6 +177,7 @@ pub fn build_version_suffix( } else if component == "gateway" { version_suffix.push_str("[GW:"); } + version_suffix.push_str(version); if is_error { if let Some(ref info) = extracted.info { version_suffix.push(' '); From 159b4ed52a6b9d91efbe7c0e963b0dd29648ce6d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 13:01:04 +0200 Subject: [PATCH 083/100] cargo fmt --- crates/defguard_core/src/grpc/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index ff8e46903b..b97bf26f08 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -486,7 +486,11 @@ struct ProxyMessageLoopContext<'a> { endpoint_uri: &'a Uri, } -#[instrument(name = "proxy_message_loop", skip(context), fields(component="proxy"))] +#[instrument( + name = "proxy_message_loop", + skip(context), + fields(component = "proxy") +)] async fn handle_proxy_message_loop( version: &str, info: &str, From f15a420430ec95583dbcc6345de7993baaf4fe41 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 13:07:36 +0200 Subject: [PATCH 084/100] fix FieldFilterVisitor to work with new field names --- crates/defguard_version/src/tracing.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index c3eefa2343..979df1861d 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -361,8 +361,7 @@ impl<'writer> FieldFilterVisitor<'writer> { impl tracing::field::Visit for FieldFilterVisitor<'_> { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { - "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" - | "gateway_info" => { + "component" | "info" | "version" => { // Skip version fields to prevent duplication } _ => { @@ -377,8 +376,7 @@ impl tracing::field::Visit for FieldFilterVisitor<'_> { fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { match field.name() { - "core_version" | "core_info" | "proxy_version" | "proxy_info" | "gateway_version" - | "gateway_info" => { + "component" | "info" | "version" => { // Skip version fields to prevent duplication } _ => { From d9073527f4225a178783df36c3686f96a77fd2e7 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 20 Aug 2025 14:28:52 +0200 Subject: [PATCH 085/100] bring back traversing the span hierarchy to extract field values --- crates/defguard_version/src/tracing.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 979df1861d..3a318ce44d 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -125,11 +125,22 @@ where let mut extracted = ExtractedVersionInfo::default(); if let Some(span_ref) = ctx.lookup_current() { - let extensions = span_ref.extensions(); - if let Some(stored_visitor) = extensions.get::() { - extracted.component.clone_from(&stored_visitor.component); - extracted.version.clone_from(&stored_visitor.version); - extracted.info.clone_from(&stored_visitor.info); + let mut current_span = Some(span_ref); + while let Some(span) = current_span { + let extensions = span.extensions(); + + if let Some(stored_visitor) = extensions.get::() { + if extracted.component.is_none() && stored_visitor.component.is_some() { + extracted.component.clone_from(&stored_visitor.component); + } + if extracted.version.is_none() && stored_visitor.version.is_some() { + extracted.version.clone_from(&stored_visitor.version); + } + if extracted.info.is_none() && stored_visitor.info.is_some() { + extracted.info.clone_from(&stored_visitor.info); + } + } + current_span = span.parent(); } } From 1daa09cdaac379bd70343702939ca7514931cc51 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 09:48:17 +0200 Subject: [PATCH 086/100] fix string-typing with DefguardComponent struct --- crates/defguard_version/src/lib.rs | 33 ++++++++++++++++++++++++++ crates/defguard_version/src/tracing.rs | 29 +++++++++++----------- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 7290783a28..551eca5c78 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -80,6 +80,39 @@ pub enum DefguardVersionError { #[error("Failed to parse SystemInfo header: {0}")] SystemInfoParseError(String), + + #[error("Invalid DefguardComponent: {0}")] + InvalidDefguardComponent(String), +} + +impl FromStr for DefguardComponent { + type Err = DefguardVersionError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "core" => Ok(DefguardComponent::Core), + "proxy" => Ok(DefguardComponent::Proxy), + "gateway" => Ok(DefguardComponent::Gateway), + _ => Err(DefguardVersionError::InvalidDefguardComponent(s.to_string())), + } + } +} + +#[derive(Debug, Clone)] +pub enum DefguardComponent { + Core, + Proxy, + Gateway, +} + +impl fmt::Display for DefguardComponent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DefguardComponent::Core => write!(f, "core"), + DefguardComponent::Proxy => write!(f, "proxy"), + DefguardComponent::Gateway => write!(f, "gateway"), + } + } } /// System information about the host running a Defguard component. diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 3a318ce44d..33eb81e13b 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -71,6 +71,8 @@ //! 3. **`VersionFilteredFields`** - Field formatter that excludes version fields from normal output //! 4. **Utility functions** - Extract and format version information from span hierarchy +use std::str::FromStr; + use semver::Version; use tracing::{Level, Subscriber}; use tracing_subscriber::{ @@ -86,7 +88,7 @@ use tracing_subscriber::{ util::SubscriberInitExt, }; -use crate::{ComponentInfo, DefguardVersionError, SystemInfo}; +use crate::{ComponentInfo, DefguardComponent, DefguardVersionError, SystemInfo}; /// Container for version information extracted from tracing span hierarchy. /// @@ -94,7 +96,7 @@ use crate::{ComponentInfo, DefguardVersionError, SystemInfo}; /// (core, proxy, gateway) found while traversing up the span tree. #[derive(Debug, Default, Clone)] pub struct ExtractedVersionInfo { - pub component: Option, + pub component: Option, pub info: Option, pub version: Option, } @@ -179,14 +181,10 @@ pub fn build_version_suffix( } if let (Some(component), Some(version)) = (&extracted.component, &extracted.version) { - // TODO enum & match - let component = component.to_lowercase(); - if component == "core" { - version_suffix.push_str("[C:"); - } else if component == "proxy" { - version_suffix.push_str("[PX:"); - } else if component == "gateway" { - version_suffix.push_str("[GW:"); + match component { + DefguardComponent::Core =>version_suffix.push_str("[C:"), + DefguardComponent::Proxy =>version_suffix.push_str("[PX:"), + DefguardComponent::Gateway =>version_suffix.push_str("[GW:"), } version_suffix.push_str(version); if is_error { @@ -313,7 +311,7 @@ impl std::fmt::Write for VersionSuffixWriter<'_> { /// A visitor that extracts version fields from spans #[derive(Default, Clone)] pub struct SpanFieldVisitor { - pub component: Option, + pub component: Option, pub info: Option, pub version: Option, } @@ -321,7 +319,7 @@ pub struct SpanFieldVisitor { impl tracing::field::Visit for SpanFieldVisitor { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { - "component" => self.component = Some(value.to_string()), + "component" => self.component = DefguardComponent::from_str(value).ok(), "version" => self.version = Some(value.to_string()), "info" => self.info = Some(value.to_string()), _ => {} @@ -329,10 +327,11 @@ impl tracing::field::Visit for SpanFieldVisitor { } fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { + let value = format!("{value:?}"); match field.name() { - "component" => self.component = Some(format!("{value:?}")), - "version" => self.version = Some(format!("{value:?}")), - "info" => self.info = Some(format!("{value:?}")), + "component" => self.component = DefguardComponent::from_str(&value).ok(), + "version" => self.version = Some(value), + "info" => self.info = Some(value), _ => {} } } From 41c2daf6e594e19596d26a01980302da463369d8 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 09:55:10 +0200 Subject: [PATCH 087/100] use DefguardComponent enum in span definitions --- crates/defguard_core/src/grpc/gateway/mod.rs | 8 ++++---- crates/defguard_core/src/grpc/mod.rs | 5 +++-- crates/defguard_version/src/lib.rs | 4 +++- crates/defguard_version/src/tracing.rs | 6 +++--- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 01d02ad62e..5acb8b759c 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -8,7 +8,7 @@ use std::{ use chrono::{DateTime, TimeDelta, Utc}; use client_state::ClientMap; -use defguard_version::version_info_from_metadata; +use defguard_version::{DefguardComponent, version_info_from_metadata}; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query}; use thiserror::Error; use tokio::{ @@ -750,7 +750,7 @@ impl gateway_service_server::GatewayService for GatewayServer { let mut stream = request.into_inner(); let mut disconnect_timer = interval(Duration::from_secs(PEER_DISCONNECT_INTERVAL)); - let span = tracing::info_span!("gateway_stats", component = "gateway", version, info); + let span = tracing::info_span!("gateway_stats", component = %DefguardComponent::Gateway, version, info); let _guard = span.enter(); loop { // wait for a message or update client map at least once a mninute if no messages are received @@ -916,7 +916,7 @@ impl gateway_service_server::GatewayService for GatewayServer { version, info, } = Self::extract_metadata(request.metadata())?; - let span = tracing::info_span!("gateway_config", component = "gateway", version, info); + let span = tracing::info_span!("gateway_config", component = %DefguardComponent::Gateway, version, info); let _guard = span.enter(); let mut conn = self.pool.acquire().await.map_err(|e| { @@ -994,7 +994,7 @@ impl gateway_service_server::GatewayService for GatewayServer { version, info, } = Self::extract_metadata(request.metadata())?; - let span = tracing::info_span!("gateway_updates", component = "gateway", version, info); + let span = tracing::info_span!("gateway_updates", component = %DefguardComponent::Gateway, version, info); let _guard = span.enter(); let Some(network) = WireguardNetwork::find_by_id(&self.pool, network_id) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index b97bf26f08..a3635561b7 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,7 +1,8 @@ use axum::http::Uri; use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - client::version_interceptor, server::DefguardVersionLayer, version_info_from_metadata, + DefguardComponent, client::version_interceptor, server::DefguardVersionLayer, + version_info_from_metadata, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -489,7 +490,7 @@ struct ProxyMessageLoopContext<'a> { #[instrument( name = "proxy_message_loop", skip(context), - fields(component = "proxy") + fields(component = %DefguardComponent::Proxy) )] async fn handle_proxy_message_loop( version: &str, diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 551eca5c78..dbe4ae28bc 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -93,7 +93,9 @@ impl FromStr for DefguardComponent { "core" => Ok(DefguardComponent::Core), "proxy" => Ok(DefguardComponent::Proxy), "gateway" => Ok(DefguardComponent::Gateway), - _ => Err(DefguardVersionError::InvalidDefguardComponent(s.to_string())), + _ => Err(DefguardVersionError::InvalidDefguardComponent( + s.to_string(), + )), } } } diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 33eb81e13b..7bf6a79776 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -182,9 +182,9 @@ pub fn build_version_suffix( if let (Some(component), Some(version)) = (&extracted.component, &extracted.version) { match component { - DefguardComponent::Core =>version_suffix.push_str("[C:"), - DefguardComponent::Proxy =>version_suffix.push_str("[PX:"), - DefguardComponent::Gateway =>version_suffix.push_str("[GW:"), + DefguardComponent::Core => version_suffix.push_str("[C:"), + DefguardComponent::Proxy => version_suffix.push_str("[PX:"), + DefguardComponent::Gateway => version_suffix.push_str("[GW:"), } version_suffix.push_str(version); if is_error { From dda78a2227c45dd93616bfde618e78a4108c5b5f Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 10:53:05 +0200 Subject: [PATCH 088/100] improve parse_metadata typing --- crates/defguard_version/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index dbe4ae28bc..e88f9800ef 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -270,16 +270,16 @@ impl ComponentInfo { /// println!("Peer system: {}", system); /// } /// ``` -pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { +pub fn parse_metadata(metadata: &MetadataMap) -> Option { let Some(version) = metadata.get(VERSION_HEADER) else { warn!("Missing version header"); return None; }; - let Some(info) = metadata.get(SYSTEM_INFO_HEADER) else { + let Some(system) = metadata.get(SYSTEM_INFO_HEADER) else { warn!("Missing system info header"); return None; }; - let (Ok(version), Ok(info)) = (version.to_str(), info.to_str()) else { + let (Ok(version), Ok(system)) = (version.to_str(), system.to_str()) else { warn!("Failed to stringify version or system info header value"); return None; }; @@ -287,12 +287,12 @@ pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { warn!("Failed to parse version: {version}"); return None; }; - let Ok(info) = SystemInfo::try_from_header_value(info) else { - warn!("Failed to parse system info: {info}"); + let Ok(system) = SystemInfo::try_from_header_value(system) else { + warn!("Failed to parse system info: {system}"); return None; }; - Some((version, info)) + Some(ComponentInfo { version, system }) } /// Extracts version information from metadata as formatted strings with fallback. @@ -325,7 +325,7 @@ pub fn parse_metadata(metadata: &MetadataMap) -> Option<(Version, SystemInfo)> { /// ``` #[must_use] pub fn version_info_from_metadata(metadata: &MetadataMap) -> (String, String) { - parse_metadata(metadata).map_or(("?".to_string(), "?".to_string()), |(version, info)| { - (version.to_string(), info.to_string()) + parse_metadata(metadata).map_or(("?".to_string(), "?".to_string()), |info| { + (info.version.to_string(), info.system.to_string()) }) } From 8251eea27bb79b808f9fc065b19a5dfcc2221c19 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 11:09:46 +0200 Subject: [PATCH 089/100] take Version in constructors / initializers instead of &str --- crates/defguard/src/main.rs | 5 ++++- crates/defguard_core/src/grpc/mod.rs | 6 +++--- crates/defguard_version/src/client.rs | 10 ++-------- crates/defguard_version/src/lib.rs | 10 +++++----- crates/defguard_version/src/server.rs | 10 +++++----- crates/defguard_version/src/tracing.rs | 15 ++++++--------- 6 files changed, 25 insertions(+), 31 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 8ea1215f20..42a40015d7 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -42,7 +42,10 @@ async fn main() -> Result<(), anyhow::Error> { SERVER_CONFIG.set(config.clone())?; // initialize tracing with version formatter - defguard_version::tracing::init(VERSION, &config.log_level)?; + defguard_version::tracing::init( + defguard_version::Version::parse(VERSION)?, + &config.log_level, + )?; info!("Starting ... version v{}", VERSION); debug!("Using config: {config:?}"); diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index a3635561b7..4aa3b44be0 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,7 +1,7 @@ use axum::http::Uri; use chrono::{NaiveDateTime, Utc}; use defguard_version::{ - DefguardComponent, client::version_interceptor, server::DefguardVersionLayer, + DefguardComponent, Version, client::version_interceptor, server::DefguardVersionLayer, version_info_from_metadata, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; @@ -881,7 +881,7 @@ pub async fn run_grpc_bidi_stream( loop { debug!("Connecting to proxy at {}", endpoint.uri()); - let interceptor = version_interceptor(VERSION); + let interceptor = version_interceptor(Version::parse(VERSION)?); let mut client = ProxyClient::with_interceptor(endpoint.connect_lazy(), interceptor); let (tx, rx) = mpsc::unbounded_channel(); let Ok(response) = client.bidi(UnboundedReceiverStream::new(rx)).await else { @@ -969,7 +969,7 @@ pub async fn run_grpc_server( #[cfg(feature = "wireguard")] let router = router.add_service( ServiceBuilder::new() - .layer(DefguardVersionLayer::new(VERSION)?) + .layer(DefguardVersionLayer::new(Version::parse(VERSION)?)) .service(gateway_service), ); #[cfg(feature = "worker")] diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index 610d782d37..ea0ae5c313 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -20,17 +20,11 @@ use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; /// let client = MyClient::with_interceptor(channel, interceptor); /// ``` pub fn version_interceptor( - version: &str, + version: crate::Version, ) -> impl Fn(Request<()>) -> Result, Status> + Clone { - let component_info = ComponentInfo::new(version) - .inspect_err(|err| warn!("Failed to get component info: {err}")) - .ok(); + let component_info = ComponentInfo::new(version); move |mut request: Request<()>| -> Result, Status> { - let Some(component_info) = &component_info else { - return Ok(request); - }; - let metadata = request.metadata_mut(); // Add version header diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index e88f9800ef..a432f5a93f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -58,7 +58,7 @@ //! ``` use ::tracing::{error, warn}; -use semver::Version; +pub use semver::Version; use std::{fmt, str::FromStr}; use thiserror::Error; use tonic::metadata::MetadataMap; @@ -233,13 +233,13 @@ impl ComponentInfo { /// let info = ComponentInfo::new("1.0.0").unwrap(); /// assert_eq!(info.version.major, 1); /// ``` - pub fn new(version: &str) -> Result { - let version = Version::from_str(version)?; + #[must_use] + pub fn new(version: Version) -> Self { let info = os_info::get(); - Ok(Self { + Self { version, system: info.into(), - }) + } } } diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index 0e24fcc7fb..e4cfca0add 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -36,7 +36,7 @@ use tonic::{ }; use tower::{Layer, Service}; -use crate::{ComponentInfo, DefguardVersionError, SYSTEM_INFO_HEADER, VERSION_HEADER}; +use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; /// A tower `Layer` that adds Defguard version and system information headers to gRPC responses. /// @@ -64,10 +64,10 @@ impl DefguardVersionLayer { /// /// * `Ok(DefguardVersionLayer)` - A new layer instance /// * `Err(DefguardVersionError)` - If the version string cannot be parsed - pub fn new(version: &str) -> Result { - Ok(Self { - component_info: ComponentInfo::new(version)?, - }) + pub fn new(version: crate::Version) -> Self { + Self { + component_info: ComponentInfo::new(version), + } } } diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 7bf6a79776..fbfe806e9a 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -213,14 +213,11 @@ pub struct VersionSuffixFormat { } impl VersionSuffixFormat { - pub fn new( - own_version: &str, - inner: Format, - ) -> Result { - Ok(Self { + pub fn new(own_version: crate::Version, inner: Format) -> Self { + Self { inner, - component_info: ComponentInfo::new(own_version)?, - }) + component_info: ComponentInfo::new(own_version), + } } } @@ -419,7 +416,7 @@ impl tracing::field::Visit for FieldFilterVisitor<'_> { /// ``` /// defguard_version::tracing::init("1.5.0", "info"); /// ``` -pub fn init(own_version: &str, log_level: &str) -> Result<(), DefguardVersionError> { +pub fn init(own_version: crate::Version, log_level: &str) -> Result<(), DefguardVersionError> { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() @@ -432,7 +429,7 @@ pub fn init(own_version: &str, log_level: &str) -> Result<(), DefguardVersionErr .event_format(VersionSuffixFormat::new( own_version, tracing_subscriber::fmt::format::Format::default().with_ansi(true), - )?) + )) .fmt_fields(VersionFilteredFields), ) .init(); From eadf3d840aed2271716f3c45995d9cd04b1f0fba Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 12:57:58 +0200 Subject: [PATCH 090/100] better span name --- crates/defguard_core/src/grpc/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 4aa3b44be0..49480bdd8b 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -488,7 +488,7 @@ struct ProxyMessageLoopContext<'a> { } #[instrument( - name = "proxy_message_loop", + name = "proxy_bidi", skip(context), fields(component = %DefguardComponent::Proxy) )] From 122541a37265b1dc259a98fcc00704b517e74784 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 12:58:14 +0200 Subject: [PATCH 091/100] re-export semver::Error to be used by downstream crates --- crates/defguard_version/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index a432f5a93f..08adce4b8b 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -58,7 +58,7 @@ //! ``` use ::tracing::{error, warn}; -pub use semver::Version; +pub use semver::{Version, Error as SemverError}; use std::{fmt, str::FromStr}; use thiserror::Error; use tonic::metadata::MetadataMap; From cfc878670cd61bb4f90fb725f177a26993b5ee6f Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 13:09:15 +0200 Subject: [PATCH 092/100] escape newline characters in log lines --- crates/defguard_version/src/tracing.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index fbfe806e9a..15d5d15fcd 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -295,12 +295,15 @@ impl<'a> VersionSuffixWriter<'a> { impl std::fmt::Write for VersionSuffixWriter<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { - if let Some(content) = s.strip_suffix('\n') { - // Remove the newline, add version suffix, then add newline back + // Replace internal newlines with escaped version to prevent log line splitting + let escaped = s.replace('\n', "\\n"); + + if let Some(content) = escaped.strip_suffix("\\n") { + // If the original string ended with a newline, add version suffix and restore newline writeln!(self.inner, "{}{}", content, self.version_suffix) } else { - // No newline at end, just pass through - write!(self.inner, "{s}") + // No trailing newline, just write the escaped content + write!(self.inner, "{}", escaped) } } } From 2d5f8c40d641a385f5c0da84de14637224edf08e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 13:16:17 +0200 Subject: [PATCH 093/100] lib rustdoc --- crates/defguard_version/src/lib.rs | 43 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 08adce4b8b..fa57216919 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -16,11 +16,12 @@ //! ## Server-side middleware //! //! ``` +//! use semver::Version; //! use tower::ServiceBuilder; //! use defguard_version::server::DefguardVersionLayer; //! -//! let my_grpc_service = ServiceBuilder::new(); -//! let layer = DefguardVersionLayer::new("1.0.0").unwrap(); +//! let version = Version::parse("1.0.0").unwrap(); +//! let layer = DefguardVersionLayer::new(version); //! let service = ServiceBuilder::new() //! .layer(layer) //! .service(my_grpc_service); @@ -29,13 +30,15 @@ //! ## Client-side interceptor //! //! ```ignore +//! use semver::Version; //! use defguard_version::client::version_interceptor; //! use tonic::transport::Channel; //! +//! let version = Version::parse("1.0.0").unwrap(); //! let channel = Channel::from_static("http://localhost:50051").connect().await.unwrap(); //! let client = MyServiceClient::with_interceptor( //! channel, -//! version_interceptor("1.0.0").unwrap() +//! version_interceptor(version) //! ); //! ``` //! @@ -48,9 +51,9 @@ //! let metadata = MetadataMap::new(); //! //! // Extract parsed version and system info -//! if let Some((version, system_info)) = parse_metadata(&metadata) { -//! println!("Client version: {version}"); -//! println!("Client system: {system_info}"); +//! if let Some(component_info) = parse_metadata(&metadata) { +//! println!("Client version: {}", component_info.version); +//! println!("Client system: {}", component_info.system); //! } //! //! // Get version info as strings (with fallback) @@ -85,6 +88,14 @@ pub enum DefguardVersionError { InvalidDefguardComponent(String), } +/// Represents the different types of Defguard components that can communicate via gRPC. +#[derive(Debug, Clone)] +pub enum DefguardComponent { + Core, + Proxy, + Gateway, +} + impl FromStr for DefguardComponent { type Err = DefguardVersionError; @@ -100,13 +111,6 @@ impl FromStr for DefguardComponent { } } -#[derive(Debug, Clone)] -pub enum DefguardComponent { - Core, - Proxy, - Gateway, -} - impl fmt::Display for DefguardComponent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -213,24 +217,21 @@ pub struct ComponentInfo { } impl ComponentInfo { - /// This method parses the provided version string and automatically detects + /// Creates a new ComponentInfo with the provided version and automatically detects /// the current system information. /// /// # Arguments /// - /// * `version` - A semantic version string (e.g., "1.2.3") - /// - /// # Returns - /// - /// * `Ok(ComponentInfo)` - Successfully created component info - /// * `Err(DefguardVersionError)` - If version parsing fails + /// * `version` - A parsed semantic version /// /// # Examples /// /// ``` /// use defguard_version::ComponentInfo; + /// use semver::Version; /// - /// let info = ComponentInfo::new("1.0.0").unwrap(); + /// let version = Version::parse("1.0.0").unwrap(); + /// let info = ComponentInfo::new(version); /// assert_eq!(info.version.major, 1); /// ``` #[must_use] From 06727ec53e85784413a32327df37364d4271a0dd Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 13:35:00 +0200 Subject: [PATCH 094/100] update rustdoc comments --- crates/defguard_version/src/client.rs | 8 +++-- crates/defguard_version/src/server.rs | 6 ++-- crates/defguard_version/src/tracing.rs | 44 ++++++++++++++------------ 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/crates/defguard_version/src/client.rs b/crates/defguard_version/src/client.rs index ea0ae5c313..e7cd941fe2 100644 --- a/crates/defguard_version/src/client.rs +++ b/crates/defguard_version/src/client.rs @@ -7,15 +7,17 @@ use crate::{ComponentInfo, SYSTEM_INFO_HEADER, VERSION_HEADER}; /// /// # Headers Added /// -/// - `defguard-version`: The semantic version of the Defguard component -/// - `defguard-system`: System information including OS type, version, and architecture +/// - `defguard-version`: Semantic version of the component +/// - `defguard-system`: System information including OS type, version and architecture /// /// # Examples /// ```ignore +/// use semver::Version; /// use tonic::transport::Channel; /// /// use defguard_version::client::version_interceptor; -/// let interceptor = version_interceptor("1.0.0"); +/// let version = Version::parse("1.0.0").unwrap(); +/// let interceptor = version_interceptor(version); /// let channel = Channel::from_static("http://localhost:50051").connect().await.unwrap(); /// let client = MyClient::with_interceptor(channel, interceptor); /// ``` diff --git a/crates/defguard_version/src/server.rs b/crates/defguard_version/src/server.rs index e4cfca0add..96cb278aa7 100644 --- a/crates/defguard_version/src/server.rs +++ b/crates/defguard_version/src/server.rs @@ -15,9 +15,11 @@ //! ``` //! use tower::ServiceBuilder; //! use defguard_version::server::DefguardVersionLayer; +//! use semver::Version; //! //! let my_grpc_service = ServiceBuilder::new(); -//! let version_layer = DefguardVersionLayer::new("1.0.0").unwrap(); +//! let version = Version::parse("1.0.0").unwrap(); +//! let version_layer = DefguardVersionLayer::new(version).unwrap(); //! let service = ServiceBuilder::new() //! .layer(version_layer) //! .service(my_grpc_service); @@ -58,7 +60,7 @@ impl DefguardVersionLayer { /// /// # Arguments /// - /// * `version` - A semantic version string (e.g., "1.0.0") + /// * `version` - Semantic version of the component /// /// # Returns /// diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 15d5d15fcd..0ae9bcc694 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -22,9 +22,9 @@ //! //! The following span fields are automatically captured and used for version display: //! -//! - `core_version`, `core_info` - Core component version and system information -//! - `proxy_version`, `proxy_info` - Proxy component version and system information -//! - `gateway_version`, `gateway_info` - Gateway component version and system information +//! - `component` - component name to use, one of `DefguardComponent` variants +//! - `version` - component version, usually retrieved from the headers +//! - `info` - system information, usually retrieved from the headers //! //! # Usage //! @@ -32,19 +32,24 @@ //! //! ```rust //! // Initialize tracing with version-aware formatting -//! defguard_version::tracing::init("1.5.0", "info"); +//! use semver::Version; +//! +//! let version = Version::parse("1.5.0").unwrap(); +//! defguard_version::tracing::init(version, "info"); //! ``` //! //! ## Creating Version-Aware Spans //! //! ```rust +//! use crate::DefguardComponent; //! use tracing::info_span; //! //! // Create a span with proxy version information //! let _span = info_span!( //! "proxy_communication", -//! proxy_version = "1.4.2", -//! proxy_info = "Linux 22.04 64-bit x86_64" +//! component = %DefguardComponent::Proxy, +//! version = "1.4.2", +//! info = "Linux 22.04 64-bit x86_64" //! ).entered(); //! //! // This log will include the proxy version information @@ -92,8 +97,7 @@ use crate::{ComponentInfo, DefguardComponent, DefguardVersionError, SystemInfo}; /// Container for version information extracted from tracing span hierarchy. /// -/// Aggregates version and system information for different Defguard components -/// (core, proxy, gateway) found while traversing up the span tree. +/// Aggregates version and system information found while traversing up the span tree. #[derive(Debug, Default, Clone)] pub struct ExtractedVersionInfo { pub component: Option, @@ -250,18 +254,18 @@ where /// Formats a tracing event, conditionally adding version information as a prefix. /// /// This method includes version information based on: - /// - For ERROR level logs: includes core_version, proxy_version, and proxy_info (if available in span) - /// - For other levels: includes only core_version and proxy_version (if available in span) + /// - For ERROR level logs: includes own and remote component version and system-info + /// - For other levels: includes only own and remote component version fn format_event( &self, ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, writer: Writer<'_>, event: &tracing::Event<'_>, ) -> std::fmt::Result { - // Extract version information from current span context using utility function + // Extract version information from current span context let extracted = extract_version_info_from_context(ctx); - // Build version suffix using utility function + // Build version suffix let is_error = *event.metadata().level() == Level::ERROR; let version_suffix = build_version_suffix( &extracted, @@ -295,9 +299,9 @@ impl<'a> VersionSuffixWriter<'a> { impl std::fmt::Write for VersionSuffixWriter<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { - // Replace internal newlines with escaped version to prevent log line splitting + // Replace newline characters with escaped version to prevent log line splitting let escaped = s.replace('\n', "\\n"); - + if let Some(content) = escaped.strip_suffix("\\n") { // If the original string ended with a newline, add version suffix and restore newline writeln!(self.inner, "{}{}", content, self.version_suffix) @@ -403,16 +407,16 @@ impl tracing::field::Visit for FieldFilterVisitor<'_> { /// Initializes tracing with custom formatter that conditionally displays version information. /// /// The formatter will: -/// - For ERROR level logs: display core_version, proxy_version, and proxy_info (if available) -/// - For other log levels: display only core_version and proxy_version (if available) +/// - For ERROR level logs: display own and remote component version and system-info +/// - For other log levels: display only own and remote component version /// /// Version information is extracted from tracing span fields with names: -/// - `core_version`: The core application version -/// - `proxy_version`: The connected proxy version -/// - `proxy_info`: Additional proxy system information +/// - `component` - component name to use, one of `DefguardComponent` variants +/// - `version` - component version, usually retrieved from the headers +/// - `info` - system information, usually retrieved from the headers /// /// # Arguments -/// * `core_version` - The core application version to use as fallback when not found in spans +/// * `own_version` - The application semantic version /// * `log_level` - The log level filter to use /// /// # Examples From 30c81e9bbf2ac9fa130d2a5b2b77edf9bccc0adf Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 14:09:26 +0200 Subject: [PATCH 095/100] cargo fmt --- crates/defguard_version/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index fa57216919..64370d646f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -61,7 +61,7 @@ //! ``` use ::tracing::{error, warn}; -pub use semver::{Version, Error as SemverError}; +pub use semver::{Error as SemverError, Version}; use std::{fmt, str::FromStr}; use thiserror::Error; use tonic::metadata::MetadataMap; From 7a56987fe5c41f59744672cb6248f2b4887fb21a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 14:19:25 +0200 Subject: [PATCH 096/100] use aws cached rust image --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3af2207a6f..9098bd1dcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: runs-on: - codebuild-defguard-core-runner-${{ github.run_id }}-${{ github.run_attempt }} - container: rust:1 + container: public.ecr.aws/docker/library/rust:1 services: postgres: From b937995ddf0b4c307860e23df11d1d288ce19345 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 14:30:05 +0200 Subject: [PATCH 097/100] clippy fix --- crates/defguard_version/src/tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index 0ae9bcc694..9cfa14a690 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -194,7 +194,7 @@ pub fn build_version_suffix( if is_error { if let Some(ref info) = extracted.info { version_suffix.push(' '); - version_suffix.push_str(&info); + version_suffix.push_str(info); } } version_suffix.push(']'); From 18e7e7de318acb7da52d3dd8fbf40d90179776ab Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 14:55:57 +0200 Subject: [PATCH 098/100] allow AGPL-3.0-only license in defguard_version crate --- deny.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deny.toml b/deny.toml index 6544e81c44..3e865a910a 100644 --- a/deny.toml +++ b/deny.toml @@ -123,6 +123,9 @@ exceptions = [ { allow = [ "AGPL-3.0-only", ], crate = "defguard_event_logger" }, + { allow = [ + "AGPL-3.0-only", + ], crate = "defguard_version" }, { allow = [ "AGPL-3.0-only", ], crate = "model_derive" }, From cee90c292c73d0539bceac6c2e1e1850ac1d8f27 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 21 Aug 2025 14:57:34 +0200 Subject: [PATCH 099/100] run cargo-deny manually instead of using gh-action --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9098bd1dcd..4150890dbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,9 @@ jobs: cargo clippy --all-targets --all-features -- -D warnings - name: Run cargo deny - uses: EmbarkStudios/cargo-deny-action@v2 + run: | + cargo install cargo-deny + cargo deny check - name: Install nextest uses: taiki-e/install-action@nextest From aaab3647c008aa609f6ce1707edc87879d3bff56 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 22 Aug 2025 09:00:34 +0200 Subject: [PATCH 100/100] sort import statements --- crates/defguard/src/main.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 42a40015d7..ab294c0e01 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -1,4 +1,11 @@ use bytes::Bytes; +use secrecy::ExposeSecret; +use std::{ + fs::read_to_string, + sync::{Arc, Mutex}, +}; +use tokio::sync::{broadcast, mpsc::unbounded_channel}; + use defguard_core::{ SERVER_CONFIG, VERSION, auth::failed_login::FailedLoginMap, @@ -23,12 +30,6 @@ use defguard_core::{ }; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; -use secrecy::ExposeSecret; -use std::{ - fs::read_to_string, - sync::{Arc, Mutex}, -}; -use tokio::sync::{broadcast, mpsc::unbounded_channel}; #[macro_use] extern crate tracing;