From 999a655a7cdeca17231a59340e05407cb5631147 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 12 Feb 2026 11:32:28 +0100 Subject: [PATCH 01/32] move tls code to separate crate, make it generic --- Cargo.lock | 19 +- Cargo.toml | 1 + crates/defguard_grpc_tls/Cargo.toml | 17 ++ crates/defguard_grpc_tls/src/certs.rs | 156 ++++++++++ crates/defguard_grpc_tls/src/connector.rs | 50 ++++ crates/defguard_grpc_tls/src/lib.rs | 2 + crates/defguard_proxy_manager/Cargo.toml | 6 +- crates/defguard_proxy_manager/src/certs.rs | 268 +----------------- .../src/proxy_handler.rs | 60 +--- 9 files changed, 250 insertions(+), 329 deletions(-) create mode 100644 crates/defguard_grpc_tls/Cargo.toml create mode 100644 crates/defguard_grpc_tls/src/certs.rs create mode 100644 crates/defguard_grpc_tls/src/connector.rs create mode 100644 crates/defguard_grpc_tls/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 42be2db151..d1e7060a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1349,6 +1349,19 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "defguard_grpc_tls" +version = "0.0.0" +dependencies = [ + "defguard_common", + "http", + "rustls", + "thiserror 2.0.18", + "tokio", + "tower-service", + "x509-parser 0.18.1", +] + [[package]] name = "defguard_mail" version = "0.0.0" @@ -1390,15 +1403,13 @@ dependencies = [ "defguard_certs", "defguard_common", "defguard_core", + "defguard_grpc_tls", "defguard_mail", "defguard_proto", "defguard_version", - "http", - "hyper", "hyper-rustls", "openidconnect", "reqwest", - "rustls", "secrecy", "semver", "sqlx", @@ -1406,9 +1417,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", - "tower-service", "tracing", - "x509-parser 0.18.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 82704ceb15..85753585c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ defguard_version = { path = "./crates/defguard_version", version = "0.0.0" } defguard_vpn_stats_purge = { path = "./crates/defguard_vpn_stats_purge", version = "0.0.0" } defguard_web_ui = { path = "./crates/defguard_web_ui", version = "0.0.0" } defguard_certs = { path = "./crates/defguard_certs", version = "0.0.0" } +defguard_grpc_tls = { path = "./crates/defguard_grpc_tls", version = "0.0.0" } defguard_setup = { path = "./crates/defguard_setup", version = "0.0.0" } model_derive = { path = "./crates/model_derive", version = "0.0.0" } diff --git a/crates/defguard_grpc_tls/Cargo.toml b/crates/defguard_grpc_tls/Cargo.toml new file mode 100644 index 0000000000..c399c53e87 --- /dev/null +++ b/crates/defguard_grpc_tls/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "defguard_grpc_tls" +version = "0.0.0" +edition.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +defguard_common.workspace = true +http = "1.1" +rustls = { version = "0.23", features = ["ring"] } +thiserror.workspace = true +tokio.workspace = true +tower-service = "0.3" +x509-parser = "0.18" diff --git a/crates/defguard_grpc_tls/src/certs.rs b/crates/defguard_grpc_tls/src/certs.rs new file mode 100644 index 0000000000..d07a574e54 --- /dev/null +++ b/crates/defguard_grpc_tls/src/certs.rs @@ -0,0 +1,156 @@ +//! Custom TLS verification for proxy and gateway connections. +//! +//! Motivation: +//! - tonic/rustls does not fetch or enforce CRL distribution points, so revocation +//! has to be enforced by the application. +//! - We pin each component to its expected certificate serial and reject mismatches +//! at the TLS layer, before any gRPC requests are processed. +//! - A lightweight in-memory cache (refreshed periodically) avoids database access +//! during the handshake and keeps verification synchronous. + +use std::{collections::HashMap, sync::Arc}; + +use defguard_common::db::Id; +use rustls::{ + client::{ + danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + WebPkiServerVerifier, + }, + crypto, + pki_types::{CertificateDer, ServerName, UnixTime}, + CertificateError, DistinguishedName, Error as RustlsError, RootCertStore, SignatureScheme, +}; +use thiserror::Error; +use tokio::sync::watch; +use x509_parser::parse_x509_certificate; + +/// Errors that can occur while building a TLS config with a pinned verifier. +#[derive(Debug, Error)] +pub enum CertConfigError { + #[error("TLS config error: {0}")] + TlsConfig(String), +} + +/// Wraps WebPKI verification to enforce component-specific certificate serials. +#[derive(Debug)] +struct CertVerifier { + inner: Arc, + certs_rx: watch::Receiver>>, + component_id: Id, +} + +impl CertVerifier { + fn new( + inner: Arc, + certs_rx: watch::Receiver>>, + component_id: Id, + ) -> Self { + Self { + inner, + certs_rx, + component_id, + } + } + + /// Validate the peer certificate serial against the expected component serial. + fn verify(&self, end_entity: &CertificateDer<'_>) -> Result<(), RustlsError> { + let (_, cert) = parse_x509_certificate(end_entity.as_ref()) + .map_err(|_| RustlsError::InvalidCertificate(CertificateError::BadEncoding))?; + let serial = cert.tbs_certificate.raw_serial_as_string(); + let certs = self.certs_rx.borrow(); + let Some(expected) = certs.get(&self.component_id) else { + return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); + }; + if !expected.eq_ignore_ascii_case(&serial) { + return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); + } + Ok(()) + } +} + +impl ServerCertVerifier for CertVerifier { + /// Delegate chain validation to WebPKI, then enforce the component-specific pin. + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + server_name: &ServerName<'_>, + ocsp_response: &[u8], + now: UnixTime, + ) -> Result { + self.inner.verify_server_cert( + end_entity, + intermediates, + server_name, + ocsp_response, + now, + )?; + self.verify(end_entity)?; + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + self.inner.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + self.inner.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec { + self.inner.supported_verify_schemes() + } + + fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> { + self.inner.root_hint_subjects() + } +} + +/// Build a root store from the configured CA for WebPKI validation. +fn root_store_from_ca(ca_cert_der: &[u8]) -> Result { + let mut roots = RootCertStore::empty(); + roots + .add(CertificateDer::from(ca_cert_der.to_vec())) + .map_err(|err| CertConfigError::TlsConfig(err.to_string()))?; + Ok(roots) +} + +/// Create a rustls client config that enforces the pinned component certificate serial. +pub fn client_config( + ca_cert_der: &[u8], + certs_rx: watch::Receiver>>, + component_id: Id, +) -> Result { + let provider = Arc::new(crypto::ring::default_provider()); + let roots = root_store_from_ca(ca_cert_der)?; + let verifier_roots = root_store_from_ca(ca_cert_der)?; + let verifier = WebPkiServerVerifier::builder_with_provider( + Arc::new(verifier_roots), + Arc::clone(&provider), + ) + .build() + .map_err(|err| CertConfigError::TlsConfig(err.to_string()))?; + let builder = rustls::ClientConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions() + .map_err(|err| CertConfigError::TlsConfig(err.to_string()))?; + let mut config = builder.with_root_certificates(roots).with_no_client_auth(); + let verifier: Arc = verifier; + config + .dangerous() + .set_certificate_verifier(Arc::new(CertVerifier::new( + verifier, + certs_rx, + component_id, + ))); + Ok(config) +} diff --git a/crates/defguard_grpc_tls/src/connector.rs b/crates/defguard_grpc_tls/src/connector.rs new file mode 100644 index 0000000000..16f438ffe6 --- /dev/null +++ b/crates/defguard_grpc_tls/src/connector.rs @@ -0,0 +1,50 @@ +use http::Uri; + +/// Rewrites the request URI scheme to https for the TLS connector. +/// +/// Tonic expects an http URI for its endpoint, but a custom connector performs +/// the TLS handshake and requires https to select the TLS path. +#[derive(Clone, Debug)] +pub struct HttpsSchemeConnector { + inner: C, +} + +impl HttpsSchemeConnector { + pub const fn new(inner: C) -> Self { + Self { inner } + } +} + +type BoxError = Box; + +impl tower_service::Service for HttpsSchemeConnector +where + C: tower_service::Service + Clone + Send + 'static, + C::Future: Send, +{ + type Response = C::Response; + type Error = BoxError; + type Future = std::pin::Pin< + Box> + Send>, + >; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.inner.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, uri: Uri) -> Self::Future { + let mut parts = uri.into_parts(); + parts.scheme = Some(http::uri::Scheme::HTTPS); + let https_uri = match Uri::from_parts(parts) { + Ok(uri) => uri, + Err(err) => { + return Box::pin(async move { Err(err.into()) }); + } + }; + let mut inner = self.inner.clone(); + Box::pin(async move { inner.call(https_uri).await }) + } +} diff --git a/crates/defguard_grpc_tls/src/lib.rs b/crates/defguard_grpc_tls/src/lib.rs new file mode 100644 index 0000000000..b7a37f7f97 --- /dev/null +++ b/crates/defguard_grpc_tls/src/lib.rs @@ -0,0 +1,2 @@ +pub mod certs; +pub mod connector; diff --git a/crates/defguard_proxy_manager/Cargo.toml b/crates/defguard_proxy_manager/Cargo.toml index 6719869278..242ab953e0 100644 --- a/crates/defguard_proxy_manager/Cargo.toml +++ b/crates/defguard_proxy_manager/Cargo.toml @@ -15,6 +15,7 @@ defguard_mail.workspace = true defguard_proto.workspace = true defguard_version.workspace = true defguard_certs.workspace = true +defguard_grpc_tls.workspace = true openidconnect.workspace = true reqwest.workspace = true semver.workspace = true @@ -29,8 +30,3 @@ tokio.workspace = true tonic.workspace = true tracing.workspace = true hyper-rustls = { version = "0.27", features = ["http2"] } -rustls = { version = "0.23", features = ["ring"] } -x509-parser = "0.18" -http = "1.1" -hyper = "1.4" -tower-service = "0.3" diff --git a/crates/defguard_proxy_manager/src/certs.rs b/crates/defguard_proxy_manager/src/certs.rs index a1010dc4cf..ac16d1e0ba 100644 --- a/crates/defguard_proxy_manager/src/certs.rs +++ b/crates/defguard_proxy_manager/src/certs.rs @@ -1,120 +1,10 @@ -//! Custom TLS verification for proxy connections. -//! -//! Motivation: -//! - tonic/rustls does not fetch or enforce CRL distribution points, so revocation -//! has to be enforced by the application. -//! - We pin each proxy to its expected certificate serial and reject mismatches at -//! the TLS layer, before any gRPC requests are processed. -//! - A lightweight in-memory cache (refreshed periodically) avoids database access -//! during the handshake and keeps verification synchronous. +//! Cached certificate serials for proxies. use std::{collections::HashMap, sync::Arc}; use defguard_common::db::{Id, models::proxy::Proxy}; -use rustls::{ - CertificateError, DistinguishedName, Error as RustlsError, RootCertStore, SignatureScheme, - client::{ - WebPkiServerVerifier, - danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, - }, - crypto, - pki_types::{CertificateDer, ServerName, UnixTime}, -}; use sqlx::PgPool; use tokio::sync::watch; -use x509_parser::parse_x509_certificate; - -use crate::error::ProxyError; - -/// Wraps WebPKI verification to enforce proxy-specific certificate serials. -#[derive(Debug)] -struct CertVerifier { - inner: Arc, - certs_rx: watch::Receiver>>, - proxy_id: Id, -} - -impl CertVerifier { - fn new( - inner: Arc, - certs_rx: watch::Receiver>>, - proxy_id: Id, - ) -> Self { - Self { - inner, - certs_rx, - proxy_id, - } - } - - /// Validate the peer certificate serial against the expected proxy serial. - fn verify(&self, end_entity: &CertificateDer<'_>) -> Result<(), RustlsError> { - let (_, cert) = parse_x509_certificate(end_entity.as_ref()) - .map_err(|_| RustlsError::InvalidCertificate(CertificateError::BadEncoding))?; - let serial = cert.tbs_certificate.raw_serial_as_string(); - let certs = self.certs_rx.borrow(); - let Some(expected) = certs.get(&self.proxy_id) else { - error!("Missing expected certificate for proxy: {}", self.proxy_id); - return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); - }; - if !expected.eq_ignore_ascii_case(&serial) { - error!( - "Invalid certificate for proxy {}: expected={expected} got={serial}", - self.proxy_id - ); - return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); - } - Ok(()) - } -} - -impl ServerCertVerifier for CertVerifier { - /// Delegate chain validation to WebPKI, then enforce the proxy-specific pin. - fn verify_server_cert( - &self, - end_entity: &CertificateDer<'_>, - intermediates: &[CertificateDer<'_>], - server_name: &ServerName<'_>, - ocsp_response: &[u8], - now: UnixTime, - ) -> Result { - self.inner.verify_server_cert( - end_entity, - intermediates, - server_name, - ocsp_response, - now, - )?; - self.verify(end_entity)?; - Ok(ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { - self.inner.verify_tls12_signature(message, cert, dss) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { - self.inner.verify_tls13_signature(message, cert, dss) - } - - fn supported_verify_schemes(&self) -> Vec { - self.inner.supported_verify_schemes() - } - - fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> { - self.inner.root_hint_subjects() - } -} /// Build a compact id->serial map, skipping proxies without a stored cert. fn collect_certs(items: I) -> HashMap @@ -143,159 +33,3 @@ pub(crate) async fn refresh_certs(pool: &PgPool, tx: &watch::Sender Result { - let mut roots = RootCertStore::empty(); - roots - .add(CertificateDer::from(ca_cert_der.to_vec())) - .map_err(|err| ProxyError::TlsConfigError(err.to_string()))?; - Ok(roots) -} - -/// Create a rustls client config that enforces the pinned proxy certificate serial. -pub(crate) fn client_config( - ca_cert_der: &[u8], - certs_rx: watch::Receiver>>, - proxy_id: Id, -) -> Result { - let provider = Arc::new(crypto::ring::default_provider()); - let roots = root_store_from_ca(ca_cert_der)?; - let verifier_roots = root_store_from_ca(ca_cert_der)?; - let verifier = WebPkiServerVerifier::builder_with_provider( - Arc::new(verifier_roots), - Arc::clone(&provider), - ) - .build() - .map_err(|err| ProxyError::TlsConfigError(err.to_string()))?; - let builder = rustls::ClientConfig::builder_with_provider(provider) - .with_safe_default_protocol_versions() - .map_err(|err| ProxyError::TlsConfigError(err.to_string()))?; - let mut config = builder.with_root_certificates(roots).with_no_client_auth(); - config - .dangerous() - .set_certificate_verifier(Arc::new(CertVerifier::new(verifier, certs_rx, proxy_id))); - Ok(config) -} - -#[cfg(test)] -mod tests { - use super::*; - - use defguard_certs::{CertificateAuthority, Csr, DnType, generate_key_pair}; - use rustls::client::danger::HandshakeSignatureValid; - - #[derive(Debug)] - struct NoopVerifier; - - impl ServerCertVerifier for NoopVerifier { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp_response: &[u8], - _now: UnixTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - - fn supported_verify_schemes(&self) -> Vec { - Vec::new() - } - - fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> { - None - } - } - - fn make_cert_and_serial() -> (CertificateDer<'static>, String) { - let ca = CertificateAuthority::new("Defguard CA", "test@example.com", 30).unwrap(); - let key_pair = generate_key_pair().unwrap(); - let csr = Csr::new( - &key_pair, - &["proxy.local".to_string()], - vec![(DnType::CommonName, "proxy.local")], - ) - .unwrap(); - let cert = ca.sign_csr(&csr).unwrap(); - let cert_der = CertificateDer::from(cert.der().to_vec()); - let (_, parsed) = parse_x509_certificate(cert_der.as_ref()).unwrap(); - let serial = parsed.tbs_certificate.raw_serial_as_string(); - (cert_der, serial) - } - - #[test] - fn collect_certs_skips_missing() { - let certs = collect_certs(vec![(1, None), (2, Some("abc".to_string()))]); - assert_eq!(certs.len(), 1); - assert_eq!(certs.get(&2), Some(&"abc".to_string())); - } - - #[test] - fn verify_accepts_expected_serial() { - let (cert_der, serial) = make_cert_and_serial(); - let (_tx, rx) = watch::channel(Arc::new(HashMap::from([(1, serial.clone())]))); - let verifier = CertVerifier::new(Arc::new(NoopVerifier), rx, 1); - let result = verifier.verify(&cert_der); - assert!(result.is_ok()); - } - - #[test] - fn verify_rejects_missing_expected_cert() { - let (cert_der, serial) = make_cert_and_serial(); - let (_tx, rx) = watch::channel(Arc::new(HashMap::from([(2, serial)]))); - let verifier = CertVerifier::new(Arc::new(NoopVerifier), rx, 1); - let result = verifier.verify(&cert_der); - assert!(matches!( - result, - Err(RustlsError::InvalidCertificate(CertificateError::Revoked)) - )); - } - - #[test] - fn verify_rejects_mismatched_serial() { - let (cert_der, _serial) = make_cert_and_serial(); - let (_tx, rx) = watch::channel(Arc::new(HashMap::from([(1, "deadbeef".to_string())]))); - let verifier = CertVerifier::new(Arc::new(NoopVerifier), rx, 1); - let result = verifier.verify(&cert_der); - assert!(matches!( - result, - Err(RustlsError::InvalidCertificate(CertificateError::Revoked)) - )); - } - - #[test] - fn verify_accepts_case_insensitive_serial() { - let (cert_der, serial) = make_cert_and_serial(); - let expected_lower = serial.to_ascii_lowercase(); - let (_tx, rx) = watch::channel(Arc::new(HashMap::from([(1, expected_lower)]))); - let verifier = CertVerifier::new(Arc::new(NoopVerifier), rx, 1); - let result = verifier.verify(&cert_der); - assert!(result.is_ok()); - - let expected_upper = serial.to_ascii_uppercase(); - let (_tx, rx) = watch::channel(Arc::new(HashMap::from([(1, expected_upper)]))); - let verifier = CertVerifier::new(Arc::new(NoopVerifier), rx, 1); - let result = verifier.verify(&cert_der); - assert!(result.is_ok()); - } -} diff --git a/crates/defguard_proxy_manager/src/proxy_handler.rs b/crates/defguard_proxy_manager/src/proxy_handler.rs index 8d66e24a2c..4424601442 100644 --- a/crates/defguard_proxy_manager/src/proxy_handler.rs +++ b/crates/defguard_proxy_manager/src/proxy_handler.rs @@ -39,7 +39,6 @@ use defguard_proto::proxy::{ use defguard_version::{ ComponentInfo, DefguardComponent, client::ClientVersionInterceptor, get_tracing_variables, }; -use http::Uri; use hyper_rustls::HttpsConnectorBuilder; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -65,9 +64,10 @@ use tonic::{ use crate::{ ProxyError, ProxyTxSet, TEN_SECS, - certs::client_config, servers::{EnrollmentServer, PasswordResetServer}, }; +use defguard_grpc_tls::connector::HttpsSchemeConnector; +use defguard_grpc_tls::certs as tls_certs; static VERSION_ZERO: Version = Version::new(0, 0, 0); @@ -202,7 +202,12 @@ impl ProxyHandler { "Core CA is not setup, can't create a Proxy endpoint.".to_string(), )); }; - let tls_config = client_config(&ca_cert_der, certs_rx.clone(), self.proxy_id)?; + let tls_config = tls_certs::client_config( + &ca_cert_der, + certs_rx.clone(), + self.proxy_id, + ) + .map_err(|err| ProxyError::TlsConfigError(err.to_string()))?; let connector = HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_only() @@ -843,52 +848,3 @@ impl ProxyServices { } } } - -/// Rewrites the request URI scheme to https for the TLS connector. -/// -/// Tonic expects an http URI for its endpoint, but our custom connector performs -/// the TLS handshake and requires https to select the TLS path. -#[derive(Clone, Debug)] -struct HttpsSchemeConnector { - inner: C, -} - -impl HttpsSchemeConnector { - const fn new(inner: C) -> Self { - Self { inner } - } -} - -type BoxError = Box; - -impl tower_service::Service for HttpsSchemeConnector -where - C: tower_service::Service + Clone + Send + 'static, - C::Future: Send, -{ - type Response = C::Response; - type Error = BoxError; - type Future = std::pin::Pin< - Box> + Send>, - >; - - fn poll_ready( - &mut self, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - self.inner.poll_ready(cx).map_err(Into::into) - } - - fn call(&mut self, uri: Uri) -> Self::Future { - let mut parts = uri.into_parts(); - parts.scheme = Some(http::uri::Scheme::HTTPS); - let https_uri = match Uri::from_parts(parts) { - Ok(uri) => uri, - Err(err) => { - return Box::pin(async move { Err(err.into()) }); - } - }; - let mut inner = self.inner.clone(); - Box::pin(async move { inner.call(https_uri).await }) - } -} From 1df77cfe5b95b79425d72559fc7115df0550ab56 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 12 Feb 2026 11:37:37 +0100 Subject: [PATCH 02/32] prime the cache --- crates/defguard_proxy_manager/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index b6b8a00c44..4e75e2cdf8 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -70,6 +70,8 @@ impl ProxyManager { let remote_mfa_responses = Arc::default(); let sessions = Arc::default(); let (certs_tx, certs_rx) = watch::channel(Arc::new(HashMap::new())); + // Prime the cache to avoid race with connection loop. + refresh_certs(&self.pool, &certs_tx).await; let refresh_pool = self.pool.clone(); tokio::spawn(async move { loop { From 0705c84a4eb7f9b346729f226b32bc5e1d8d9027 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 12 Feb 2026 11:48:57 +0100 Subject: [PATCH 03/32] restore error log when certs don't match --- Cargo.lock | 1 + crates/defguard_grpc_tls/Cargo.toml | 1 + crates/defguard_grpc_tls/src/certs.rs | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d1e7060a54..5c22b584cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1359,6 +1359,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tower-service", + "tracing", "x509-parser 0.18.1", ] diff --git a/crates/defguard_grpc_tls/Cargo.toml b/crates/defguard_grpc_tls/Cargo.toml index c399c53e87..3d79cb5622 100644 --- a/crates/defguard_grpc_tls/Cargo.toml +++ b/crates/defguard_grpc_tls/Cargo.toml @@ -15,3 +15,4 @@ thiserror.workspace = true tokio.workspace = true tower-service = "0.3" x509-parser = "0.18" +tracing.workspace = true diff --git a/crates/defguard_grpc_tls/src/certs.rs b/crates/defguard_grpc_tls/src/certs.rs index d07a574e54..788a612652 100644 --- a/crates/defguard_grpc_tls/src/certs.rs +++ b/crates/defguard_grpc_tls/src/certs.rs @@ -22,6 +22,7 @@ use rustls::{ }; use thiserror::Error; use tokio::sync::watch; +use tracing::error; use x509_parser::parse_x509_certificate; /// Errors that can occur while building a TLS config with a pinned verifier. @@ -59,9 +60,17 @@ impl CertVerifier { let serial = cert.tbs_certificate.raw_serial_as_string(); let certs = self.certs_rx.borrow(); let Some(expected) = certs.get(&self.component_id) else { + error!( + "Missing expected certificate for component id={}, serial={}", + self.component_id, serial + ); return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); }; if !expected.eq_ignore_ascii_case(&serial) { + error!( + "Invalid certificate for component id={}: expected={} got={}.", + self.component_id, expected, serial + ); return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); } Ok(()) From c5673c386f16a578a311c4e35c9c4f178715ad23 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 08:16:09 +0100 Subject: [PATCH 04/32] non-optional ProxyHandler::shutdown_signal --- crates/defguard_proxy_manager/src/lib.rs | 4 +- .../src/proxy_handler.rs | 59 +++++++++---------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index b9f2bfe5cf..83728ab2f7 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -93,7 +93,7 @@ impl ProxyManager { &self.tx, Arc::clone(&remote_mfa_responses), Arc::clone(&sessions), - Arc::new(Mutex::new(Some(shutdown_rx))), + Arc::new(Mutex::new(shutdown_rx)), ) }) .collect::, _>>()?; @@ -135,7 +135,7 @@ impl ProxyManager { &self.tx, Arc::clone(&remote_mfa_responses), Arc::clone(&sessions), - Arc::new(Mutex::new(Some(shutdown_rx))), + Arc::new(Mutex::new(shutdown_rx)), ) { Ok(proxy) => { debug!("Spawning proxy task for proxy {}", proxy.url); diff --git a/crates/defguard_proxy_manager/src/proxy_handler.rs b/crates/defguard_proxy_manager/src/proxy_handler.rs index 4d4908d599..b997f9aeeb 100644 --- a/crates/defguard_proxy_manager/src/proxy_handler.rs +++ b/crates/defguard_proxy_manager/src/proxy_handler.rs @@ -86,7 +86,7 @@ pub(super) struct ProxyHandler { services: ProxyServices, /// Proxy server gRPC URL pub(super) url: Url, - shutdown_signal: Arc>>, + shutdown_signal: Arc>, proxy_id: Id, client: Option>>, } @@ -98,7 +98,7 @@ impl ProxyHandler { tx: &ProxyTxSet, remote_mfa_responses: Arc>>>, sessions: Arc>>, - shutdown_signal: Arc>>, + shutdown_signal: Arc>, proxy_id: Id, ) -> Self { // Instantiate gRPC servers. @@ -120,7 +120,7 @@ impl ProxyHandler { tx: &ProxyTxSet, remote_mfa_responses: Arc>>>, sessions: Arc>>, - shutdown_signal: Arc>>, + shutdown_signal: Arc>, ) -> Result { let url = Url::from_str(&format!("http://{}:{}", proxy.address, proxy.port))?; let proxy_id = proxy.id; @@ -287,42 +287,37 @@ impl ProxyHandler { payload: Some(core_response::Payload::InitialInfo(initial_info)), }); - let shutdown_signal = self.shutdown_signal.lock().await.take(); - if let Some(shutdown_signal) = shutdown_signal { - select! { - res = self.message_loop(tx, tx_set.wireguard.clone(), &mut resp_stream) => { - if let Err(err) = res { - error!("Proxy message loop ended with error: {err}, reconnecting in {TEN_SECS:?}",); - } else { - info!("Proxy message loop ended, reconnecting in {TEN_SECS:?}"); - } - self.mark_disconnected().await?; - sleep(TEN_SECS).await; + let shutdown_signal = Arc::clone(&self.shutdown_signal); + select! { + res = self.message_loop(tx, tx_set.wireguard.clone(), &mut resp_stream) => { + if let Err(err) = res { + error!("Proxy message loop ended with error: {err}, reconnecting in {TEN_SECS:?}",); + } else { + info!("Proxy message loop ended, reconnecting in {TEN_SECS:?}"); } - res = shutdown_signal => { - match res { - Err(err) => { - error!("An error occurred when trying to wait for a shutdown signal for Proxy: {err}. Reconnecting to: {}", endpoint.uri()); - } - Ok(purge) => { - info!("Shutdown signal received, purge: {purge}, stopping proxy connection to {}", endpoint.uri()); - if purge { - debug!("Sending purge request to proxy {}", endpoint.uri()); - if let Some(client) = self.client.as_mut() { - if let Err(err) = client.purge(Request::new(())).await { - error!("Error sending purge request to proxy {}: {err}", endpoint.uri()); - } + self.mark_disconnected().await?; + sleep(TEN_SECS).await; + } + res = &mut *shutdown_signal.lock().await => { + match res { + Err(err) => { + error!("An error occurred when trying to wait for a shutdown signal for Proxy: {err}. Reconnecting to: {}", endpoint.uri()); + } + Ok(purge) => { + info!("Shutdown signal received, purge: {purge}, stopping proxy connection to {}", endpoint.uri()); + if purge { + debug!("Sending purge request to proxy {}", endpoint.uri()); + if let Some(client) = self.client.as_mut() { + if let Err(err) = client.purge(Request::new(())).await { + error!("Error sending purge request to proxy {}: {err}", endpoint.uri()); } } } } - self.mark_disconnected().await?; - break; } + self.mark_disconnected().await?; + break; } - } else { - self.message_loop(tx, tx_set.wireguard.clone(), &mut resp_stream) - .await?; } } From c5d5453d3520f580632580a1064ab0b7f30949ab Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 10:13:24 +0100 Subject: [PATCH 05/32] gateway cert verification --- Cargo.lock | 2 + .../defguard_common/src/db/models/gateway.rs | 4 +- crates/defguard_core/Cargo.toml | 2 + .../defguard_core/src/grpc/gateway/certs.rs | 33 +++++++ .../defguard_core/src/grpc/gateway/handler.rs | 89 ++++++++----------- crates/defguard_core/src/grpc/gateway/mod.rs | 13 ++- .../src/handlers/component_setup.rs | 3 +- crates/defguard_core/src/handlers/mail.rs | 2 +- ...2.0.0]_gateway_certificate_serial.down.sql | 3 + ..._[2.0.0]_gateway_certificate_serial.up.sql | 3 + 10 files changed, 99 insertions(+), 55 deletions(-) create mode 100644 crates/defguard_core/src/grpc/gateway/certs.rs create mode 100644 migrations/20260213090000_[2.0.0]_gateway_certificate_serial.down.sql create mode 100644 migrations/20260213090000_[2.0.0]_gateway_certificate_serial.up.sql diff --git a/Cargo.lock b/Cargo.lock index 48edb478bb..5292ca9672 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,12 +1242,14 @@ dependencies = [ "claims", "defguard_certs", "defguard_common", + "defguard_grpc_tls", "defguard_mail", "defguard_proto", "defguard_version", "defguard_web_ui", "futures", "humantime", + "hyper-rustls", "hyper-util", "ipnetwork", "jsonwebkey", diff --git a/crates/defguard_common/src/db/models/gateway.rs b/crates/defguard_common/src/db/models/gateway.rs index 613d5f2b47..82e1f53655 100644 --- a/crates/defguard_common/src/db/models/gateway.rs +++ b/crates/defguard_common/src/db/models/gateway.rs @@ -15,7 +15,7 @@ pub struct Gateway { pub hostname: Option, pub connected_at: Option, pub disconnected_at: Option, - pub has_certificate: bool, + pub certificate: Option, pub certificate_expiry: Option, pub version: Option, pub name: String, @@ -43,7 +43,7 @@ impl Gateway { hostname: None, connected_at: None, disconnected_at: None, - has_certificate: false, + certificate: None, certificate_expiry: None, version: None, name: name.into(), diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index c6d2f82b7d..b551e4b12c 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -16,6 +16,7 @@ defguard_web_ui = { workspace = true } defguard_version = { workspace = true } model_derive = { workspace = true } defguard_certs = { workspace = true } +defguard_grpc_tls = { workspace = true } # external dependencies anyhow = { workspace = true } @@ -42,6 +43,7 @@ prost.workspace = true # match version used by sqlx rand = { workspace = true } reqwest = { workspace = true } +hyper-rustls = { version = "0.27", features = ["http2"] } rsa = { workspace = true } rust-ini = { workspace = true } secrecy = { workspace = true } diff --git a/crates/defguard_core/src/grpc/gateway/certs.rs b/crates/defguard_core/src/grpc/gateway/certs.rs new file mode 100644 index 0000000000..f7347e065f --- /dev/null +++ b/crates/defguard_core/src/grpc/gateway/certs.rs @@ -0,0 +1,33 @@ +//! Cached certificate serials for gateways. + +use std::{collections::HashMap, sync::Arc}; + +use defguard_common::db::{Id, models::gateway::Gateway}; +use sqlx::PgPool; +use tokio::sync::watch; + +fn collect_certs(items: I) -> HashMap +where + I: IntoIterator)>, +{ + items + .into_iter() + .filter_map(|(id, cert)| cert.map(|cert| (id, cert))) + .collect() +} + +pub(crate) async fn refresh_certs(pool: &PgPool, tx: &watch::Sender>>) { + match Gateway::all(pool).await { + Ok(gateways) => { + let certs = collect_certs( + gateways + .into_iter() + .map(|gateway| (gateway.id, gateway.certificate)), + ); + let _ = tx.send(Arc::new(certs)); + } + Err(err) => { + warn!("Failed to refresh gateway certificate list: {err}"); + } + } +} diff --git a/crates/defguard_core/src/grpc/gateway/handler.rs b/crates/defguard_core/src/grpc/gateway/handler.rs index d4974b8a1d..eaead63e33 100644 --- a/crates/defguard_core/src/grpc/gateway/handler.rs +++ b/crates/defguard_core/src/grpc/gateway/handler.rs @@ -1,9 +1,9 @@ use std::{ + collections::HashMap, str::FromStr, - sync::atomic::{AtomicU64, Ordering}, + sync::{Arc, atomic::{AtomicU64, Ordering}}, }; -use defguard_certs::der_to_pem; use defguard_common::{ VERSION, db::{ @@ -14,6 +14,11 @@ use defguard_common::{ }; use defguard_proto::gateway::{CoreResponse, core_request, core_response, gateway_client}; use defguard_version::client::ClientVersionInterceptor; +use defguard_grpc_tls::{ + certs as tls_certs, + connector::HttpsSchemeConnector, +}; +use hyper_rustls::HttpsConnectorBuilder; use reqwest::Url; use semver::Version; use sqlx::PgPool; @@ -21,11 +26,12 @@ use tokio::{ sync::{ broadcast::Sender, mpsc::{self, UnboundedSender}, + watch, }, time::sleep, }; use tokio_stream::wrappers::UnboundedReceiverStream; -use tonic::transport::{Certificate, ClientTlsConfig, Endpoint}; +use tonic::transport::Endpoint; use crate::{ enterprise::firewall::try_get_location_firewall_config, @@ -37,23 +43,6 @@ use crate::{ location_management::allowed_peers::get_location_allowed_peers, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Scheme { - #[allow(dead_code)] - Http, - Https, -} - -impl Scheme { - #[must_use] - pub const fn as_str(&self) -> &str { - match self { - Self::Http => "http", - Self::Https => "https", - } - } -} - /// One instance per connected Gateway. pub(crate) struct GatewayHandler { // Gateway server endpoint URL. @@ -63,6 +52,7 @@ pub(crate) struct GatewayHandler { pool: PgPool, events_tx: Sender, peer_stats_tx: UnboundedSender, + certs_rx: watch::Receiver>>, } impl GatewayHandler { @@ -71,6 +61,7 @@ impl GatewayHandler { pool: PgPool, events_tx: Sender, peer_stats_tx: UnboundedSender, + certs_rx: watch::Receiver>>, ) -> Result { let url = Url::from_str(&gateway.url).map_err(|err| { GatewayError::EndpointError(format!( @@ -86,16 +77,16 @@ impl GatewayHandler { pool, events_tx, peer_stats_tx, + certs_rx, }) } - fn endpoint(&self, scheme: Scheme) -> Result { + fn endpoint(&self) -> Result { let mut url = self.url.clone(); - if let Err(()) = url.set_scheme(scheme.as_str()) { + if let Err(()) = url.set_scheme("http") { return Err(GatewayError::EndpointError(format!( - "Failed to set scheme {} for Gateway URL {:?}", - scheme.as_str(), + "Failed to set http scheme for Gateway URL {:?}", self.url ))); } @@ -110,30 +101,7 @@ impl GatewayHandler { .tcp_keepalive(Some(TEN_SECS)) .keep_alive_while_idle(true); - if scheme == Scheme::Https { - let settings = Settings::get_current_settings(); - let Some(ca_cert_der) = settings.ca_cert_der else { - return Err(GatewayError::EndpointError( - "Core CA is not setup, can't create a Gateway endpoint.".to_string(), - )); - }; - - let cert_pem = der_to_pem(&ca_cert_der, defguard_certs::PemLabel::Certificate) - .map_err(|err| { - GatewayError::EndpointError(format!( - "Failed to convert CA certificate DER to PEM for Gateway URL {url:?}: {err}", - )) - })?; - let tls = ClientTlsConfig::new().ca_certificate(Certificate::from_pem(&cert_pem)); - - Ok(endpoint.tls_config(tls).map_err(|err| { - GatewayError::EndpointError(format!( - "Failed to set TLS config for Gateway URL {url:?}: {err}", - )) - })?) - } else { - Ok(endpoint) - } + Ok(endpoint) } /// Send network and VPN configuration to Gateway. @@ -236,11 +204,32 @@ impl GatewayHandler { /// Connect to Gateway and handle its messages through gRPC. pub(crate) async fn handle_connection(&mut self) -> Result<(), GatewayError> { - let endpoint = self.endpoint(Scheme::Https)?; + let endpoint = self.endpoint()?; let uri = endpoint.uri().to_string(); loop { + // TODO(jck) how does proxy do this? can this be moved to lib? #[cfg(not(test))] - let channel = endpoint.connect_lazy(); + let channel = { + let settings = Settings::get_current_settings(); + let Some(ca_cert_der) = settings.ca_cert_der else { + return Err(GatewayError::EndpointError( + "Core CA is not setup, can't create a Gateway endpoint.".to_string(), + )); + }; + let tls_config = tls_certs::client_config( + &ca_cert_der, + self.certs_rx.clone(), + self.gateway.id, + ) + .map_err(|err| GatewayError::EndpointError(err.to_string()))?; + let connector = HttpsConnectorBuilder::new() + .with_tls_config(tls_config) + .https_only() + .enable_http2() + .build(); + let connector = HttpsSchemeConnector::new(connector); + endpoint.connect_with_connector_lazy(connector) + }; #[cfg(test)] let channel = endpoint.connect_with_connector_lazy(tower::service_fn( |_: tonic::transport::Uri| async { diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index d50436a7cc..960762d159 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, net::IpAddr, time::Duration}; +use std::{collections::HashMap, net::IpAddr, sync::Arc, time::Duration}; use chrono::DateTime; use defguard_common::{ @@ -33,6 +33,7 @@ use crate::{ grpc::gateway::{events::GatewayEvent, handler::GatewayHandler}, }; +mod certs; pub mod events; pub(crate) mod handler; // #[cfg(test)] @@ -223,6 +224,15 @@ pub async fn run_grpc_gateway_stream( events_tx: Sender, peer_stats_tx: UnboundedSender, ) -> Result<(), anyhow::Error> { + let (certs_tx, certs_rx) = tokio::sync::watch::channel(Arc::new(HashMap::new())); + certs::refresh_certs(&pool, &certs_tx).await; + let refresh_pool = pool.clone(); + tokio::spawn(async move { + loop { + certs::refresh_certs(&refresh_pool, &certs_tx).await; + tokio::time::sleep(super::TEN_SECS).await; + } + }); let mut abort_handles = HashMap::new(); let mut tasks = JoinSet::new(); @@ -233,6 +243,7 @@ pub async fn run_grpc_gateway_stream( pool.clone(), events_tx.clone(), peer_stats_tx.clone(), + certs_rx.clone(), )?; let abort_handle = tasks.spawn(async move { loop { diff --git a/crates/defguard_core/src/handlers/component_setup.rs b/crates/defguard_core/src/handlers/component_setup.rs index 44dbdaf05e..fd8fa9d03f 100644 --- a/crates/defguard_core/src/handlers/component_setup.rs +++ b/crates/defguard_core/src/handlers/component_setup.rs @@ -925,6 +925,7 @@ pub async fn setup_gateway_tls_stream( let defguard_certs::CertificateInfo { not_after: expiry, + serial, .. } = match parse_certificate_info(cert.der()) { Ok(dt) => { @@ -944,7 +945,7 @@ pub async fn setup_gateway_tls_stream( request.common_name, ); - gateway.has_certificate = true; + gateway.certificate = Some(serial); gateway.certificate_expiry = Some(expiry); if let Err(err) = gateway.save(&pool).await { diff --git a/crates/defguard_core/src/handlers/mail.rs b/crates/defguard_core/src/handlers/mail.rs index 26cf6a3c54..8e431fbd59 100644 --- a/crates/defguard_core/src/handlers/mail.rs +++ b/crates/defguard_core/src/handlers/mail.rs @@ -127,7 +127,7 @@ pub async fn send_support_data( "network_id": g.network_id, "version": g.version.as_deref().unwrap_or("unknown"), "url": g.url, - "has_certificate": g.has_certificate, + "certificate": g.certificate, "hostname": g.hostname, "connected_at": g.connected_at, })).collect::>(), diff --git a/migrations/20260213090000_[2.0.0]_gateway_certificate_serial.down.sql b/migrations/20260213090000_[2.0.0]_gateway_certificate_serial.down.sql new file mode 100644 index 0000000000..66425c97dc --- /dev/null +++ b/migrations/20260213090000_[2.0.0]_gateway_certificate_serial.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE gateway + DROP COLUMN certificate, + ADD COLUMN has_certificate boolean NOT NULL DEFAULT false; diff --git a/migrations/20260213090000_[2.0.0]_gateway_certificate_serial.up.sql b/migrations/20260213090000_[2.0.0]_gateway_certificate_serial.up.sql new file mode 100644 index 0000000000..b728d7a16c --- /dev/null +++ b/migrations/20260213090000_[2.0.0]_gateway_certificate_serial.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE gateway + DROP COLUMN has_certificate, + ADD COLUMN certificate TEXT; From 26b03588851cb0fc4ac2038d29a6a9da91a2b411 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 12:51:28 +0100 Subject: [PATCH 06/32] defguard_gateway_manager crate --- Cargo.lock | 30 +++- Cargo.toml | 6 + crates/defguard/Cargo.toml | 1 + crates/defguard/src/main.rs | 5 +- crates/defguard_core/Cargo.toml | 3 +- crates/defguard_core/src/appstate.rs | 2 +- .../src/enterprise/db/models/acl.rs | 3 +- .../src/enterprise/directory_sync/mod.rs | 5 +- .../src/enterprise/snat/handlers.rs | 10 +- .../defguard_core/src/grpc/gateway/events.rs | 30 ---- crates/defguard_core/src/grpc/mod.rs | 128 ++++++++---------- .../src/grpc/proxy/client_mfa.rs | 2 +- crates/defguard_core/src/grpc/utils.rs | 41 +----- .../src/handlers/network_devices.rs | 8 +- .../defguard_core/src/handlers/wireguard.rs | 12 +- crates/defguard_core/src/lib.rs | 2 +- .../src/location_management/allowed_peers.rs | 2 +- .../src/location_management/mod.rs | 5 +- crates/defguard_core/src/user_management.rs | 5 +- crates/defguard_core/src/utility_thread.rs | 5 +- crates/defguard_event_router/src/lib.rs | 3 +- crates/defguard_gateway_manager/Cargo.toml | 35 +++++ .../src}/auth.rs | 2 +- .../src}/certs.rs | 0 .../src}/handler.rs | 18 +-- .../src/lib.rs} | 123 +++++++++++------ .../src}/tests.rs | 0 crates/defguard_proxy_manager/Cargo.toml | 7 +- crates/defguard_proxy_manager/src/lib.rs | 2 +- .../src/proxy_handler.rs | 3 +- .../src/servers/enrollment.rs | 45 +++++- crates/defguard_session_manager/src/error.rs | 2 +- crates/defguard_session_manager/src/lib.rs | 2 +- crates/defguard_version/Cargo.toml | 4 +- 34 files changed, 284 insertions(+), 267 deletions(-) delete mode 100644 crates/defguard_core/src/grpc/gateway/events.rs create mode 100644 crates/defguard_gateway_manager/Cargo.toml rename crates/{defguard_core/src/grpc => defguard_gateway_manager/src}/auth.rs (96%) rename crates/{defguard_core/src/grpc/gateway => defguard_gateway_manager/src}/certs.rs (100%) rename crates/{defguard_core/src/grpc/gateway => defguard_gateway_manager/src}/handler.rs (97%) rename crates/{defguard_core/src/grpc/gateway/mod.rs => defguard_gateway_manager/src/lib.rs} (89%) rename crates/{defguard_core/src/grpc/gateway => defguard_gateway_manager/src}/tests.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 5292ca9672..2e1d43fa2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1161,6 +1161,7 @@ dependencies = [ "defguard_core", "defguard_event_logger", "defguard_event_router", + "defguard_gateway_manager", "defguard_proxy_manager", "defguard_session_manager", "defguard_setup", @@ -1250,7 +1251,6 @@ dependencies = [ "futures", "humantime", "hyper-rustls", - "hyper-util", "ipnetwork", "jsonwebkey", "jsonwebtoken", @@ -1333,6 +1333,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "defguard_gateway_manager" +version = "0.0.0" +dependencies = [ + "anyhow", + "chrono", + "defguard_certs", + "defguard_common", + "defguard_core", + "defguard_grpc_tls", + "defguard_proto", + "defguard_version", + "hyper-rustls", + "hyper-util", + "jsonwebtoken", + "reqwest", + "semver", + "serde_json", + "sqlx", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tonic-health", + "tower", + "tracing", +] + [[package]] name = "defguard_generator" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 823eff1c9d..462cc433df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ defguard_common = { path = "./crates/defguard_common", version = "2.0.0" } defguard_core = { path = "./crates/defguard_core", version = "0.0.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_gateway_manager = { path = "./crates/defguard_gateway_manager", version = "0.0.0" } defguard_mail = { path = "./crates/defguard_mail", version = "0.0.0" } defguard_proto = { path = "./crates/defguard_proto", version = "0.0.0" } defguard_proxy_manager = { path = "./crates/defguard_proxy_manager", version = "0.0.0" } @@ -50,6 +51,7 @@ claims = "0.8" clap = { version = "4.5", features = ["derive", "env"] } futures = "0.3" http = "1.4" +hyper-rustls = { version = "0.27", features = ["http2"] } humantime = "2.1" # match version used by sqlx ipnetwork = "0.20" @@ -62,6 +64,7 @@ md4 = "0.10" openidconnect = { version = "4.0", default-features = false, features = [ "reqwest", ] } +os_info = "3.12" parse_link_header = "0.4" paste = "1.0" pgp = { version = "0.16", default-features = false } @@ -73,6 +76,7 @@ rcgen = { version = "0.14", features = ["x509-parser", "pem"] } reqwest = { version = "0.12", features = ["json"] } rsa = "0.9" rust-ini = "0.21" +rustls = { version = "0.23", features = ["ring"] } rustls-pki-types = "1.14" semver = { version = "1.0", features = ["serde"] } secrecy = { version = "0.10", features = ["serde"] } @@ -116,7 +120,9 @@ tonic-health = "0.14" tonic-prost = "0.14" tonic-prost-build = "0.14" totp-lite = { version = "2.0" } +tower = "0.5" tower-http = { version = "0.6", features = ["fs", "trace", "set-header"] } +tower-service = "0.3" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } trait-variant = "0.1" diff --git a/crates/defguard/Cargo.toml b/crates/defguard/Cargo.toml index 1378b80074..e6fef7e6b7 100644 --- a/crates/defguard/Cargo.toml +++ b/crates/defguard/Cargo.toml @@ -13,6 +13,7 @@ defguard_common = { workspace = true } defguard_core = { workspace = true } defguard_event_router = { workspace = true } defguard_event_logger = { workspace = true } +defguard_gateway_manager = { workspace = true } defguard_proxy_manager = { workspace = true } defguard_session_manager = { workspace = true } defguard_version = { workspace = true } diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 0e65a8f648..87b05304f1 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -24,9 +24,7 @@ use defguard_core::{ }, events::{ApiEvent, BidiStreamEvent}, grpc::{ - WorkerState, - gateway::{events::GatewayEvent, run_grpc_gateway_stream}, - run_grpc_server, + GatewayEvent, WorkerState }, init_dev_env, init_vpn_location, run_web_server, utility_thread::run_utility_thread, @@ -34,6 +32,7 @@ use defguard_core::{ }; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; +use defguard_gateway_manager::{run_grpc_gateway_stream, run_grpc_server}; use defguard_proxy_manager::{ProxyManager, ProxyTxSet}; use defguard_session_manager::{events::SessionManagerEvent, run_session_manager}; use defguard_setup::setup::run_setup_web_server; diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index b551e4b12c..360d02b2b5 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -68,6 +68,7 @@ tokio-util = { workspace = true } tonic = { workspace = true } tonic-health = { workspace = true } totp-lite = { workspace = true } +tower = { workspace = true } tower-http = { workspace = true } tracing = { workspace = true } trait-variant = { workspace = true } @@ -81,13 +82,11 @@ webauthn-rs-proto = { workspace = true } x25519-dalek = { workspace = true } ammonia = "4.1" regex = "1.10" -tower = "0.5" uaparser = "0.6" async-stream = "0.3" [dev-dependencies] claims.workspace = true -hyper-util = "0.1" matches.workspace = true reqwest = { version = "0.12", features = [ "cookies", diff --git a/crates/defguard_core/src/appstate.rs b/crates/defguard_core/src/appstate.rs index 6b1ea4b8d2..2db868f0af 100644 --- a/crates/defguard_core/src/appstate.rs +++ b/crates/defguard_core/src/appstate.rs @@ -23,7 +23,7 @@ use crate::{ db::{AppEvent, WebHook}, error::WebError, events::ApiEvent, - grpc::gateway::{events::GatewayEvent, send_multiple_wireguard_events, send_wireguard_event}, + grpc::{GatewayEvent, send_multiple_wireguard_events, send_wireguard_event}, version::IncompatibleComponents, }; diff --git a/crates/defguard_core/src/enterprise/db/models/acl.rs b/crates/defguard_core/src/enterprise/db/models/acl.rs index ccf1f7edd9..6c5aea90dd 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl.rs @@ -31,8 +31,7 @@ use crate::{ handlers::acl::{ ApiAclRule, EditAclRule, alias::EditAclAlias, destination::EditAclDestination, }, - }, - grpc::gateway::events::GatewayEvent, + }, grpc::GatewayEvent, }; #[derive(Debug, Error)] diff --git a/crates/defguard_core/src/enterprise/directory_sync/mod.rs b/crates/defguard_core/src/enterprise/directory_sync/mod.rs index ea73b0b6d1..a9cc7ea521 100644 --- a/crates/defguard_core/src/enterprise/directory_sync/mod.rs +++ b/crates/defguard_core/src/enterprise/directory_sync/mod.rs @@ -28,10 +28,7 @@ use crate::{ model::ldap_sync_allowed_for_user, utils::{ldap_add_users_to_groups, ldap_delete_users, ldap_remove_users_from_groups}, }, - }, - grpc::gateway::events::GatewayEvent, - handlers::user::check_username, - user_management::{delete_user_and_cleanup_devices, disable_user, sync_allowed_user_devices}, + }, grpc::GatewayEvent, handlers::user::check_username, user_management::{delete_user_and_cleanup_devices, disable_user, sync_allowed_user_devices} }; const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/crates/defguard_core/src/enterprise/snat/handlers.rs b/crates/defguard_core/src/enterprise/snat/handlers.rs index 790a02e8cf..6e087d4b6c 100644 --- a/crates/defguard_core/src/enterprise/snat/handlers.rs +++ b/crates/defguard_core/src/enterprise/snat/handlers.rs @@ -13,16 +13,10 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::{ - appstate::AppState, - auth::{AdminRole, SessionInfo}, - enterprise::{ + appstate::AppState, auth::{AdminRole, SessionInfo}, enterprise::{ db::models::snat::UserSnatBinding, firewall::try_get_location_firewall_config, handlers::LicenseInfo, snat::error::UserSnatBindingError, - }, - error::WebError, - events::{ApiEvent, ApiEventType, ApiRequestContext}, - grpc::gateway::events::GatewayEvent, - handlers::{ApiResponse, ApiResult}, + }, error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, grpc::GatewayEvent, handlers::{ApiResponse, ApiResult} }; /// List all SNAT bindings for a WireGuard location diff --git a/crates/defguard_core/src/grpc/gateway/events.rs b/crates/defguard_core/src/grpc/gateway/events.rs deleted file mode 100644 index 68596f21b8..0000000000 --- a/crates/defguard_core/src/grpc/gateway/events.rs +++ /dev/null @@ -1,30 +0,0 @@ -use defguard_common::db::{ - Id, - models::{ - Device, WireguardNetwork, - device::{DeviceInfo, WireguardNetworkDevice}, - }, -}; -use defguard_proto::{enterprise::firewall::FirewallConfig, gateway::Peer}; - -type LocationId = Id; - -// TODO: move this to common crate -#[derive(Clone, Debug)] -pub enum GatewayEvent { - NetworkCreated(LocationId, WireguardNetwork), - NetworkModified( - LocationId, - WireguardNetwork, - Vec, - Option, - ), - NetworkDeleted(LocationId, String), - DeviceCreated(DeviceInfo), - DeviceModified(DeviceInfo), - DeviceDeleted(DeviceInfo), - FirewallConfigChanged(LocationId, FirewallConfig), - FirewallDisabled(LocationId), - MfaSessionAuthorized(LocationId, Device, WireguardNetworkDevice), - MfaSessionDisconnected(LocationId, Device), -} diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index b582e18b55..1616c752e6 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -7,16 +7,16 @@ use std::{ use defguard_common::{ auth::claims::ClaimsType, - db::{Id, models::Settings}, + db::{Id, models::{Device, Settings, WireguardNetwork, device::{DeviceInfo, WireguardNetworkDevice}, wireguard::ServiceLocationMode}}, types::UrlParseError, }; use reqwest::Url; use serde::Serialize; use sqlx::PgPool; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; use tonic::transport::{Identity, Server, ServerTlsConfig, server::Router}; -use self::{auth::AuthServer, interceptor::JwtInterceptor, worker::WorkerServer}; +use self::{interceptor::JwtInterceptor}; use crate::{ auth::failed_login::FailedLoginMap, db::AppEvent, @@ -25,15 +25,13 @@ use crate::{ enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings}, openid_provider::OpenIdProvider, }, - is_business_license_active, + is_business_license_active, is_enterprise_license_active, }, server_config, }; -mod auth; pub mod client_version; -pub mod gateway; -mod interceptor; +pub mod interceptor; pub mod proxy; pub mod utils; pub mod worker; @@ -47,8 +45,7 @@ pub mod proto { } use defguard_proto::{ - auth::auth_service_server::AuthServiceServer, - worker::worker_service_server::WorkerServiceServer, + auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, gateway::Peer, worker::worker_service_server::WorkerServiceServer }; // gRPC header for passing auth token from clients @@ -57,69 +54,6 @@ pub static AUTHORIZATION_HEADER: &str = "authorization"; // gRPC header for passing hostname from clients pub static HOSTNAME_HEADER: &str = "hostname"; -const TEN_SECS: Duration = Duration::from_secs(10); - -/// Runs gRPC server with core services. -#[instrument(skip_all)] -pub async fn run_grpc_server( - worker_state: Arc>, - pool: PgPool, - grpc_cert: Option, - grpc_key: Option, - failed_logins: Arc>, -) -> Result<(), anyhow::Error> { - // Build gRPC services - let server = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { - let identity = Identity::from_pem(cert, key); - Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? - } else { - Server::builder() - }; - - let router = build_grpc_service_router(server, pool, worker_state, failed_logins).await?; - - // Run gRPC server - let addr = SocketAddr::new( - server_config() - .grpc_bind_address - .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - server_config().grpc_port, - ); - debug!("Starting gRPC services"); - router.serve(addr).await?; - info!("gRPC server started on {addr}"); - Ok(()) -} - -pub async fn build_grpc_service_router( - server: Server, - pool: PgPool, - worker_state: Arc>, - failed_logins: Arc>, - // incompatible_components: Arc>, -) -> Result { - let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); - - let worker_service = WorkerServiceServer::with_interceptor( - WorkerServer::new(pool.clone(), worker_state), - JwtInterceptor::new(ClaimsType::YubiBridge), - ); - - let (health_reporter, health_service) = tonic_health::server::health_reporter(); - health_reporter - .set_serving::>() - .await; - - let router = server - .http2_keepalive_interval(Some(TEN_SECS)) - .tcp_keepalive(Some(TEN_SECS)) - .add_service(health_service) - .add_service(auth_service); - let router = router.add_service(worker_service); - - Ok(router) -} - pub struct Job { id: u32, first_name: String, @@ -212,3 +146,53 @@ impl From for defguard_proto::proxy::InstanceInfo { } } } + +// TODO: move this to common crate +#[derive(Clone, Debug)] +pub enum GatewayEvent { + NetworkCreated(Id, WireguardNetwork), + NetworkModified( + Id, + WireguardNetwork, + Vec, + Option, + ), + NetworkDeleted(Id, String), + DeviceCreated(DeviceInfo), + DeviceModified(DeviceInfo), + DeviceDeleted(DeviceInfo), + FirewallConfigChanged(Id, FirewallConfig), + FirewallDisabled(Id), + MfaSessionAuthorized(Id, Device, WireguardNetworkDevice), + MfaSessionDisconnected(Id, Device), +} + +/// Sends given `GatewayEvent` to be handled by gateway GRPC server +/// +/// If you want to use it inside the API context, use [`crate::AppState::send_wireguard_event`] instead +pub fn send_wireguard_event(event: GatewayEvent, wg_tx: &Sender) { + debug!("Sending the following WireGuard event to Defguard Gateway: {event:?}"); + if let Err(err) = wg_tx.send(event) { + error!("Error sending WireGuard event {err}"); + } +} + +/// Sends multiple events to be handled by gateway gRPC server. +/// +/// If you want to use it inside the API context, use [`crate::AppState::send_multiple_wireguard_events`] instead +pub fn send_multiple_wireguard_events(events: Vec, wg_tx: &Sender) { + debug!("Sending {} WireGuard events", events.len()); + for event in events { + send_wireguard_event(event, wg_tx); + } +} + + +/// If this location is marked as a service location, checks if all requirements are met for it to +/// function: +/// - Enterprise is enabled +#[must_use] +pub fn should_prevent_service_location_usage(location: &WireguardNetwork) -> bool { + location.service_location_mode != ServiceLocationMode::Disabled + && !is_enterprise_license_active() +} diff --git a/crates/defguard_core/src/grpc/proxy/client_mfa.rs b/crates/defguard_core/src/grpc/proxy/client_mfa.rs index 9fdcb75151..a2aab9a605 100644 --- a/crates/defguard_core/src/grpc/proxy/client_mfa.rs +++ b/crates/defguard_core/src/grpc/proxy/client_mfa.rs @@ -40,7 +40,7 @@ use tonic::{Code, Status}; use crate::{ enterprise::{db::models::openid_provider::OpenIdProvider, is_business_license_active}, events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, DesktopClientMfaEvent}, - grpc::{gateway::events::GatewayEvent, utils::parse_client_ip_agent}, + grpc::{GatewayEvent, utils::parse_client_ip_agent}, }; const CLIENT_SESSION_TIMEOUT: u64 = 60 * 5; // 10 minutes diff --git a/crates/defguard_core/src/grpc/utils.rs b/crates/defguard_core/src/grpc/utils.rs index 64e1e2e619..7210e9dc39 100644 --- a/crates/defguard_core/src/grpc/utils.rs +++ b/crates/defguard_core/src/grpc/utils.rs @@ -24,48 +24,9 @@ use crate::{ enterprise::db::models::{ enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider, }, - grpc::{client_version::ClientFeature, gateway::should_prevent_service_location_usage}, + grpc::{client_version::ClientFeature, should_prevent_service_location_usage}, }; -// Create a new token for configuration polling. -pub async fn new_polling_token(pool: &PgPool, device: &Device) -> Result { - debug!( - "Making a new polling token for device {}", - device.wireguard_pubkey - ); - let mut transaction = pool.begin().await.map_err(|err| { - error!("Failed to start transaction while making a new polling token: {err}"); - Status::internal(format!("unexpected error: {err}")) - })?; - - // 1. Delete existing polling token for the device, if it exists - // 2. Create a new polling token for the device - PollingToken::delete_for_device_id(&mut *transaction, device.id) - .await - .map_err(|err| { - error!("Failed to delete polling token: {err}"); - Status::internal(format!("unexpected error: {err}")) - })?; - let new_token = PollingToken::new(device.id) - .save(&mut *transaction) - .await - .map_err(|err| { - error!("Failed to save new polling token: {err}"); - Status::internal(format!("unexpected error: {err}")) - })?; - - transaction.commit().await.map_err(|err| { - error!("Failed to commit transaction while making a new polling token: {err}"); - Status::internal(format!("unexpected error: {err}")) - })?; - info!( - "New polling token created for device {}", - device.wireguard_pubkey - ); - - Ok(new_token.token) -} - pub async fn build_device_config_response( pool: &PgPool, device: Device, diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index 2a29be7f09..358e422990 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -26,13 +26,7 @@ use sqlx::PgConnection; use super::{ApiResponse, ApiResult, WebError}; use crate::{ - appstate::AppState, - auth::{AdminRole, SessionInfo}, - enrollment_management::start_desktop_configuration, - enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, - events::{ApiEvent, ApiEventType, ApiRequestContext}, - grpc::gateway::events::GatewayEvent, - server_config, + appstate::AppState, auth::{AdminRole, SessionInfo}, enrollment_management::start_desktop_configuration, enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, events::{ApiEvent, ApiEventType, ApiRequestContext}, grpc::GatewayEvent, server_config }; #[derive(Serialize)] diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 0d8ee2ced5..07c26d85d9 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -33,22 +33,16 @@ use utoipa::ToSchema; use super::{ApiResponse, ApiResult, WebError, device_for_admin_or_self, user_for_admin_or_self}; use crate::{ - appstate::AppState, - auth::{AdminRole, SessionInfo}, - enterprise::{ + appstate::AppState, auth::{AdminRole, SessionInfo}, enterprise::{ db::models::{enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider}, firewall::try_get_location_firewall_config, handlers::CanManageDevices, is_business_license_active, limits::update_counts, - }, - events::{ApiEvent, ApiEventType, ApiRequestContext}, - grpc::gateway::events::GatewayEvent, - location_management::{ + }, events::{ApiEvent, ApiEventType, ApiRequestContext}, grpc::GatewayEvent, location_management::{ allowed_peers::get_location_allowed_peers, handle_imported_devices, handle_mapped_devices, sync_location_allowed_devices, - }, - wg_config::{ImportedDevice, parse_wireguard_config}, + }, wg_config::{ImportedDevice, parse_wireguard_config} }; #[derive(Serialize, ToSchema)] diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index ee100c6825..94a2a06616 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -108,7 +108,7 @@ use crate::{ create_snat_binding, delete_snat_binding, list_snat_bindings, modify_snat_binding, }, }, - grpc::{WorkerState, gateway::events::GatewayEvent}, + grpc::{GatewayEvent, WorkerState}, handlers::{ app_info::get_app_info, auth::{ diff --git a/crates/defguard_core/src/location_management/allowed_peers.rs b/crates/defguard_core/src/location_management/allowed_peers.rs index 8017ca1c83..31a0575158 100644 --- a/crates/defguard_core/src/location_management/allowed_peers.rs +++ b/crates/defguard_core/src/location_management/allowed_peers.rs @@ -2,7 +2,7 @@ use defguard_common::db::{Id, models::WireguardNetwork}; use defguard_proto::gateway::Peer; use sqlx::{Error as SqlxError, PgExecutor, query}; -use crate::grpc::gateway::should_prevent_service_location_usage; +use crate::grpc::should_prevent_service_location_usage; /// Get a list of all allowed peers for a given location /// diff --git a/crates/defguard_core/src/location_management/mod.rs b/crates/defguard_core/src/location_management/mod.rs index 8ff7a45642..3b55fbfe59 100644 --- a/crates/defguard_core/src/location_management/mod.rs +++ b/crates/defguard_core/src/location_management/mod.rs @@ -18,9 +18,7 @@ use thiserror::Error; use tokio::sync::broadcast::Sender; use crate::{ - enterprise::firewall::{FirewallError, try_get_location_firewall_config}, - grpc::gateway::{events::GatewayEvent, send_multiple_wireguard_events}, - wg_config::ImportedDevice, + enterprise::firewall::{FirewallError, try_get_location_firewall_config}, grpc::{GatewayEvent, send_multiple_wireguard_events}, wg_config::ImportedDevice }; pub mod allowed_peers; @@ -410,7 +408,6 @@ mod test { use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; - use crate::grpc::gateway::events::GatewayEvent; #[sqlx::test] async fn test_sync_allowed_devices_for_user(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/src/user_management.rs b/crates/defguard_core/src/user_management.rs index 449e5d5b25..e27d0484d3 100644 --- a/crates/defguard_core/src/user_management.rs +++ b/crates/defguard_core/src/user_management.rs @@ -8,10 +8,7 @@ use sqlx::PgConnection; use tokio::sync::broadcast::Sender; use crate::{ - enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, - error::WebError, - grpc::gateway::{events::GatewayEvent, send_multiple_wireguard_events, send_wireguard_event}, - location_management::sync_allowed_devices_for_user, + enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, error::WebError, grpc::{GatewayEvent, send_multiple_wireguard_events, send_wireguard_event}, location_management::sync_allowed_devices_for_user }; /// Deletes the user and cleans up his devices from gateways diff --git a/crates/defguard_core/src/utility_thread.rs b/crates/defguard_core/src/utility_thread.rs index c4d1f416d2..68927a7a98 100644 --- a/crates/defguard_core/src/utility_thread.rs +++ b/crates/defguard_core/src/utility_thread.rs @@ -19,10 +19,7 @@ use crate::{ is_business_license_active, ldap::{do_ldap_sync, sync::get_ldap_sync_interval}, limits::do_count_update, - }, - grpc::gateway::events::GatewayEvent, - location_management::allowed_peers::get_location_allowed_peers, - updates::do_new_version_check, + }, grpc::GatewayEvent, location_management::allowed_peers::get_location_allowed_peers, updates::do_new_version_check }; // Times in seconds diff --git a/crates/defguard_event_router/src/lib.rs b/crates/defguard_event_router/src/lib.rs index 0d8a90b972..7e42e98593 100644 --- a/crates/defguard_event_router/src/lib.rs +++ b/crates/defguard_event_router/src/lib.rs @@ -20,8 +20,7 @@ use std::sync::Arc; use defguard_core::{ - events::{ApiEvent, BidiStreamEvent}, - grpc::gateway::events::GatewayEvent, + events::{ApiEvent, BidiStreamEvent}, grpc::GatewayEvent, }; use defguard_event_logger::message::{EventContext, EventLoggerMessage, LoggerEvent}; use defguard_session_manager::events::SessionManagerEvent; diff --git a/crates/defguard_gateway_manager/Cargo.toml b/crates/defguard_gateway_manager/Cargo.toml new file mode 100644 index 0000000000..711eed19ab --- /dev/null +++ b/crates/defguard_gateway_manager/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "defguard_gateway_manager" +version = "0.0.0" +edition.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +defguard_certs.workspace = true +defguard_common.workspace = true +defguard_core.workspace = true +defguard_grpc_tls.workspace = true +defguard_proto.workspace = true +defguard_version.workspace = true + +anyhow.workspace = true +chrono.workspace = true +hyper-rustls.workspace = true +jsonwebtoken.workspace = true +reqwest.workspace = true +semver.workspace = true +serde_json.workspace = true +sqlx.workspace = true +thiserror.workspace = true +tokio.workspace = true +tokio-stream.workspace = true +tonic.workspace = true +tonic-health.workspace = true +tower.workspace = true +tracing.workspace = true + +[dev-dependencies] +hyper-util = "0.1" diff --git a/crates/defguard_core/src/grpc/auth.rs b/crates/defguard_gateway_manager/src/auth.rs similarity index 96% rename from crates/defguard_core/src/grpc/auth.rs rename to crates/defguard_gateway_manager/src/auth.rs index 6f61986355..d6edaca02a 100644 --- a/crates/defguard_core/src/grpc/auth.rs +++ b/crates/defguard_gateway_manager/src/auth.rs @@ -9,7 +9,7 @@ use jsonwebtoken::errors::Error as JWTError; use sqlx::PgPool; use tonic::{Request, Response, Status}; -use crate::auth::failed_login::{FailedLoginMap, check_failed_logins, log_failed_login_attempt}; +use defguard_core::auth::failed_login::{FailedLoginMap, check_failed_logins, log_failed_login_attempt}; pub struct AuthServer { pool: PgPool, diff --git a/crates/defguard_core/src/grpc/gateway/certs.rs b/crates/defguard_gateway_manager/src/certs.rs similarity index 100% rename from crates/defguard_core/src/grpc/gateway/certs.rs rename to crates/defguard_gateway_manager/src/certs.rs diff --git a/crates/defguard_core/src/grpc/gateway/handler.rs b/crates/defguard_gateway_manager/src/handler.rs similarity index 97% rename from crates/defguard_core/src/grpc/gateway/handler.rs rename to crates/defguard_gateway_manager/src/handler.rs index eaead63e33..914220d012 100644 --- a/crates/defguard_core/src/grpc/gateway/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -24,25 +24,21 @@ use semver::Version; use sqlx::PgPool; use tokio::{ sync::{ - broadcast::Sender, - mpsc::{self, UnboundedSender}, - watch, + Mutex, broadcast::Sender, mpsc::{self, UnboundedSender}, watch }, time::sleep, }; use tokio_stream::wrappers::UnboundedReceiverStream; use tonic::transport::Endpoint; -use crate::{ - enterprise::firewall::try_get_location_firewall_config, - grpc::{ - TEN_SECS, - gateway::{GatewayError, events::GatewayEvent, try_protos_into_stats_message}, - }, - handlers::mail::send_gateway_disconnected_email, - location_management::allowed_peers::get_location_allowed_peers, +use defguard_core::{ + enterprise::firewall::try_get_location_firewall_config, grpc::GatewayEvent, handlers::mail::send_gateway_disconnected_email, location_management::allowed_peers::get_location_allowed_peers }; +use crate::{GatewayError, TEN_SECS, try_protos_into_stats_message}; + +type ShutdownReceiver = tokio::sync::oneshot::Receiver; + /// One instance per connected Gateway. pub(crate) struct GatewayHandler { // Gateway server endpoint URL. diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_gateway_manager/src/lib.rs similarity index 89% rename from crates/defguard_core/src/grpc/gateway/mod.rs rename to crates/defguard_gateway_manager/src/lib.rs index 960762d159..6eb87ed329 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -1,20 +1,19 @@ -use std::{collections::HashMap, net::IpAddr, sync::Arc, time::Duration}; +use std::{collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, time::Duration}; use chrono::DateTime; use defguard_common::{ - db::{ + auth::claims::ClaimsType, config::server_config, db::{ ChangeNotification, Id, TriggerOperation, models::{ WireguardNetwork, gateway::Gateway, wireguard::{DEFAULT_WIREGUARD_MTU, ServiceLocationMode}, }, - }, - messages::peer_stats_update::PeerStatsUpdate, + }, messages::peer_stats_update::PeerStatsUpdate }; +use defguard_core::{auth::failed_login::FailedLoginMap, grpc::{GatewayEvent, WorkerState, interceptor::JwtInterceptor, should_prevent_service_location_usage, worker::WorkerServer}}; use defguard_proto::{ - enterprise::firewall::FirewallConfig, - gateway::{Configuration, CoreResponse, Peer, PeerStats, Update, core_response, update}, + auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, gateway::{Configuration, CoreResponse, Peer, PeerStats, Update, core_response, update}, worker::worker_service_server::WorkerServiceServer }; use sqlx::{PgExecutor, PgPool, postgres::PgListener, query}; use thiserror::Error; @@ -25,42 +24,29 @@ use tokio::{ }, task::{AbortHandle, JoinSet}, }; -use tonic::{Code, Status}; +use tonic::{Code, Status, transport::{Identity, Server, ServerTlsConfig, server::Router}}; -use crate::{ +use defguard_core::{ enterprise::{firewall::FirewallError, is_enterprise_license_active}, events::GrpcEvent, - grpc::gateway::{events::GatewayEvent, handler::GatewayHandler}, }; +use crate::{auth::AuthServer, handler::GatewayHandler}; + +#[macro_use] +extern crate tracing; + +mod auth; mod certs; -pub mod events; pub(crate) mod handler; // #[cfg(test)] // mod tests; #[cfg(test)] -pub(super) static TONIC_SOCKET: &str = "tonic.sock"; - -/// Sends given `GatewayEvent` to be handled by gateway GRPC server -/// -/// If you want to use it inside the API context, use [`crate::AppState::send_wireguard_event`] instead -pub fn send_wireguard_event(event: GatewayEvent, wg_tx: &Sender) { - debug!("Sending the following WireGuard event to Defguard Gateway: {event:?}"); - if let Err(err) = wg_tx.send(event) { - error!("Error sending WireGuard event {err}"); - } -} - -/// Sends multiple events to be handled by gateway gRPC server. -/// -/// If you want to use it inside the API context, use [`crate::AppState::send_multiple_wireguard_events`] instead -pub fn send_multiple_wireguard_events(events: Vec, wg_tx: &Sender) { - debug!("Sending {} WireGuard events", events.len()); - for event in events { - send_wireguard_event(event, wg_tx); - } -} +pub(crate) static TONIC_SOCKET: &str = "tonic.sock"; +const GATEWAY_TABLE_TRIGGER: &str = "gateway_change"; +const GATEWAY_RECONNECT_DELAY: Duration = Duration::from_secs(5); +const TEN_SECS: Duration = Duration::from_secs(10); /// Helper used to convert peer stats coming from gRPC client /// into an internal representation @@ -121,15 +107,6 @@ impl From for Status { } } -/// If this location is marked as a service location, checks if all requirements are met for it to -/// function: -/// - Enterprise is enabled -#[must_use] -pub fn should_prevent_service_location_usage(location: &WireguardNetwork) -> bool { - location.service_location_mode != ServiceLocationMode::Disabled - && !is_enterprise_license_active() -} - /// Get a list of all allowed peers /// /// Each device is marked as allowed or not allowed in a given network, @@ -215,9 +192,6 @@ fn gen_config( } } -const GATEWAY_TABLE_TRIGGER: &str = "gateway_change"; -const GATEWAY_RECONNECT_DELAY: Duration = Duration::from_secs(5); - /// Bi-directional gRPC stream for communication with Defguard Gateway. pub async fn run_grpc_gateway_stream( pool: PgPool, @@ -230,7 +204,7 @@ pub async fn run_grpc_gateway_stream( tokio::spawn(async move { loop { certs::refresh_certs(&refresh_pool, &certs_tx).await; - tokio::time::sleep(super::TEN_SECS).await; + tokio::time::sleep(TEN_SECS).await; } }); let mut abort_handles = HashMap::new(); @@ -707,3 +681,64 @@ impl GatewayUpdatesHandler { Ok(()) } } + +/// Runs gRPC server with core services. +#[instrument(skip_all)] +pub async fn run_grpc_server( + worker_state: Arc>, + pool: PgPool, + grpc_cert: Option, + grpc_key: Option, + failed_logins: Arc>, +) -> Result<(), anyhow::Error> { + // Build gRPC services + let server = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { + let identity = Identity::from_pem(cert, key); + Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? + } else { + Server::builder() + }; + + let router = build_grpc_service_router(server, pool, worker_state, failed_logins).await?; + + // Run gRPC server + let addr = SocketAddr::new( + server_config() + .grpc_bind_address + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + server_config().grpc_port, + ); + debug!("Starting gRPC services"); + router.serve(addr).await?; + info!("gRPC server started on {addr}"); + Ok(()) +} + +pub async fn build_grpc_service_router( + server: Server, + pool: PgPool, + worker_state: Arc>, + failed_logins: Arc>, + // incompatible_components: Arc>, +) -> Result { + let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); + + let worker_service = WorkerServiceServer::with_interceptor( + WorkerServer::new(pool.clone(), worker_state), + JwtInterceptor::new(ClaimsType::YubiBridge), + ); + + let (health_reporter, health_service) = tonic_health::server::health_reporter(); + health_reporter + .set_serving::>() + .await; + + let router = server + .http2_keepalive_interval(Some(TEN_SECS)) + .tcp_keepalive(Some(TEN_SECS)) + .add_service(health_service) + .add_service(auth_service); + let router = router.add_service(worker_service); + + Ok(router) +} diff --git a/crates/defguard_core/src/grpc/gateway/tests.rs b/crates/defguard_gateway_manager/src/tests.rs similarity index 100% rename from crates/defguard_core/src/grpc/gateway/tests.rs rename to crates/defguard_gateway_manager/src/tests.rs diff --git a/crates/defguard_proxy_manager/Cargo.toml b/crates/defguard_proxy_manager/Cargo.toml index 45eebd9179..3d0ae0e9d4 100644 --- a/crates/defguard_proxy_manager/Cargo.toml +++ b/crates/defguard_proxy_manager/Cargo.toml @@ -22,16 +22,15 @@ axum-extra.workspace = true semver.workspace = true secrecy.workspace = true http.workspace = true +hyper-rustls.workspace = true openidconnect.workspace = true reqwest.workspace = true +rustls.workspace = true sqlx.workspace = true thiserror.workspace = true tokio.workspace = true tokio-stream.workspace = true tonic.workspace = true +tower-service.workspace = true tracing.workspace = true x509-parser.workspace = true - -hyper-rustls = { version = "0.27", features = ["http2"] } -rustls = { version = "0.23", features = ["ring"] } -tower-service = "0.3" diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index 83728ab2f7..2034e4fca8 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -6,7 +6,7 @@ use std::{ use defguard_common::{db::models::proxy::Proxy, types::proxy::ProxyControlMessage}; use defguard_core::{ - events::BidiStreamEvent, grpc::gateway::events::GatewayEvent, version::IncompatibleComponents, + events::BidiStreamEvent, grpc::GatewayEvent, version::IncompatibleComponents }; use sqlx::PgPool; diff --git a/crates/defguard_proxy_manager/src/proxy_handler.rs b/crates/defguard_proxy_manager/src/proxy_handler.rs index b997f9aeeb..60b20c49a5 100644 --- a/crates/defguard_proxy_manager/src/proxy_handler.rs +++ b/crates/defguard_proxy_manager/src/proxy_handler.rs @@ -27,8 +27,7 @@ use defguard_core::{ ldap::utils::ldap_update_user_state, }, grpc::{ - gateway::events::GatewayEvent, - proxy::client_mfa::{ClientLoginSession, ClientMfaServer}, + GatewayEvent, proxy::client_mfa::{ClientLoginSession, ClientMfaServer} }, version::{IncompatibleComponents, IncompatibleProxyData, is_proxy_version_supported}, }; diff --git a/crates/defguard_proxy_manager/src/servers/enrollment.rs b/crates/defguard_proxy_manager/src/servers/enrollment.rs index 4fea5f0401..525d1339ab 100644 --- a/crates/defguard_proxy_manager/src/servers/enrollment.rs +++ b/crates/defguard_proxy_manager/src/servers/enrollment.rs @@ -22,10 +22,7 @@ use defguard_core::{ }, events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, EnrollmentEvent}, grpc::{ - InstanceInfo, - client_version::ClientFeature, - gateway::events::GatewayEvent, - utils::{build_device_config_response, new_polling_token, parse_client_ip_agent}, + GatewayEvent, InstanceInfo, client_version::ClientFeature, utils::{build_device_config_response, parse_client_ip_agent} }, handlers::{ mail::{send_email_mfa_activation_email, send_mfa_configured_email}, @@ -1055,6 +1052,46 @@ async fn initial_info_from_user( }) } +// Create a new token for configuration polling. +pub async fn new_polling_token(pool: &PgPool, device: &Device) -> Result { + debug!( + "Making a new polling token for device {}", + device.wireguard_pubkey + ); + let mut transaction = pool.begin().await.map_err(|err| { + error!("Failed to start transaction while making a new polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + + // 1. Delete existing polling token for the device, if it exists + // 2. Create a new polling token for the device + PollingToken::delete_for_device_id(&mut *transaction, device.id) + .await + .map_err(|err| { + error!("Failed to delete polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + let new_token = PollingToken::new(device.id) + .save(&mut *transaction) + .await + .map_err(|err| { + error!("Failed to save new polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + + transaction.commit().await.map_err(|err| { + error!("Failed to commit transaction while making a new polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + info!( + "New polling token created for device {}", + device.wireguard_pubkey + ); + + Ok(new_token.token) +} + + #[cfg(test)] mod test { use defguard_common::{ diff --git a/crates/defguard_session_manager/src/error.rs b/crates/defguard_session_manager/src/error.rs index 8e4c6bca7f..5242bf2a08 100644 --- a/crates/defguard_session_manager/src/error.rs +++ b/crates/defguard_session_manager/src/error.rs @@ -1,5 +1,5 @@ use defguard_common::db::Id; -use defguard_core::grpc::gateway::events::GatewayEvent; +use defguard_core::grpc::GatewayEvent; use thiserror::Error; use tokio::sync::{broadcast::error::SendError as BroadcastSendError, mpsc::error::SendError}; diff --git a/crates/defguard_session_manager/src/lib.rs b/crates/defguard_session_manager/src/lib.rs index 58135192a2..cfe8505ee9 100644 --- a/crates/defguard_session_manager/src/lib.rs +++ b/crates/defguard_session_manager/src/lib.rs @@ -12,7 +12,7 @@ use defguard_common::{ }, messages::peer_stats_update::PeerStatsUpdate, }; -use defguard_core::grpc::gateway::events::GatewayEvent; +use defguard_core::grpc::GatewayEvent; use sqlx::{PgConnection, PgPool}; use tokio::{ sync::{ diff --git a/crates/defguard_version/Cargo.toml b/crates/defguard_version/Cargo.toml index f05ace0e92..b5e5c00067 100644 --- a/crates/defguard_version/Cargo.toml +++ b/crates/defguard_version/Cargo.toml @@ -10,11 +10,11 @@ rust-version.workspace = true [dependencies] axum.workspace = true http.workspace = true -os_info = "3.12" +os_info.workspace = true semver.workspace = true serde.workspace = true thiserror.workspace = true tonic.workspace = true -tower = "0.5" +tower.workspace = true tracing.workspace = true tracing-subscriber.workspace = true From 738a1cf03b0ad70a8fef3604dcc1c57d10007f23 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 12:58:12 +0100 Subject: [PATCH 07/32] fix tests --- crates/defguard_core/tests/integration/api/common/mod.rs | 2 +- crates/defguard_core/tests/integration/api/wireguard.rs | 4 +--- .../tests/integration/api/wireguard_network_allowed_groups.rs | 4 +--- .../tests/integration/api/wireguard_network_devices.rs | 3 +-- .../tests/integration/api/wireguard_network_import.rs | 3 +-- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 2abc0db1cf..6d9ad02340 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -20,7 +20,7 @@ use defguard_core::{ db::AppEvent, enterprise::license::{License, LicenseTier, set_cached_license}, events::ApiEvent, - grpc::{WorkerState, gateway::events::GatewayEvent}, + grpc::{GatewayEvent, WorkerState}, handlers::{Auth, user::UserDetails}, }; use reqwest::{StatusCode, header::HeaderName}; diff --git a/crates/defguard_core/tests/integration/api/wireguard.rs b/crates/defguard_core/tests/integration/api/wireguard.rs index a80082623e..ea00465676 100644 --- a/crates/defguard_core/tests/integration/api/wireguard.rs +++ b/crates/defguard_core/tests/integration/api/wireguard.rs @@ -19,9 +19,7 @@ use defguard_core::{ }, handlers::openid_providers::AddProviderData, license::{get_cached_license, set_cached_license}, - }, - grpc::gateway::events::GatewayEvent, - handlers::{Auth, GroupInfo, wireguard::WireguardNetworkData}, + }, grpc::GatewayEvent, handlers::{Auth, GroupInfo, wireguard::WireguardNetworkData} }; use ipnetwork::IpNetwork; use matches::assert_matches; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs index 86f5edaa47..a7ca87d48a 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs @@ -9,9 +9,7 @@ use defguard_common::{ }, }; use defguard_core::{ - grpc::gateway::events::GatewayEvent, - handlers::{Auth, wireguard::ImportedNetworkData}, - location_management::allowed_peers::get_location_allowed_peers, + grpc::GatewayEvent, handlers::{Auth, wireguard::ImportedNetworkData}, location_management::allowed_peers::get_location_allowed_peers }; use matches::assert_matches; use reqwest::StatusCode; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs index 211e7cd33d..53574fb78e 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs @@ -5,8 +5,7 @@ use defguard_common::db::{ models::{Device, WireguardNetwork}, }; use defguard_core::{ - grpc::gateway::events::GatewayEvent, - handlers::{Auth, network_devices::AddNetworkDevice}, + grpc::GatewayEvent, handlers::{Auth, network_devices::AddNetworkDevice} }; use ipnetwork::IpNetwork; use matches::assert_matches; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_import.rs b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs index 958f62e5a2..06b12ca165 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_import.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs @@ -9,8 +9,7 @@ use defguard_common::db::models::{ }, }; use defguard_core::{ - grpc::gateway::events::GatewayEvent, - handlers::{Auth, wireguard::ImportedNetworkData}, + grpc::GatewayEvent, handlers::{Auth, wireguard::ImportedNetworkData} }; use matches::assert_matches; use reqwest::StatusCode; From 120f65cb4ebf414b4a811c208cfa90dccfd89c8c Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 12:58:32 +0100 Subject: [PATCH 08/32] cargo fmt --- crates/defguard/src/main.rs | 4 +-- .../src/enterprise/db/models/acl.rs | 3 +- .../src/enterprise/directory_sync/mod.rs | 5 ++- .../src/enterprise/snat/handlers.rs | 10 ++++-- crates/defguard_core/src/grpc/mod.rs | 22 +++++++------ .../src/handlers/network_devices.rs | 8 ++++- .../defguard_core/src/handlers/wireguard.rs | 12 +++++-- .../src/location_management/mod.rs | 4 ++- crates/defguard_core/src/user_management.rs | 5 ++- crates/defguard_core/src/utility_thread.rs | 5 ++- .../tests/integration/api/wireguard.rs | 4 ++- .../api/wireguard_network_allowed_groups.rs | 4 ++- .../api/wireguard_network_devices.rs | 3 +- .../api/wireguard_network_import.rs | 3 +- crates/defguard_event_router/src/lib.rs | 3 +- crates/defguard_gateway_manager/src/auth.rs | 4 ++- .../defguard_gateway_manager/src/handler.rs | 30 +++++++++-------- crates/defguard_gateway_manager/src/lib.rs | 32 +++++++++++++++---- crates/defguard_grpc_tls/src/certs.rs | 4 +-- crates/defguard_proxy_manager/src/lib.rs | 4 +-- .../src/proxy_handler.rs | 15 ++++----- .../src/servers/enrollment.rs | 5 +-- 22 files changed, 123 insertions(+), 66 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 87b05304f1..0d678c23df 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -23,9 +23,7 @@ use defguard_core::{ limits::update_counts, }, events::{ApiEvent, BidiStreamEvent}, - grpc::{ - GatewayEvent, WorkerState - }, + grpc::{GatewayEvent, WorkerState}, init_dev_env, init_vpn_location, run_web_server, utility_thread::run_utility_thread, version::IncompatibleComponents, diff --git a/crates/defguard_core/src/enterprise/db/models/acl.rs b/crates/defguard_core/src/enterprise/db/models/acl.rs index 6c5aea90dd..c876dff947 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl.rs @@ -31,7 +31,8 @@ use crate::{ handlers::acl::{ ApiAclRule, EditAclRule, alias::EditAclAlias, destination::EditAclDestination, }, - }, grpc::GatewayEvent, + }, + grpc::GatewayEvent, }; #[derive(Debug, Error)] diff --git a/crates/defguard_core/src/enterprise/directory_sync/mod.rs b/crates/defguard_core/src/enterprise/directory_sync/mod.rs index a9cc7ea521..1bb93a4098 100644 --- a/crates/defguard_core/src/enterprise/directory_sync/mod.rs +++ b/crates/defguard_core/src/enterprise/directory_sync/mod.rs @@ -28,7 +28,10 @@ use crate::{ model::ldap_sync_allowed_for_user, utils::{ldap_add_users_to_groups, ldap_delete_users, ldap_remove_users_from_groups}, }, - }, grpc::GatewayEvent, handlers::user::check_username, user_management::{delete_user_and_cleanup_devices, disable_user, sync_allowed_user_devices} + }, + grpc::GatewayEvent, + handlers::user::check_username, + user_management::{delete_user_and_cleanup_devices, disable_user, sync_allowed_user_devices}, }; const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/crates/defguard_core/src/enterprise/snat/handlers.rs b/crates/defguard_core/src/enterprise/snat/handlers.rs index 6e087d4b6c..f0552c3938 100644 --- a/crates/defguard_core/src/enterprise/snat/handlers.rs +++ b/crates/defguard_core/src/enterprise/snat/handlers.rs @@ -13,10 +13,16 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::{ - appstate::AppState, auth::{AdminRole, SessionInfo}, enterprise::{ + appstate::AppState, + auth::{AdminRole, SessionInfo}, + enterprise::{ db::models::snat::UserSnatBinding, firewall::try_get_location_firewall_config, handlers::LicenseInfo, snat::error::UserSnatBindingError, - }, error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, grpc::GatewayEvent, handlers::{ApiResponse, ApiResult} + }, + error::WebError, + events::{ApiEvent, ApiEventType, ApiRequestContext}, + grpc::GatewayEvent, + handlers::{ApiResponse, ApiResult}, }; /// List all SNAT bindings for a WireGuard location diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 1616c752e6..9567eb4a55 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -7,7 +7,14 @@ use std::{ use defguard_common::{ auth::claims::ClaimsType, - db::{Id, models::{Device, Settings, WireguardNetwork, device::{DeviceInfo, WireguardNetworkDevice}, wireguard::ServiceLocationMode}}, + db::{ + Id, + models::{ + Device, Settings, WireguardNetwork, + device::{DeviceInfo, WireguardNetworkDevice}, + wireguard::ServiceLocationMode, + }, + }, types::UrlParseError, }; use reqwest::Url; @@ -16,7 +23,7 @@ use sqlx::PgPool; use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; use tonic::transport::{Identity, Server, ServerTlsConfig, server::Router}; -use self::{interceptor::JwtInterceptor}; +use self::interceptor::JwtInterceptor; use crate::{ auth::failed_login::FailedLoginMap, db::AppEvent, @@ -45,7 +52,8 @@ pub mod proto { } use defguard_proto::{ - auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, gateway::Peer, worker::worker_service_server::WorkerServiceServer + auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, + gateway::Peer, worker::worker_service_server::WorkerServiceServer, }; // gRPC header for passing auth token from clients @@ -151,12 +159,7 @@ impl From for defguard_proto::proxy::InstanceInfo { #[derive(Clone, Debug)] pub enum GatewayEvent { NetworkCreated(Id, WireguardNetwork), - NetworkModified( - Id, - WireguardNetwork, - Vec, - Option, - ), + NetworkModified(Id, WireguardNetwork, Vec, Option), NetworkDeleted(Id, String), DeviceCreated(DeviceInfo), DeviceModified(DeviceInfo), @@ -187,7 +190,6 @@ pub fn send_multiple_wireguard_events(events: Vec, wg_tx: &Sender< } } - /// If this location is marked as a service location, checks if all requirements are met for it to /// function: /// - Enterprise is enabled diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index 358e422990..1120c730fc 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -26,7 +26,13 @@ use sqlx::PgConnection; use super::{ApiResponse, ApiResult, WebError}; use crate::{ - appstate::AppState, auth::{AdminRole, SessionInfo}, enrollment_management::start_desktop_configuration, enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, events::{ApiEvent, ApiEventType, ApiRequestContext}, grpc::GatewayEvent, server_config + appstate::AppState, + auth::{AdminRole, SessionInfo}, + enrollment_management::start_desktop_configuration, + enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, + events::{ApiEvent, ApiEventType, ApiRequestContext}, + grpc::GatewayEvent, + server_config, }; #[derive(Serialize)] diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 07c26d85d9..37a895a537 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -33,16 +33,22 @@ use utoipa::ToSchema; use super::{ApiResponse, ApiResult, WebError, device_for_admin_or_self, user_for_admin_or_self}; use crate::{ - appstate::AppState, auth::{AdminRole, SessionInfo}, enterprise::{ + appstate::AppState, + auth::{AdminRole, SessionInfo}, + enterprise::{ db::models::{enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider}, firewall::try_get_location_firewall_config, handlers::CanManageDevices, is_business_license_active, limits::update_counts, - }, events::{ApiEvent, ApiEventType, ApiRequestContext}, grpc::GatewayEvent, location_management::{ + }, + events::{ApiEvent, ApiEventType, ApiRequestContext}, + grpc::GatewayEvent, + location_management::{ allowed_peers::get_location_allowed_peers, handle_imported_devices, handle_mapped_devices, sync_location_allowed_devices, - }, wg_config::{ImportedDevice, parse_wireguard_config} + }, + wg_config::{ImportedDevice, parse_wireguard_config}, }; #[derive(Serialize, ToSchema)] diff --git a/crates/defguard_core/src/location_management/mod.rs b/crates/defguard_core/src/location_management/mod.rs index 3b55fbfe59..400be93f6c 100644 --- a/crates/defguard_core/src/location_management/mod.rs +++ b/crates/defguard_core/src/location_management/mod.rs @@ -18,7 +18,9 @@ use thiserror::Error; use tokio::sync::broadcast::Sender; use crate::{ - enterprise::firewall::{FirewallError, try_get_location_firewall_config}, grpc::{GatewayEvent, send_multiple_wireguard_events}, wg_config::ImportedDevice + enterprise::firewall::{FirewallError, try_get_location_firewall_config}, + grpc::{GatewayEvent, send_multiple_wireguard_events}, + wg_config::ImportedDevice, }; pub mod allowed_peers; diff --git a/crates/defguard_core/src/user_management.rs b/crates/defguard_core/src/user_management.rs index e27d0484d3..1fc5d7cea9 100644 --- a/crates/defguard_core/src/user_management.rs +++ b/crates/defguard_core/src/user_management.rs @@ -8,7 +8,10 @@ use sqlx::PgConnection; use tokio::sync::broadcast::Sender; use crate::{ - enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, error::WebError, grpc::{GatewayEvent, send_multiple_wireguard_events, send_wireguard_event}, location_management::sync_allowed_devices_for_user + enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, + error::WebError, + grpc::{GatewayEvent, send_multiple_wireguard_events, send_wireguard_event}, + location_management::sync_allowed_devices_for_user, }; /// Deletes the user and cleans up his devices from gateways diff --git a/crates/defguard_core/src/utility_thread.rs b/crates/defguard_core/src/utility_thread.rs index 68927a7a98..cf071fe5da 100644 --- a/crates/defguard_core/src/utility_thread.rs +++ b/crates/defguard_core/src/utility_thread.rs @@ -19,7 +19,10 @@ use crate::{ is_business_license_active, ldap::{do_ldap_sync, sync::get_ldap_sync_interval}, limits::do_count_update, - }, grpc::GatewayEvent, location_management::allowed_peers::get_location_allowed_peers, updates::do_new_version_check + }, + grpc::GatewayEvent, + location_management::allowed_peers::get_location_allowed_peers, + updates::do_new_version_check, }; // Times in seconds diff --git a/crates/defguard_core/tests/integration/api/wireguard.rs b/crates/defguard_core/tests/integration/api/wireguard.rs index ea00465676..396a9ea755 100644 --- a/crates/defguard_core/tests/integration/api/wireguard.rs +++ b/crates/defguard_core/tests/integration/api/wireguard.rs @@ -19,7 +19,9 @@ use defguard_core::{ }, handlers::openid_providers::AddProviderData, license::{get_cached_license, set_cached_license}, - }, grpc::GatewayEvent, handlers::{Auth, GroupInfo, wireguard::WireguardNetworkData} + }, + grpc::GatewayEvent, + handlers::{Auth, GroupInfo, wireguard::WireguardNetworkData}, }; use ipnetwork::IpNetwork; use matches::assert_matches; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs index a7ca87d48a..9fb3b58fe7 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs @@ -9,7 +9,9 @@ use defguard_common::{ }, }; use defguard_core::{ - grpc::GatewayEvent, handlers::{Auth, wireguard::ImportedNetworkData}, location_management::allowed_peers::get_location_allowed_peers + grpc::GatewayEvent, + handlers::{Auth, wireguard::ImportedNetworkData}, + location_management::allowed_peers::get_location_allowed_peers, }; use matches::assert_matches; use reqwest::StatusCode; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs index 53574fb78e..d91ae93c88 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs @@ -5,7 +5,8 @@ use defguard_common::db::{ models::{Device, WireguardNetwork}, }; use defguard_core::{ - grpc::GatewayEvent, handlers::{Auth, network_devices::AddNetworkDevice} + grpc::GatewayEvent, + handlers::{Auth, network_devices::AddNetworkDevice}, }; use ipnetwork::IpNetwork; use matches::assert_matches; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_import.rs b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs index 06b12ca165..b609239668 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_import.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs @@ -9,7 +9,8 @@ use defguard_common::db::models::{ }, }; use defguard_core::{ - grpc::GatewayEvent, handlers::{Auth, wireguard::ImportedNetworkData} + grpc::GatewayEvent, + handlers::{Auth, wireguard::ImportedNetworkData}, }; use matches::assert_matches; use reqwest::StatusCode; diff --git a/crates/defguard_event_router/src/lib.rs b/crates/defguard_event_router/src/lib.rs index 7e42e98593..b46e6d2579 100644 --- a/crates/defguard_event_router/src/lib.rs +++ b/crates/defguard_event_router/src/lib.rs @@ -20,7 +20,8 @@ use std::sync::Arc; use defguard_core::{ - events::{ApiEvent, BidiStreamEvent}, grpc::GatewayEvent, + events::{ApiEvent, BidiStreamEvent}, + grpc::GatewayEvent, }; use defguard_event_logger::message::{EventContext, EventLoggerMessage, LoggerEvent}; use defguard_session_manager::events::SessionManagerEvent; diff --git a/crates/defguard_gateway_manager/src/auth.rs b/crates/defguard_gateway_manager/src/auth.rs index d6edaca02a..f1668eaef0 100644 --- a/crates/defguard_gateway_manager/src/auth.rs +++ b/crates/defguard_gateway_manager/src/auth.rs @@ -9,7 +9,9 @@ use jsonwebtoken::errors::Error as JWTError; use sqlx::PgPool; use tonic::{Request, Response, Status}; -use defguard_core::auth::failed_login::{FailedLoginMap, check_failed_logins, log_failed_login_attempt}; +use defguard_core::auth::failed_login::{ + FailedLoginMap, check_failed_logins, log_failed_login_attempt, +}; pub struct AuthServer { pool: PgPool, diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 914220d012..9e38186bd4 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -1,7 +1,10 @@ use std::{ collections::HashMap, str::FromStr, - sync::{Arc, atomic::{AtomicU64, Ordering}}, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, }; use defguard_common::{ @@ -12,19 +15,19 @@ use defguard_common::{ }, messages::peer_stats_update::PeerStatsUpdate, }; +use defguard_grpc_tls::{certs as tls_certs, connector::HttpsSchemeConnector}; use defguard_proto::gateway::{CoreResponse, core_request, core_response, gateway_client}; use defguard_version::client::ClientVersionInterceptor; -use defguard_grpc_tls::{ - certs as tls_certs, - connector::HttpsSchemeConnector, -}; use hyper_rustls::HttpsConnectorBuilder; use reqwest::Url; use semver::Version; use sqlx::PgPool; use tokio::{ sync::{ - Mutex, broadcast::Sender, mpsc::{self, UnboundedSender}, watch + Mutex, + broadcast::Sender, + mpsc::{self, UnboundedSender}, + watch, }, time::sleep, }; @@ -32,7 +35,9 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use tonic::transport::Endpoint; use defguard_core::{ - enterprise::firewall::try_get_location_firewall_config, grpc::GatewayEvent, handlers::mail::send_gateway_disconnected_email, location_management::allowed_peers::get_location_allowed_peers + enterprise::firewall::try_get_location_firewall_config, grpc::GatewayEvent, + handlers::mail::send_gateway_disconnected_email, + location_management::allowed_peers::get_location_allowed_peers, }; use crate::{GatewayError, TEN_SECS, try_protos_into_stats_message}; @@ -203,7 +208,7 @@ impl GatewayHandler { let endpoint = self.endpoint()?; let uri = endpoint.uri().to_string(); loop { - // TODO(jck) how does proxy do this? can this be moved to lib? + // TODO(jck) how does proxy do this? can this be moved to lib? #[cfg(not(test))] let channel = { let settings = Settings::get_current_settings(); @@ -212,12 +217,9 @@ impl GatewayHandler { "Core CA is not setup, can't create a Gateway endpoint.".to_string(), )); }; - let tls_config = tls_certs::client_config( - &ca_cert_der, - self.certs_rx.clone(), - self.gateway.id, - ) - .map_err(|err| GatewayError::EndpointError(err.to_string()))?; + let tls_config = + tls_certs::client_config(&ca_cert_der, self.certs_rx.clone(), self.gateway.id) + .map_err(|err| GatewayError::EndpointError(err.to_string()))?; let connector = HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_only() diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 6eb87ed329..958aaa9f58 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -1,19 +1,36 @@ -use std::{collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, time::Duration}; +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::{Arc, Mutex}, + time::Duration, +}; use chrono::DateTime; use defguard_common::{ - auth::claims::ClaimsType, config::server_config, db::{ + auth::claims::ClaimsType, + config::server_config, + db::{ ChangeNotification, Id, TriggerOperation, models::{ WireguardNetwork, gateway::Gateway, wireguard::{DEFAULT_WIREGUARD_MTU, ServiceLocationMode}, }, - }, messages::peer_stats_update::PeerStatsUpdate + }, + messages::peer_stats_update::PeerStatsUpdate, +}; +use defguard_core::{ + auth::failed_login::FailedLoginMap, + grpc::{ + GatewayEvent, WorkerState, interceptor::JwtInterceptor, + should_prevent_service_location_usage, worker::WorkerServer, + }, }; -use defguard_core::{auth::failed_login::FailedLoginMap, grpc::{GatewayEvent, WorkerState, interceptor::JwtInterceptor, should_prevent_service_location_usage, worker::WorkerServer}}; use defguard_proto::{ - auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, gateway::{Configuration, CoreResponse, Peer, PeerStats, Update, core_response, update}, worker::worker_service_server::WorkerServiceServer + auth::auth_service_server::AuthServiceServer, + enterprise::firewall::FirewallConfig, + gateway::{Configuration, CoreResponse, Peer, PeerStats, Update, core_response, update}, + worker::worker_service_server::WorkerServiceServer, }; use sqlx::{PgExecutor, PgPool, postgres::PgListener, query}; use thiserror::Error; @@ -24,7 +41,10 @@ use tokio::{ }, task::{AbortHandle, JoinSet}, }; -use tonic::{Code, Status, transport::{Identity, Server, ServerTlsConfig, server::Router}}; +use tonic::{ + Code, Status, + transport::{Identity, Server, ServerTlsConfig, server::Router}, +}; use defguard_core::{ enterprise::{firewall::FirewallError, is_enterprise_license_active}, diff --git a/crates/defguard_grpc_tls/src/certs.rs b/crates/defguard_grpc_tls/src/certs.rs index 788a612652..add8cea3af 100644 --- a/crates/defguard_grpc_tls/src/certs.rs +++ b/crates/defguard_grpc_tls/src/certs.rs @@ -12,13 +12,13 @@ use std::{collections::HashMap, sync::Arc}; use defguard_common::db::Id; use rustls::{ + CertificateError, DistinguishedName, Error as RustlsError, RootCertStore, SignatureScheme, client::{ - danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, WebPkiServerVerifier, + danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, }, crypto, pki_types::{CertificateDer, ServerName, UnixTime}, - CertificateError, DistinguishedName, Error as RustlsError, RootCertStore, SignatureScheme, }; use thiserror::Error; use tokio::sync::watch; diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index 2034e4fca8..3dc33a09ee 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -5,9 +5,7 @@ use std::{ }; use defguard_common::{db::models::proxy::Proxy, types::proxy::ProxyControlMessage}; -use defguard_core::{ - events::BidiStreamEvent, grpc::GatewayEvent, version::IncompatibleComponents -}; +use defguard_core::{events::BidiStreamEvent, grpc::GatewayEvent, version::IncompatibleComponents}; use sqlx::PgPool; use tokio::{ diff --git a/crates/defguard_proxy_manager/src/proxy_handler.rs b/crates/defguard_proxy_manager/src/proxy_handler.rs index 60b20c49a5..d8c143406f 100644 --- a/crates/defguard_proxy_manager/src/proxy_handler.rs +++ b/crates/defguard_proxy_manager/src/proxy_handler.rs @@ -27,7 +27,8 @@ use defguard_core::{ ldap::utils::ldap_update_user_state, }, grpc::{ - GatewayEvent, proxy::client_mfa::{ClientLoginSession, ClientMfaServer} + GatewayEvent, + proxy::client_mfa::{ClientLoginSession, ClientMfaServer}, }, version::{IncompatibleComponents, IncompatibleProxyData, is_proxy_version_supported}, }; @@ -65,8 +66,7 @@ use crate::{ ProxyError, ProxyTxSet, TEN_SECS, servers::{EnrollmentServer, PasswordResetServer}, }; -use defguard_grpc_tls::connector::HttpsSchemeConnector; -use defguard_grpc_tls::certs as tls_certs; +use defguard_grpc_tls::{certs as tls_certs, connector::HttpsSchemeConnector}; static VERSION_ZERO: Version = Version::new(0, 0, 0); @@ -201,12 +201,9 @@ impl ProxyHandler { "Core CA is not setup, can't create a Proxy endpoint.".to_string(), )); }; - let tls_config = tls_certs::client_config( - &ca_cert_der, - certs_rx.clone(), - self.proxy_id, - ) - .map_err(|err| ProxyError::TlsConfigError(err.to_string()))?; + let tls_config = + tls_certs::client_config(&ca_cert_der, certs_rx.clone(), self.proxy_id) + .map_err(|err| ProxyError::TlsConfigError(err.to_string()))?; let connector = HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_only() diff --git a/crates/defguard_proxy_manager/src/servers/enrollment.rs b/crates/defguard_proxy_manager/src/servers/enrollment.rs index 525d1339ab..bd1bbef18e 100644 --- a/crates/defguard_proxy_manager/src/servers/enrollment.rs +++ b/crates/defguard_proxy_manager/src/servers/enrollment.rs @@ -22,7 +22,9 @@ use defguard_core::{ }, events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, EnrollmentEvent}, grpc::{ - GatewayEvent, InstanceInfo, client_version::ClientFeature, utils::{build_device_config_response, parse_client_ip_agent} + GatewayEvent, InstanceInfo, + client_version::ClientFeature, + utils::{build_device_config_response, parse_client_ip_agent}, }, handlers::{ mail::{send_email_mfa_activation_email, send_mfa_configured_email}, @@ -1091,7 +1093,6 @@ pub async fn new_polling_token(pool: &PgPool, device: &Device) -> Result Date: Fri, 13 Feb 2026 13:01:57 +0100 Subject: [PATCH 09/32] remove unused imports --- crates/defguard_core/src/grpc/mod.rs | 19 ++----------------- crates/defguard_core/src/grpc/utils.rs | 1 - .../defguard_gateway_manager/src/handler.rs | 1 - crates/defguard_gateway_manager/src/lib.rs | 7 ++----- 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 9567eb4a55..289d29b363 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,12 +1,6 @@ -use std::{ - collections::hash_map::HashMap, - net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::{Arc, Mutex}, - time::{Duration, Instant}, -}; +use std::{collections::hash_map::HashMap, net::IpAddr, time::Instant}; use defguard_common::{ - auth::claims::ClaimsType, db::{ Id, models::{ @@ -19,13 +13,8 @@ use defguard_common::{ }; use reqwest::Url; use serde::Serialize; -use sqlx::PgPool; use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; -use tonic::transport::{Identity, Server, ServerTlsConfig, server::Router}; - -use self::interceptor::JwtInterceptor; use crate::{ - auth::failed_login::FailedLoginMap, db::AppEvent, enterprise::{ db::models::{ @@ -34,7 +23,6 @@ use crate::{ }, is_business_license_active, is_enterprise_license_active, }, - server_config, }; pub mod client_version; @@ -51,10 +39,7 @@ pub mod proto { } } -use defguard_proto::{ - auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, - gateway::Peer, worker::worker_service_server::WorkerServiceServer, -}; +use defguard_proto::{enterprise::firewall::FirewallConfig, gateway::Peer}; // gRPC header for passing auth token from clients pub static AUTHORIZATION_HEADER: &str = "authorization"; diff --git a/crates/defguard_core/src/grpc/utils.rs b/crates/defguard_core/src/grpc/utils.rs index 7210e9dc39..a9ac22b5fc 100644 --- a/crates/defguard_core/src/grpc/utils.rs +++ b/crates/defguard_core/src/grpc/utils.rs @@ -7,7 +7,6 @@ use defguard_common::{ models::{ Device, DeviceType, Settings, User, WireguardNetwork, device::WireguardNetworkDevice, - polling_token::PollingToken, wireguard::{LocationMfaMode, ServiceLocationMode}, }, }, diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 9e38186bd4..d3054ab23c 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -24,7 +24,6 @@ use semver::Version; use sqlx::PgPool; use tokio::{ sync::{ - Mutex, broadcast::Sender, mpsc::{self, UnboundedSender}, watch, diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 958aaa9f58..a9ebca3e34 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -14,7 +14,7 @@ use defguard_common::{ models::{ WireguardNetwork, gateway::Gateway, - wireguard::{DEFAULT_WIREGUARD_MTU, ServiceLocationMode}, + wireguard::DEFAULT_WIREGUARD_MTU, }, }, messages::peer_stats_update::PeerStatsUpdate, @@ -46,10 +46,7 @@ use tonic::{ transport::{Identity, Server, ServerTlsConfig, server::Router}, }; -use defguard_core::{ - enterprise::{firewall::FirewallError, is_enterprise_license_active}, - events::GrpcEvent, -}; +use defguard_core::{enterprise::firewall::FirewallError, events::GrpcEvent}; use crate::{auth::AuthServer, handler::GatewayHandler}; From ec0810ee75385edb801a2129f780f1e5e4e3cb9d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 13:06:39 +0100 Subject: [PATCH 10/32] allow(clippy::result_large_err) --- crates/defguard_core/src/lib.rs | 2 -- crates/defguard_gateway_manager/src/lib.rs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 94a2a06616..bb237404d6 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -1,6 +1,4 @@ #![allow(clippy::too_many_arguments)] -// FIXME: actually refactor errors instead -#![allow(clippy::result_large_err)] use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, LazyLock, Mutex, RwLock}, diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index a9ebca3e34..f56d046a48 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -1,3 +1,5 @@ +// FIXME: actually refactor errors instead +#![allow(clippy::result_large_err)] use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, From 6ffbd2714f45ba95e041ca2416964586a064606e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 13:08:25 +0100 Subject: [PATCH 11/32] rename proxy_handler module to handler --- .../src/{proxy_handler.rs => handler.rs} | 0 crates/defguard_proxy_manager/src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/defguard_proxy_manager/src/{proxy_handler.rs => handler.rs} (100%) diff --git a/crates/defguard_proxy_manager/src/proxy_handler.rs b/crates/defguard_proxy_manager/src/handler.rs similarity index 100% rename from crates/defguard_proxy_manager/src/proxy_handler.rs rename to crates/defguard_proxy_manager/src/handler.rs diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index 3dc33a09ee..4821aa3212 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -19,11 +19,11 @@ use tokio::{ task::JoinSet, }; -use crate::{certs::refresh_certs, error::ProxyError, proxy_handler::ProxyHandler}; +use crate::{certs::refresh_certs, error::ProxyError, handler::ProxyHandler}; mod certs; mod error; -mod proxy_handler; +mod handler; mod servers; #[macro_use] From 4a5b4459722af8d9bd1f59a906e2db6ca6c52211 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 13:17:20 +0100 Subject: [PATCH 12/32] defguard_gateway_manager::error module --- crates/defguard_gateway_manager/src/error.rs | 39 +++++++++++++++++++ .../defguard_gateway_manager/src/handler.rs | 2 +- crates/defguard_gateway_manager/src/lib.rs | 39 +------------------ crates/defguard_proxy_manager/src/lib.rs | 2 - 4 files changed, 42 insertions(+), 40 deletions(-) create mode 100644 crates/defguard_gateway_manager/src/error.rs diff --git a/crates/defguard_gateway_manager/src/error.rs b/crates/defguard_gateway_manager/src/error.rs new file mode 100644 index 0000000000..ef04cbdcea --- /dev/null +++ b/crates/defguard_gateway_manager/src/error.rs @@ -0,0 +1,39 @@ +use defguard_core::{enterprise::firewall::FirewallError, events::GrpcEvent}; +use thiserror::Error; +use tokio::sync::mpsc::error::SendError; +use tonic::{Code, Status}; + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Error)] +pub enum GatewayError { + #[error("Failed to acquire lock on VPN client state map")] + ClientStateMutexError, + #[error("gRPC event channel error: {0}")] + GrpcEventChannelError(#[from] SendError), + #[error("Endpoint error: {0}")] + EndpointError(String), + #[error("gRPC communication error: {0}")] + GrpcCommunicationError(#[from] tonic::Status), + #[error(transparent)] + CertificateError(#[from] defguard_certs::CertificateError), + #[error("Configuration error: {0}")] + ConfigurationError(String), + #[error("Conversion error: {0}")] + ConversionError(String), + #[error(transparent)] + SqlxError(#[from] sqlx::Error), + #[error("Not found: {0}")] + NotFound(String), + // mpsc channel send/receive error + #[error("Message channel error: {0}")] + MessageChannelError(String), + #[error(transparent)] + FirewallError(#[from] FirewallError), +} + +impl From for Status { + fn from(value: GatewayError) -> Self { + Self::new(Code::Internal, value.to_string()) + } +} + diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index d3054ab23c..e8b91295a6 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -39,7 +39,7 @@ use defguard_core::{ location_management::allowed_peers::get_location_allowed_peers, }; -use crate::{GatewayError, TEN_SECS, try_protos_into_stats_message}; +use crate::{TEN_SECS, error::GatewayError, try_protos_into_stats_message}; type ShutdownReceiver = tokio::sync::oneshot::Receiver; diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index f56d046a48..fc26f3ac9c 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -35,11 +35,10 @@ use defguard_proto::{ worker::worker_service_server::WorkerServiceServer, }; use sqlx::{PgExecutor, PgPool, postgres::PgListener, query}; -use thiserror::Error; use tokio::{ sync::{ broadcast::{Receiver as BroadcastReceiver, Sender}, - mpsc::{UnboundedSender, error::SendError}, + mpsc::UnboundedSender, }, task::{AbortHandle, JoinSet}, }; @@ -48,7 +47,6 @@ use tonic::{ transport::{Identity, Server, ServerTlsConfig, server::Router}, }; -use defguard_core::{enterprise::firewall::FirewallError, events::GrpcEvent}; use crate::{auth::AuthServer, handler::GatewayHandler}; @@ -57,6 +55,7 @@ extern crate tracing; mod auth; mod certs; +mod error; pub(crate) mod handler; // #[cfg(test)] // mod tests; @@ -92,40 +91,6 @@ fn try_protos_into_stats_message( )) } -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Error)] -pub enum GatewayError { - #[error("Failed to acquire lock on VPN client state map")] - ClientStateMutexError, - #[error("gRPC event channel error: {0}")] - GrpcEventChannelError(#[from] SendError), - #[error("Endpoint error: {0}")] - EndpointError(String), - #[error("gRPC communication error: {0}")] - GrpcCommunicationError(#[from] tonic::Status), - #[error(transparent)] - CertificateError(#[from] defguard_certs::CertificateError), - #[error("Configuration error: {0}")] - ConfigurationError(String), - #[error("Conversion error: {0}")] - ConversionError(String), - #[error(transparent)] - SqlxError(#[from] sqlx::Error), - #[error("Not found: {0}")] - NotFound(String), - // mpsc channel send/receive error - #[error("Message channel error: {0}")] - MessageChannelError(String), - #[error(transparent)] - FirewallError(#[from] FirewallError), -} - -impl From for Status { - fn from(value: GatewayError) -> Self { - Self::new(Code::Internal, value.to_string()) - } -} - /// Get a list of all allowed peers /// /// Each device is marked as allowed or not allowed in a given network, diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index 4821aa3212..351f279001 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -35,7 +35,6 @@ const TEN_SECS: Duration = Duration::from_secs(10); /// /// Responsibilities include: /// - instantiating and supervising proxy connections, -/// - routing responses to the appropriate proxy based on correlation state, /// - providing shared infrastructure (database access, outbound channels), pub struct ProxyManager { pool: PgPool, @@ -62,7 +61,6 @@ impl ProxyManager { /// Spawns and supervises asynchronous tasks for all configured proxies. /// /// Each proxy runs in its own task and shares Core-side infrastructure - /// such as routing state and compatibility tracking. pub async fn run(mut self) -> Result<(), ProxyError> { debug!("ProxyManager starting"); let remote_mfa_responses = Arc::default(); From dd97ee1015662553d7d0a3e47bbeecc5ae6d7f7a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 13:33:56 +0100 Subject: [PATCH 13/32] move gateway handler-related structs to handler module --- crates/defguard_core/src/grpc/mod.rs | 20 +- crates/defguard_gateway_manager/src/error.rs | 1 - .../defguard_gateway_manager/src/handler.rs | 435 ++++++++++++++++- crates/defguard_gateway_manager/src/lib.rs | 436 +----------------- 4 files changed, 440 insertions(+), 452 deletions(-) diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 289d29b363..2947dd868f 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,5 +1,15 @@ use std::{collections::hash_map::HashMap, net::IpAddr, time::Instant}; +use crate::{ + db::AppEvent, + enterprise::{ + db::models::{ + enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings}, + openid_provider::OpenIdProvider, + }, + is_business_license_active, is_enterprise_license_active, + }, +}; use defguard_common::{ db::{ Id, @@ -14,16 +24,6 @@ use defguard_common::{ use reqwest::Url; use serde::Serialize; use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; -use crate::{ - db::AppEvent, - enterprise::{ - db::models::{ - enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings}, - openid_provider::OpenIdProvider, - }, - is_business_license_active, is_enterprise_license_active, - }, -}; pub mod client_version; pub mod interceptor; diff --git a/crates/defguard_gateway_manager/src/error.rs b/crates/defguard_gateway_manager/src/error.rs index ef04cbdcea..bebd230533 100644 --- a/crates/defguard_gateway_manager/src/error.rs +++ b/crates/defguard_gateway_manager/src/error.rs @@ -36,4 +36,3 @@ impl From for Status { Self::new(Code::Internal, value.to_string()) } } - diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index e8b91295a6..8ca050d7fb 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -1,22 +1,23 @@ use std::{ - collections::HashMap, - str::FromStr, - sync::{ + collections::HashMap, net::IpAddr, str::FromStr, sync::{ Arc, atomic::{AtomicU64, Ordering}, - }, + } }; +use chrono::DateTime; use defguard_common::{ VERSION, db::{ Id, - models::{Settings, WireguardNetwork, gateway::Gateway}, + models::{Settings, WireguardNetwork, gateway::Gateway, wireguard::DEFAULT_WIREGUARD_MTU}, }, messages::peer_stats_update::PeerStatsUpdate, }; use defguard_grpc_tls::{certs as tls_certs, connector::HttpsSchemeConnector}; -use defguard_proto::gateway::{CoreResponse, core_request, core_response, gateway_client}; +use defguard_proto::{enterprise::firewall::FirewallConfig, gateway::{ + Configuration, CoreResponse, Peer, PeerStats, Update, core_request, core_response, gateway_client, update +}}; use defguard_version::client::ClientVersionInterceptor; use hyper_rustls::HttpsConnectorBuilder; use reqwest::Url; @@ -24,14 +25,14 @@ use semver::Version; use sqlx::PgPool; use tokio::{ sync::{ - broadcast::Sender, + broadcast::{self, Sender}, mpsc::{self, UnboundedSender}, watch, }, time::sleep, }; use tokio_stream::wrappers::UnboundedReceiverStream; -use tonic::transport::Endpoint; +use tonic::{Code, Status, transport::Endpoint}; use defguard_core::{ enterprise::firewall::try_get_location_firewall_config, grpc::GatewayEvent, @@ -39,7 +40,7 @@ use defguard_core::{ location_management::allowed_peers::get_location_allowed_peers, }; -use crate::{TEN_SECS, error::GatewayError, try_protos_into_stats_message}; +use crate::{TEN_SECS, error::GatewayError}; type ShutdownReceiver = tokio::sync::oneshot::Receiver; @@ -292,7 +293,7 @@ impl GatewayHandler { .gateway .touch_connected(&self.pool, config_request.hostname) .await; - let mut updates_handler = super::GatewayUpdatesHandler::new( + let mut updates_handler = GatewayUpdatesHandler::new( self.gateway.network_id, network, self.gateway @@ -363,3 +364,417 @@ impl GatewayHandler { } } } + +/// Helper struct for handling gateway events. +struct GatewayUpdatesHandler { + network_id: Id, + network: WireguardNetwork, + gateway_hostname: String, + events_rx: broadcast::Receiver, + tx: UnboundedSender, +} + +impl GatewayUpdatesHandler { + pub fn new( + network_id: Id, + network: WireguardNetwork, + gateway_hostname: String, + events_rx: broadcast::Receiver, + tx: UnboundedSender, + ) -> Self { + Self { + network_id, + network, + gateway_hostname, + events_rx, + tx, + } + } + + /// Process incoming Gateway events + /// + /// Main gRPC server uses a shared channel for broadcasting all gateway events + /// so the handler must determine if an event is relevant for the network being serviced + pub async fn run(&mut self) { + info!( + "Starting update stream to gateway: {}, network {}", + self.gateway_hostname, self.network + ); + while let Ok(update) = self.events_rx.recv().await { + debug!("Received WireGuard update: {update:?}"); + let result = match update { + GatewayEvent::NetworkCreated(network_id, network) => { + if network_id == self.network_id { + self.send_network_update(&network, Vec::new(), None, 0) + } else { + Ok(()) + } + } + GatewayEvent::NetworkModified( + network_id, + network, + peers, + maybe_firewall_config, + ) => { + if network_id == self.network_id { + let result = + self.send_network_update(&network, peers, maybe_firewall_config, 1); + // update stored network data + self.network = network; + result + } else { + Ok(()) + } + } + GatewayEvent::NetworkDeleted(network_id, network_name) => { + if network_id == self.network_id { + self.send_network_delete(&network_name) + } else { + Ok(()) + } + } + GatewayEvent::DeviceCreated(device) => { + // check if a peer has to be added in the current network + match device + .network_info + .iter() + .find(|info| info.network_id == self.network_id) + { + Some(network_info) => { + // FIXME: this shouldn't happen, since when the device is created + // it's impossible for MFA authorization to already be completed + if self.network.mfa_enabled() && !network_info.is_authorized { + debug!( + "Created WireGuard device {} is not authorized to connect to \ + MFA enabled location {}", + device.device.name, self.network.name + ); + continue; + } + self.send_peer_update( + Peer { + pubkey: device.device.wireguard_pubkey, + allowed_ips: network_info + .device_wireguard_ips + .iter() + .map(IpAddr::to_string) + .collect(), + preshared_key: network_info.preshared_key.clone(), + keepalive_interval: Some( + self.network.keepalive_interval as u32, + ), + }, + 0, + ) + } + None => Ok(()), + } + } + GatewayEvent::DeviceModified(device) => { + // check if a peer has to be updated in the current network + match device + .network_info + .iter() + .find(|info| info.network_id == self.network_id) + { + Some(network_info) => { + if self.network.mfa_enabled() && !network_info.is_authorized { + debug!( + "Modified WireGuard device {} is not authorized to connect to \ + MFA enabled location {}", + device.device.name, self.network.name + ); + continue; + } + self.send_peer_update( + Peer { + pubkey: device.device.wireguard_pubkey, + allowed_ips: network_info + .device_wireguard_ips + .iter() + .map(IpAddr::to_string) + .collect(), + preshared_key: network_info.preshared_key.clone(), + keepalive_interval: Some( + self.network.keepalive_interval as u32, + ), + }, + 1, + ) + } + None => Ok(()), + } + } + GatewayEvent::DeviceDeleted(device) => { + // check if a peer has to be updated in the current network + match device + .network_info + .iter() + .find(|info| info.network_id == self.network_id) + { + Some(_) => self.send_peer_delete(&device.device.wireguard_pubkey), + None => Ok(()), + } + } + GatewayEvent::FirewallConfigChanged(location_id, firewall_config) => { + if location_id == self.network_id { + self.send_firewall_update(firewall_config) + } else { + Ok(()) + } + } + GatewayEvent::FirewallDisabled(location_id) => { + if location_id == self.network_id { + self.send_firewall_disable() + } else { + Ok(()) + } + } + GatewayEvent::MfaSessionDisconnected(location_id, device) => { + if location_id == self.network_id { + self.send_peer_delete(&device.wireguard_pubkey) + } else { + Ok(()) + } + } + GatewayEvent::MfaSessionAuthorized(location_id, device, network_device) => { + if location_id == self.network_id { + // validate that network info is for the correct location + if network_device.wireguard_network_id != location_id { + error!( + "Received MFA authorization success event for location {location_id} with invalid device config: {network_device:?}" + ); + continue; + } + + // FIXME: at this point the device authorization should already have been verified + if self.network.mfa_enabled() && !network_device.is_authorized { + debug!( + "Created WireGuard device {} is not authorized to connect to \ + MFA enabled location {}", + device.name, self.network.name + ); + continue; + } + + self.send_peer_update( + Peer { + pubkey: device.wireguard_pubkey, + allowed_ips: network_device + .wireguard_ips + .iter() + .map(IpAddr::to_string) + .collect(), + preshared_key: network_device.preshared_key.clone(), + keepalive_interval: Some(self.network.keepalive_interval as u32), + }, + 0, + ) + } else { + Ok(()) + } + } + }; + if result.is_err() { + error!( + "Closing update steam to gateway: {}, network {}", + self.gateway_hostname, self.network + ); + break; + } + } + } + + /// Sends updated network configuration + fn send_network_update( + &self, + network: &WireguardNetwork, + peers: Vec, + firewall_config: Option, + update_type: i32, + ) -> Result<(), Status> { + debug!("Sending network update for network {network}"); + if let Err(err) = self.tx.send(CoreResponse { + id: 0, + payload: Some(core_response::Payload::Update(Update { + update_type, + update: Some(update::Update::Network(Configuration { + name: network.name.clone(), + prvkey: network.prvkey.clone(), + addresses: network.address.iter().map(ToString::to_string).collect(), + port: network.port as u32, + peers, + firewall_config, + mtu: network.mtu as u32, + fwmark: network.fwmark as u32, + })), + })), + }) { + let msg = format!( + "Failed to send network update, network {network}, update type: {update_type} \ + ({}), error: {err}", + if update_type == 0 { "CREATE" } else { "MODIFY" }, + ); + error!(msg); + return Err(Status::new(Code::Internal, msg)); + } + debug!("Network update sent for network {network}"); + Ok(()) + } + + /// Sends delete network command to gateway + fn send_network_delete(&self, network_name: &str) -> Result<(), Status> { + debug!( + "Sending network delete command for network {}", + self.network + ); + if let Err(err) = self.tx.send(CoreResponse { + id: 0, + payload: Some(core_response::Payload::Update(Update { + update_type: 2, + update: Some(update::Update::Network(Configuration { + name: network_name.to_string(), + prvkey: String::new(), + addresses: Vec::new(), + port: 0, + peers: Vec::new(), + firewall_config: None, + mtu: DEFAULT_WIREGUARD_MTU as u32, + fwmark: 0, + })), + })), + }) { + let msg = format!( + "Failed to send network update, network {}, update type: 2 (DELETE), error: {err}", + self.network, + ); + error!(msg); + return Err(Status::new(Code::Internal, msg)); + } + debug!("Network delete command sent for network {}", self.network); + Ok(()) + } + + /// Send update peer command to gateway + fn send_peer_update(&self, peer: Peer, update_type: i32) -> Result<(), Status> { + debug!("Sending peer update for network {}", self.network); + if let Err(err) = self.tx.send(CoreResponse { + id: 0, + payload: Some(core_response::Payload::Update(Update { + update_type, + update: Some(update::Update::Peer(peer)), + })), + }) { + let msg = format!( + "Failed to send peer update for network {}, update type: {update_type} ({}), \ + error: {err}", + self.network, + if update_type == 0 { "CREATE" } else { "MODIFY" }, + ); + error!(msg); + return Err(Status::new(Code::Internal, msg)); + } + debug!("Peer update sent for network {}", self.network); + Ok(()) + } + + /// Send delete peer command to gateway + fn send_peer_delete(&self, peer_pubkey: &str) -> Result<(), Status> { + debug!("Sending peer delete for network {}", self.network); + if let Err(err) = self.tx.send(CoreResponse { + id: 0, + payload: Some(core_response::Payload::Update(Update { + update_type: 2, + update: Some(update::Update::Peer(Peer { + pubkey: peer_pubkey.into(), + allowed_ips: Vec::new(), + preshared_key: None, + keepalive_interval: None, + })), + })), + }) { + let msg = format!( + "Failed to send peer update for network {}, peer {peer_pubkey}, update type: 2 \ + (DELETE), error: {err}", + self.network, + ); + error!(msg); + return Err(Status::new(Code::Internal, msg)); + } + debug!("Peer delete command sent for network {}", self.network); + Ok(()) + } + + /// Send firewall config update command to gateway + fn send_firewall_update(&self, firewall_config: FirewallConfig) -> Result<(), Status> { + debug!( + "Sending firewall config update for network {} with config {firewall_config:?}", + self.network + ); + if let Err(err) = self.tx.send(CoreResponse { + id: 0, + payload: Some(core_response::Payload::Update(Update { + update_type: 1, + update: Some(update::Update::FirewallConfig(firewall_config)), + })), + }) { + let msg = format!( + "Failed to send firewall config update for network {}, error: {err}", + self.network, + ); + error!(msg); + return Err(Status::new(Code::Internal, msg)); + } + debug!("Firewall config update sent for network {}", self.network); + Ok(()) + } + + /// Send firewall disable command to gateway + fn send_firewall_disable(&self) -> Result<(), Status> { + debug!( + "Sending firewall disable command for network {}", + self.network + ); + if let Err(err) = self.tx.send(CoreResponse { + id: 0, + payload: Some(core_response::Payload::Update(Update { + update_type: 2, + update: Some(update::Update::DisableFirewall(())), + })), + }) { + let msg = format!( + "Failed to send firewall disable command for network {}, error: {err}", + self.network, + ); + error!(msg); + return Err(Status::new(Code::Internal, msg)); + } + debug!("Firewall disable command sent for network {}", self.network); + Ok(()) + } +} + +/// Helper used to convert peer stats coming from gRPC client +/// into an internal representation +fn try_protos_into_stats_message( + proto_stats: PeerStats, + location_id: Id, + gateway_id: Id, +) -> Option { + // try to parse endpoint + let endpoint = proto_stats.endpoint.parse().ok()?; + + let latest_handshake = DateTime::from_timestamp(proto_stats.latest_handshake as i64, 0) + .unwrap_or_default() + .naive_utc(); + + Some(PeerStatsUpdate::new( + location_id, + gateway_id, + proto_stats.public_key, + endpoint, + proto_stats.upload, + proto_stats.download, + latest_handshake, + )) +} diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index fc26f3ac9c..33933fab69 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -1,4 +1,4 @@ -// FIXME: actually refactor errors instead +// FIXME: actua, Updatelly refactor errors instead #![allow(clippy::result_large_err)] use std::{ collections::HashMap, @@ -7,17 +7,12 @@ use std::{ time::Duration, }; -use chrono::DateTime; use defguard_common::{ auth::claims::ClaimsType, config::server_config, db::{ ChangeNotification, Id, TriggerOperation, - models::{ - WireguardNetwork, - gateway::Gateway, - wireguard::DEFAULT_WIREGUARD_MTU, - }, + models::{WireguardNetwork, gateway::Gateway}, }, messages::peer_stats_update::PeerStatsUpdate, }; @@ -31,22 +26,15 @@ use defguard_core::{ use defguard_proto::{ auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, - gateway::{Configuration, CoreResponse, Peer, PeerStats, Update, core_response, update}, + gateway::{Configuration, Peer}, worker::worker_service_server::WorkerServiceServer, }; use sqlx::{PgExecutor, PgPool, postgres::PgListener, query}; use tokio::{ - sync::{ - broadcast::{Receiver as BroadcastReceiver, Sender}, - mpsc::UnboundedSender, - }, + sync::{broadcast::Sender, mpsc::UnboundedSender}, task::{AbortHandle, JoinSet}, }; -use tonic::{ - Code, Status, - transport::{Identity, Server, ServerTlsConfig, server::Router}, -}; - +use tonic::transport::{Identity, Server, ServerTlsConfig, server::Router}; use crate::{auth::AuthServer, handler::GatewayHandler}; @@ -66,31 +54,6 @@ const GATEWAY_TABLE_TRIGGER: &str = "gateway_change"; const GATEWAY_RECONNECT_DELAY: Duration = Duration::from_secs(5); const TEN_SECS: Duration = Duration::from_secs(10); -/// Helper used to convert peer stats coming from gRPC client -/// into an internal representation -fn try_protos_into_stats_message( - proto_stats: PeerStats, - location_id: Id, - gateway_id: Id, -) -> Option { - // try to parse endpoint - let endpoint = proto_stats.endpoint.parse().ok()?; - - let latest_handshake = DateTime::from_timestamp(proto_stats.latest_handshake as i64, 0) - .unwrap_or_default() - .naive_utc(); - - Some(PeerStatsUpdate::new( - location_id, - gateway_id, - proto_stats.public_key, - endpoint, - proto_stats.upload, - proto_stats.download, - latest_handshake, - )) -} - /// Get a list of all allowed peers /// /// Each device is marked as allowed or not allowed in a given network, @@ -277,395 +240,6 @@ pub async fn run_grpc_gateway_stream( Ok(()) } -/// Helper struct for handling gateway events. -struct GatewayUpdatesHandler { - network_id: Id, - network: WireguardNetwork, - gateway_hostname: String, - events_rx: BroadcastReceiver, - tx: UnboundedSender, -} - -impl GatewayUpdatesHandler { - pub fn new( - network_id: Id, - network: WireguardNetwork, - gateway_hostname: String, - events_rx: BroadcastReceiver, - tx: UnboundedSender, - ) -> Self { - Self { - network_id, - network, - gateway_hostname, - events_rx, - tx, - } - } - - /// Process incoming Gateway events - /// - /// Main gRPC server uses a shared channel for broadcasting all gateway events - /// so the handler must determine if an event is relevant for the network being serviced - pub async fn run(&mut self) { - info!( - "Starting update stream to gateway: {}, network {}", - self.gateway_hostname, self.network - ); - while let Ok(update) = self.events_rx.recv().await { - debug!("Received WireGuard update: {update:?}"); - let result = match update { - GatewayEvent::NetworkCreated(network_id, network) => { - if network_id == self.network_id { - self.send_network_update(&network, Vec::new(), None, 0) - } else { - Ok(()) - } - } - GatewayEvent::NetworkModified( - network_id, - network, - peers, - maybe_firewall_config, - ) => { - if network_id == self.network_id { - let result = - self.send_network_update(&network, peers, maybe_firewall_config, 1); - // update stored network data - self.network = network; - result - } else { - Ok(()) - } - } - GatewayEvent::NetworkDeleted(network_id, network_name) => { - if network_id == self.network_id { - self.send_network_delete(&network_name) - } else { - Ok(()) - } - } - GatewayEvent::DeviceCreated(device) => { - // check if a peer has to be added in the current network - match device - .network_info - .iter() - .find(|info| info.network_id == self.network_id) - { - Some(network_info) => { - // FIXME: this shouldn't happen, since when the device is created - // it's impossible for MFA authorization to already be completed - if self.network.mfa_enabled() && !network_info.is_authorized { - debug!( - "Created WireGuard device {} is not authorized to connect to \ - MFA enabled location {}", - device.device.name, self.network.name - ); - continue; - } - self.send_peer_update( - Peer { - pubkey: device.device.wireguard_pubkey, - allowed_ips: network_info - .device_wireguard_ips - .iter() - .map(IpAddr::to_string) - .collect(), - preshared_key: network_info.preshared_key.clone(), - keepalive_interval: Some( - self.network.keepalive_interval as u32, - ), - }, - 0, - ) - } - None => Ok(()), - } - } - GatewayEvent::DeviceModified(device) => { - // check if a peer has to be updated in the current network - match device - .network_info - .iter() - .find(|info| info.network_id == self.network_id) - { - Some(network_info) => { - if self.network.mfa_enabled() && !network_info.is_authorized { - debug!( - "Modified WireGuard device {} is not authorized to connect to \ - MFA enabled location {}", - device.device.name, self.network.name - ); - continue; - } - self.send_peer_update( - Peer { - pubkey: device.device.wireguard_pubkey, - allowed_ips: network_info - .device_wireguard_ips - .iter() - .map(IpAddr::to_string) - .collect(), - preshared_key: network_info.preshared_key.clone(), - keepalive_interval: Some( - self.network.keepalive_interval as u32, - ), - }, - 1, - ) - } - None => Ok(()), - } - } - GatewayEvent::DeviceDeleted(device) => { - // check if a peer has to be updated in the current network - match device - .network_info - .iter() - .find(|info| info.network_id == self.network_id) - { - Some(_) => self.send_peer_delete(&device.device.wireguard_pubkey), - None => Ok(()), - } - } - GatewayEvent::FirewallConfigChanged(location_id, firewall_config) => { - if location_id == self.network_id { - self.send_firewall_update(firewall_config) - } else { - Ok(()) - } - } - GatewayEvent::FirewallDisabled(location_id) => { - if location_id == self.network_id { - self.send_firewall_disable() - } else { - Ok(()) - } - } - GatewayEvent::MfaSessionDisconnected(location_id, device) => { - if location_id == self.network_id { - self.send_peer_delete(&device.wireguard_pubkey) - } else { - Ok(()) - } - } - GatewayEvent::MfaSessionAuthorized(location_id, device, network_device) => { - if location_id == self.network_id { - // validate that network info is for the correct location - if network_device.wireguard_network_id != location_id { - error!( - "Received MFA authorization success event for location {location_id} with invalid device config: {network_device:?}" - ); - continue; - } - - // FIXME: at this point the device authorization should already have been verified - if self.network.mfa_enabled() && !network_device.is_authorized { - debug!( - "Created WireGuard device {} is not authorized to connect to \ - MFA enabled location {}", - device.name, self.network.name - ); - continue; - } - - self.send_peer_update( - Peer { - pubkey: device.wireguard_pubkey, - allowed_ips: network_device - .wireguard_ips - .iter() - .map(IpAddr::to_string) - .collect(), - preshared_key: network_device.preshared_key.clone(), - keepalive_interval: Some(self.network.keepalive_interval as u32), - }, - 0, - ) - } else { - Ok(()) - } - } - }; - if result.is_err() { - error!( - "Closing update steam to gateway: {}, network {}", - self.gateway_hostname, self.network - ); - break; - } - } - } - - /// Sends updated network configuration - fn send_network_update( - &self, - network: &WireguardNetwork, - peers: Vec, - firewall_config: Option, - update_type: i32, - ) -> Result<(), Status> { - debug!("Sending network update for network {network}"); - if let Err(err) = self.tx.send(CoreResponse { - id: 0, - payload: Some(core_response::Payload::Update(Update { - update_type, - update: Some(update::Update::Network(Configuration { - name: network.name.clone(), - prvkey: network.prvkey.clone(), - addresses: network.address.iter().map(ToString::to_string).collect(), - port: network.port as u32, - peers, - firewall_config, - mtu: network.mtu as u32, - fwmark: network.fwmark as u32, - })), - })), - }) { - let msg = format!( - "Failed to send network update, network {network}, update type: {update_type} \ - ({}), error: {err}", - if update_type == 0 { "CREATE" } else { "MODIFY" }, - ); - error!(msg); - return Err(Status::new(Code::Internal, msg)); - } - debug!("Network update sent for network {network}"); - Ok(()) - } - - /// Sends delete network command to gateway - fn send_network_delete(&self, network_name: &str) -> Result<(), Status> { - debug!( - "Sending network delete command for network {}", - self.network - ); - if let Err(err) = self.tx.send(CoreResponse { - id: 0, - payload: Some(core_response::Payload::Update(Update { - update_type: 2, - update: Some(update::Update::Network(Configuration { - name: network_name.to_string(), - prvkey: String::new(), - addresses: Vec::new(), - port: 0, - peers: Vec::new(), - firewall_config: None, - mtu: DEFAULT_WIREGUARD_MTU as u32, - fwmark: 0, - })), - })), - }) { - let msg = format!( - "Failed to send network update, network {}, update type: 2 (DELETE), error: {err}", - self.network, - ); - error!(msg); - return Err(Status::new(Code::Internal, msg)); - } - debug!("Network delete command sent for network {}", self.network); - Ok(()) - } - - /// Send update peer command to gateway - fn send_peer_update(&self, peer: Peer, update_type: i32) -> Result<(), Status> { - debug!("Sending peer update for network {}", self.network); - if let Err(err) = self.tx.send(CoreResponse { - id: 0, - payload: Some(core_response::Payload::Update(Update { - update_type, - update: Some(update::Update::Peer(peer)), - })), - }) { - let msg = format!( - "Failed to send peer update for network {}, update type: {update_type} ({}), \ - error: {err}", - self.network, - if update_type == 0 { "CREATE" } else { "MODIFY" }, - ); - error!(msg); - return Err(Status::new(Code::Internal, msg)); - } - debug!("Peer update sent for network {}", self.network); - Ok(()) - } - - /// Send delete peer command to gateway - fn send_peer_delete(&self, peer_pubkey: &str) -> Result<(), Status> { - debug!("Sending peer delete for network {}", self.network); - if let Err(err) = self.tx.send(CoreResponse { - id: 0, - payload: Some(core_response::Payload::Update(Update { - update_type: 2, - update: Some(update::Update::Peer(Peer { - pubkey: peer_pubkey.into(), - allowed_ips: Vec::new(), - preshared_key: None, - keepalive_interval: None, - })), - })), - }) { - let msg = format!( - "Failed to send peer update for network {}, peer {peer_pubkey}, update type: 2 \ - (DELETE), error: {err}", - self.network, - ); - error!(msg); - return Err(Status::new(Code::Internal, msg)); - } - debug!("Peer delete command sent for network {}", self.network); - Ok(()) - } - - /// Send firewall config update command to gateway - fn send_firewall_update(&self, firewall_config: FirewallConfig) -> Result<(), Status> { - debug!( - "Sending firewall config update for network {} with config {firewall_config:?}", - self.network - ); - if let Err(err) = self.tx.send(CoreResponse { - id: 0, - payload: Some(core_response::Payload::Update(Update { - update_type: 1, - update: Some(update::Update::FirewallConfig(firewall_config)), - })), - }) { - let msg = format!( - "Failed to send firewall config update for network {}, error: {err}", - self.network, - ); - error!(msg); - return Err(Status::new(Code::Internal, msg)); - } - debug!("Firewall config update sent for network {}", self.network); - Ok(()) - } - - /// Send firewall disable command to gateway - fn send_firewall_disable(&self) -> Result<(), Status> { - debug!( - "Sending firewall disable command for network {}", - self.network - ); - if let Err(err) = self.tx.send(CoreResponse { - id: 0, - payload: Some(core_response::Payload::Update(Update { - update_type: 2, - update: Some(update::Update::DisableFirewall(())), - })), - }) { - let msg = format!( - "Failed to send firewall disable command for network {}, error: {err}", - self.network, - ); - error!(msg); - return Err(Status::new(Code::Internal, msg)); - } - debug!("Firewall disable command sent for network {}", self.network); - Ok(()) - } -} - /// Runs gRPC server with core services. #[instrument(skip_all)] pub async fn run_grpc_server( From b9d5485eafb67049cacc0acfa0fc112a0fb04539 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 13:37:53 +0100 Subject: [PATCH 14/32] move gen_config function to handler module --- .../defguard_gateway_manager/src/handler.rs | 36 +++++-- crates/defguard_gateway_manager/src/lib.rs | 99 +------------------ 2 files changed, 33 insertions(+), 102 deletions(-) diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 8ca050d7fb..a30623a259 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -1,8 +1,11 @@ use std::{ - collections::HashMap, net::IpAddr, str::FromStr, sync::{ + collections::HashMap, + net::IpAddr, + str::FromStr, + sync::{ Arc, atomic::{AtomicU64, Ordering}, - } + }, }; use chrono::DateTime; @@ -15,9 +18,13 @@ use defguard_common::{ messages::peer_stats_update::PeerStatsUpdate, }; use defguard_grpc_tls::{certs as tls_certs, connector::HttpsSchemeConnector}; -use defguard_proto::{enterprise::firewall::FirewallConfig, gateway::{ - Configuration, CoreResponse, Peer, PeerStats, Update, core_request, core_response, gateway_client, update -}}; +use defguard_proto::{ + enterprise::firewall::FirewallConfig, + gateway::{ + Configuration, CoreResponse, Peer, PeerStats, Update, core_request, core_response, + gateway_client, update, + }, +}; use defguard_version::client::ClientVersionInterceptor; use hyper_rustls::HttpsConnectorBuilder; use reqwest::Url; @@ -135,7 +142,7 @@ impl GatewayHandler { let peers = get_location_allowed_peers(&network, &self.pool).await?; let maybe_firewall_config = try_get_location_firewall_config(&network, &mut conn).await?; - let payload = Some(core_response::Payload::Config(super::gen_config( + let payload = Some(core_response::Payload::Config(gen_config( &network, peers, maybe_firewall_config, @@ -778,3 +785,20 @@ fn try_protos_into_stats_message( latest_handshake, )) } + +fn gen_config( + network: &WireguardNetwork, + peers: Vec, + maybe_firewall_config: Option, +) -> Configuration { + Configuration { + name: network.name.clone(), + port: network.port as u32, + prvkey: network.prvkey.clone(), + addresses: network.address.iter().map(ToString::to_string).collect(), + peers, + firewall_config: maybe_firewall_config, + mtu: network.mtu as u32, + fwmark: network.fwmark as u32, + } +} diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 33933fab69..bcd36c2f34 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -10,26 +10,18 @@ use std::{ use defguard_common::{ auth::claims::ClaimsType, config::server_config, - db::{ - ChangeNotification, Id, TriggerOperation, - models::{WireguardNetwork, gateway::Gateway}, - }, + db::{ChangeNotification, Id, TriggerOperation, models::gateway::Gateway}, messages::peer_stats_update::PeerStatsUpdate, }; use defguard_core::{ auth::failed_login::FailedLoginMap, - grpc::{ - GatewayEvent, WorkerState, interceptor::JwtInterceptor, - should_prevent_service_location_usage, worker::WorkerServer, - }, + grpc::{GatewayEvent, WorkerState, interceptor::JwtInterceptor, worker::WorkerServer}, }; use defguard_proto::{ auth::auth_service_server::AuthServiceServer, - enterprise::firewall::FirewallConfig, - gateway::{Configuration, Peer}, worker::worker_service_server::WorkerServiceServer, }; -use sqlx::{PgExecutor, PgPool, postgres::PgListener, query}; +use sqlx::{PgPool, postgres::PgListener}; use tokio::{ sync::{broadcast::Sender, mpsc::UnboundedSender}, task::{AbortHandle, JoinSet}, @@ -54,91 +46,6 @@ const GATEWAY_TABLE_TRIGGER: &str = "gateway_change"; const GATEWAY_RECONNECT_DELAY: Duration = Duration::from_secs(5); const TEN_SECS: Duration = Duration::from_secs(10); -/// Get a list of all allowed peers -/// -/// Each device is marked as allowed or not allowed in a given network, -/// which enables enforcing peer disconnect in MFA-protected networks. -/// -/// If the location is a service location, only returns peers if enterprise features are enabled. -/// -/// XXX: should be implemented in defguard_core::db::models::wireguard::WireguardNetwork. -pub async fn get_peers<'e, E>( - location: &WireguardNetwork, - executor: E, -) -> Result, sqlx::Error> -where - E: PgExecutor<'e>, -{ - debug!("Fetching all peers for network {}", location.id); - - if should_prevent_service_location_usage(location) { - warn!( - "Tried to use service location {} with disabled enterprise features. No clients \ - will be allowed to connect.", - location.name - ); - return Ok(Vec::new()); - } - - // TODO: possible to not use ARRAY-unnest here? - let rows = query!( - "SELECT d.wireguard_pubkey pubkey, preshared_key, \ - ARRAY( - SELECT host(ip) - FROM unnest(wnd.wireguard_ips) AS ip - ) \"allowed_ips!: Vec\" \ - FROM wireguard_network_device wnd \ - JOIN device d ON wnd.device_id = d.id \ - JOIN \"user\" u ON d.user_id = u.id \ - WHERE wireguard_network_id = $1 AND (is_authorized = true OR NOT $2) \ - AND d.configured = true \ - AND u.is_active = true \ - ORDER BY d.id ASC", - location.id, - location.mfa_enabled() - ) - .fetch_all(executor) - .await?; - - // keepalive has to be added manually because Postgres - // doesn't support unsigned integers - let result = rows - .into_iter() - .map(|row| Peer { - pubkey: row.pubkey, - allowed_ips: row.allowed_ips, - // Don't send preshared key if MFA is not enabled, it can't be used and may - // cause issues with clients connecting if they expect no preshared key - // e.g. when you disable MFA on a location - preshared_key: if location.mfa_enabled() { - row.preshared_key - } else { - None - }, - keepalive_interval: Some(location.keepalive_interval as u32), - }) - .collect(); - - Ok(result) -} - -fn gen_config( - network: &WireguardNetwork, - peers: Vec, - maybe_firewall_config: Option, -) -> Configuration { - Configuration { - name: network.name.clone(), - port: network.port as u32, - prvkey: network.prvkey.clone(), - addresses: network.address.iter().map(ToString::to_string).collect(), - peers, - firewall_config: maybe_firewall_config, - mtu: network.mtu as u32, - fwmark: network.fwmark as u32, - } -} - /// Bi-directional gRPC stream for communication with Defguard Gateway. pub async fn run_grpc_gateway_stream( pool: PgPool, From 13ce050a665d9e4e3b819b9c7f6e045f51fbf3da Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 13:46:34 +0100 Subject: [PATCH 15/32] tighten modules visibility --- crates/defguard_gateway_manager/src/auth.rs | 2 +- crates/defguard_gateway_manager/src/certs.rs | 5 ++++- crates/defguard_gateway_manager/src/error.rs | 2 +- crates/defguard_gateway_manager/src/handler.rs | 11 ++++++----- crates/defguard_gateway_manager/src/lib.rs | 6 +++--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/defguard_gateway_manager/src/auth.rs b/crates/defguard_gateway_manager/src/auth.rs index f1668eaef0..1ae014ad4c 100644 --- a/crates/defguard_gateway_manager/src/auth.rs +++ b/crates/defguard_gateway_manager/src/auth.rs @@ -13,7 +13,7 @@ use defguard_core::auth::failed_login::{ FailedLoginMap, check_failed_logins, log_failed_login_attempt, }; -pub struct AuthServer { +pub(super) struct AuthServer { pool: PgPool, failed_logins: Arc>, } diff --git a/crates/defguard_gateway_manager/src/certs.rs b/crates/defguard_gateway_manager/src/certs.rs index f7347e065f..7bf31dcf8d 100644 --- a/crates/defguard_gateway_manager/src/certs.rs +++ b/crates/defguard_gateway_manager/src/certs.rs @@ -16,7 +16,10 @@ where .collect() } -pub(crate) async fn refresh_certs(pool: &PgPool, tx: &watch::Sender>>) { +pub(super) async fn refresh_certs( + pool: &PgPool, + tx: &watch::Sender>>, +) { match Gateway::all(pool).await { Ok(gateways) => { let certs = collect_certs( diff --git a/crates/defguard_gateway_manager/src/error.rs b/crates/defguard_gateway_manager/src/error.rs index bebd230533..34b6d656c3 100644 --- a/crates/defguard_gateway_manager/src/error.rs +++ b/crates/defguard_gateway_manager/src/error.rs @@ -5,7 +5,7 @@ use tonic::{Code, Status}; #[allow(clippy::large_enum_variant)] #[derive(Debug, Error)] -pub enum GatewayError { +pub(crate) enum GatewayError { #[error("Failed to acquire lock on VPN client state map")] ClientStateMutexError, #[error("gRPC event channel error: {0}")] diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index a30623a259..a21ab0f4e6 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -52,7 +52,7 @@ use crate::{TEN_SECS, error::GatewayError}; type ShutdownReceiver = tokio::sync::oneshot::Receiver; /// One instance per connected Gateway. -pub(crate) struct GatewayHandler { +pub(super) struct GatewayHandler { // Gateway server endpoint URL. url: Url, gateway: Gateway, @@ -64,7 +64,7 @@ pub(crate) struct GatewayHandler { } impl GatewayHandler { - pub(crate) fn new( + pub fn new( gateway: Gateway, pool: PgPool, events_tx: Sender, @@ -211,7 +211,7 @@ impl GatewayHandler { } /// Connect to Gateway and handle its messages through gRPC. - pub(crate) async fn handle_connection(&mut self) -> Result<(), GatewayError> { + pub(super) async fn handle_connection(&mut self) -> Result<(), GatewayError> { let endpoint = self.endpoint()?; let uri = endpoint.uri().to_string(); loop { @@ -382,7 +382,8 @@ struct GatewayUpdatesHandler { } impl GatewayUpdatesHandler { - pub fn new( + #[must_use] + fn new( network_id: Id, network: WireguardNetwork, gateway_hostname: String, @@ -402,7 +403,7 @@ impl GatewayUpdatesHandler { /// /// Main gRPC server uses a shared channel for broadcasting all gateway events /// so the handler must determine if an event is relevant for the network being serviced - pub async fn run(&mut self) { + async fn run(&mut self) { info!( "Starting update stream to gateway: {}, network {}", self.gateway_hostname, self.network diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index bcd36c2f34..9201499461 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -36,12 +36,12 @@ extern crate tracing; mod auth; mod certs; mod error; -pub(crate) mod handler; +mod handler; // #[cfg(test)] // mod tests; #[cfg(test)] -pub(crate) static TONIC_SOCKET: &str = "tonic.sock"; +static TONIC_SOCKET: &str = "tonic.sock"; const GATEWAY_TABLE_TRIGGER: &str = "gateway_change"; const GATEWAY_RECONNECT_DELAY: Duration = Duration::from_secs(5); const TEN_SECS: Duration = Duration::from_secs(10); @@ -179,7 +179,7 @@ pub async fn run_grpc_server( Ok(()) } -pub async fn build_grpc_service_router( +pub(crate) async fn build_grpc_service_router( server: Server, pool: PgPool, worker_state: Arc>, From 8e6e802c9d8c1818ac1ff5924015fb42c4e43c90 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Feb 2026 13:54:06 +0100 Subject: [PATCH 16/32] remove unused GatewayError variants --- crates/defguard_gateway_manager/src/error.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/defguard_gateway_manager/src/error.rs b/crates/defguard_gateway_manager/src/error.rs index 34b6d656c3..7fde13348e 100644 --- a/crates/defguard_gateway_manager/src/error.rs +++ b/crates/defguard_gateway_manager/src/error.rs @@ -6,8 +6,6 @@ use tonic::{Code, Status}; #[allow(clippy::large_enum_variant)] #[derive(Debug, Error)] pub(crate) enum GatewayError { - #[error("Failed to acquire lock on VPN client state map")] - ClientStateMutexError, #[error("gRPC event channel error: {0}")] GrpcEventChannelError(#[from] SendError), #[error("Endpoint error: {0}")] @@ -16,10 +14,6 @@ pub(crate) enum GatewayError { GrpcCommunicationError(#[from] tonic::Status), #[error(transparent)] CertificateError(#[from] defguard_certs::CertificateError), - #[error("Configuration error: {0}")] - ConfigurationError(String), - #[error("Conversion error: {0}")] - ConversionError(String), #[error(transparent)] SqlxError(#[from] sqlx::Error), #[error("Not found: {0}")] From 1ad7ba69e0e06ee1aeb8e382e8fce7251c695e3b Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Sat, 14 Feb 2026 07:30:55 +0100 Subject: [PATCH 17/32] send purge request on gateway delete trigger, GatewayManager --- crates/defguard/src/main.rs | 6 +- .../defguard_gateway_manager/src/handler.rs | 13 +- crates/defguard_gateway_manager/src/lib.rs | 222 +++++++++++------- crates/defguard_proxy_manager/src/handler.rs | 4 +- crates/defguard_proxy_manager/src/lib.rs | 1 + 5 files changed, 155 insertions(+), 91 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 0d678c23df..c1c9f6e0a4 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -30,7 +30,7 @@ use defguard_core::{ }; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; -use defguard_gateway_manager::{run_grpc_gateway_stream, run_grpc_server}; +use defguard_gateway_manager::{GatewayManager, run_grpc_server}; use defguard_proxy_manager::{ProxyManager, ProxyTxSet}; use defguard_session_manager::{events::SessionManagerEvent, run_session_manager}; use defguard_setup::setup::run_setup_web_server; @@ -180,10 +180,12 @@ async fn main() -> Result<(), anyhow::Error> { proxy_control_rx, ); + let mut gateway_manager = GatewayManager::new(); + // run services tokio::select! { res = proxy_manager.run() => error!("ProxyManager returned early: {res:?}"), - res = run_grpc_gateway_stream( + res = gateway_manager.run_grpc_gateway_stream( pool.clone(), gateway_tx.clone(), peer_stats_tx, diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index a21ab0f4e6..072f9ddedc 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -3,7 +3,7 @@ use std::{ net::IpAddr, str::FromStr, sync::{ - Arc, + Arc, Mutex, atomic::{AtomicU64, Ordering}, }, }; @@ -47,7 +47,7 @@ use defguard_core::{ location_management::allowed_peers::get_location_allowed_peers, }; -use crate::{TEN_SECS, error::GatewayError}; +use crate::{Client, TEN_SECS, error::GatewayError}; type ShutdownReceiver = tokio::sync::oneshot::Receiver; @@ -211,7 +211,10 @@ impl GatewayHandler { } /// Connect to Gateway and handle its messages through gRPC. - pub(super) async fn handle_connection(&mut self) -> Result<(), GatewayError> { + pub(super) async fn handle_connection( + &mut self, + clients: Arc>>, + ) -> Result<(), GatewayError> { let endpoint = self.endpoint()?; let uri = endpoint.uri().to_string(); loop { @@ -249,6 +252,10 @@ impl GatewayHandler { Version::parse(VERSION).expect("failed to parse self version"), ); let mut client = gateway_client::GatewayClient::with_interceptor(channel, interceptor); + clients + .lock() + .expect("GatewayHandler failed to lock clients") + .insert(self.gateway.id, client.clone()); let (tx, rx) = mpsc::unbounded_channel(); let response = match client.bidi(UnboundedReceiverStream::new(rx)).await { Ok(response) => response, diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 9201499461..20bba54a6f 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -18,15 +18,20 @@ use defguard_core::{ grpc::{GatewayEvent, WorkerState, interceptor::JwtInterceptor, worker::WorkerServer}, }; use defguard_proto::{ - auth::auth_service_server::AuthServiceServer, + auth::auth_service_server::AuthServiceServer, gateway::gateway_client::GatewayClient, worker::worker_service_server::WorkerServiceServer, }; +use defguard_version::client::ClientVersionInterceptor; use sqlx::{PgPool, postgres::PgListener}; use tokio::{ sync::{broadcast::Sender, mpsc::UnboundedSender}, task::{AbortHandle, JoinSet}, }; -use tonic::transport::{Identity, Server, ServerTlsConfig, server::Router}; +use tonic::{ + Request, + service::interceptor::InterceptedService, + transport::{Channel, Identity, Server, ServerTlsConfig, server::Router}, +}; use crate::{auth::AuthServer, handler::GatewayHandler}; @@ -46,107 +51,153 @@ const GATEWAY_TABLE_TRIGGER: &str = "gateway_change"; const GATEWAY_RECONNECT_DELAY: Duration = Duration::from_secs(5); const TEN_SECS: Duration = Duration::from_secs(10); -/// Bi-directional gRPC stream for communication with Defguard Gateway. -pub async fn run_grpc_gateway_stream( - pool: PgPool, - events_tx: Sender, - peer_stats_tx: UnboundedSender, -) -> Result<(), anyhow::Error> { - let (certs_tx, certs_rx) = tokio::sync::watch::channel(Arc::new(HashMap::new())); - certs::refresh_certs(&pool, &certs_tx).await; - let refresh_pool = pool.clone(); - tokio::spawn(async move { - loop { - certs::refresh_certs(&refresh_pool, &certs_tx).await; - tokio::time::sleep(TEN_SECS).await; +type Client = GatewayClient>; + +pub struct GatewayManager { + clients: Arc>>, +} + +impl GatewayManager { + pub fn new() -> Self { + Self { + clients: Arc::default(), } - }); - let mut abort_handles = HashMap::new(); - - let mut tasks = JoinSet::new(); - // Helper closure to launch `GatewayHandler`. - let mut launch_gateway_handler = |gateway: Gateway| -> Result { - let mut gateway_handler = GatewayHandler::new( - gateway, - pool.clone(), - events_tx.clone(), - peer_stats_tx.clone(), - certs_rx.clone(), - )?; - let abort_handle = tasks.spawn(async move { + } + + /// Bi-directional gRPC stream for communication with Defguard Gateway. + pub async fn run_grpc_gateway_stream( + &mut self, + pool: PgPool, + events_tx: Sender, + peer_stats_tx: UnboundedSender, + ) -> Result<(), anyhow::Error> { + let (certs_tx, certs_rx) = tokio::sync::watch::channel(Arc::new(HashMap::new())); + certs::refresh_certs(&pool, &certs_tx).await; + let refresh_pool = pool.clone(); + tokio::spawn(async move { loop { - if let Err(err) = gateway_handler.handle_connection().await { - error!("Gateway connection error: {err}, retrying in 5 seconds..."); - tokio::time::sleep(GATEWAY_RECONNECT_DELAY).await; - } + certs::refresh_certs(&refresh_pool, &certs_tx).await; + tokio::time::sleep(TEN_SECS).await; } }); - Ok(abort_handle) - }; + let mut abort_handles = HashMap::new(); - for gateway in Gateway::all(&pool).await? { - let id = gateway.id; - let abort_handle = launch_gateway_handler(gateway)?; - abort_handles.insert(id, abort_handle); - } - - // Observe gateway URL changes. - let mut listener = PgListener::connect_with(&pool).await?; - listener.listen(GATEWAY_TABLE_TRIGGER).await?; - while let Ok(notification) = listener.recv().await { - let payload = notification.payload(); - match serde_json::from_str::>>(payload) { - Ok(gateway_notification) => match gateway_notification.operation { - TriggerOperation::Insert => { - if let Some(new) = gateway_notification.new { - let id = new.id; - let abort_handle = launch_gateway_handler(new)?; - abort_handles.insert(id, abort_handle); + let mut tasks = JoinSet::new(); + // Helper closure to launch `GatewayHandler`. + // TODO(jck) store arguments in GatewayManager + // TODO(jck) rewrite this to method + let mut launch_gateway_handler = |gateway: Gateway, + clients: Arc>>| + -> Result { + let mut gateway_handler = GatewayHandler::new( + gateway, + pool.clone(), + events_tx.clone(), + peer_stats_tx.clone(), + certs_rx.clone(), + )?; + let abort_handle = tasks.spawn(async move { + loop { + if let Err(err) = gateway_handler + .handle_connection(Arc::clone(&clients)) + .await + { + error!("Gateway connection error: {err}, retrying in 5 seconds..."); + tokio::time::sleep(GATEWAY_RECONNECT_DELAY).await; } } - TriggerOperation::Update => { - if let (Some(old), Some(new)) = - (gateway_notification.old, gateway_notification.new) - { - if old.url == new.url { - debug!( - "Gateway URL didn't change. Keeping the current gateway handler" - ); - } else if let Some(abort_handle) = abort_handles.remove(&old.id) { - info!("Aborting connection to {old}, it has changed in the database"); - abort_handle.abort(); + }); + Ok(abort_handle) + }; + for gateway in Gateway::all(&pool).await? { + let id = gateway.id; + let abort_handle = launch_gateway_handler(gateway, Arc::clone(&self.clients))?; + abort_handles.insert(id, abort_handle); + } + + // Observe gateway URL changes. + let mut listener = PgListener::connect_with(&pool).await?; + listener.listen(GATEWAY_TABLE_TRIGGER).await?; + while let Ok(notification) = listener.recv().await { + let payload = notification.payload(); + match serde_json::from_str::>>(payload) { + Ok(gateway_notification) => match gateway_notification.operation { + TriggerOperation::Insert => { + if let Some(new) = gateway_notification.new { let id = new.id; - let abort_handle = launch_gateway_handler(new)?; + let abort_handle = + launch_gateway_handler(new, Arc::clone(&self.clients))?; abort_handles.insert(id, abort_handle); - } else { - warn!("Cannot find {old} on the list of connected gateways"); } } - } - TriggerOperation::Delete => { - if let Some(old) = gateway_notification.old { - if let Some(abort_handle) = abort_handles.remove(&old.id) { - info!( - "Aborting connection to {old}, it has disappeard from the database" - ); - abort_handle.abort(); - } else { - warn!("Cannot find {old} on the list of connected gateways"); + TriggerOperation::Update => { + if let (Some(old), Some(new)) = + (gateway_notification.old, gateway_notification.new) + { + if old.url == new.url { + debug!( + "Gateway URL didn't change. Keeping the current gateway handler" + ); + } else if let Some(abort_handle) = abort_handles.remove(&old.id) { + info!( + "Aborting connection to {old}, it has changed in the database" + ); + abort_handle.abort(); + let id = new.id; + let abort_handle = + launch_gateway_handler(new, Arc::clone(&self.clients))?; + abort_handles.insert(id, abort_handle); + } else { + warn!("Cannot find {old} on the list of connected gateways"); + } } } - } - }, - Err(err) => error!("Failed to de-serialize database notification object: {err}"), + TriggerOperation::Delete => { + // TODO(jck) refactor + if let Some(old) = gateway_notification.old { + if let Some(mut client) = self + .clients + .lock() + .expect("Failed to lock GatewayManager::clients") + .remove(&old.id) + { + debug!("Sending purge request to gateway {old}"); + if let Err(err) = client.purge(Request::new(())).await { + error!("Error sending purge request to gateway {old}: {err}"); + } else { + info!("Sent purge request to gateway {old}"); + } + } else { + warn!( + "Cannot find gateway {old} on the list of connected gateways" + ); + } + if let Some(abort_handle) = abort_handles.remove(&old.id) { + info!( + "Aborting connection to gateway {old}, it has disappeard from the database" + ); + abort_handle.abort(); + } else { + warn!( + "Cannot find gateway {old} on the list of connected gateways" + ); + } + } + } + }, + Err(err) => error!("Failed to de-serialize database notification object: {err}"), + } } - } - while let Some(Ok(_result)) = tasks.join_next().await { - debug!("Gateway gRPC task has ended"); - } + while let Some(Ok(_result)) = tasks.join_next().await { + debug!("Gateway gRPC task has ended"); + } - Ok(()) + Ok(()) + } } +// TODO(jck) move this to core/grpc/lib /// Runs gRPC server with core services. #[instrument(skip_all)] pub async fn run_grpc_server( @@ -179,6 +230,7 @@ pub async fn run_grpc_server( Ok(()) } +// TODO(jck) move this to core/grpc/lib pub(crate) async fn build_grpc_service_router( server: Server, pool: PgPool, diff --git a/crates/defguard_proxy_manager/src/handler.rs b/crates/defguard_proxy_manager/src/handler.rs index d8c143406f..5ee8fea4ad 100644 --- a/crates/defguard_proxy_manager/src/handler.rs +++ b/crates/defguard_proxy_manager/src/handler.rs @@ -302,10 +302,12 @@ impl ProxyHandler { Ok(purge) => { info!("Shutdown signal received, purge: {purge}, stopping proxy connection to {}", endpoint.uri()); if purge { - debug!("Sending purge request to proxy {}", endpoint.uri()); if let Some(client) = self.client.as_mut() { + debug!("Sending purge request to proxy {}", endpoint.uri()); if let Err(err) = client.purge(Request::new(())).await { error!("Error sending purge request to proxy {}: {err}", endpoint.uri()); + } else { + info!("Sent purge request to proxy {}", endpoint.uri()); } } } diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index 351f279001..1b8b8009c9 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -69,6 +69,7 @@ impl ProxyManager { // Prime the cache to avoid race with connection loop. refresh_certs(&self.pool, &certs_tx).await; let refresh_pool = self.pool.clone(); + // TODO(jck) monitor this thread somewhere tokio::spawn(async move { loop { refresh_certs(&refresh_pool, &certs_tx).await; From 87f3b159ee32229321d0d7377daac86e39a2e167 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 09:46:43 +0100 Subject: [PATCH 18/32] refactor TriggerOperation::Delete match branch --- crates/defguard_gateway_manager/src/lib.rs | 59 +++++++++++----------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 20bba54a6f..4b5147b110 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -84,8 +84,7 @@ impl GatewayManager { let mut tasks = JoinSet::new(); // Helper closure to launch `GatewayHandler`. - // TODO(jck) store arguments in GatewayManager - // TODO(jck) rewrite this to method + // TODO: Store arguments in GatewayManager and rewrite this to method let mut launch_gateway_handler = |gateway: Gateway, clients: Arc>>| -> Result { @@ -153,35 +152,37 @@ impl GatewayManager { } } TriggerOperation::Delete => { - // TODO(jck) refactor - if let Some(old) = gateway_notification.old { - if let Some(mut client) = self - .clients - .lock() - .expect("Failed to lock GatewayManager::clients") - .remove(&old.id) - { - debug!("Sending purge request to gateway {old}"); - if let Err(err) = client.purge(Request::new(())).await { - error!("Error sending purge request to gateway {old}: {err}"); - } else { - info!("Sent purge request to gateway {old}"); - } - } else { - warn!( - "Cannot find gateway {old} on the list of connected gateways" - ); - } - if let Some(abort_handle) = abort_handles.remove(&old.id) { - info!( - "Aborting connection to gateway {old}, it has disappeard from the database" - ); - abort_handle.abort(); + let Some(old) = gateway_notification.old else { + continue; + }; + + // Send purge request to the gateway. + if let Some(mut client) = self + .clients + .lock() + .expect("Failed to lock GatewayManager::clients") + .remove(&old.id) + { + debug!("Sending purge request to gateway {old}"); + if let Err(err) = client.purge(Request::new(())).await { + error!("Error sending purge request to gateway {old}: {err}"); } else { - warn!( - "Cannot find gateway {old} on the list of connected gateways" - ); + info!("Sent purge request to gateway {old}"); } + } else { + warn!("Cannot find gRPC client for gateway {old}, won't send purge request"); + } + + // Kill the `GatewayHandler` and the connection. + if let Some(abort_handle) = abort_handles.remove(&old.id) { + info!( + "Aborting connection to gateway {old}, it has disappeard from the database" + ); + abort_handle.abort(); + } else { + warn!( + "Cannot find abort handle for gateway {old}" + ); } } }, From bd30462cd13508c807a0368b5fa5bcce9a62f835 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 09:47:13 +0100 Subject: [PATCH 19/32] cert verification throws CertificateError::ApplicationVerificationFailure --- crates/defguard_grpc_tls/src/certs.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/defguard_grpc_tls/src/certs.rs b/crates/defguard_grpc_tls/src/certs.rs index add8cea3af..e9f9f44aa5 100644 --- a/crates/defguard_grpc_tls/src/certs.rs +++ b/crates/defguard_grpc_tls/src/certs.rs @@ -64,14 +64,18 @@ impl CertVerifier { "Missing expected certificate for component id={}, serial={}", self.component_id, serial ); - return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); + return Err(RustlsError::InvalidCertificate( + CertificateError::ApplicationVerificationFailure, + )); }; if !expected.eq_ignore_ascii_case(&serial) { error!( "Invalid certificate for component id={}: expected={} got={}.", self.component_id, expected, serial ); - return Err(RustlsError::InvalidCertificate(CertificateError::Revoked)); + return Err(RustlsError::InvalidCertificate( + CertificateError::ApplicationVerificationFailure, + )); } Ok(()) } From b38066b0c4fa08ed6249293e83b752e4699ec88d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 10:00:41 +0100 Subject: [PATCH 20/32] move auth, generic grpc methods to core --- crates/defguard/src/main.rs | 4 +- .../src => defguard_core/src/grpc}/auth.rs | 2 +- crates/defguard_core/src/grpc/mod.rs | 79 +++++++++++++++++-- crates/defguard_gateway_manager/src/lib.rs | 68 +--------------- 4 files changed, 76 insertions(+), 77 deletions(-) rename crates/{defguard_gateway_manager/src => defguard_core/src/grpc}/auth.rs (98%) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index c1c9f6e0a4..81b2591436 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -23,14 +23,14 @@ use defguard_core::{ limits::update_counts, }, events::{ApiEvent, BidiStreamEvent}, - grpc::{GatewayEvent, WorkerState}, + grpc::{GatewayEvent, WorkerState, run_grpc_server}, init_dev_env, init_vpn_location, run_web_server, utility_thread::run_utility_thread, version::IncompatibleComponents, }; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; -use defguard_gateway_manager::{GatewayManager, run_grpc_server}; +use defguard_gateway_manager::GatewayManager; use defguard_proxy_manager::{ProxyManager, ProxyTxSet}; use defguard_session_manager::{events::SessionManagerEvent, run_session_manager}; use defguard_setup::setup::run_setup_web_server; diff --git a/crates/defguard_gateway_manager/src/auth.rs b/crates/defguard_core/src/grpc/auth.rs similarity index 98% rename from crates/defguard_gateway_manager/src/auth.rs rename to crates/defguard_core/src/grpc/auth.rs index 1ae014ad4c..eafc0f165b 100644 --- a/crates/defguard_gateway_manager/src/auth.rs +++ b/crates/defguard_core/src/grpc/auth.rs @@ -9,7 +9,7 @@ use jsonwebtoken::errors::Error as JWTError; use sqlx::PgPool; use tonic::{Request, Response, Status}; -use defguard_core::auth::failed_login::{ +use crate::auth::failed_login::{ FailedLoginMap, check_failed_logins, log_failed_login_attempt, }; diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 2947dd868f..e5686dedf7 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,30 +1,30 @@ -use std::{collections::hash_map::HashMap, net::IpAddr, time::Instant}; +use std::{collections::hash_map::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, time::{Duration, Instant}}; use crate::{ - db::AppEvent, - enterprise::{ + auth::failed_login::FailedLoginMap, db::AppEvent, enterprise::{ db::models::{ enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings}, openid_provider::OpenIdProvider, }, is_business_license_active, is_enterprise_license_active, - }, + }, grpc::{auth::AuthServer, interceptor::JwtInterceptor, worker::WorkerServer} }; use defguard_common::{ - db::{ + auth::claims::ClaimsType, config::server_config, db::{ Id, models::{ Device, Settings, WireguardNetwork, device::{DeviceInfo, WireguardNetworkDevice}, wireguard::ServiceLocationMode, }, - }, - types::UrlParseError, + }, types::UrlParseError }; use reqwest::Url; use serde::Serialize; +use sqlx::PgPool; use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; +mod auth; pub mod client_version; pub mod interceptor; pub mod proxy; @@ -39,13 +39,76 @@ pub mod proto { } } -use defguard_proto::{enterprise::firewall::FirewallConfig, gateway::Peer}; +use defguard_proto::{auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, gateway::Peer, worker::worker_service_server::WorkerServiceServer}; +use tonic::transport::{Identity, Server, ServerTlsConfig, server::Router}; // gRPC header for passing auth token from clients pub static AUTHORIZATION_HEADER: &str = "authorization"; // gRPC header for passing hostname from clients pub static HOSTNAME_HEADER: &str = "hostname"; +const TEN_SECS: Duration = Duration::from_secs(10); + +/// Runs gRPC server with core services. +#[instrument(skip_all)] +pub async fn run_grpc_server( + worker_state: Arc>, + pool: PgPool, + grpc_cert: Option, + grpc_key: Option, + failed_logins: Arc>, +) -> Result<(), anyhow::Error> { + // Build gRPC services + let server = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { + let identity = Identity::from_pem(cert, key); + Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? + } else { + Server::builder() + }; + + let router = build_grpc_service_router(server, pool, worker_state, failed_logins).await?; + + // Run gRPC server + let addr = SocketAddr::new( + server_config() + .grpc_bind_address + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + server_config().grpc_port, + ); + debug!("Starting gRPC services"); + router.serve(addr).await?; + info!("gRPC server started on {addr}"); + Ok(()) +} + +pub(crate) async fn build_grpc_service_router( + server: Server, + pool: PgPool, + worker_state: Arc>, + failed_logins: Arc>, + // incompatible_components: Arc>, +) -> Result { + let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); + + let worker_service = WorkerServiceServer::with_interceptor( + WorkerServer::new(pool.clone(), worker_state), + JwtInterceptor::new(ClaimsType::YubiBridge), + ); + + let (health_reporter, health_service) = tonic_health::server::health_reporter(); + health_reporter + .set_serving::>() + .await; + + let router = server + .http2_keepalive_interval(Some(TEN_SECS)) + .tcp_keepalive(Some(TEN_SECS)) + .add_service(health_service) + .add_service(auth_service); + let router = router.add_service(worker_service); + + Ok(router) +} pub struct Job { id: u32, diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 4b5147b110..4853495d5a 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -1,4 +1,4 @@ -// FIXME: actua, Updatelly refactor errors instead +// FIXME: actua, Updatelly refactov errors instead #![allow(clippy::result_large_err)] use std::{ collections::HashMap, @@ -33,12 +33,11 @@ use tonic::{ transport::{Channel, Identity, Server, ServerTlsConfig, server::Router}, }; -use crate::{auth::AuthServer, handler::GatewayHandler}; +use crate::handler::GatewayHandler; #[macro_use] extern crate tracing; -mod auth; mod certs; mod error; mod handler; @@ -197,66 +196,3 @@ impl GatewayManager { Ok(()) } } - -// TODO(jck) move this to core/grpc/lib -/// Runs gRPC server with core services. -#[instrument(skip_all)] -pub async fn run_grpc_server( - worker_state: Arc>, - pool: PgPool, - grpc_cert: Option, - grpc_key: Option, - failed_logins: Arc>, -) -> Result<(), anyhow::Error> { - // Build gRPC services - let server = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { - let identity = Identity::from_pem(cert, key); - Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? - } else { - Server::builder() - }; - - let router = build_grpc_service_router(server, pool, worker_state, failed_logins).await?; - - // Run gRPC server - let addr = SocketAddr::new( - server_config() - .grpc_bind_address - .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - server_config().grpc_port, - ); - debug!("Starting gRPC services"); - router.serve(addr).await?; - info!("gRPC server started on {addr}"); - Ok(()) -} - -// TODO(jck) move this to core/grpc/lib -pub(crate) async fn build_grpc_service_router( - server: Server, - pool: PgPool, - worker_state: Arc>, - failed_logins: Arc>, - // incompatible_components: Arc>, -) -> Result { - let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); - - let worker_service = WorkerServiceServer::with_interceptor( - WorkerServer::new(pool.clone(), worker_state), - JwtInterceptor::new(ClaimsType::YubiBridge), - ); - - let (health_reporter, health_service) = tonic_health::server::health_reporter(); - health_reporter - .set_serving::>() - .await; - - let router = server - .http2_keepalive_interval(Some(TEN_SECS)) - .tcp_keepalive(Some(TEN_SECS)) - .add_service(health_service) - .add_service(auth_service); - let router = router.add_service(worker_service); - - Ok(router) -} From 412429157ec525914f5fd0bb1354ade0b175fdcb Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 10:17:32 +0100 Subject: [PATCH 21/32] fix imports, cargo fmt --- crates/defguard/src/main.rs | 2 +- crates/defguard_core/src/grpc/auth.rs | 4 +-- crates/defguard_core/src/grpc/mod.rs | 26 ++++++++++---- crates/defguard_gateway_manager/src/certs.rs | 5 +-- .../defguard_gateway_manager/src/handler.rs | 4 +-- crates/defguard_gateway_manager/src/lib.rs | 36 +++++-------------- crates/defguard_proxy_manager/src/lib.rs | 2 +- 7 files changed, 34 insertions(+), 45 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 81b2591436..d69245c10b 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -180,7 +180,7 @@ async fn main() -> Result<(), anyhow::Error> { proxy_control_rx, ); - let mut gateway_manager = GatewayManager::new(); + let mut gateway_manager = GatewayManager::default(); // run services tokio::select! { diff --git a/crates/defguard_core/src/grpc/auth.rs b/crates/defguard_core/src/grpc/auth.rs index eafc0f165b..b68ce63556 100644 --- a/crates/defguard_core/src/grpc/auth.rs +++ b/crates/defguard_core/src/grpc/auth.rs @@ -9,9 +9,7 @@ use jsonwebtoken::errors::Error as JWTError; use sqlx::PgPool; use tonic::{Request, Response, Status}; -use crate::auth::failed_login::{ - FailedLoginMap, check_failed_logins, log_failed_login_attempt, -}; +use crate::auth::failed_login::{FailedLoginMap, check_failed_logins, log_failed_login_attempt}; pub(super) struct AuthServer { pool: PgPool, diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index e5686dedf7..ef8a277d61 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,23 +1,34 @@ -use std::{collections::hash_map::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, time::{Duration, Instant}}; +use std::{ + collections::hash_map::HashMap, + net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::{Arc, Mutex}, + time::{Duration, Instant}, +}; use crate::{ - auth::failed_login::FailedLoginMap, db::AppEvent, enterprise::{ + auth::failed_login::FailedLoginMap, + db::AppEvent, + enterprise::{ db::models::{ enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings}, openid_provider::OpenIdProvider, }, is_business_license_active, is_enterprise_license_active, - }, grpc::{auth::AuthServer, interceptor::JwtInterceptor, worker::WorkerServer} + }, + grpc::{auth::AuthServer, interceptor::JwtInterceptor, worker::WorkerServer}, }; use defguard_common::{ - auth::claims::ClaimsType, config::server_config, db::{ + auth::claims::ClaimsType, + config::server_config, + db::{ Id, models::{ Device, Settings, WireguardNetwork, device::{DeviceInfo, WireguardNetworkDevice}, wireguard::ServiceLocationMode, }, - }, types::UrlParseError + }, + types::UrlParseError, }; use reqwest::Url; use serde::Serialize; @@ -39,7 +50,10 @@ pub mod proto { } } -use defguard_proto::{auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, gateway::Peer, worker::worker_service_server::WorkerServiceServer}; +use defguard_proto::{ + auth::auth_service_server::AuthServiceServer, enterprise::firewall::FirewallConfig, + gateway::Peer, worker::worker_service_server::WorkerServiceServer, +}; use tonic::transport::{Identity, Server, ServerTlsConfig, server::Router}; // gRPC header for passing auth token from clients diff --git a/crates/defguard_gateway_manager/src/certs.rs b/crates/defguard_gateway_manager/src/certs.rs index 7bf31dcf8d..a1daf0b53a 100644 --- a/crates/defguard_gateway_manager/src/certs.rs +++ b/crates/defguard_gateway_manager/src/certs.rs @@ -16,10 +16,7 @@ where .collect() } -pub(super) async fn refresh_certs( - pool: &PgPool, - tx: &watch::Sender>>, -) { +pub(super) async fn refresh_certs(pool: &PgPool, tx: &watch::Sender>>) { match Gateway::all(pool).await { Ok(gateways) => { let certs = collect_certs( diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 072f9ddedc..0f894726a6 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -49,8 +49,6 @@ use defguard_core::{ use crate::{Client, TEN_SECS, error::GatewayError}; -type ShutdownReceiver = tokio::sync::oneshot::Receiver; - /// One instance per connected Gateway. pub(super) struct GatewayHandler { // Gateway server endpoint URL. @@ -255,7 +253,7 @@ impl GatewayHandler { clients .lock() .expect("GatewayHandler failed to lock clients") - .insert(self.gateway.id, client.clone()); + .insert(self.gateway.id, client.clone()); let (tx, rx) = mpsc::unbounded_channel(); let response = match client.bidi(UnboundedReceiverStream::new(rx)).await { Ok(response) => response, diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 4853495d5a..e5882445ad 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -1,37 +1,24 @@ -// FIXME: actua, Updatelly refactov errors instead +// FIXME: actually refactor errors instead #![allow(clippy::result_large_err)] use std::{ collections::HashMap, - net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, time::Duration, }; use defguard_common::{ - auth::claims::ClaimsType, - config::server_config, db::{ChangeNotification, Id, TriggerOperation, models::gateway::Gateway}, messages::peer_stats_update::PeerStatsUpdate, }; -use defguard_core::{ - auth::failed_login::FailedLoginMap, - grpc::{GatewayEvent, WorkerState, interceptor::JwtInterceptor, worker::WorkerServer}, -}; -use defguard_proto::{ - auth::auth_service_server::AuthServiceServer, gateway::gateway_client::GatewayClient, - worker::worker_service_server::WorkerServiceServer, -}; +use defguard_core::grpc::GatewayEvent; +use defguard_proto::gateway::gateway_client::GatewayClient; use defguard_version::client::ClientVersionInterceptor; use sqlx::{PgPool, postgres::PgListener}; use tokio::{ sync::{broadcast::Sender, mpsc::UnboundedSender}, task::{AbortHandle, JoinSet}, }; -use tonic::{ - Request, - service::interceptor::InterceptedService, - transport::{Channel, Identity, Server, ServerTlsConfig, server::Router}, -}; +use tonic::{Request, service::interceptor::InterceptedService, transport::Channel}; use crate::handler::GatewayHandler; @@ -52,17 +39,12 @@ const TEN_SECS: Duration = Duration::from_secs(10); type Client = GatewayClient>; +#[derive(Default)] pub struct GatewayManager { clients: Arc>>, } impl GatewayManager { - pub fn new() -> Self { - Self { - clients: Arc::default(), - } - } - /// Bi-directional gRPC stream for communication with Defguard Gateway. pub async fn run_grpc_gateway_stream( &mut self, @@ -169,7 +151,9 @@ impl GatewayManager { info!("Sent purge request to gateway {old}"); } } else { - warn!("Cannot find gRPC client for gateway {old}, won't send purge request"); + warn!( + "Cannot find gRPC client for gateway {old}, won't send purge request" + ); } // Kill the `GatewayHandler` and the connection. @@ -179,9 +163,7 @@ impl GatewayManager { ); abort_handle.abort(); } else { - warn!( - "Cannot find abort handle for gateway {old}" - ); + warn!("Cannot find abort handle for gateway {old}"); } } }, diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index 1b8b8009c9..e8bba7aefe 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -69,7 +69,7 @@ impl ProxyManager { // Prime the cache to avoid race with connection loop. refresh_certs(&self.pool, &certs_tx).await; let refresh_pool = self.pool.clone(); - // TODO(jck) monitor this thread somewhere + // TODO(jck) monitor this thread somewhere tokio::spawn(async move { loop { refresh_certs(&refresh_pool, &certs_tx).await; From 94a0eb79fde057a9e78bb9c9d5d1a96f86b57449 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 10:19:21 +0100 Subject: [PATCH 22/32] fix sync Mutex used across await issue --- crates/defguard_gateway_manager/src/lib.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index e5882445ad..7fe2b89b02 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -138,12 +138,14 @@ impl GatewayManager { }; // Send purge request to the gateway. - if let Some(mut client) = self - .clients - .lock() - .expect("Failed to lock GatewayManager::clients") - .remove(&old.id) - { + let maybe_client = { + self.clients + .lock() + .expect("Failed to lock GatewayManager::clients") + .remove(&old.id) + }; + + if let Some(mut client) = maybe_client { debug!("Sending purge request to gateway {old}"); if let Err(err) = client.purge(Request::new(())).await { error!("Error sending purge request to gateway {old}: {err}"); From acc399b7d875cd06e40088bb80ed0b70a70f3b52 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 10:34:31 +0100 Subject: [PATCH 23/32] GatewayManager::run --- crates/defguard/src/main.rs | 2 +- crates/defguard_gateway_manager/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index d69245c10b..b38f497619 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -185,7 +185,7 @@ async fn main() -> Result<(), anyhow::Error> { // run services tokio::select! { res = proxy_manager.run() => error!("ProxyManager returned early: {res:?}"), - res = gateway_manager.run_grpc_gateway_stream( + res = gateway_manager.run( pool.clone(), gateway_tx.clone(), peer_stats_tx, diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 7fe2b89b02..b100560eb1 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -46,7 +46,7 @@ pub struct GatewayManager { impl GatewayManager { /// Bi-directional gRPC stream for communication with Defguard Gateway. - pub async fn run_grpc_gateway_stream( + pub async fn run( &mut self, pool: PgPool, events_tx: Sender, From 418919e9831f51f5f87bd3c76a9d9498d4833930 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 10:35:40 +0100 Subject: [PATCH 24/32] update protos --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 8326216b71..faebcc5449 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 8326216b71edc64acf8fe091bb27d690c8d6885f +Subproject commit faebcc5449ae803e15cf5faf838c0c508401caf1 From 2f7a09fffaa1b4b05a4a72c915cd05375fb7c309 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 11:05:30 +0100 Subject: [PATCH 25/32] cargo update --- Cargo.lock | 186 ++++++++++++++++++++++++----------------------------- 1 file changed, 83 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e1d43fa2a..94cbfda631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,9 +492,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -618,9 +618,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -1685,7 +1685,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", ] @@ -2067,9 +2067,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -2082,9 +2082,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -2092,15 +2092,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -2120,15 +2120,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -2137,21 +2137,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -2161,7 +2161,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2241,7 +2240,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "libgit2-sys", "log", @@ -2267,7 +2266,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "ignore", "walkdir", ] @@ -2874,7 +2873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba781c43eb46c3bbf5bfda541139eed9a52b78d7c3aa74d516918885ecd63c40" dependencies = [ "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "num-bigint", "serde", "serde_json", @@ -2922,9 +2921,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -3007,9 +3006,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libgit2-sys" @@ -3035,7 +3034,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "redox_syscall 0.7.1", ] @@ -3257,17 +3256,17 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -3284,7 +3283,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -3464,7 +3463,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-foundation", ] @@ -3485,7 +3484,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "dispatch2", "objc2", ] @@ -3496,7 +3495,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -3529,7 +3528,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -3547,7 +3546,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "block2", "libc", "objc2", @@ -3560,7 +3559,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", ] @@ -3571,7 +3570,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -3583,7 +3582,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "block2", "objc2", "objc2-cloud-kit", @@ -3702,7 +3701,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -3722,12 +3721,6 @@ dependencies = [ "syn", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -4313,7 +4306,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "getopts", "memchr", "pulldown-cmark-escape", @@ -4496,7 +4489,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -4505,7 +4498,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -4736,7 +4729,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -4765,10 +4758,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -4880,24 +4873,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4906,9 +4886,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -5217,9 +5197,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", @@ -5405,7 +5385,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "bytes", "chrono", @@ -5449,7 +5429,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "chrono", "crc", @@ -5658,9 +5638,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -5693,7 +5673,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5971,18 +5951,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.7+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow", ] [[package]] name = "tonic" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" dependencies = [ "async-trait", "axum", @@ -6012,9 +5992,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27aac809edf60b741e2d7db6367214d078856b8a5bff0087e94ff330fb97b6fc" +checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e" dependencies = [ "prettyplease", "proc-macro2", @@ -6024,9 +6004,9 @@ dependencies = [ [[package]] name = "tonic-health" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbde2c702c4be12b9b2f6f7e6c824a84a7b7be177070cada8ee575a581af359" +checksum = "163e5ad9be2924d9cef75f02fcd44c1803a5af250f4ef7e085992270ac51fb9b" dependencies = [ "prost", "tokio", @@ -6037,9 +6017,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" dependencies = [ "bytes", "prost", @@ -6048,9 +6028,9 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4556786613791cfef4ed134aa670b61a85cfcacf71543ef33e8d801abae988f" +checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e" dependencies = [ "prettyplease", "proc-macro2", @@ -6099,7 +6079,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -6261,9 +6241,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -6403,11 +6383,11 @@ checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -6619,7 +6599,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap 2.13.0", "semver", @@ -7154,7 +7134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap 2.13.0", "log", "serde", From 21c4cc6f43deb9172dabb45ad8138792c233c63d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 13:56:18 +0100 Subject: [PATCH 26/32] update sqlx query data --- ...5351bf9bf9650acca042a12ad75a71fdee71.json} | 10 +++--- ...7004f869008e9dec303033ddbec8b0cee53f5.json | 35 ------------------- ...0fb8517656b090c5f93e017a1d4ffe552975.json} | 10 +++--- ...87d9bdb32e1938592cd31ba98aca73d69746.json} | 6 ++-- ...f997807b66e0b532da747b146513c34e15c5c.json | 16 ++++----- ...36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json | 16 ++++----- ...7ffa5343609a16229c4a86726cc5d5148402.json} | 6 ++-- 7 files changed, 32 insertions(+), 67 deletions(-) rename .sqlx/{query-b43694450d7abe3b93ea88fa7c95c38d3e2deb43d5ca3458724deb3ead69389a.json => query-161dca354966b0bc33849d2ef1245351bf9bf9650acca042a12ad75a71fdee71.json} (81%) delete mode 100644 .sqlx/query-2f614ae8a1c1c62c11ed2e9b11e7004f869008e9dec303033ddbec8b0cee53f5.json rename .sqlx/{query-ae3e3cef524f2a911808bf72e7c57b7f32e22adefc9b9185a9b3cd80c169a6e2.json => query-5eee502cace9cd11b8d12f7345660fb8517656b090c5f93e017a1d4ffe552975.json} (82%) rename .sqlx/{query-5af0fbf61295a5a23149c6248ea0b4a7afcbee1b63e34932c143f4697a0bc2cc.json => query-6bcef8e62bfbb66c4787a95bea3187d9bdb32e1938592cd31ba98aca73d69746.json} (64%) rename .sqlx/{query-ed3266f5f0d7b1613ad8745c9be953a7d9ef0becedf668c1d2225a1673003c77.json => query-f653c2bf5fc813e1358004e2dfb77ffa5343609a16229c4a86726cc5d5148402.json} (68%) diff --git a/.sqlx/query-b43694450d7abe3b93ea88fa7c95c38d3e2deb43d5ca3458724deb3ead69389a.json b/.sqlx/query-161dca354966b0bc33849d2ef1245351bf9bf9650acca042a12ad75a71fdee71.json similarity index 81% rename from .sqlx/query-b43694450d7abe3b93ea88fa7c95c38d3e2deb43d5ca3458724deb3ead69389a.json rename to .sqlx/query-161dca354966b0bc33849d2ef1245351bf9bf9650acca042a12ad75a71fdee71.json index 10051d513f..982c7ea725 100644 --- a/.sqlx/query-b43694450d7abe3b93ea88fa7c95c38d3e2deb43d5ca3458724deb3ead69389a.json +++ b/.sqlx/query-161dca354966b0bc33849d2ef1245351bf9bf9650acca042a12ad75a71fdee71.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"network_id\",\"url\",\"hostname\",\"connected_at\",\"disconnected_at\",\"has_certificate\",\"certificate_expiry\",\"version\",\"name\" FROM \"gateway\" WHERE id = $1", + "query": "SELECT id, \"network_id\",\"url\",\"hostname\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"name\" FROM \"gateway\" WHERE id = $1", "describe": { "columns": [ { @@ -35,8 +35,8 @@ }, { "ordinal": 6, - "name": "has_certificate", - "type_info": "Bool" + "name": "certificate", + "type_info": "Text" }, { "ordinal": 7, @@ -66,11 +66,11 @@ true, true, true, - false, + true, true, true, false ] }, - "hash": "b43694450d7abe3b93ea88fa7c95c38d3e2deb43d5ca3458724deb3ead69389a" + "hash": "161dca354966b0bc33849d2ef1245351bf9bf9650acca042a12ad75a71fdee71" } diff --git a/.sqlx/query-2f614ae8a1c1c62c11ed2e9b11e7004f869008e9dec303033ddbec8b0cee53f5.json b/.sqlx/query-2f614ae8a1c1c62c11ed2e9b11e7004f869008e9dec303033ddbec8b0cee53f5.json deleted file mode 100644 index 9af90ba77d..0000000000 --- a/.sqlx/query-2f614ae8a1c1c62c11ed2e9b11e7004f869008e9dec303033ddbec8b0cee53f5.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT d.wireguard_pubkey pubkey, preshared_key, ARRAY(\n SELECT host(ip)\n FROM unnest(wnd.wireguard_ips) AS ip\n ) \"allowed_ips!: Vec\" FROM wireguard_network_device wnd JOIN device d ON wnd.device_id = d.id JOIN \"user\" u ON d.user_id = u.id WHERE wireguard_network_id = $1 AND (is_authorized = true OR NOT $2) AND d.configured = true AND u.is_active = true ORDER BY d.id ASC", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "pubkey", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "preshared_key", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "allowed_ips!: Vec", - "type_info": "TextArray" - } - ], - "parameters": { - "Left": [ - "Int8", - "Bool" - ] - }, - "nullable": [ - false, - true, - null - ] - }, - "hash": "2f614ae8a1c1c62c11ed2e9b11e7004f869008e9dec303033ddbec8b0cee53f5" -} diff --git a/.sqlx/query-ae3e3cef524f2a911808bf72e7c57b7f32e22adefc9b9185a9b3cd80c169a6e2.json b/.sqlx/query-5eee502cace9cd11b8d12f7345660fb8517656b090c5f93e017a1d4ffe552975.json similarity index 82% rename from .sqlx/query-ae3e3cef524f2a911808bf72e7c57b7f32e22adefc9b9185a9b3cd80c169a6e2.json rename to .sqlx/query-5eee502cace9cd11b8d12f7345660fb8517656b090c5f93e017a1d4ffe552975.json index 77722daf50..8e155ee205 100644 --- a/.sqlx/query-ae3e3cef524f2a911808bf72e7c57b7f32e22adefc9b9185a9b3cd80c169a6e2.json +++ b/.sqlx/query-5eee502cace9cd11b8d12f7345660fb8517656b090c5f93e017a1d4ffe552975.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"network_id\",\"url\",\"hostname\",\"connected_at\",\"disconnected_at\",\"has_certificate\",\"certificate_expiry\",\"version\",\"name\" FROM \"gateway\"", + "query": "SELECT id, \"network_id\",\"url\",\"hostname\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"name\" FROM \"gateway\"", "describe": { "columns": [ { @@ -35,8 +35,8 @@ }, { "ordinal": 6, - "name": "has_certificate", - "type_info": "Bool" + "name": "certificate", + "type_info": "Text" }, { "ordinal": 7, @@ -64,11 +64,11 @@ true, true, true, - false, + true, true, true, false ] }, - "hash": "ae3e3cef524f2a911808bf72e7c57b7f32e22adefc9b9185a9b3cd80c169a6e2" + "hash": "5eee502cace9cd11b8d12f7345660fb8517656b090c5f93e017a1d4ffe552975" } diff --git a/.sqlx/query-5af0fbf61295a5a23149c6248ea0b4a7afcbee1b63e34932c143f4697a0bc2cc.json b/.sqlx/query-6bcef8e62bfbb66c4787a95bea3187d9bdb32e1938592cd31ba98aca73d69746.json similarity index 64% rename from .sqlx/query-5af0fbf61295a5a23149c6248ea0b4a7afcbee1b63e34932c143f4697a0bc2cc.json rename to .sqlx/query-6bcef8e62bfbb66c4787a95bea3187d9bdb32e1938592cd31ba98aca73d69746.json index 0ee0514bda..7a00633baf 100644 --- a/.sqlx/query-5af0fbf61295a5a23149c6248ea0b4a7afcbee1b63e34932c143f4697a0bc2cc.json +++ b/.sqlx/query-6bcef8e62bfbb66c4787a95bea3187d9bdb32e1938592cd31ba98aca73d69746.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO \"gateway\" (\"network_id\",\"url\",\"hostname\",\"connected_at\",\"disconnected_at\",\"has_certificate\",\"certificate_expiry\",\"version\",\"name\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING id", + "query": "INSERT INTO \"gateway\" (\"network_id\",\"url\",\"hostname\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"name\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING id", "describe": { "columns": [ { @@ -16,7 +16,7 @@ "Text", "Timestamp", "Timestamp", - "Bool", + "Text", "Timestamp", "Text", "Text" @@ -26,5 +26,5 @@ false ] }, - "hash": "5af0fbf61295a5a23149c6248ea0b4a7afcbee1b63e34932c143f4697a0bc2cc" + "hash": "6bcef8e62bfbb66c4787a95bea3187d9bdb32e1938592cd31ba98aca73d69746" } diff --git a/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json b/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json index a2e62691a2..47f529e92a 100644 --- a/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json +++ b/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json @@ -35,22 +35,22 @@ }, { "ordinal": 6, - "name": "has_certificate", - "type_info": "Bool" + "name": "certificate_expiry", + "type_info": "Timestamp" }, { "ordinal": 7, - "name": "certificate_expiry", - "type_info": "Timestamp" + "name": "version", + "type_info": "Text" }, { "ordinal": 8, - "name": "version", + "name": "name", "type_info": "Text" }, { "ordinal": 9, - "name": "name", + "name": "certificate", "type_info": "Text" } ], @@ -66,10 +66,10 @@ true, true, true, - false, true, true, - false + false, + true ] }, "hash": "d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c" diff --git a/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json b/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json index db1d8414a5..ce7ff19423 100644 --- a/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json +++ b/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json @@ -35,22 +35,22 @@ }, { "ordinal": 6, - "name": "has_certificate", - "type_info": "Bool" + "name": "certificate_expiry", + "type_info": "Timestamp" }, { "ordinal": 7, - "name": "certificate_expiry", - "type_info": "Timestamp" + "name": "version", + "type_info": "Text" }, { "ordinal": 8, - "name": "version", + "name": "name", "type_info": "Text" }, { "ordinal": 9, - "name": "name", + "name": "certificate", "type_info": "Text" } ], @@ -66,10 +66,10 @@ true, true, true, - false, true, true, - false + false, + true ] }, "hash": "e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a" diff --git a/.sqlx/query-ed3266f5f0d7b1613ad8745c9be953a7d9ef0becedf668c1d2225a1673003c77.json b/.sqlx/query-f653c2bf5fc813e1358004e2dfb77ffa5343609a16229c4a86726cc5d5148402.json similarity index 68% rename from .sqlx/query-ed3266f5f0d7b1613ad8745c9be953a7d9ef0becedf668c1d2225a1673003c77.json rename to .sqlx/query-f653c2bf5fc813e1358004e2dfb77ffa5343609a16229c4a86726cc5d5148402.json index 48849d4d38..d126e73ffc 100644 --- a/.sqlx/query-ed3266f5f0d7b1613ad8745c9be953a7d9ef0becedf668c1d2225a1673003c77.json +++ b/.sqlx/query-f653c2bf5fc813e1358004e2dfb77ffa5343609a16229c4a86726cc5d5148402.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"gateway\" SET \"network_id\" = $2,\"url\" = $3,\"hostname\" = $4,\"connected_at\" = $5,\"disconnected_at\" = $6,\"has_certificate\" = $7,\"certificate_expiry\" = $8,\"version\" = $9,\"name\" = $10 WHERE id = $1", + "query": "UPDATE \"gateway\" SET \"network_id\" = $2,\"url\" = $3,\"hostname\" = $4,\"connected_at\" = $5,\"disconnected_at\" = $6,\"certificate\" = $7,\"certificate_expiry\" = $8,\"version\" = $9,\"name\" = $10 WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -11,7 +11,7 @@ "Text", "Timestamp", "Timestamp", - "Bool", + "Text", "Timestamp", "Text", "Text" @@ -19,5 +19,5 @@ }, "nullable": [] }, - "hash": "ed3266f5f0d7b1613ad8745c9be953a7d9ef0becedf668c1d2225a1673003c77" + "hash": "f653c2bf5fc813e1358004e2dfb77ffa5343609a16229c4a86726cc5d5148402" } From 37d731565725318271108bec4ec708320a613069 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 14:28:39 +0100 Subject: [PATCH 27/32] fix clippy issues --- crates/defguard_gateway_manager/src/handler.rs | 9 +++++++-- crates/defguard_proxy_manager/src/lib.rs | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 0f894726a6..09e36e54bf 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -13,10 +13,13 @@ use defguard_common::{ VERSION, db::{ Id, - models::{Settings, WireguardNetwork, gateway::Gateway, wireguard::DEFAULT_WIREGUARD_MTU}, + models::{WireguardNetwork, gateway::Gateway, wireguard::DEFAULT_WIREGUARD_MTU}, }, messages::peer_stats_update::PeerStatsUpdate, }; +#[cfg(not(test))] +use defguard_common::db::models::Settings; +#[cfg(not(test))] use defguard_grpc_tls::{certs as tls_certs, connector::HttpsSchemeConnector}; use defguard_proto::{ enterprise::firewall::FirewallConfig, @@ -26,6 +29,7 @@ use defguard_proto::{ }, }; use defguard_version::client::ClientVersionInterceptor; +#[cfg(not(test))] use hyper_rustls::HttpsConnectorBuilder; use reqwest::Url; use semver::Version; @@ -213,10 +217,11 @@ impl GatewayHandler { &mut self, clients: Arc>>, ) -> Result<(), GatewayError> { + #[cfg(test)] + let _ = &self.certs_rx; let endpoint = self.endpoint()?; let uri = endpoint.uri().to_string(); loop { - // TODO(jck) how does proxy do this? can this be moved to lib? #[cfg(not(test))] let channel = { let settings = Settings::get_current_settings(); diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index e8bba7aefe..351f279001 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -69,7 +69,6 @@ impl ProxyManager { // Prime the cache to avoid race with connection loop. refresh_certs(&self.pool, &certs_tx).await; let refresh_pool = self.pool.clone(); - // TODO(jck) monitor this thread somewhere tokio::spawn(async move { loop { refresh_certs(&refresh_pool, &certs_tx).await; From b27dc23984d5b9594f0b8e7fc4633b2484239341 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 14:32:08 +0100 Subject: [PATCH 28/32] cargo fmt --- crates/defguard_gateway_manager/src/handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 09e36e54bf..5b301bf191 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -9,6 +9,8 @@ use std::{ }; use chrono::DateTime; +#[cfg(not(test))] +use defguard_common::db::models::Settings; use defguard_common::{ VERSION, db::{ @@ -18,8 +20,6 @@ use defguard_common::{ messages::peer_stats_update::PeerStatsUpdate, }; #[cfg(not(test))] -use defguard_common::db::models::Settings; -#[cfg(not(test))] use defguard_grpc_tls::{certs as tls_certs, connector::HttpsSchemeConnector}; use defguard_proto::{ enterprise::firewall::FirewallConfig, From 2465a9dc9e87439e6d73cb71babae68d61c4304e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 16 Feb 2026 14:53:25 +0100 Subject: [PATCH 29/32] fix cargo deny issues --- deny.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/deny.toml b/deny.toml index ae7303ded6..558e412364 100644 --- a/deny.toml +++ b/deny.toml @@ -143,6 +143,14 @@ exceptions = [ "AGPL-3.0-only", "AGPL-3.0-or-later", ], crate = "defguard_event_logger" }, + { allow = [ + "AGPL-3.0-only", + "AGPL-3.0-or-later", + ], crate = "defguard_gateway_manager" }, + { allow = [ + "AGPL-3.0-only", + "AGPL-3.0-or-later", + ], crate = "defguard_grpc_tls" }, { allow = [ "AGPL-3.0-only", "AGPL-3.0-or-later", From 1d8bc4eead250fbb728ddfb06d6c182116b963ac Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 17 Feb 2026 09:41:57 +0100 Subject: [PATCH 30/32] cargo machete, i32::cast_unsigned --- Cargo.lock | 8 -------- Cargo.toml | 2 +- crates/defguard_core/Cargo.toml | 2 -- crates/defguard_gateway_manager/Cargo.toml | 2 -- crates/defguard_gateway_manager/src/handler.rs | 18 +++++++++--------- crates/defguard_proxy_manager/Cargo.toml | 4 ---- tools/defguard_generator/Cargo.toml | 2 +- 7 files changed, 11 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12cbfa7fb9..5fa79b802c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1378,14 +1378,12 @@ dependencies = [ "claims", "defguard_certs", "defguard_common", - "defguard_grpc_tls", "defguard_mail", "defguard_proto", "defguard_version", "defguard_web_ui", "futures", "humantime", - "hyper-rustls", "ipnetwork", "jsonwebkey", "jsonwebtoken", @@ -1482,7 +1480,6 @@ dependencies = [ "defguard_version", "hyper-rustls", "hyper-util", - "jsonwebtoken", "reqwest", "semver", "serde_json", @@ -1491,7 +1488,6 @@ dependencies = [ "tokio", "tokio-stream", "tonic", - "tonic-health", "tower", "tracing", ] @@ -1573,11 +1569,9 @@ dependencies = [ "defguard_mail", "defguard_proto", "defguard_version", - "http", "hyper-rustls", "openidconnect", "reqwest", - "rustls", "secrecy", "semver", "sqlx", @@ -1585,9 +1579,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", - "tower-service", "tracing", - "x509-parser 0.18.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ef6f98c486..7910a0505e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ edition = "2024" license-file = "LICENSE.md" homepage = "https://defguard.net/" repository = "https://github.com/DefGuard/defguard" -rust-version = "1.85.1" +rust-version = "1.87.0" [workspace] members = ["crates/*", "tools/*"] diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index 3b900fb301..15ec63bf76 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -16,7 +16,6 @@ defguard_web_ui = { workspace = true } defguard_version = { workspace = true } model_derive = { workspace = true } defguard_certs = { workspace = true } -defguard_grpc_tls = { workspace = true } # external dependencies anyhow = { workspace = true } @@ -43,7 +42,6 @@ prost.workspace = true # match version used by sqlx rand = { workspace = true } reqwest = { workspace = true } -hyper-rustls = { version = "0.27", features = ["http2"] } rsa = { workspace = true } rust-ini = { workspace = true } secrecy = { workspace = true } diff --git a/crates/defguard_gateway_manager/Cargo.toml b/crates/defguard_gateway_manager/Cargo.toml index 711eed19ab..9afb53828e 100644 --- a/crates/defguard_gateway_manager/Cargo.toml +++ b/crates/defguard_gateway_manager/Cargo.toml @@ -18,7 +18,6 @@ defguard_version.workspace = true anyhow.workspace = true chrono.workspace = true hyper-rustls.workspace = true -jsonwebtoken.workspace = true reqwest.workspace = true semver.workspace = true serde_json.workspace = true @@ -27,7 +26,6 @@ thiserror.workspace = true tokio.workspace = true tokio-stream.workspace = true tonic.workspace = true -tonic-health.workspace = true tower.workspace = true tracing.workspace = true diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 5b301bf191..2f08c769fc 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -75,8 +75,8 @@ impl GatewayHandler { ) -> Result { let url = Url::from_str(&gateway.url).map_err(|err| { GatewayError::EndpointError(format!( - "Failed to parse Gateway URL {}: {}", - &gateway.url, err + "Failed to parse Gateway URL {}: {err}", + &gateway.url )) })?; @@ -479,7 +479,7 @@ impl GatewayUpdatesHandler { .collect(), preshared_key: network_info.preshared_key.clone(), keepalive_interval: Some( - self.network.keepalive_interval as u32, + self.network.keepalive_interval.cast_unsigned(), ), }, 0, @@ -514,7 +514,7 @@ impl GatewayUpdatesHandler { .collect(), preshared_key: network_info.preshared_key.clone(), keepalive_interval: Some( - self.network.keepalive_interval as u32, + self.network.keepalive_interval.cast_unsigned(), ), }, 1, @@ -584,7 +584,7 @@ impl GatewayUpdatesHandler { .map(IpAddr::to_string) .collect(), preshared_key: network_device.preshared_key.clone(), - keepalive_interval: Some(self.network.keepalive_interval as u32), + keepalive_interval: Some(self.network.keepalive_interval.cast_unsigned()), }, 0, ) @@ -620,10 +620,10 @@ impl GatewayUpdatesHandler { name: network.name.clone(), prvkey: network.prvkey.clone(), addresses: network.address.iter().map(ToString::to_string).collect(), - port: network.port as u32, + port: network.port.cast_unsigned(), peers, firewall_config, - mtu: network.mtu as u32, + mtu: network.mtu.cast_unsigned(), fwmark: network.fwmark as u32, })), })), @@ -657,7 +657,7 @@ impl GatewayUpdatesHandler { port: 0, peers: Vec::new(), firewall_config: None, - mtu: DEFAULT_WIREGUARD_MTU as u32, + mtu: DEFAULT_WIREGUARD_MTU.cast_unsigned(), fwmark: 0, })), })), @@ -804,7 +804,7 @@ fn gen_config( ) -> Configuration { Configuration { name: network.name.clone(), - port: network.port as u32, + port: network.port.cast_unsigned(), prvkey: network.prvkey.clone(), addresses: network.address.iter().map(ToString::to_string).collect(), peers, diff --git a/crates/defguard_proxy_manager/Cargo.toml b/crates/defguard_proxy_manager/Cargo.toml index 3d0ae0e9d4..2d27616711 100644 --- a/crates/defguard_proxy_manager/Cargo.toml +++ b/crates/defguard_proxy_manager/Cargo.toml @@ -21,16 +21,12 @@ axum.workspace = true axum-extra.workspace = true semver.workspace = true secrecy.workspace = true -http.workspace = true hyper-rustls.workspace = true openidconnect.workspace = true reqwest.workspace = true -rustls.workspace = true sqlx.workspace = true thiserror.workspace = true tokio.workspace = true tokio-stream.workspace = true tonic.workspace = true -tower-service.workspace = true tracing.workspace = true -x509-parser.workspace = true diff --git a/tools/defguard_generator/Cargo.toml b/tools/defguard_generator/Cargo.toml index 6c37f70ba9..06255adc2b 100644 --- a/tools/defguard_generator/Cargo.toml +++ b/tools/defguard_generator/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" license-file = "../../LICENSE.md" homepage = "https://defguard.net/" repository = "https://github.com/DefGuard/defguard" -rust-version = "1.85.1" +rust-version = "1.87.0" [dependencies] defguard_common = { workspace = true } From e4477d5ddb6702c054ec0fd4267d4860ab90a92a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 17 Feb 2026 09:44:15 +0100 Subject: [PATCH 31/32] more i32::cast_unsigned conversions --- crates/defguard_core/src/location_management/allowed_peers.rs | 2 +- crates/defguard_gateway_manager/src/handler.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/defguard_core/src/location_management/allowed_peers.rs b/crates/defguard_core/src/location_management/allowed_peers.rs index 31a0575158..44bd0b14d9 100644 --- a/crates/defguard_core/src/location_management/allowed_peers.rs +++ b/crates/defguard_core/src/location_management/allowed_peers.rs @@ -62,7 +62,7 @@ where } else { None }, - keepalive_interval: Some(location.keepalive_interval as u32), + keepalive_interval: Some(location.keepalive_interval.cast_unsigned()), }) .collect(); diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 2f08c769fc..1f8a3193fb 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -809,7 +809,7 @@ fn gen_config( addresses: network.address.iter().map(ToString::to_string).collect(), peers, firewall_config: maybe_firewall_config, - mtu: network.mtu as u32, + mtu: network.mtu.cast_unsigned(), fwmark: network.fwmark as u32, } } From 0cc8182fc95969534bc8bd33c309ea8e9b79faa6 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 17 Feb 2026 11:23:58 +0100 Subject: [PATCH 32/32] cargo fmt --- crates/defguard_gateway_manager/src/handler.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/defguard_gateway_manager/src/handler.rs b/crates/defguard_gateway_manager/src/handler.rs index 1f8a3193fb..3dc908d0ee 100644 --- a/crates/defguard_gateway_manager/src/handler.rs +++ b/crates/defguard_gateway_manager/src/handler.rs @@ -584,7 +584,9 @@ impl GatewayUpdatesHandler { .map(IpAddr::to_string) .collect(), preshared_key: network_device.preshared_key.clone(), - keepalive_interval: Some(self.network.keepalive_interval.cast_unsigned()), + keepalive_interval: Some( + self.network.keepalive_interval.cast_unsigned(), + ), }, 0, )