From 6ec14c2b6e1268fd342466aae92190dbf3fb5c11 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 27 Feb 2026 11:21:05 +0100 Subject: [PATCH 01/20] migration with config options --- ...260227091211_[2.0.0]_settings_in_db.down.sql | 17 +++++++++++++++++ ...20260227091211_[2.0.0]_settings_in_db.up.sql | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 migrations/20260227091211_[2.0.0]_settings_in_db.down.sql create mode 100644 migrations/20260227091211_[2.0.0]_settings_in_db.up.sql diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql new file mode 100644 index 0000000000..3fa342d2ff --- /dev/null +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql @@ -0,0 +1,17 @@ +ALTER TABLE settings + DROP COLUMN auth_cookie_timeout, + DROP COLUMN secret_key, + DROP COLUMN grpc_ca, + DROP COLUMN grpc_cert, + DROP COLUMN grpc_key, + DROP COLUMN openid_signing_key, + DROP COLUMN webauthn_rp_id, + DROP COLUMN grpc_url, + DROP COLUMN disable_stats_purge, + DROP COLUMN stats_purge_frequency, + DROP COLUMN stats_purge_threshold, + DROP COLUMN enrollment_token_timeout, + DROP COLUMN password_reset_token_timeout, + DROP COLUMN enrollment_session_timeout, + DROP COLUMN password_reset_session_timeout, + DROP COLUMN proxy_grpc_ca; diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql new file mode 100644 index 0000000000..18e02af571 --- /dev/null +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql @@ -0,0 +1,16 @@ +ALTER TABLE settings + ADD COLUMN auth_cookie_timeout interval DEFAULT interval '7 days', + ADD COLUMN secret_key text DEFAULT 'UNSET', -- TODO(jck) + ADD COLUMN grpc_ca text, + ADD COLUMN grpc_cert text, + ADD COLUMN grpc_key text, ADD COLUMN openid_signing_key text, + ADD COLUMN webauthn_rp_id text, + ADD COLUMN grpc_url text DEFAULT 'http://localhost:50055', + ADD COLUMN disable_stats_purge boolean DEFAULT false, + ADD COLUMN stats_purge_frequency interval DEFAULT interval '24 hours', + ADD COLUMN stats_purge_threshold interval DEFAULT interval '30 days', + ADD COLUMN enrollment_token_timeout interval DEFAULT interval '24 hours', + ADD COLUMN password_reset_token_timeout interval DEFAULT interval '24 hours', + ADD COLUMN enrollment_session_timeout interval DEFAULT interval '10 minutes', + ADD COLUMN password_reset_session_timeout interval DEFAULT interval '10 minutes', + ADD COLUMN proxy_grpc_ca text; From d7b81f9acc827ba178b3ff69640f6f1ccfc2468a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 27 Feb 2026 12:53:54 +0100 Subject: [PATCH 02/20] Settings::update_from_config --- crates/defguard/src/main.rs | 4 ++ .../defguard_common/src/db/models/settings.rs | 70 ++++++++++++++++++- ...0227091211_[2.0.0]_settings_in_db.down.sql | 14 ++-- ...260227091211_[2.0.0]_settings_in_db.up.sql | 23 +++--- 4 files changed, 91 insertions(+), 20 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 2b397163eb..c73db9dc8e 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -112,6 +112,10 @@ async fn main() -> Result<(), anyhow::Error> { settings = Settings::get_current_settings(); } + if wizard_flags.migration_wizard_needed { + + } + config.initialize_post_settings(); SERVER_CONFIG diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 8daaf158a3..a4f7a3854e 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, fmt, time::Duration}; use chrono::NaiveDateTime; +use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, PgPool, Type, query, query_as}; use struct_patch::Patch; @@ -10,7 +11,7 @@ use url::Url; use utoipa::ToSchema; use uuid::Uuid; -use crate::{db::Id, global_value, secret::SecretStringWrapper}; +use crate::{config::DefGuardConfig, db::Id, global_value, secret::SecretStringWrapper}; global_value!(SETTINGS, Option, None, set_settings, get_settings); @@ -175,6 +176,22 @@ pub struct Settings { pub public_proxy_url: String, pub initial_setup_step: InitialSetupStep, pub default_admin_id: Option, + // 1.6 config options + pub auth_cookie_timeout_days: i32, + pub secret_key: String, + pub grpc_ca: Option, + pub grpc_cert: Option, + pub grpc_key: Option, + pub webauthn_rp_id: Option, + pub grpc_url: String, + pub disable_stats_purge: bool, + pub stats_purge_frequency_hours: i32, + pub stats_purge_threshold_days: i32, + pub enrollment_token_timeout_hours: i32, + pub password_reset_token_timeout_hours: i32, + pub enrollment_session_timeout_minutes: i32, + pub password_reset_session_timeout_minutes: i32, + pub proxy_grpc_ca: Option, } // Implement manually to avoid exposing the license key. @@ -297,7 +314,22 @@ impl Settings { ca_key_der, ca_cert_der, ca_expiry, initial_setup_completed, defguard_url, \ default_admin_group_name, authentication_period_days, mfa_code_timeout_seconds, \ public_proxy_url, initial_setup_step \"initial_setup_step: InitialSetupStep\", \ - default_admin_id \ + default_admin_id, \ + auth_cookie_timeout_days, \ + secret_key, \ + grpc_ca, \ + grpc_cert, \ + grpc_key, \ + webauthn_rp_id, \ + grpc_url, \ + disable_stats_purge, \ + stats_purge_frequency_hours, \ + stats_purge_threshold_days, \ + enrollment_token_timeout_hours, \ + password_reset_token_timeout_hours, \ + enrollment_session_timeout_minutes, \ + password_reset_session_timeout_minutes, \ + proxy_grpc_ca \ FROM \"settings\" WHERE id = 1", ) .fetch_optional(executor) @@ -529,6 +561,40 @@ impl Settings { pub fn proxy_public_url(&self) -> Result { Url::parse(&self.public_proxy_url) } + + pub async fn update_from_config<'e, E>( + &mut self, + executor: E, + config: &DefGuardConfig, + ) -> Result<(), sqlx::Error> + where + E: PgExecutor<'e>, + { + let minute = 60; + let hour = minute * 60; + let day = hour * 24; + self.auth_cookie_timeout_days = (config.auth_cookie_timeout.as_secs() / day) as i32; + self.secret_key = config.secret_key.expose_secret().to_string(); + self.grpc_ca = config.grpc_ca.clone(); + self.grpc_cert = config.grpc_cert.clone(); + self.grpc_key = config.grpc_key.clone(); + self.webauthn_rp_id = config.webauthn_rp_id.clone(); + self.grpc_url = config.grpc_url.to_string(); + self.disable_stats_purge = config.disable_stats_purge; + self.stats_purge_frequency_hours = (config.stats_purge_frequency.as_secs() / hour) as i32; + self.stats_purge_threshold_days = (config.stats_purge_threshold.as_secs() / day) as i32; + self.enrollment_token_timeout_hours = + (config.enrollment_token_timeout.as_secs() / hour) as i32; + self.password_reset_token_timeout_hours = + (config.password_reset_token_timeout.as_secs() / hour) as i32; + self.enrollment_session_timeout_minutes = + (config.enrollment_session_timeout.as_secs() / minute) as i32; + self.password_reset_session_timeout_minutes = + (config.password_reset_session_timeout.as_secs() / minute) as i32; + self.proxy_grpc_ca = config.proxy_grpc_ca.clone(); + + self.save(executor).await + } } #[derive(Serialize)] diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql index 3fa342d2ff..84621f4ac9 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql @@ -1,5 +1,5 @@ ALTER TABLE settings - DROP COLUMN auth_cookie_timeout, + DROP COLUMN auth_cookie_timeout_days, DROP COLUMN secret_key, DROP COLUMN grpc_ca, DROP COLUMN grpc_cert, @@ -8,10 +8,10 @@ ALTER TABLE settings DROP COLUMN webauthn_rp_id, DROP COLUMN grpc_url, DROP COLUMN disable_stats_purge, - DROP COLUMN stats_purge_frequency, - DROP COLUMN stats_purge_threshold, - DROP COLUMN enrollment_token_timeout, - DROP COLUMN password_reset_token_timeout, - DROP COLUMN enrollment_session_timeout, - DROP COLUMN password_reset_session_timeout, + DROP COLUMN stats_purge_frequency_hours, + DROP COLUMN stats_purge_threshold_days, + DROP COLUMN enrollment_token_timeout_hours, + DROP COLUMN password_reset_token_timeout_hours, + DROP COLUMN enrollment_session_timeout_minutes, + DROP COLUMN password_reset_session_timeout_minutes, DROP COLUMN proxy_grpc_ca; diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql index 18e02af571..90527a257c 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql @@ -1,16 +1,17 @@ ALTER TABLE settings - ADD COLUMN auth_cookie_timeout interval DEFAULT interval '7 days', - ADD COLUMN secret_key text DEFAULT 'UNSET', -- TODO(jck) + ADD COLUMN auth_cookie_timeout_days int4 NOT NULL DEFAULT 7, + ADD COLUMN secret_key text NOT NULL DEFAULT 'UNSET', -- TODO(jck) ADD COLUMN grpc_ca text, ADD COLUMN grpc_cert text, - ADD COLUMN grpc_key text, ADD COLUMN openid_signing_key text, + ADD COLUMN grpc_key text, + ADD COLUMN openid_signing_key text, ADD COLUMN webauthn_rp_id text, - ADD COLUMN grpc_url text DEFAULT 'http://localhost:50055', - ADD COLUMN disable_stats_purge boolean DEFAULT false, - ADD COLUMN stats_purge_frequency interval DEFAULT interval '24 hours', - ADD COLUMN stats_purge_threshold interval DEFAULT interval '30 days', - ADD COLUMN enrollment_token_timeout interval DEFAULT interval '24 hours', - ADD COLUMN password_reset_token_timeout interval DEFAULT interval '24 hours', - ADD COLUMN enrollment_session_timeout interval DEFAULT interval '10 minutes', - ADD COLUMN password_reset_session_timeout interval DEFAULT interval '10 minutes', + ADD COLUMN grpc_url text NOT NULL DEFAULT 'http://localhost:50055', + ADD COLUMN disable_stats_purge boolean NOT NULL DEFAULT false, + ADD COLUMN stats_purge_frequency_hours int4 NOT NULL DEFAULT 24, + ADD COLUMN stats_purge_threshold_days int4 NOT NULL DEFAULT 30, + ADD COLUMN enrollment_token_timeout_hours int4 NOT NULL DEFAULT 24, + ADD COLUMN password_reset_token_timeout_hours int4 NOT NULL DEFAULT 24, + ADD COLUMN enrollment_session_timeout_minutes int4 NOT NULL DEFAULT 10, + ADD COLUMN password_reset_session_timeout_minutes int4 NOT NULL DEFAULT 10, ADD COLUMN proxy_grpc_ca text; From cbf44403c6bad4a302959c6fad8cd18a61b8b94a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 27 Feb 2026 13:41:50 +0100 Subject: [PATCH 03/20] fix Settings::save function --- crates/defguard/src/main.rs | 8 +++-- .../defguard_common/src/db/models/settings.rs | 32 ++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index c73db9dc8e..24d66b7d51 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -112,9 +112,11 @@ async fn main() -> Result<(), anyhow::Error> { settings = Settings::get_current_settings(); } - if wizard_flags.migration_wizard_needed { - - } + if wizard_flags.migration_wizard_needed { + info!("Migration from 1.6: copying configuration options to DB"); + settings.update_from_config(&pool, &config).await?; + info!("Migration from 1.6: copyied configuration options to DB"); + } config.initialize_post_settings(); diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index a4f7a3854e..d54de30d3e 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -417,7 +417,22 @@ impl Settings { mfa_code_timeout_seconds = $56, \ public_proxy_url = $57, \ initial_setup_step = $58, \ - default_admin_id = $59 \ + default_admin_id = $59, \ + auth_cookie_timeout_days = $60, \ + secret_key = $61, \ + grpc_ca = $62, \ + grpc_cert = $63, \ + grpc_key = $64, \ + webauthn_rp_id = $65, \ + grpc_url = $66, \ + disable_stats_purge = $67, \ + stats_purge_frequency_hours = $68, \ + stats_purge_threshold_days = $69, \ + enrollment_token_timeout_hours = $70, \ + password_reset_token_timeout_hours = $71, \ + enrollment_session_timeout_minutes = $72, \ + password_reset_session_timeout_minutes = $73, \ + proxy_grpc_ca = $74 \ WHERE id = 1", self.openid_enabled, self.wireguard_enabled, @@ -478,6 +493,21 @@ impl Settings { self.public_proxy_url, &self.initial_setup_step as &InitialSetupStep, self.default_admin_id, + self.auth_cookie_timeout_days, + self.secret_key, + self.grpc_ca, + self.grpc_cert, + self.grpc_key, + self.webauthn_rp_id, + self.grpc_url, + self.disable_stats_purge, + self.stats_purge_frequency_hours, + self.stats_purge_threshold_days, + self.enrollment_token_timeout_hours, + self.password_reset_token_timeout_hours, + self.enrollment_session_timeout_minutes, + self.password_reset_session_timeout_minutes, + self.proxy_grpc_ca, ) .execute(executor) .await?; From 6317617405727503b0b53ace42da735f843cec7b Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 07:44:32 +0100 Subject: [PATCH 04/20] replace config variables occurences with settings, part 1 --- crates/defguard_common/src/config.rs | 230 +++++++++--------- .../defguard_common/src/db/models/settings.rs | 98 ++++---- crates/defguard_core/src/appstate.rs | 6 +- .../src/enterprise/handlers/openid_login.rs | 2 +- crates/defguard_core/src/handlers/auth.rs | 2 +- .../src/handlers/network_devices.rs | 5 +- crates/defguard_core/src/handlers/user.rs | 13 +- crates/defguard_proxy_manager/src/handler.rs | 8 +- .../src/servers/enrollment.rs | 7 +- .../src/servers/password_reset.rs | 11 +- 10 files changed, 185 insertions(+), 197 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 6db70311aa..ce5ab4559c 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -38,13 +38,13 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_LOG_FILE")] pub log_file: Option, - #[arg(long, env = "DEFGUARD_AUTH_COOKIE_TIMEOUT", default_value = "7d")] - #[serde(skip_serializing)] - pub auth_cookie_timeout: Duration, + // #[arg(long, env = "DEFGUARD_AUTH_COOKIE_TIMEOUT", default_value = "7d")] + // #[serde(skip_serializing)] + // pub auth_cookie_timeout: Duration, - #[arg(long, env = "DEFGUARD_SECRET_KEY")] - #[serde(skip_serializing)] - pub secret_key: SecretString, + // #[arg(long, env = "DEFGUARD_SECRET_KEY")] + // #[serde(skip_serializing)] + // pub secret_key: SecretString, #[arg(long, env = "DEFGUARD_DB_HOST", default_value = "localhost")] pub database_host: String, @@ -68,13 +68,13 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_GRPC_PORT", default_value_t = 50055)] pub grpc_port: u16, - // Certificate authority (CA), certificate, and key for gRPC communication over HTTPS. - #[arg(long, env = "DEFGUARD_GRPC_CA")] - pub grpc_ca: Option, - #[arg(long, env = "DEFGUARD_GRPC_CERT")] - pub grpc_cert: Option, - #[arg(long, env = "DEFGUARD_GRPC_KEY")] - pub grpc_key: Option, + // // Certificate authority (CA), certificate, and key for gRPC communication over HTTPS. + // #[arg(long, env = "DEFGUARD_GRPC_CA")] + // pub grpc_ca: Option, + // #[arg(long, env = "DEFGUARD_GRPC_CERT")] + // pub grpc_cert: Option, + // #[arg(long, env = "DEFGUARD_GRPC_KEY")] + // pub grpc_key: Option, #[arg( long, @@ -90,34 +90,34 @@ pub struct DefGuardConfig { #[serde(skip_serializing)] pub openid_signing_key: Option, - // relying party id and relying party origin for WebAuthn - #[arg(long, env = "DEFGUARD_WEBAUTHN_RP_ID")] - pub webauthn_rp_id: Option, + // // relying party id and relying party origin for WebAuthn + // #[arg(long, env = "DEFGUARD_WEBAUTHN_RP_ID")] + // pub webauthn_rp_id: Option, #[arg(long, env = "DEFGUARD_URL", value_parser = Url::parse, default_value = "http://localhost:8000")] #[deprecated(since = "2.0.0", note = "Use Settings.defguard_url instead")] pub url: Url, - #[arg(long, env = "DEFGUARD_GRPC_URL", value_parser = Url::parse, default_value = "http://localhost:50055")] - pub grpc_url: Url, + // #[arg(long, env = "DEFGUARD_GRPC_URL", value_parser = Url::parse, default_value = "http://localhost:50055")] + // pub grpc_url: Url, - #[arg(long, env = "DEFGUARD_DISABLE_STATS_PURGE")] - pub disable_stats_purge: bool, + // #[arg(long, env = "DEFGUARD_DISABLE_STATS_PURGE")] + // pub disable_stats_purge: bool, - #[arg(long, env = "DEFGUARD_STATS_PURGE_FREQUENCY", default_value = "24h")] - #[serde(skip_serializing)] - pub stats_purge_frequency: Duration, + // #[arg(long, env = "DEFGUARD_STATS_PURGE_FREQUENCY", default_value = "24h")] + // #[serde(skip_serializing)] + // pub stats_purge_frequency: Duration, - #[arg(long, env = "DEFGUARD_STATS_PURGE_THRESHOLD", default_value = "30d")] - #[serde(skip_serializing)] - pub stats_purge_threshold: Duration, + // #[arg(long, env = "DEFGUARD_STATS_PURGE_THRESHOLD", default_value = "30d")] + // #[serde(skip_serializing)] + // pub stats_purge_threshold: Duration, #[arg(long, env = "DEFGUARD_ENROLLMENT_URL", value_parser = Url::parse, default_value = "http://localhost:8080")] #[deprecated(since = "2.0.0", note = "Use Settings.public_proxy_url instead")] pub enrollment_url: Url, - #[arg(long, env = "DEFGUARD_ENROLLMENT_TOKEN_TIMEOUT", default_value = "24h")] - #[serde(skip_serializing)] - pub enrollment_token_timeout: Duration, + // #[arg(long, env = "DEFGUARD_ENROLLMENT_TOKEN_TIMEOUT", default_value = "24h")] + // #[serde(skip_serializing)] + // pub enrollment_token_timeout: Duration, #[arg(long, env = "DEFGUARD_MFA_CODE_TIMEOUT", default_value = "60s")] #[serde(skip_serializing)] @@ -132,29 +132,29 @@ pub struct DefGuardConfig { #[deprecated(since = "2.0.0", note = "Use Settings.default_authentication instead")] pub session_timeout: Duration, - #[arg( - long, - env = "DEFGUARD_PASSWORD_RESET_TOKEN_TIMEOUT", - default_value = "24h" - )] - #[serde(skip_serializing)] - pub password_reset_token_timeout: Duration, - - #[arg( - long, - env = "DEFGUARD_ENROLLMENT_SESSION_TIMEOUT", - default_value = "10m" - )] - #[serde(skip_serializing)] - pub enrollment_session_timeout: Duration, - - #[arg( - long, - env = "DEFGUARD_PASSWORD_RESET_SESSION_TIMEOUT", - default_value = "10m" - )] - #[serde(skip_serializing)] - pub password_reset_session_timeout: Duration, + // #[arg( + // long, + // env = "DEFGUARD_PASSWORD_RESET_TOKEN_TIMEOUT", + // default_value = "24h" + // )] + // #[serde(skip_serializing)] + // pub password_reset_token_timeout: Duration, + + // #[arg( + // long, + // env = "DEFGUARD_ENROLLMENT_SESSION_TIMEOUT", + // default_value = "10m" + // )] + // #[serde(skip_serializing)] + // pub enrollment_session_timeout: Duration, + + // #[arg( + // long, + // env = "DEFGUARD_PASSWORD_RESET_SESSION_TIMEOUT", + // default_value = "10m" + // )] + // #[serde(skip_serializing)] + // pub password_reset_session_timeout: Duration, #[arg(long, env = "DEFGUARD_COOKIE_DOMAIN")] pub cookie_domain: Option, @@ -162,9 +162,9 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_COOKIE_INSECURE")] pub cookie_insecure: bool, - // path to certificate `.pem` file used if connecting to proxy over HTTPS - #[arg(long, env = "DEFGUARD_PROXY_GRPC_CA")] - pub proxy_grpc_ca: Option, + // // path to certificate `.pem` file used if connecting to proxy over HTTPS + // #[arg(long, env = "DEFGUARD_PROXY_GRPC_CA")] + // pub proxy_grpc_ca: Option, #[command(subcommand)] #[serde(skip_serializing)] @@ -227,7 +227,8 @@ impl DefGuardConfig { #[must_use] pub fn new() -> Self { let config = Self::parse(); - config.validate_secret_key(); + // TODO(jck) + // config.validate_secret_key(); config } @@ -240,19 +241,20 @@ impl DefGuardConfig { /// Initialize values that depend on Settings. pub fn initialize_post_settings(&mut self) { let url = Settings::url().expect("Unable to parse Defguard URL."); - self.initialize_rp_id(&url); + // TODO(jck) + // self.initialize_rp_id(&url); self.initialize_cookie_domain(&url); } - fn initialize_rp_id(&mut self, url: &Url) { - if self.webauthn_rp_id.is_none() { - self.webauthn_rp_id = Some( - url.domain() - .expect("Unable to get domain for server URL.") - .to_string(), - ); - } - } + // fn initialize_rp_id(&mut self, url: &Url) { + // if self.webauthn_rp_id.is_none() { + // self.webauthn_rp_id = Some( + // url.domain() + // .expect("Unable to get domain for server URL.") + // .to_string(), + // ); + // } + // } fn initialize_cookie_domain(&mut self, url: &Url) { if self.cookie_domain.is_none() { @@ -264,19 +266,19 @@ impl DefGuardConfig { } } - fn validate_secret_key(&self) { - let secret_key = self.secret_key.expose_secret(); - assert!( - secret_key.trim().len() == secret_key.len(), - "SECRET_KEY cannot have leading and trailing space", - ); + // fn validate_secret_key(&self) { + // let secret_key = self.secret_key.expose_secret(); + // assert!( + // secret_key.trim().len() == secret_key.len(), + // "SECRET_KEY cannot have leading and trailing space", + // ); - assert!( - secret_key.len() >= 64, - "SECRET_KEY must be at least 64 characters long, provided value has {} characters", - secret_key.len() - ); - } + // assert!( + // secret_key.len() >= 64, + // "SECRET_KEY must be at least 64 characters long, provided value has {} characters", + // secret_key.len() + // ); + // } /// Try PKCS#1 and PKCS#8 PEM formats. fn parse_openid_key(path: &str) -> Result { @@ -298,24 +300,24 @@ impl DefGuardConfig { } } - /// Provide [`ClientTlsConfig`] from paths to cerfiticate, key, and cerfiticate authority (CA). - pub fn grpc_client_tls_config(&self) -> Result, io::Error> { - if self.grpc_ca.is_none() && (self.grpc_cert.is_none() || self.grpc_key.is_none()) { - return Ok(None); - } - let mut tls = ClientTlsConfig::new(); - if let (Some(cert_path), Some(key_path)) = (&self.grpc_cert, &self.grpc_key) { - let cert = read_to_string(cert_path)?; - let key = read_to_string(key_path)?; - tls = tls.identity(Identity::from_pem(cert, key)); - } - if let Some(ca_path) = &self.grpc_ca { - let ca = read_to_string(ca_path)?; - tls = tls.ca_certificate(Certificate::from_pem(ca)); - } - - Ok(Some(tls)) - } + // /// Provide [`ClientTlsConfig`] from paths to cerfiticate, key, and cerfiticate authority (CA). + // pub fn grpc_client_tls_config(&self) -> Result, io::Error> { + // if self.grpc_ca.is_none() && (self.grpc_cert.is_none() || self.grpc_key.is_none()) { + // return Ok(None); + // } + // let mut tls = ClientTlsConfig::new(); + // if let (Some(cert_path), Some(key_path)) = (&self.grpc_cert, &self.grpc_key) { + // let cert = read_to_string(cert_path)?; + // let key = read_to_string(key_path)?; + // tls = tls.identity(Identity::from_pem(cert, key)); + // } + // if let Some(ca_path) = &self.grpc_ca { + // let ca = read_to_string(ca_path)?; + // tls = tls.ca_certificate(Certificate::from_pem(ca)); + // } + + // Ok(Some(tls)) + // } } impl Default for DefGuardConfig { @@ -336,29 +338,29 @@ mod tests { DefGuardConfig::command().debug_assert(); } - #[test] - fn test_generate_rp_id() { - unsafe { - env::remove_var("DEFGUARD_WEBAUTHN_RP_ID"); - } + // #[test] + // fn test_generate_rp_id() { + // unsafe { + // env::remove_var("DEFGUARD_WEBAUTHN_RP_ID"); + // } - let url = Url::parse("https://defguard.example.com").unwrap(); - let mut config = DefGuardConfig::new(); - config.initialize_rp_id(&url); + // let url = Url::parse("https://defguard.example.com").unwrap(); + // let mut config = DefGuardConfig::new(); + // config.initialize_rp_id(&url); - assert_eq!( - config.webauthn_rp_id, - Some("defguard.example.com".to_string()) - ); + // assert_eq!( + // config.webauthn_rp_id, + // Some("defguard.example.com".to_string()) + // ); - unsafe { - env::set_var("DEFGUARD_WEBAUTHN_RP_ID", "example.com"); - } + // unsafe { + // env::set_var("DEFGUARD_WEBAUTHN_RP_ID", "example.com"); + // } - let config = DefGuardConfig::new(); + // let config = DefGuardConfig::new(); - assert_eq!(config.webauthn_rp_id, Some("example.com".to_string())); - } + // assert_eq!(config.webauthn_rp_id, Some("example.com".to_string())); + // } #[test] fn test_generate_cookie_domain() { diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index d54de30d3e..4660dabd3d 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -177,7 +177,6 @@ pub struct Settings { pub initial_setup_step: InitialSetupStep, pub default_admin_id: Option, // 1.6 config options - pub auth_cookie_timeout_days: i32, pub secret_key: String, pub grpc_ca: Option, pub grpc_cert: Option, @@ -185,6 +184,7 @@ pub struct Settings { pub webauthn_rp_id: Option, pub grpc_url: String, pub disable_stats_purge: bool, + pub auth_cookie_timeout_days: i32, pub stats_purge_frequency_hours: i32, pub stats_purge_threshold_days: i32, pub enrollment_token_timeout_hours: i32, @@ -314,22 +314,12 @@ impl Settings { ca_key_der, ca_cert_der, ca_expiry, initial_setup_completed, defguard_url, \ default_admin_group_name, authentication_period_days, mfa_code_timeout_seconds, \ public_proxy_url, initial_setup_step \"initial_setup_step: InitialSetupStep\", \ - default_admin_id, \ - auth_cookie_timeout_days, \ - secret_key, \ - grpc_ca, \ - grpc_cert, \ - grpc_key, \ - webauthn_rp_id, \ - grpc_url, \ - disable_stats_purge, \ - stats_purge_frequency_hours, \ - stats_purge_threshold_days, \ - enrollment_token_timeout_hours, \ - password_reset_token_timeout_hours, \ - enrollment_session_timeout_minutes, \ - password_reset_session_timeout_minutes, \ - proxy_grpc_ca \ + default_admin_id, auth_cookie_timeout_days, secret_key, grpc_ca, grpc_cert, \ + grpc_key, webauthn_rp_id, grpc_url, disable_stats_purge, \ + stats_purge_frequency_hours, stats_purge_threshold_days, \ + enrollment_token_timeout_hours, password_reset_token_timeout_hours, \ + enrollment_session_timeout_minutes, password_reset_session_timeout_minutes, \ + proxy_grpc_ca \ FROM \"settings\" WHERE id = 1", ) .fetch_optional(executor) @@ -418,21 +408,21 @@ impl Settings { public_proxy_url = $57, \ initial_setup_step = $58, \ default_admin_id = $59, \ - auth_cookie_timeout_days = $60, \ - secret_key = $61, \ - grpc_ca = $62, \ - grpc_cert = $63, \ - grpc_key = $64, \ - webauthn_rp_id = $65, \ - grpc_url = $66, \ - disable_stats_purge = $67, \ - stats_purge_frequency_hours = $68, \ - stats_purge_threshold_days = $69, \ - enrollment_token_timeout_hours = $70, \ - password_reset_token_timeout_hours = $71, \ - enrollment_session_timeout_minutes = $72, \ - password_reset_session_timeout_minutes = $73, \ - proxy_grpc_ca = $74 \ + auth_cookie_timeout_days = $60, \ + secret_key = $61, \ + grpc_ca = $62, \ + grpc_cert = $63, \ + grpc_key = $64, \ + webauthn_rp_id = $65, \ + grpc_url = $66, \ + disable_stats_purge = $67, \ + stats_purge_frequency_hours = $68, \ + stats_purge_threshold_days = $69, \ + enrollment_token_timeout_hours = $70, \ + password_reset_token_timeout_hours = $71, \ + enrollment_session_timeout_minutes = $72, \ + password_reset_session_timeout_minutes = $73, \ + proxy_grpc_ca = $74 \ WHERE id = 1", self.openid_enabled, self.wireguard_enabled, @@ -600,28 +590,28 @@ impl Settings { where E: PgExecutor<'e>, { - let minute = 60; - let hour = minute * 60; - let day = hour * 24; - self.auth_cookie_timeout_days = (config.auth_cookie_timeout.as_secs() / day) as i32; - self.secret_key = config.secret_key.expose_secret().to_string(); - self.grpc_ca = config.grpc_ca.clone(); - self.grpc_cert = config.grpc_cert.clone(); - self.grpc_key = config.grpc_key.clone(); - self.webauthn_rp_id = config.webauthn_rp_id.clone(); - self.grpc_url = config.grpc_url.to_string(); - self.disable_stats_purge = config.disable_stats_purge; - self.stats_purge_frequency_hours = (config.stats_purge_frequency.as_secs() / hour) as i32; - self.stats_purge_threshold_days = (config.stats_purge_threshold.as_secs() / day) as i32; - self.enrollment_token_timeout_hours = - (config.enrollment_token_timeout.as_secs() / hour) as i32; - self.password_reset_token_timeout_hours = - (config.password_reset_token_timeout.as_secs() / hour) as i32; - self.enrollment_session_timeout_minutes = - (config.enrollment_session_timeout.as_secs() / minute) as i32; - self.password_reset_session_timeout_minutes = - (config.password_reset_session_timeout.as_secs() / minute) as i32; - self.proxy_grpc_ca = config.proxy_grpc_ca.clone(); + // let minute = 60; + // let hour = minute * 60; + // let day = hour * 24; + // self.auth_cookie_timeout_days = (config.auth_cookie_timeout.as_secs() / day) as i32; + // self.secret_key = config.secret_key.expose_secret().to_string(); + // self.grpc_ca = config.grpc_ca.clone(); + // self.grpc_cert = config.grpc_cert.clone(); + // self.grpc_key = config.grpc_key.clone(); + // self.webauthn_rp_id = config.webauthn_rp_id.clone(); + // self.grpc_url = config.grpc_url.to_string(); + // self.disable_stats_purge = config.disable_stats_purge; + // self.stats_purge_frequency_hours = (config.stats_purge_frequency.as_secs() / hour) as i32; + // self.stats_purge_threshold_days = (config.stats_purge_threshold.as_secs() / day) as i32; + // self.enrollment_token_timeout_hours = + // (config.enrollment_token_timeout.as_secs() / hour) as i32; + // self.password_reset_token_timeout_hours = + // (config.password_reset_token_timeout.as_secs() / hour) as i32; + // self.enrollment_session_timeout_minutes = + // (config.enrollment_session_timeout.as_secs() / minute) as i32; + // self.password_reset_session_timeout_minutes = + // (config.password_reset_session_timeout.as_secs() / minute) as i32; + // self.proxy_grpc_ca = config.proxy_grpc_ca.clone(); self.save(executor).await } diff --git a/crates/defguard_core/src/appstate.rs b/crates/defguard_core/src/appstate.rs index 2db868f0af..6aac5c567f 100644 --- a/crates/defguard_core/src/appstate.rs +++ b/crates/defguard_core/src/appstate.rs @@ -120,10 +120,10 @@ impl AppState { ) -> Self { spawn(Self::handle_triggers(pool.clone(), rx)); - let config = server_config(); let url = Settings::url().expect("Invalid Defguard URL configuration"); + let settings = Settings::get_current_settings(); let webauthn_builder = WebauthnBuilder::new( - config + settings .webauthn_rp_id .as_ref() .expect("Webauth RP ID configuration is required"), @@ -136,7 +136,7 @@ impl AppState { .expect("Invalid WebAuthn configuration"), ); - let key = Key::from(config.secret_key.expose_secret().as_bytes()); + let key = Key::from(settings.secret_key.as_bytes()); Self { pool, diff --git a/crates/defguard_core/src/enterprise/handlers/openid_login.rs b/crates/defguard_core/src/enterprise/handlers/openid_login.rs index 3c2e41dc29..9b0e5fb4af 100644 --- a/crates/defguard_core/src/enterprise/handlers/openid_login.rs +++ b/crates/defguard_core/src/enterprise/handlers/openid_login.rs @@ -605,7 +605,7 @@ pub(crate) async fn auth_callback( let (session, user_info, mfa_info) = create_session(&appstate.pool, insecure_ip, user_agent.as_str(), &mut user).await?; - let max_age = Duration::seconds(config.auth_cookie_timeout.as_secs() as i64); + let max_age = Duration::days(settings.auth_cookie_timeout_days as i64); let cookie_domain = config .cookie_domain .as_ref() diff --git a/crates/defguard_core/src/handlers/auth.rs b/crates/defguard_core/src/handlers/auth.rs index dc6cb84942..0732dbe6e3 100644 --- a/crates/defguard_core/src/handlers/auth.rs +++ b/crates/defguard_core/src/handlers/auth.rs @@ -237,7 +237,7 @@ pub(crate) async fn authenticate( let (session, user_info, mfa_info) = create_session(&appstate.pool, insecure_ip, user_agent.as_str(), &mut user).await?; - let max_age = Duration::seconds(server_config().auth_cookie_timeout.as_secs() as i64); + let max_age = Duration::days(settings.auth_cookie_timeout_days as i64); let config = server_config(); let cookie_domain = config .cookie_domain diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index 10a3fa94e8..a18b4816d1 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -455,7 +455,7 @@ pub(crate) async fn start_network_device_setup( &mut transaction, &user, None, - config.enrollment_token_timeout.as_secs(), + (settings.enrollment_token_timeout_hours * 3600) as u64, settings.proxy_public_url()?.clone(), false, Some(result.device.id), @@ -514,14 +514,13 @@ pub(crate) async fn start_network_device_setup_for_device( user which added the device not found" )) })?; - let config = server_config(); let settings = Settings::get_current_settings(); let configuration_token = start_desktop_configuration( &user, &mut transaction, &user, None, - config.enrollment_token_timeout.as_secs(), + (settings.enrollment_token_timeout_hours * 3600) as u64, settings.proxy_public_url()?, false, Some(device.id), diff --git a/crates/defguard_core/src/handlers/user.rs b/crates/defguard_core/src/handlers/user.rs index bbbb8bfb93..15e9115bd2 100644 --- a/crates/defguard_core/src/handlers/user.rs +++ b/crates/defguard_core/src/handlers/user.rs @@ -465,7 +465,7 @@ pub async fn start_enrollment( let mut transaction = appstate.pool.begin().await?; // try to parse token expiration time if provided - let config = server_config(); + let settings = Settings::get_current_settings(); let token_expiration_time_seconds = match data.token_expiration_time { Some(time) => parse_duration(&time) .map_err(|err| { @@ -473,10 +473,9 @@ pub async fn start_enrollment( WebError::BadRequest("Failed to parse token expiration time".to_owned()) })? .as_secs(), - None => config.enrollment_token_timeout.as_secs(), + None => (settings.enrollment_token_timeout_hours * 3600) as u64, }; - let settings: Settings = Settings::get_current_settings(); let public_proxy_url = settings.proxy_public_url()?; let enrollment_token = start_user_enrollment( @@ -580,7 +579,6 @@ pub async fn start_remote_desktop_configuration( "Generating a new desktop activation token by {}.", session.user.username ); - let config = server_config(); let settings = Settings::get_current_settings(); let public_proxy_url = settings.proxy_public_url()?; let desktop_configuration_token = start_desktop_configuration( @@ -588,7 +586,7 @@ pub async fn start_remote_desktop_configuration( &mut transaction, &session.user, Some(email), - config.enrollment_token_timeout.as_secs(), + (settings.enrollment_token_timeout_hours * 3600) as u64, public_proxy_url.clone(), data.send_enrollment_notification, None, @@ -1105,16 +1103,15 @@ pub async fn reset_password( Token::delete_unused_user_password_reset_tokens(&mut transaction, user.id).await?; - let config = server_config(); + let settings = Settings::get_current_settings(); let enrollment = Token::new( user.id, Some(session.user.id), Some(user.email.clone()), - config.password_reset_token_timeout.as_secs(), + (settings.password_reset_token_timeout_hours * 3600) as u64, Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), ); enrollment.save(&mut *transaction).await?; - let settings = Settings::get_current_settings(); let public_proxy_url = settings.proxy_public_url()?; let result = Mail::new( diff --git a/crates/defguard_proxy_manager/src/handler.rs b/crates/defguard_proxy_manager/src/handler.rs index 06b24aba06..294d549ed5 100644 --- a/crates/defguard_proxy_manager/src/handler.rs +++ b/crates/defguard_proxy_manager/src/handler.rs @@ -271,8 +271,7 @@ impl ProxyHandler { let mut resp_stream = response.into_inner(); // Derive proxy cookie key from core secret to avoid transmitting it over gRPC. - let config = server_config(); - let proxy_cookie_key = Key::derive_from(config.secret_key.expose_secret().as_bytes()); + let proxy_cookie_key = Key::derive_from(settings.secret_key.as_bytes()); // Send initial info with private cookies key. let initial_info = InitialInfo { @@ -722,12 +721,12 @@ impl ProxyHandler { as a result of proxy OpenID auth callback.", user.username ); - let config = server_config(); + let settings = Settings::get_current_settings(); let desktop_configuration = Token::new( user.id, Some(user.id), Some(user.email), - config.enrollment_token_timeout.as_secs(), + (settings.enrollment_token_timeout_hours * 3600) as u64, Some(ENROLLMENT_TOKEN_TYPE.to_string()), ); debug!("Saving a new desktop configuration token..."); @@ -736,7 +735,6 @@ impl ProxyHandler { "Saved desktop configuration token. Responding to \ proxy with the token." ); - let settings = Settings::get_current_settings(); let public_proxy_url = settings.proxy_public_url()?; Some(core_response::Payload::AuthCallback( diff --git a/crates/defguard_proxy_manager/src/servers/enrollment.rs b/crates/defguard_proxy_manager/src/servers/enrollment.rs index bd1bbef18e..e0961c100b 100644 --- a/crates/defguard_proxy_manager/src/servers/enrollment.rs +++ b/crates/defguard_proxy_manager/src/servers/enrollment.rs @@ -87,7 +87,8 @@ impl EnrollmentServer { ); return Err(Status::permission_denied("invalid token")); } - if enrollment.is_session_valid(server_config().enrollment_session_timeout.as_secs()) { + let settings = Settings::get_current_settings(); + if enrollment.is_session_valid((settings.enrollment_session_timeout_minutes * 60) as u64) { info!("Enrollment session validated: {enrollment:?}"); Ok(enrollment) } else { @@ -164,10 +165,11 @@ impl EnrollmentServer { "Validating enrollment token and starting session for user {}({:?})", user.username, user.id, ); + let settings = Settings::get_current_settings(); let session_deadline = enrollment .start_session( &mut transaction, - server_config().enrollment_session_timeout.as_secs(), + (settings.enrollment_session_timeout_minutes * 60) as u64, ) .await?; info!( @@ -179,7 +181,6 @@ impl EnrollmentServer { "Retrieving settings for enrollment of user {}({:?}).", user.username, user.id ); - let settings = Settings::get_current_settings(); debug!("Settings: {settings:?}"); debug!( diff --git a/crates/defguard_proxy_manager/src/servers/password_reset.rs b/crates/defguard_proxy_manager/src/servers/password_reset.rs index b6d94f2532..89a4a16f35 100644 --- a/crates/defguard_proxy_manager/src/servers/password_reset.rs +++ b/crates/defguard_proxy_manager/src/servers/password_reset.rs @@ -59,7 +59,8 @@ impl PasswordResetServer { return Err(Status::permission_denied("invalid token")); } - if enrollment.is_session_valid(server_config().enrollment_session_timeout.as_secs()) { + let settings = Settings::get_current_settings(); + if enrollment.is_session_valid((settings.enrollment_session_timeout_minutes * 60) as u64) { info!("Password reset session validated: {enrollment:?}.",); Ok(enrollment) } else { @@ -88,7 +89,6 @@ impl PasswordResetServer { request: PasswordResetInitializeRequest, req_device_info: Option, ) -> Result<(), Status> { - let config = server_config(); debug!("Starting password reset request"); let ip_address; @@ -133,11 +133,12 @@ impl PasswordResetServer { Token::delete_unused_user_password_reset_tokens(&mut transaction, user.id).await?; + let settings = Settings::get_current_settings(); let enrollment = Token::new( user.id, None, Some(email.clone()), - config.password_reset_token_timeout.as_secs(), + (settings.password_reset_token_timeout_hours * 3600) as u64, Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), ); enrollment.save(&mut *transaction).await?; @@ -147,7 +148,6 @@ impl PasswordResetServer { Status::internal("unexpected error") })?; - let settings = Settings::get_current_settings(); let public_proxy_url = settings.proxy_public_url().map_err(|err| { error!("Failed to get public proxy URL: {err}"); Status::internal("unexpected error") @@ -212,10 +212,11 @@ impl PasswordResetServer { Status::internal("unexpected error") })?; + let settings = Settings::get_current_settings(); let session_deadline = enrollment .start_session( &mut transaction, - server_config().password_reset_session_timeout.as_secs(), + (settings.password_reset_session_timeout_minutes * 60) as u64, ) .await?; From 87f35a5e42590d93c8617e0bd6043358dd7ccf72 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 07:55:32 +0100 Subject: [PATCH 05/20] replace config variables occurences with settings, part 2 --- crates/defguard/src/main.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 24d66b7d51..ecfd82c035 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -150,11 +150,11 @@ async fn main() -> Result<(), anyhow::Error> { } // read grpc TLS cert and key - let grpc_cert = config + let grpc_cert = settings .grpc_cert .as_ref() .and_then(|path| read_to_string(path).ok()); - let grpc_key = config + let grpc_key = settings .grpc_key .as_ref() .and_then(|path| read_to_string(path).ok()); @@ -216,9 +216,13 @@ async fn main() -> Result<(), anyhow::Error> { ) => error!("Web server returned early: {res:?}"), res = run_periodic_stats_purge( pool.clone(), - config.stats_purge_frequency.into(), - config.stats_purge_threshold.into() - ), if !config.disable_stats_purge => + std::time::Duration::from_secs( + (settings.stats_purge_frequency_hours as u64) * 3600, + ), + std::time::Duration::from_secs( + (settings.stats_purge_threshold_days as u64) * 24 * 3600, + ) + ), if !settings.disable_stats_purge => error!("Periodic stats purge task returned early: {res:?}"), res = run_periodic_license_check(&pool) => error!("Periodic license check task returned early: {res:?}"), From 1cf605f809c2324a61337c1188ebbf4c089e235f Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 08:33:43 +0100 Subject: [PATCH 06/20] implement Settings time variables accessors with unit conversion --- crates/defguard/src/main.rs | 8 +-- .../defguard_common/src/db/models/settings.rs | 52 +++++++++++++++---- .../src/enterprise/handlers/openid_login.rs | 2 +- crates/defguard_core/src/handlers/auth.rs | 2 +- .../src/handlers/network_devices.rs | 4 +- crates/defguard_core/src/handlers/user.rs | 6 +-- crates/defguard_proxy_manager/src/handler.rs | 2 +- .../src/servers/enrollment.rs | 4 +- .../src/servers/password_reset.rs | 6 +-- 9 files changed, 58 insertions(+), 28 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index ecfd82c035..19ca6c58ec 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -216,12 +216,8 @@ async fn main() -> Result<(), anyhow::Error> { ) => error!("Web server returned early: {res:?}"), res = run_periodic_stats_purge( pool.clone(), - std::time::Duration::from_secs( - (settings.stats_purge_frequency_hours as u64) * 3600, - ), - std::time::Duration::from_secs( - (settings.stats_purge_threshold_days as u64) * 24 * 3600, - ) + settings.stats_purge_frequency(), + settings.stats_purge_threshold() ), if !settings.disable_stats_purge => error!("Periodic stats purge task returned early: {res:?}"), res = run_periodic_license_check(&pool) => diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 4660dabd3d..891f50e7e9 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, fmt, time::Duration}; use chrono::NaiveDateTime; -use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, PgPool, Type, query, query_as}; use struct_patch::Patch; @@ -184,13 +183,13 @@ pub struct Settings { pub webauthn_rp_id: Option, pub grpc_url: String, pub disable_stats_purge: bool, - pub auth_cookie_timeout_days: i32, - pub stats_purge_frequency_hours: i32, - pub stats_purge_threshold_days: i32, - pub enrollment_token_timeout_hours: i32, - pub password_reset_token_timeout_hours: i32, - pub enrollment_session_timeout_minutes: i32, - pub password_reset_session_timeout_minutes: i32, + auth_cookie_timeout_days: i32, + stats_purge_frequency_hours: i32, + stats_purge_threshold_days: i32, + enrollment_token_timeout_hours: i32, + password_reset_token_timeout_hours: i32, + enrollment_session_timeout_minutes: i32, + password_reset_session_timeout_minutes: i32, pub proxy_grpc_ca: Option, } @@ -578,6 +577,41 @@ impl Settings { Duration::from_secs(self.authentication_period_days as u64 * 24 * 3600) } + #[must_use] + pub fn auth_cookie_timeout(&self) -> Duration { + Duration::from_secs(self.auth_cookie_timeout_days as u64 * 24 * 3600) + } + + #[must_use] + pub fn stats_purge_frequency(&self) -> Duration { + Duration::from_secs(self.stats_purge_frequency_hours as u64 * 3600) + } + + #[must_use] + pub fn stats_purge_threshold(&self) -> Duration { + Duration::from_secs(self.stats_purge_threshold_days as u64 * 24 * 3600) + } + + #[must_use] + pub fn enrollment_token_timeout(&self) -> Duration { + Duration::from_secs(self.enrollment_token_timeout_hours as u64 * 3600) + } + + #[must_use] + pub fn password_reset_token_timeout(&self) -> Duration { + Duration::from_secs(self.password_reset_token_timeout_hours as u64 * 3600) + } + + #[must_use] + pub fn enrollment_session_timeout(&self) -> Duration { + Duration::from_secs(self.enrollment_session_timeout_minutes as u64 * 60) + } + + #[must_use] + pub fn password_reset_session_timeout(&self) -> Duration { + Duration::from_secs(self.password_reset_session_timeout_minutes as u64 * 60) + } + pub fn proxy_public_url(&self) -> Result { Url::parse(&self.public_proxy_url) } @@ -585,7 +619,7 @@ impl Settings { pub async fn update_from_config<'e, E>( &mut self, executor: E, - config: &DefGuardConfig, + _config: &DefGuardConfig, ) -> Result<(), sqlx::Error> where E: PgExecutor<'e>, diff --git a/crates/defguard_core/src/enterprise/handlers/openid_login.rs b/crates/defguard_core/src/enterprise/handlers/openid_login.rs index 9b0e5fb4af..69811d35c1 100644 --- a/crates/defguard_core/src/enterprise/handlers/openid_login.rs +++ b/crates/defguard_core/src/enterprise/handlers/openid_login.rs @@ -605,7 +605,7 @@ pub(crate) async fn auth_callback( let (session, user_info, mfa_info) = create_session(&appstate.pool, insecure_ip, user_agent.as_str(), &mut user).await?; - let max_age = Duration::days(settings.auth_cookie_timeout_days as i64); + let max_age = Duration::seconds(settings.auth_cookie_timeout().as_secs() as i64); let cookie_domain = config .cookie_domain .as_ref() diff --git a/crates/defguard_core/src/handlers/auth.rs b/crates/defguard_core/src/handlers/auth.rs index 0732dbe6e3..05c956eefc 100644 --- a/crates/defguard_core/src/handlers/auth.rs +++ b/crates/defguard_core/src/handlers/auth.rs @@ -237,7 +237,7 @@ pub(crate) async fn authenticate( let (session, user_info, mfa_info) = create_session(&appstate.pool, insecure_ip, user_agent.as_str(), &mut user).await?; - let max_age = Duration::days(settings.auth_cookie_timeout_days as i64); + let max_age = Duration::seconds(settings.auth_cookie_timeout().as_secs() as i64); let config = server_config(); let cookie_domain = config .cookie_domain diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index a18b4816d1..67fbe52f56 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -455,7 +455,7 @@ pub(crate) async fn start_network_device_setup( &mut transaction, &user, None, - (settings.enrollment_token_timeout_hours * 3600) as u64, + settings.enrollment_token_timeout().as_secs(), settings.proxy_public_url()?.clone(), false, Some(result.device.id), @@ -520,7 +520,7 @@ pub(crate) async fn start_network_device_setup_for_device( &mut transaction, &user, None, - (settings.enrollment_token_timeout_hours * 3600) as u64, + settings.enrollment_token_timeout().as_secs(), settings.proxy_public_url()?, false, Some(device.id), diff --git a/crates/defguard_core/src/handlers/user.rs b/crates/defguard_core/src/handlers/user.rs index 15e9115bd2..809f50b2ff 100644 --- a/crates/defguard_core/src/handlers/user.rs +++ b/crates/defguard_core/src/handlers/user.rs @@ -473,7 +473,7 @@ pub async fn start_enrollment( WebError::BadRequest("Failed to parse token expiration time".to_owned()) })? .as_secs(), - None => (settings.enrollment_token_timeout_hours * 3600) as u64, + None => settings.enrollment_token_timeout().as_secs(), }; let public_proxy_url = settings.proxy_public_url()?; @@ -586,7 +586,7 @@ pub async fn start_remote_desktop_configuration( &mut transaction, &session.user, Some(email), - (settings.enrollment_token_timeout_hours * 3600) as u64, + settings.enrollment_token_timeout().as_secs(), public_proxy_url.clone(), data.send_enrollment_notification, None, @@ -1108,7 +1108,7 @@ pub async fn reset_password( user.id, Some(session.user.id), Some(user.email.clone()), - (settings.password_reset_token_timeout_hours * 3600) as u64, + settings.password_reset_token_timeout().as_secs(), Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), ); enrollment.save(&mut *transaction).await?; diff --git a/crates/defguard_proxy_manager/src/handler.rs b/crates/defguard_proxy_manager/src/handler.rs index 294d549ed5..5d92876bb6 100644 --- a/crates/defguard_proxy_manager/src/handler.rs +++ b/crates/defguard_proxy_manager/src/handler.rs @@ -726,7 +726,7 @@ impl ProxyHandler { user.id, Some(user.id), Some(user.email), - (settings.enrollment_token_timeout_hours * 3600) as u64, + settings.enrollment_token_timeout().as_secs(), Some(ENROLLMENT_TOKEN_TYPE.to_string()), ); debug!("Saving a new desktop configuration token..."); diff --git a/crates/defguard_proxy_manager/src/servers/enrollment.rs b/crates/defguard_proxy_manager/src/servers/enrollment.rs index e0961c100b..7e20d8a413 100644 --- a/crates/defguard_proxy_manager/src/servers/enrollment.rs +++ b/crates/defguard_proxy_manager/src/servers/enrollment.rs @@ -88,7 +88,7 @@ impl EnrollmentServer { return Err(Status::permission_denied("invalid token")); } let settings = Settings::get_current_settings(); - if enrollment.is_session_valid((settings.enrollment_session_timeout_minutes * 60) as u64) { + if enrollment.is_session_valid(settings.enrollment_session_timeout().as_secs()) { info!("Enrollment session validated: {enrollment:?}"); Ok(enrollment) } else { @@ -169,7 +169,7 @@ impl EnrollmentServer { let session_deadline = enrollment .start_session( &mut transaction, - (settings.enrollment_session_timeout_minutes * 60) as u64, + settings.enrollment_session_timeout().as_secs(), ) .await?; info!( diff --git a/crates/defguard_proxy_manager/src/servers/password_reset.rs b/crates/defguard_proxy_manager/src/servers/password_reset.rs index 89a4a16f35..c319b44f17 100644 --- a/crates/defguard_proxy_manager/src/servers/password_reset.rs +++ b/crates/defguard_proxy_manager/src/servers/password_reset.rs @@ -60,7 +60,7 @@ impl PasswordResetServer { } let settings = Settings::get_current_settings(); - if enrollment.is_session_valid((settings.enrollment_session_timeout_minutes * 60) as u64) { + if enrollment.is_session_valid(settings.enrollment_session_timeout().as_secs()) { info!("Password reset session validated: {enrollment:?}.",); Ok(enrollment) } else { @@ -138,7 +138,7 @@ impl PasswordResetServer { user.id, None, Some(email.clone()), - (settings.password_reset_token_timeout_hours * 3600) as u64, + settings.password_reset_token_timeout().as_secs(), Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), ); enrollment.save(&mut *transaction).await?; @@ -216,7 +216,7 @@ impl PasswordResetServer { let session_deadline = enrollment .start_session( &mut transaction, - (settings.password_reset_session_timeout_minutes * 60) as u64, + settings.password_reset_session_timeout().as_secs(), ) .await?; From caef1312a3197dd9162b3397f34da49a22fb37ee Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 09:02:29 +0100 Subject: [PATCH 07/20] make old DefguardConfig fields optional and deprecated --- crates/defguard_common/src/config.rs | 133 ++++++++++-------- .../defguard_common/src/db/models/settings.rs | 76 +++++++--- 2 files changed, 127 insertions(+), 82 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index ce5ab4559c..d18ed01385 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -3,13 +3,13 @@ use std::{fs::read_to_string, io, net::IpAddr, sync::OnceLock}; use clap::{Args, Parser, Subcommand}; use humantime::Duration; use ipnetwork::IpNetwork; -use openidconnect::{JsonWebKeyId, core::CoreRsaPrivateSigningKey}; +use openidconnect::{core::CoreRsaPrivateSigningKey, JsonWebKeyId}; use reqwest::Url; use rsa::{ - RsaPrivateKey, pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey}, pkcs8::{DecodePrivateKey, LineEnding}, traits::PublicKeyParts, + RsaPrivateKey, }; use secrecy::{ExposeSecret, SecretString}; use serde::Serialize; @@ -38,13 +38,15 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_LOG_FILE")] pub log_file: Option, - // #[arg(long, env = "DEFGUARD_AUTH_COOKIE_TIMEOUT", default_value = "7d")] - // #[serde(skip_serializing)] - // pub auth_cookie_timeout: Duration, + #[arg(long, env = "DEFGUARD_AUTH_COOKIE_TIMEOUT")] + #[serde(skip_serializing)] + #[deprecated(since = "2.0.0", note = "Use Settings.auth_cookie_timeout instead")] + pub auth_cookie_timeout: Option, - // #[arg(long, env = "DEFGUARD_SECRET_KEY")] - // #[serde(skip_serializing)] - // pub secret_key: SecretString, + #[arg(long, env = "DEFGUARD_SECRET_KEY")] + #[serde(skip_serializing)] + #[deprecated(since = "2.0.0", note = "Use Settings.secret_key instead")] + pub secret_key: Option, #[arg(long, env = "DEFGUARD_DB_HOST", default_value = "localhost")] pub database_host: String, @@ -68,13 +70,16 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_GRPC_PORT", default_value_t = 50055)] pub grpc_port: u16, - // // Certificate authority (CA), certificate, and key for gRPC communication over HTTPS. - // #[arg(long, env = "DEFGUARD_GRPC_CA")] - // pub grpc_ca: Option, - // #[arg(long, env = "DEFGUARD_GRPC_CERT")] - // pub grpc_cert: Option, - // #[arg(long, env = "DEFGUARD_GRPC_KEY")] - // pub grpc_key: Option, + // Certificate authority (CA), certificate, and key for gRPC communication over HTTPS. + #[arg(long, env = "DEFGUARD_GRPC_CA")] + #[deprecated(since = "2.0.0", note = "Use Settings.grpc_ca instead")] + pub grpc_ca: Option, + #[arg(long, env = "DEFGUARD_GRPC_CERT")] + #[deprecated(since = "2.0.0", note = "Use Settings.grpc_cert instead")] + pub grpc_cert: Option, + #[arg(long, env = "DEFGUARD_GRPC_KEY")] + #[deprecated(since = "2.0.0", note = "Use Settings.grpc_key instead")] + pub grpc_key: Option, #[arg( long, @@ -90,34 +95,43 @@ pub struct DefGuardConfig { #[serde(skip_serializing)] pub openid_signing_key: Option, - // // relying party id and relying party origin for WebAuthn - // #[arg(long, env = "DEFGUARD_WEBAUTHN_RP_ID")] - // pub webauthn_rp_id: Option, + // relying party id and relying party origin for WebAuthn + #[arg(long, env = "DEFGUARD_WEBAUTHN_RP_ID")] + #[deprecated(since = "2.0.0", note = "Use Settings.webauthn_rp_id instead")] + pub webauthn_rp_id: Option, #[arg(long, env = "DEFGUARD_URL", value_parser = Url::parse, default_value = "http://localhost:8000")] #[deprecated(since = "2.0.0", note = "Use Settings.defguard_url instead")] pub url: Url, - // #[arg(long, env = "DEFGUARD_GRPC_URL", value_parser = Url::parse, default_value = "http://localhost:50055")] - // pub grpc_url: Url, + #[arg(long, env = "DEFGUARD_GRPC_URL", value_parser = Url::parse)] + #[deprecated(since = "2.0.0", note = "Use Settings.grpc_url instead")] + pub grpc_url: Option, - // #[arg(long, env = "DEFGUARD_DISABLE_STATS_PURGE")] - // pub disable_stats_purge: bool, + #[arg(long, env = "DEFGUARD_DISABLE_STATS_PURGE")] + #[deprecated(since = "2.0.0", note = "Use Settings.disable_stats_purge instead")] + pub disable_stats_purge: Option, - // #[arg(long, env = "DEFGUARD_STATS_PURGE_FREQUENCY", default_value = "24h")] - // #[serde(skip_serializing)] - // pub stats_purge_frequency: Duration, + #[arg(long, env = "DEFGUARD_STATS_PURGE_FREQUENCY")] + #[serde(skip_serializing)] + #[deprecated(since = "2.0.0", note = "Use Settings.stats_purge_frequency instead")] + pub stats_purge_frequency: Option, - // #[arg(long, env = "DEFGUARD_STATS_PURGE_THRESHOLD", default_value = "30d")] - // #[serde(skip_serializing)] - // pub stats_purge_threshold: Duration, + #[arg(long, env = "DEFGUARD_STATS_PURGE_THRESHOLD")] + #[serde(skip_serializing)] + #[deprecated(since = "2.0.0", note = "Use Settings.stats_purge_threshold instead")] + pub stats_purge_threshold: Option, #[arg(long, env = "DEFGUARD_ENROLLMENT_URL", value_parser = Url::parse, default_value = "http://localhost:8080")] #[deprecated(since = "2.0.0", note = "Use Settings.public_proxy_url instead")] pub enrollment_url: Url, - // #[arg(long, env = "DEFGUARD_ENROLLMENT_TOKEN_TIMEOUT", default_value = "24h")] - // #[serde(skip_serializing)] - // pub enrollment_token_timeout: Duration, + #[arg(long, env = "DEFGUARD_ENROLLMENT_TOKEN_TIMEOUT")] + #[serde(skip_serializing)] + #[deprecated( + since = "2.0.0", + note = "Use Settings.enrollment_token_timeout instead" + )] + pub enrollment_token_timeout: Option, #[arg(long, env = "DEFGUARD_MFA_CODE_TIMEOUT", default_value = "60s")] #[serde(skip_serializing)] @@ -132,29 +146,29 @@ pub struct DefGuardConfig { #[deprecated(since = "2.0.0", note = "Use Settings.default_authentication instead")] pub session_timeout: Duration, - // #[arg( - // long, - // env = "DEFGUARD_PASSWORD_RESET_TOKEN_TIMEOUT", - // default_value = "24h" - // )] - // #[serde(skip_serializing)] - // pub password_reset_token_timeout: Duration, - - // #[arg( - // long, - // env = "DEFGUARD_ENROLLMENT_SESSION_TIMEOUT", - // default_value = "10m" - // )] - // #[serde(skip_serializing)] - // pub enrollment_session_timeout: Duration, - - // #[arg( - // long, - // env = "DEFGUARD_PASSWORD_RESET_SESSION_TIMEOUT", - // default_value = "10m" - // )] - // #[serde(skip_serializing)] - // pub password_reset_session_timeout: Duration, + #[arg(long, env = "DEFGUARD_PASSWORD_RESET_TOKEN_TIMEOUT")] + #[serde(skip_serializing)] + #[deprecated( + since = "2.0.0", + note = "Use Settings.password_reset_token_timeout instead" + )] + pub password_reset_token_timeout: Option, + + #[arg(long, env = "DEFGUARD_ENROLLMENT_SESSION_TIMEOUT")] + #[serde(skip_serializing)] + #[deprecated( + since = "2.0.0", + note = "Use Settings.enrollment_session_timeout instead" + )] + pub enrollment_session_timeout: Option, + + #[arg(long, env = "DEFGUARD_PASSWORD_RESET_SESSION_TIMEOUT")] + #[serde(skip_serializing)] + #[deprecated( + since = "2.0.0", + note = "Use Settings.password_reset_session_timeout instead" + )] + pub password_reset_session_timeout: Option, #[arg(long, env = "DEFGUARD_COOKIE_DOMAIN")] pub cookie_domain: Option, @@ -162,9 +176,10 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_COOKIE_INSECURE")] pub cookie_insecure: bool, - // // path to certificate `.pem` file used if connecting to proxy over HTTPS - // #[arg(long, env = "DEFGUARD_PROXY_GRPC_CA")] - // pub proxy_grpc_ca: Option, + // path to certificate `.pem` file used if connecting to proxy over HTTPS + #[arg(long, env = "DEFGUARD_PROXY_GRPC_CA")] + #[deprecated(since = "2.0.0", note = "Use Settings.proxy_grpc_ca instead")] + pub proxy_grpc_ca: Option, #[command(subcommand)] #[serde(skip_serializing)] @@ -227,7 +242,7 @@ impl DefGuardConfig { #[must_use] pub fn new() -> Self { let config = Self::parse(); - // TODO(jck) + // TODO(jck) // config.validate_secret_key(); config } @@ -241,7 +256,7 @@ impl DefGuardConfig { /// Initialize values that depend on Settings. pub fn initialize_post_settings(&mut self) { let url = Settings::url().expect("Unable to parse Defguard URL."); - // TODO(jck) + // TODO(jck) // self.initialize_rp_id(&url); self.initialize_cookie_domain(&url); } diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 891f50e7e9..ca419e4f68 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, fmt, time::Duration}; use chrono::NaiveDateTime; +use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, PgPool, Type, query, query_as}; use struct_patch::Patch; @@ -619,33 +620,62 @@ impl Settings { pub async fn update_from_config<'e, E>( &mut self, executor: E, - _config: &DefGuardConfig, + config: &DefGuardConfig, ) -> Result<(), sqlx::Error> where E: PgExecutor<'e>, { - // let minute = 60; - // let hour = minute * 60; - // let day = hour * 24; - // self.auth_cookie_timeout_days = (config.auth_cookie_timeout.as_secs() / day) as i32; - // self.secret_key = config.secret_key.expose_secret().to_string(); - // self.grpc_ca = config.grpc_ca.clone(); - // self.grpc_cert = config.grpc_cert.clone(); - // self.grpc_key = config.grpc_key.clone(); - // self.webauthn_rp_id = config.webauthn_rp_id.clone(); - // self.grpc_url = config.grpc_url.to_string(); - // self.disable_stats_purge = config.disable_stats_purge; - // self.stats_purge_frequency_hours = (config.stats_purge_frequency.as_secs() / hour) as i32; - // self.stats_purge_threshold_days = (config.stats_purge_threshold.as_secs() / day) as i32; - // self.enrollment_token_timeout_hours = - // (config.enrollment_token_timeout.as_secs() / hour) as i32; - // self.password_reset_token_timeout_hours = - // (config.password_reset_token_timeout.as_secs() / hour) as i32; - // self.enrollment_session_timeout_minutes = - // (config.enrollment_session_timeout.as_secs() / minute) as i32; - // self.password_reset_session_timeout_minutes = - // (config.password_reset_session_timeout.as_secs() / minute) as i32; - // self.proxy_grpc_ca = config.proxy_grpc_ca.clone(); + let minute = 60; + let hour = minute * 60; + let day = hour * 24; + if let Some(auth_cookie_timeout) = config.auth_cookie_timeout { + self.auth_cookie_timeout_days = (auth_cookie_timeout.as_secs() / day) as i32; + } + if let Some(secret_key) = &config.secret_key { + self.secret_key = secret_key.expose_secret().to_string(); + } + if let Some(grpc_ca) = &config.grpc_ca { + self.grpc_ca = Some(grpc_ca.clone()); + } + if let Some(grpc_cert) = &config.grpc_cert { + self.grpc_cert = Some(grpc_cert.clone()); + } + if let Some(grpc_key) = &config.grpc_key { + self.grpc_key = Some(grpc_key.clone()); + } + if let Some(webauthn_rp_id) = &config.webauthn_rp_id { + self.webauthn_rp_id = Some(webauthn_rp_id.clone()); + } + if let Some(grpc_url) = &config.grpc_url { + self.grpc_url = grpc_url.to_string(); + } + if let Some(disable_stats_purge) = config.disable_stats_purge { + self.disable_stats_purge = disable_stats_purge; + } + if let Some(stats_purge_frequency) = config.stats_purge_frequency { + self.stats_purge_frequency_hours = (stats_purge_frequency.as_secs() / hour) as i32; + } + if let Some(stats_purge_threshold) = config.stats_purge_threshold { + self.stats_purge_threshold_days = (stats_purge_threshold.as_secs() / day) as i32; + } + if let Some(enrollment_token_timeout) = config.enrollment_token_timeout { + self.enrollment_token_timeout_hours = (enrollment_token_timeout.as_secs() / hour) as i32; + } + if let Some(password_reset_token_timeout) = config.password_reset_token_timeout { + self.password_reset_token_timeout_hours = + (password_reset_token_timeout.as_secs() / hour) as i32; + } + if let Some(enrollment_session_timeout) = config.enrollment_session_timeout { + self.enrollment_session_timeout_minutes = + (enrollment_session_timeout.as_secs() / minute) as i32; + } + if let Some(password_reset_session_timeout) = config.password_reset_session_timeout { + self.password_reset_session_timeout_minutes = + (password_reset_session_timeout.as_secs() / minute) as i32; + } + if let Some(proxy_grpc_ca) = &config.proxy_grpc_ca { + self.proxy_grpc_ca = Some(proxy_grpc_ca.clone()); + } self.save(executor).await } From 6339f7562aff35b928af6433e8b18fab328b18c2 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 10:31:03 +0100 Subject: [PATCH 08/20] make secret_key optional at the DB level, ensure presence during startup --- .../defguard_common/src/db/models/settings.rs | 37 +++++++++++++++++-- crates/defguard_core/src/appstate.rs | 8 +--- crates/defguard_core/src/lib.rs | 7 ++++ .../tests/integration/api/common/mod.rs | 11 +++++- crates/defguard_proxy_manager/src/handler.rs | 9 ++++- ...260227091211_[2.0.0]_settings_in_db.up.sql | 2 +- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index ca419e4f68..ecab43c0d7 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -47,6 +47,14 @@ pub enum SettingsValidationError { CannotEnableGatewayNotifications, } +#[derive(Error, Debug)] +pub enum SettingsRequiredValueError { + #[error("Missing required setting: {0}")] + Missing(&'static str), + #[error("Invalid required setting `{0}`: {1}")] + Invalid(&'static str, &'static str), +} + #[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Type, Debug, Default)] #[sqlx(type_name = "smtp_encryption", rename_all = "lowercase")] pub enum SmtpEncryption { @@ -177,7 +185,7 @@ pub struct Settings { pub initial_setup_step: InitialSetupStep, pub default_admin_id: Option, // 1.6 config options - pub secret_key: String, + pub secret_key: Option, pub grpc_ca: Option, pub grpc_cert: Option, pub grpc_key: Option, @@ -613,6 +621,29 @@ impl Settings { Duration::from_secs(self.password_reset_session_timeout_minutes as u64 * 60) } + pub fn secret_key_required(&self) -> Result<&str, SettingsRequiredValueError> { + let secret_key = self + .secret_key + .as_deref() + .ok_or(SettingsRequiredValueError::Missing("secret_key"))?; + + if secret_key.trim().len() != secret_key.len() { + return Err(SettingsRequiredValueError::Invalid( + "secret_key", + "cannot have leading or trailing whitespace", + )); + } + + if secret_key.len() < 64 { + return Err(SettingsRequiredValueError::Invalid( + "secret_key", + "must be at least 64 characters long", + )); + } + + Ok(secret_key) + } + pub fn proxy_public_url(&self) -> Result { Url::parse(&self.public_proxy_url) } @@ -632,7 +663,7 @@ impl Settings { self.auth_cookie_timeout_days = (auth_cookie_timeout.as_secs() / day) as i32; } if let Some(secret_key) = &config.secret_key { - self.secret_key = secret_key.expose_secret().to_string(); + self.secret_key = Some(secret_key.expose_secret().to_string()); } if let Some(grpc_ca) = &config.grpc_ca { self.grpc_ca = Some(grpc_ca.clone()); @@ -677,7 +708,7 @@ impl Settings { self.proxy_grpc_ca = Some(proxy_grpc_ca.clone()); } - self.save(executor).await + update_current_settings(executor, self.clone()).await } } diff --git a/crates/defguard_core/src/appstate.rs b/crates/defguard_core/src/appstate.rs index 6aac5c567f..06e6ed2113 100644 --- a/crates/defguard_core/src/appstate.rs +++ b/crates/defguard_core/src/appstate.rs @@ -2,11 +2,8 @@ use std::sync::{Arc, Mutex, RwLock}; use axum::extract::FromRef; use axum_extra::extract::cookie::Key; -use defguard_common::{ - config::server_config, db::models::Settings, types::proxy::ProxyControlMessage, -}; +use defguard_common::{db::models::Settings, types::proxy::ProxyControlMessage}; use reqwest::Client; -use secrecy::ExposeSecret; use serde_json::json; use sqlx::PgPool; use tokio::{ @@ -113,6 +110,7 @@ impl AppState { tx: UnboundedSender, rx: UnboundedReceiver, wireguard_tx: Sender, + key: Key, failed_logins: Arc>, event_tx: UnboundedSender, incompatible_components: Arc>, @@ -136,8 +134,6 @@ impl AppState { .expect("Invalid WebAuthn configuration"), ); - let key = Key::from(settings.secret_key.as_bytes()); - Self { pool, tx, diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 79a2499641..f6bfba9af8 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -11,6 +11,7 @@ use axum::{ routing::{delete, get, post, put}, serve, }; +use axum_extra::extract::cookie::Key; use defguard_certs::CertificateAuthority; use defguard_common::{ VERSION, @@ -219,6 +220,7 @@ pub fn build_webapp( wireguard_tx: Sender, worker_state: Arc>, pool: PgPool, + key: Key, failed_logins: Arc>, event_tx: UnboundedSender, version: Version, @@ -604,6 +606,7 @@ pub fn build_webapp( webhook_tx, webhook_rx, wireguard_tx, + key, failed_logins, event_tx, incompatible_components, @@ -638,12 +641,16 @@ pub async fn run_web_server( incompatible_components: Arc>, proxy_control_tx: tokio::sync::mpsc::Sender, ) -> Result<(), anyhow::Error> { + let settings = Settings::get_current_settings(); + let key = Key::from(settings.secret_key_required()?.as_bytes()); + let webapp = build_webapp( webhook_tx, webhook_rx, wireguard_tx, worker_state, pool, + key, failed_logins, event_tx, Version::parse(VERSION)?, diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 6d9ad02340..eb73c11454 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -11,7 +11,7 @@ use defguard_common::{ config::DefGuardConfig, db::{ Id, - models::{Device, User, WireguardNetwork, settings::initialize_current_settings}, + models::{Device, Settings, User, WireguardNetwork, settings::initialize_current_settings}, }, }; use defguard_core::{ @@ -23,6 +23,7 @@ use defguard_core::{ grpc::{GatewayEvent, WorkerState}, handlers::{Auth, user::UserDetails}, }; +use axum_extra::extract::cookie::Key; use reqwest::{StatusCode, header::HeaderName}; use semver::Version; use serde_json::json; @@ -123,12 +124,20 @@ pub(crate) async fn make_base_client( // .with(tracing_subscriber::fmt::layer()) // .init(); + let key = Key::from( + Settings::get_current_settings() + .secret_key_required() + .unwrap() + .as_bytes(), + ); + let webapp = build_webapp( tx, rx, wg_tx, worker_state, pool, + key, failed_logins, api_event_tx, Version::parse(VERSION).unwrap(), diff --git a/crates/defguard_proxy_manager/src/handler.rs b/crates/defguard_proxy_manager/src/handler.rs index 5d92876bb6..9506d6c6be 100644 --- a/crates/defguard_proxy_manager/src/handler.rs +++ b/crates/defguard_proxy_manager/src/handler.rs @@ -196,7 +196,7 @@ impl ProxyHandler { loop { let endpoint = self.endpoint()?; let settings = Settings::get_current_settings(); - let Some(ca_cert_der) = settings.ca_cert_der else { + let Some(ref ca_cert_der) = settings.ca_cert_der else { return Err(ProxyError::MissingConfiguration( "Core CA is not setup, can't create a Proxy endpoint.".to_string(), )); @@ -271,7 +271,12 @@ impl ProxyHandler { let mut resp_stream = response.into_inner(); // Derive proxy cookie key from core secret to avoid transmitting it over gRPC. - let proxy_cookie_key = Key::derive_from(settings.secret_key.as_bytes()); + let proxy_cookie_key = Key::derive_from( + settings + .secret_key_required() + .map_err(|err| ProxyError::MissingConfiguration(err.to_string()))? + .as_bytes(), + ); // Send initial info with private cookies key. let initial_info = InitialInfo { diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql index 90527a257c..5791e05929 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql @@ -1,6 +1,6 @@ ALTER TABLE settings ADD COLUMN auth_cookie_timeout_days int4 NOT NULL DEFAULT 7, - ADD COLUMN secret_key text NOT NULL DEFAULT 'UNSET', -- TODO(jck) + ADD COLUMN secret_key text, ADD COLUMN grpc_ca text, ADD COLUMN grpc_cert text, ADD COLUMN grpc_key text, From 99440967b2e34a51ed381542c7c4e24839c03f7e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 11:24:39 +0100 Subject: [PATCH 09/20] validate secret for proxy during startup --- crates/defguard/src/main.rs | 2 ++ crates/defguard_proxy_manager/src/handler.rs | 17 ++++++----------- crates/defguard_proxy_manager/src/lib.rs | 6 ++++++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 19ca6c58ec..a8bfe2ec0f 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -180,11 +180,13 @@ async fn main() -> Result<(), anyhow::Error> { } let (proxy_control_tx, proxy_control_rx) = channel::(100); + let proxy_secret_key = settings.secret_key_required()?.to_string(); let proxy_manager = ProxyManager::new( pool.clone(), ProxyTxSet::new(gateway_tx.clone(), bidi_event_tx.clone()), Arc::clone(&incompatible_components), proxy_control_rx, + proxy_secret_key, ); let mut gateway_manager = GatewayManager::new( diff --git a/crates/defguard_proxy_manager/src/handler.rs b/crates/defguard_proxy_manager/src/handler.rs index 9506d6c6be..ab95634a8d 100644 --- a/crates/defguard_proxy_manager/src/handler.rs +++ b/crates/defguard_proxy_manager/src/handler.rs @@ -7,7 +7,6 @@ use std::{ use axum_extra::extract::cookie::Key; use defguard_common::{ VERSION, - config::server_config, db::{ Id, models::{Settings, proxy::Proxy}, @@ -43,7 +42,6 @@ use defguard_version::{ use hyper_rustls::HttpsConnectorBuilder; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; -use secrecy::ExposeSecret; use semver::Version; use sqlx::PgPool; use tokio::{ @@ -87,6 +85,7 @@ pub(super) struct ProxyHandler { pub(super) url: Url, shutdown_signal: Arc>, proxy_id: Id, + proxy_cookie_key: Key, client: Option>>, } @@ -99,6 +98,7 @@ impl ProxyHandler { sessions: Arc>>, shutdown_signal: Arc>, proxy_id: Id, + proxy_cookie_key: Key, ) -> Self { // Instantiate gRPC servers. let services = ProxyServices::new(&pool, tx, remote_mfa_responses, sessions); @@ -109,6 +109,7 @@ impl ProxyHandler { url, shutdown_signal, proxy_id, + proxy_cookie_key, client: None, } } @@ -120,6 +121,7 @@ impl ProxyHandler { remote_mfa_responses: Arc>>>, sessions: Arc>>, shutdown_signal: Arc>, + proxy_cookie_key: Key, ) -> Result { let url = Url::from_str(&format!("http://{}:{}", proxy.address, proxy.port))?; let proxy_id = proxy.id; @@ -131,6 +133,7 @@ impl ProxyHandler { sessions, shutdown_signal, proxy_id, + proxy_cookie_key, )) } @@ -270,17 +273,9 @@ impl ProxyHandler { info!("Connected to proxy at {}", endpoint.uri()); let mut resp_stream = response.into_inner(); - // Derive proxy cookie key from core secret to avoid transmitting it over gRPC. - let proxy_cookie_key = Key::derive_from( - settings - .secret_key_required() - .map_err(|err| ProxyError::MissingConfiguration(err.to_string()))? - .as_bytes(), - ); - // Send initial info with private cookies key. let initial_info = InitialInfo { - private_cookies_key: proxy_cookie_key.master().to_vec(), + private_cookies_key: self.proxy_cookie_key.master().to_vec(), }; let _ = tx.send(CoreResponse { id: 0, diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index b4bd75255b..fad1fe58ca 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -4,6 +4,7 @@ use std::{ time::Duration, }; +use axum_extra::extract::cookie::Key; use defguard_common::{db::models::proxy::Proxy, types::proxy::ProxyControlMessage}; use defguard_core::{events::BidiStreamEvent, grpc::GatewayEvent, version::IncompatibleComponents}; use sqlx::PgPool; @@ -40,6 +41,7 @@ pub struct ProxyManager { tx: ProxyTxSet, incompatible_components: Arc>, proxy_control: Receiver, + proxy_cookie_key: Key, } impl ProxyManager { @@ -48,12 +50,14 @@ impl ProxyManager { tx: ProxyTxSet, incompatible_components: Arc>, proxy_control_rx: Receiver, + core_secret_key: String, ) -> Self { Self { pool, tx, incompatible_components, proxy_control: proxy_control_rx, + proxy_cookie_key: Key::derive_from(core_secret_key.as_bytes()), } } @@ -89,6 +93,7 @@ impl ProxyManager { Arc::clone(&remote_mfa_responses), Arc::clone(&sessions), Arc::new(Mutex::new(shutdown_rx)), + self.proxy_cookie_key.clone(), ) }) .collect::, _>>()?; @@ -131,6 +136,7 @@ impl ProxyManager { Arc::clone(&remote_mfa_responses), Arc::clone(&sessions), Arc::new(Mutex::new(shutdown_rx)), + self.proxy_cookie_key.clone(), ) { Ok(proxy) => { debug!("Spawning proxy task for proxy {}", proxy.url); From acbbcc2aa650ccae27c1968d9d83e48aeb1430db Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 12:33:14 +0100 Subject: [PATCH 10/20] remove cert options from DefguardConfig --- crates/defguard/src/main.rs | 19 ++------- crates/defguard_common/src/config.rs | 11 ----- .../defguard_common/src/db/models/settings.rs | 41 +++++-------------- ...0227091211_[2.0.0]_settings_in_db.down.sql | 3 -- ...260227091211_[2.0.0]_settings_in_db.up.sql | 3 -- 5 files changed, 14 insertions(+), 63 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index a8bfe2ec0f..06de6162ed 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -1,7 +1,4 @@ -use std::{ - fs::read_to_string, - sync::{Arc, Mutex, RwLock}, -}; +use std::sync::{Arc, Mutex, RwLock}; use bytes::Bytes; use defguard_common::{ @@ -149,16 +146,6 @@ async fn main() -> Result<(), anyhow::Error> { anyhow::bail!("CA certificate or key were not found in settings, despite completing setup.") } - // read grpc TLS cert and key - let grpc_cert = settings - .grpc_cert - .as_ref() - .and_then(|path| read_to_string(path).ok()); - let grpc_key = settings - .grpc_key - .as_ref() - .and_then(|path| read_to_string(path).ok()); - // initialize failed login attempt tracker let failed_logins = FailedLoginMap::new(); let failed_logins = Arc::new(Mutex::new(failed_logins)); @@ -201,8 +188,8 @@ async fn main() -> Result<(), anyhow::Error> { res = run_grpc_server( Arc::clone(&worker_state), pool.clone(), - grpc_cert, - grpc_key, + None, + None, failed_logins.clone(), ) => error!("gRPC server returned early: {res:?}"), res = run_web_server( diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index d18ed01385..b279a7776b 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -70,17 +70,6 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_GRPC_PORT", default_value_t = 50055)] pub grpc_port: u16, - // Certificate authority (CA), certificate, and key for gRPC communication over HTTPS. - #[arg(long, env = "DEFGUARD_GRPC_CA")] - #[deprecated(since = "2.0.0", note = "Use Settings.grpc_ca instead")] - pub grpc_ca: Option, - #[arg(long, env = "DEFGUARD_GRPC_CERT")] - #[deprecated(since = "2.0.0", note = "Use Settings.grpc_cert instead")] - pub grpc_cert: Option, - #[arg(long, env = "DEFGUARD_GRPC_KEY")] - #[deprecated(since = "2.0.0", note = "Use Settings.grpc_key instead")] - pub grpc_key: Option, - #[arg( long, env = "DEFGUARD_DEFAULT_ADMIN_PASSWORD", diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index ecab43c0d7..1f60f21250 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -186,9 +186,6 @@ pub struct Settings { pub default_admin_id: Option, // 1.6 config options pub secret_key: Option, - pub grpc_ca: Option, - pub grpc_cert: Option, - pub grpc_key: Option, pub webauthn_rp_id: Option, pub grpc_url: String, pub disable_stats_purge: bool, @@ -322,8 +319,7 @@ impl Settings { ca_key_der, ca_cert_der, ca_expiry, initial_setup_completed, defguard_url, \ default_admin_group_name, authentication_period_days, mfa_code_timeout_seconds, \ public_proxy_url, initial_setup_step \"initial_setup_step: InitialSetupStep\", \ - default_admin_id, auth_cookie_timeout_days, secret_key, grpc_ca, grpc_cert, \ - grpc_key, webauthn_rp_id, grpc_url, disable_stats_purge, \ + default_admin_id, auth_cookie_timeout_days, secret_key, webauthn_rp_id, grpc_url, disable_stats_purge, \ stats_purge_frequency_hours, stats_purge_threshold_days, \ enrollment_token_timeout_hours, password_reset_token_timeout_hours, \ enrollment_session_timeout_minutes, password_reset_session_timeout_minutes, \ @@ -418,19 +414,16 @@ impl Settings { default_admin_id = $59, \ auth_cookie_timeout_days = $60, \ secret_key = $61, \ - grpc_ca = $62, \ - grpc_cert = $63, \ - grpc_key = $64, \ - webauthn_rp_id = $65, \ - grpc_url = $66, \ - disable_stats_purge = $67, \ - stats_purge_frequency_hours = $68, \ - stats_purge_threshold_days = $69, \ - enrollment_token_timeout_hours = $70, \ - password_reset_token_timeout_hours = $71, \ - enrollment_session_timeout_minutes = $72, \ - password_reset_session_timeout_minutes = $73, \ - proxy_grpc_ca = $74 \ + webauthn_rp_id = $62, \ + grpc_url = $63, \ + disable_stats_purge = $64, \ + stats_purge_frequency_hours = $65, \ + stats_purge_threshold_days = $66, \ + enrollment_token_timeout_hours = $67, \ + password_reset_token_timeout_hours = $68, \ + enrollment_session_timeout_minutes = $69, \ + password_reset_session_timeout_minutes = $70, \ + proxy_grpc_ca = $71 \ WHERE id = 1", self.openid_enabled, self.wireguard_enabled, @@ -493,9 +486,6 @@ impl Settings { self.default_admin_id, self.auth_cookie_timeout_days, self.secret_key, - self.grpc_ca, - self.grpc_cert, - self.grpc_key, self.webauthn_rp_id, self.grpc_url, self.disable_stats_purge, @@ -665,15 +655,6 @@ impl Settings { if let Some(secret_key) = &config.secret_key { self.secret_key = Some(secret_key.expose_secret().to_string()); } - if let Some(grpc_ca) = &config.grpc_ca { - self.grpc_ca = Some(grpc_ca.clone()); - } - if let Some(grpc_cert) = &config.grpc_cert { - self.grpc_cert = Some(grpc_cert.clone()); - } - if let Some(grpc_key) = &config.grpc_key { - self.grpc_key = Some(grpc_key.clone()); - } if let Some(webauthn_rp_id) = &config.webauthn_rp_id { self.webauthn_rp_id = Some(webauthn_rp_id.clone()); } diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql index 84621f4ac9..4b055248a1 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql @@ -1,9 +1,6 @@ ALTER TABLE settings DROP COLUMN auth_cookie_timeout_days, DROP COLUMN secret_key, - DROP COLUMN grpc_ca, - DROP COLUMN grpc_cert, - DROP COLUMN grpc_key, DROP COLUMN openid_signing_key, DROP COLUMN webauthn_rp_id, DROP COLUMN grpc_url, diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql index 5791e05929..c7cee422c2 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql @@ -1,9 +1,6 @@ ALTER TABLE settings ADD COLUMN auth_cookie_timeout_days int4 NOT NULL DEFAULT 7, ADD COLUMN secret_key text, - ADD COLUMN grpc_ca text, - ADD COLUMN grpc_cert text, - ADD COLUMN grpc_key text, ADD COLUMN openid_signing_key text, ADD COLUMN webauthn_rp_id text, ADD COLUMN grpc_url text NOT NULL DEFAULT 'http://localhost:50055', From 8991b80c21481094700f0d24061ee58d4b0d3672 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 13:01:01 +0100 Subject: [PATCH 11/20] restore grpc_cert, grpc_key config - workers still use this --- crates/defguard/src/main.rs | 19 ++++++++++++++++--- crates/defguard_common/src/config.rs | 7 +++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 06de6162ed..3ff109a5d6 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, Mutex, RwLock}; +use std::{ + fs::read_to_string, + sync::{Arc, Mutex, RwLock}, +}; use bytes::Bytes; use defguard_common::{ @@ -146,6 +149,16 @@ async fn main() -> Result<(), anyhow::Error> { anyhow::bail!("CA certificate or key were not found in settings, despite completing setup.") } + // read grpc TLS cert and key from legacy config values + let grpc_cert = config + .grpc_cert + .as_ref() + .and_then(|path| read_to_string(path).ok()); + let grpc_key = config + .grpc_key + .as_ref() + .and_then(|path| read_to_string(path).ok()); + // initialize failed login attempt tracker let failed_logins = FailedLoginMap::new(); let failed_logins = Arc::new(Mutex::new(failed_logins)); @@ -188,8 +201,8 @@ async fn main() -> Result<(), anyhow::Error> { res = run_grpc_server( Arc::clone(&worker_state), pool.clone(), - None, - None, + grpc_cert, + grpc_key, failed_logins.clone(), ) => error!("gRPC server returned early: {res:?}"), res = run_web_server( diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index b279a7776b..b504c07059 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -70,6 +70,13 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_GRPC_PORT", default_value_t = 50055)] pub grpc_port: u16, + // Certificate and key for gRPC communication over HTTPS. + // Kept in runtime config for backwards compatibility - workers still use this. + #[arg(long, env = "DEFGUARD_GRPC_CERT")] + pub grpc_cert: Option, + #[arg(long, env = "DEFGUARD_GRPC_KEY")] + pub grpc_key: Option, + #[arg( long, env = "DEFGUARD_DEFAULT_ADMIN_PASSWORD", From 878401c9364fb729449e735b6b51fb709df04eb0 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 13:14:50 +0100 Subject: [PATCH 12/20] remove proxy_grpc_ca config field --- crates/defguard_common/src/config.rs | 5 ----- crates/defguard_common/src/db/models/settings.rs | 14 +++----------- .../20260227091211_[2.0.0]_settings_in_db.down.sql | 3 +-- .../20260227091211_[2.0.0]_settings_in_db.up.sql | 3 +-- 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index b504c07059..6d7eadd019 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -172,11 +172,6 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_COOKIE_INSECURE")] pub cookie_insecure: bool, - // path to certificate `.pem` file used if connecting to proxy over HTTPS - #[arg(long, env = "DEFGUARD_PROXY_GRPC_CA")] - #[deprecated(since = "2.0.0", note = "Use Settings.proxy_grpc_ca instead")] - pub proxy_grpc_ca: Option, - #[command(subcommand)] #[serde(skip_serializing)] pub cmd: Option, diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 1f60f21250..896b195541 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -196,7 +196,6 @@ pub struct Settings { password_reset_token_timeout_hours: i32, enrollment_session_timeout_minutes: i32, password_reset_session_timeout_minutes: i32, - pub proxy_grpc_ca: Option, } // Implement manually to avoid exposing the license key. @@ -322,8 +321,7 @@ impl Settings { default_admin_id, auth_cookie_timeout_days, secret_key, webauthn_rp_id, grpc_url, disable_stats_purge, \ stats_purge_frequency_hours, stats_purge_threshold_days, \ enrollment_token_timeout_hours, password_reset_token_timeout_hours, \ - enrollment_session_timeout_minutes, password_reset_session_timeout_minutes, \ - proxy_grpc_ca \ + enrollment_session_timeout_minutes, password_reset_session_timeout_minutes \ FROM \"settings\" WHERE id = 1", ) .fetch_optional(executor) @@ -422,8 +420,7 @@ impl Settings { enrollment_token_timeout_hours = $67, \ password_reset_token_timeout_hours = $68, \ enrollment_session_timeout_minutes = $69, \ - password_reset_session_timeout_minutes = $70, \ - proxy_grpc_ca = $71 \ + password_reset_session_timeout_minutes = $70 \ WHERE id = 1", self.openid_enabled, self.wireguard_enabled, @@ -495,7 +492,6 @@ impl Settings { self.password_reset_token_timeout_hours, self.enrollment_session_timeout_minutes, self.password_reset_session_timeout_minutes, - self.proxy_grpc_ca, ) .execute(executor) .await?; @@ -685,11 +681,7 @@ impl Settings { self.password_reset_session_timeout_minutes = (password_reset_session_timeout.as_secs() / minute) as i32; } - if let Some(proxy_grpc_ca) = &config.proxy_grpc_ca { - self.proxy_grpc_ca = Some(proxy_grpc_ca.clone()); - } - - update_current_settings(executor, self.clone()).await + update_current_settings(executor, self.clone()).await } } diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql index 4b055248a1..5e68c99968 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql @@ -10,5 +10,4 @@ ALTER TABLE settings DROP COLUMN enrollment_token_timeout_hours, DROP COLUMN password_reset_token_timeout_hours, DROP COLUMN enrollment_session_timeout_minutes, - DROP COLUMN password_reset_session_timeout_minutes, - DROP COLUMN proxy_grpc_ca; + DROP COLUMN password_reset_session_timeout_minutes; diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql index c7cee422c2..b414c04c1c 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql @@ -10,5 +10,4 @@ ALTER TABLE settings ADD COLUMN enrollment_token_timeout_hours int4 NOT NULL DEFAULT 24, ADD COLUMN password_reset_token_timeout_hours int4 NOT NULL DEFAULT 24, ADD COLUMN enrollment_session_timeout_minutes int4 NOT NULL DEFAULT 10, - ADD COLUMN password_reset_session_timeout_minutes int4 NOT NULL DEFAULT 10, - ADD COLUMN proxy_grpc_ca text; + ADD COLUMN password_reset_session_timeout_minutes int4 NOT NULL DEFAULT 10; From 8a8a8ca73595f722d68ff406eb4f2dee2133e8de Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 13:36:41 +0100 Subject: [PATCH 13/20] migrate DefguardConfig::enrollment_url --- crates/defguard/src/main.rs | 2 +- crates/defguard_common/src/config.rs | 4 ++-- crates/defguard_common/src/db/models/settings.rs | 3 +++ crates/defguard_core/src/lib.rs | 2 -- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 3ff109a5d6..574995dd83 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -115,7 +115,7 @@ async fn main() -> Result<(), anyhow::Error> { if wizard_flags.migration_wizard_needed { info!("Migration from 1.6: copying configuration options to DB"); settings.update_from_config(&pool, &config).await?; - info!("Migration from 1.6: copyied configuration options to DB"); + info!("Migration from 1.6: copied configuration options to DB"); } config.initialize_post_settings(); diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 6d7eadd019..0af292395d 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -117,9 +117,9 @@ pub struct DefGuardConfig { #[deprecated(since = "2.0.0", note = "Use Settings.stats_purge_threshold instead")] pub stats_purge_threshold: Option, - #[arg(long, env = "DEFGUARD_ENROLLMENT_URL", value_parser = Url::parse, default_value = "http://localhost:8080")] + #[arg(long, env = "DEFGUARD_ENROLLMENT_URL", value_parser = Url::parse)] #[deprecated(since = "2.0.0", note = "Use Settings.public_proxy_url instead")] - pub enrollment_url: Url, + pub enrollment_url: Option, #[arg(long, env = "DEFGUARD_ENROLLMENT_TOKEN_TIMEOUT")] #[serde(skip_serializing)] diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 896b195541..4160f12d0e 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -657,6 +657,9 @@ impl Settings { if let Some(grpc_url) = &config.grpc_url { self.grpc_url = grpc_url.to_string(); } + if let Some(enrollment_url) = &config.enrollment_url { + self.public_proxy_url = enrollment_url.to_string(); + } if let Some(disable_stats_purge) = config.disable_stats_purge { self.disable_stats_purge = disable_stats_purge; } diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index f6bfba9af8..baf11401f5 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -709,8 +709,6 @@ pub async fn init_dev_env(config: &DefGuardConfig) { settings.ca_key_der = Some(ca.key_pair_der().to_vec()); settings.ca_expiry = Some(ca.expiry().expect("Failed to get CA expiry")); settings.initial_setup_completed = true; - // This should possibly be initialized somehow differently in the future since we are deprecating the enrollment URL env var. - settings.public_proxy_url = config.enrollment_url.to_string(); settings.defguard_url = config.url.to_string(); update_current_settings(&pool, settings) .await From a3585fa6aad8622d8cf6cbb3eb0e6167111c7961 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 13:56:06 +0100 Subject: [PATCH 14/20] migrate mfa_code_timeout --- crates/defguard_common/src/config.rs | 6 +++--- crates/defguard_common/src/db/models/settings.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 0af292395d..d9feaa31e2 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -129,13 +129,13 @@ pub struct DefGuardConfig { )] pub enrollment_token_timeout: Option, - #[arg(long, env = "DEFGUARD_MFA_CODE_TIMEOUT", default_value = "60s")] + #[arg(long, env = "DEFGUARD_MFA_CODE_TIMEOUT")] #[serde(skip_serializing)] #[deprecated( since = "2.0.0", - note = "Use Settings.default_mfa_code_lifetime instead" + note = "Use Settings.mfa_code_timeout_seconds instead" )] - pub mfa_code_timeout: Duration, + pub mfa_code_timeout: Option, #[arg(long, env = "DEFGUARD_SESSION_TIMEOUT", default_value = "7d")] #[serde(skip_serializing)] diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 4160f12d0e..0d9949fbae 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -660,6 +660,9 @@ impl Settings { if let Some(enrollment_url) = &config.enrollment_url { self.public_proxy_url = enrollment_url.to_string(); } + if let Some(mfa_code_timeout) = config.mfa_code_timeout { + self.mfa_code_timeout_seconds = mfa_code_timeout.as_secs() as i32; + } if let Some(disable_stats_purge) = config.disable_stats_purge { self.disable_stats_purge = disable_stats_purge; } From 03de230185c1c5e52c40bca1f017ef19ad503331 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 14:07:27 +0100 Subject: [PATCH 15/20] migrate session_timeout --- crates/defguard_common/src/config.rs | 9 ++++++--- crates/defguard_common/src/db/models/settings.rs | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index d9feaa31e2..967f94e55e 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -137,10 +137,13 @@ pub struct DefGuardConfig { )] pub mfa_code_timeout: Option, - #[arg(long, env = "DEFGUARD_SESSION_TIMEOUT", default_value = "7d")] + #[arg(long, env = "DEFGUARD_SESSION_TIMEOUT")] #[serde(skip_serializing)] - #[deprecated(since = "2.0.0", note = "Use Settings.default_authentication instead")] - pub session_timeout: Duration, + #[deprecated( + since = "2.0.0", + note = "Use Settings.authentication_period_days instead" + )] + pub session_timeout: Option, #[arg(long, env = "DEFGUARD_PASSWORD_RESET_TOKEN_TIMEOUT")] #[serde(skip_serializing)] diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 0d9949fbae..6a671b8900 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -663,6 +663,9 @@ impl Settings { if let Some(mfa_code_timeout) = config.mfa_code_timeout { self.mfa_code_timeout_seconds = mfa_code_timeout.as_secs() as i32; } + if let Some(session_timeout) = config.session_timeout { + self.authentication_period_days = (session_timeout.as_secs() / day) as i32; + } if let Some(disable_stats_purge) = config.disable_stats_purge { self.disable_stats_purge = disable_stats_purge; } From df30caf964b21209e2df8d21872675950d641902 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 15:30:07 +0100 Subject: [PATCH 16/20] generate secret key during startup if not present --- crates/defguard/src/main.rs | 3 + .../defguard_common/src/db/models/settings.rs | 68 +++++++++++++++---- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 574995dd83..427833dee1 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -118,6 +118,9 @@ async fn main() -> Result<(), anyhow::Error> { info!("Migration from 1.6: copied configuration options to DB"); } + Settings::ensure_secret_key(&pool, &config).await?; + settings = Settings::get_current_settings(); + config.initialize_post_settings(); SERVER_CONFIG diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 6a671b8900..1f1b1beca4 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, fmt, time::Duration}; use chrono::NaiveDateTime; +use rand::{RngCore, rngs::OsRng}; use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, PgPool, Type, query, query_as}; @@ -290,6 +291,59 @@ impl fmt::Debug for Settings { } impl Settings { + fn validate_secret_key(secret_key: &str) -> Result<(), SettingsRequiredValueError> { + if secret_key.trim().len() != secret_key.len() { + return Err(SettingsRequiredValueError::Invalid( + "secret_key", + "cannot have leading or trailing whitespace", + )); + } + + if secret_key.len() < 64 { + return Err(SettingsRequiredValueError::Invalid( + "secret_key", + "must be at least 64 characters long", + )); + } + + Ok(()) + } + + fn generate_secret_key() -> String { + let mut bytes = [0_u8; 32]; + OsRng.fill_bytes(&mut bytes); + let mut secret_key = String::with_capacity(64); + for byte in bytes { + use std::fmt::Write as _; + let _ = write!(secret_key, "{byte:02x}"); + } + secret_key + } + + pub async fn ensure_secret_key(pool: &PgPool, config: &DefGuardConfig) -> Result<(), anyhow::Error> { + let mut settings = Settings::get_current_settings(); + + if let Some(secret_key) = &config.secret_key { + let secret_key = secret_key.expose_secret(); + Settings::validate_secret_key(secret_key)?; + if settings.secret_key.as_deref() != Some(secret_key) { + settings.secret_key = Some(secret_key.to_string()); + update_current_settings(pool, settings).await?; + } + return Ok(()); + } + + if let Some(secret_key) = settings.secret_key.as_deref() { + Settings::validate_secret_key(secret_key)?; + return Ok(()); + } + + settings.secret_key = Some(Settings::generate_secret_key()); + update_current_settings(pool, settings).await?; + + Ok(()) + } + pub async fn get<'e, E>(executor: E) -> Result, sqlx::Error> where E: PgExecutor<'e>, @@ -613,19 +667,7 @@ impl Settings { .as_deref() .ok_or(SettingsRequiredValueError::Missing("secret_key"))?; - if secret_key.trim().len() != secret_key.len() { - return Err(SettingsRequiredValueError::Invalid( - "secret_key", - "cannot have leading or trailing whitespace", - )); - } - - if secret_key.len() < 64 { - return Err(SettingsRequiredValueError::Invalid( - "secret_key", - "must be at least 64 characters long", - )); - } + Settings::validate_secret_key(secret_key)?; Ok(secret_key) } From 7555d6992b60bc268e12a601b82589fe8702030d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 15:36:09 +0100 Subject: [PATCH 17/20] remove unused imports, allow deprecated where necessary --- crates/defguard_common/src/config.rs | 5 ++--- crates/defguard_common/src/db/models/settings.rs | 2 ++ crates/defguard_core/src/handlers/network_devices.rs | 2 -- crates/defguard_core/src/handlers/user.rs | 2 +- crates/defguard_proxy_manager/src/servers/enrollment.rs | 1 - crates/defguard_proxy_manager/src/servers/password_reset.rs | 5 +---- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 967f94e55e..f222494b5c 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -1,4 +1,4 @@ -use std::{fs::read_to_string, io, net::IpAddr, sync::OnceLock}; +use std::{net::IpAddr, sync::OnceLock}; use clap::{Args, Parser, Subcommand}; use humantime::Duration; @@ -11,9 +11,8 @@ use rsa::{ traits::PublicKeyParts, RsaPrivateKey, }; -use secrecy::{ExposeSecret, SecretString}; +use secrecy::SecretString; use serde::Serialize; -use tonic::transport::{Certificate, ClientTlsConfig, Identity}; use crate::db::models::Settings; diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 1f1b1beca4..2c30217e90 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -323,6 +323,7 @@ impl Settings { pub async fn ensure_secret_key(pool: &PgPool, config: &DefGuardConfig) -> Result<(), anyhow::Error> { let mut settings = Settings::get_current_settings(); + #[allow(deprecated)] if let Some(secret_key) = &config.secret_key { let secret_key = secret_key.expose_secret(); Settings::validate_secret_key(secret_key)?; @@ -676,6 +677,7 @@ impl Settings { Url::parse(&self.public_proxy_url) } + #[allow(deprecated)] pub async fn update_from_config<'e, E>( &mut self, executor: E, diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index 67fbe52f56..f076d096c9 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -32,7 +32,6 @@ use crate::{ enterprise::{firewall::try_get_location_firewall_config, limits::update_counts}, events::{ApiEvent, ApiEventType, ApiRequestContext}, grpc::GatewayEvent, - server_config, }; #[derive(Serialize)] @@ -448,7 +447,6 @@ pub(crate) async fn start_network_device_setup( config, device: NetworkDeviceInfo::from_device(device, &mut transaction).await?, }; - let config = server_config(); let settings = Settings::get_current_settings(); let configuration_token = start_desktop_configuration( &user, diff --git a/crates/defguard_core/src/handlers/user.rs b/crates/defguard_core/src/handlers/user.rs index 809f50b2ff..f1211b452e 100644 --- a/crates/defguard_core/src/handlers/user.rs +++ b/crates/defguard_core/src/handlers/user.rs @@ -48,7 +48,7 @@ use crate::{ }, error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, - is_valid_phone_number, server_config, + is_valid_phone_number, user_management::{delete_user_and_cleanup_devices, sync_allowed_user_devices}, }; diff --git a/crates/defguard_proxy_manager/src/servers/enrollment.rs b/crates/defguard_proxy_manager/src/servers/enrollment.rs index 7e20d8a413..1c84423dc8 100644 --- a/crates/defguard_proxy_manager/src/servers/enrollment.rs +++ b/crates/defguard_proxy_manager/src/servers/enrollment.rs @@ -1,7 +1,6 @@ use std::collections::HashSet; use defguard_common::{ - config::server_config, csv::AsCsv, db::{ Id, diff --git a/crates/defguard_proxy_manager/src/servers/password_reset.rs b/crates/defguard_proxy_manager/src/servers/password_reset.rs index c319b44f17..f063ee2c9f 100644 --- a/crates/defguard_proxy_manager/src/servers/password_reset.rs +++ b/crates/defguard_proxy_manager/src/servers/password_reset.rs @@ -1,7 +1,4 @@ -use defguard_common::{ - config::server_config, - db::models::{Settings, User}, -}; +use defguard_common::db::models::{Settings, User}; use defguard_core::{ db::models::enrollment::{PASSWORD_RESET_TOKEN_TYPE, Token}, enterprise::ldap::utils::ldap_change_password, From b41653130306a964b782d6eed7d86189067056b4 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 15:46:55 +0100 Subject: [PATCH 18/20] remove unused methods --- crates/defguard_common/src/config.rs | 33 ---------------------------- 1 file changed, 33 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index f222494b5c..8038932219 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -274,20 +274,6 @@ impl DefGuardConfig { } } - // fn validate_secret_key(&self) { - // let secret_key = self.secret_key.expose_secret(); - // assert!( - // secret_key.trim().len() == secret_key.len(), - // "SECRET_KEY cannot have leading and trailing space", - // ); - - // assert!( - // secret_key.len() >= 64, - // "SECRET_KEY must be at least 64 characters long, provided value has {} characters", - // secret_key.len() - // ); - // } - /// Try PKCS#1 and PKCS#8 PEM formats. fn parse_openid_key(path: &str) -> Result { if let Ok(key) = RsaPrivateKey::read_pkcs1_pem_file(path) { @@ -307,25 +293,6 @@ impl DefGuardConfig { None } } - - // /// Provide [`ClientTlsConfig`] from paths to cerfiticate, key, and cerfiticate authority (CA). - // pub fn grpc_client_tls_config(&self) -> Result, io::Error> { - // if self.grpc_ca.is_none() && (self.grpc_cert.is_none() || self.grpc_key.is_none()) { - // return Ok(None); - // } - // let mut tls = ClientTlsConfig::new(); - // if let (Some(cert_path), Some(key_path)) = (&self.grpc_cert, &self.grpc_key) { - // let cert = read_to_string(cert_path)?; - // let key = read_to_string(key_path)?; - // tls = tls.identity(Identity::from_pem(cert, key)); - // } - // if let Some(ca_path) = &self.grpc_ca { - // let ca = read_to_string(ca_path)?; - // tls = tls.ca_certificate(Certificate::from_pem(ca)); - // } - - // Ok(Some(tls)) - // } } impl Default for DefGuardConfig { From 42769eef7aa7c8088b5b50db77519ee4394b8890 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 15:55:41 +0100 Subject: [PATCH 19/20] config secret validation --- crates/defguard_common/src/config.rs | 8 +++++--- crates/defguard_common/src/db/models/settings.rs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 8038932219..406318526a 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -11,7 +11,7 @@ use rsa::{ traits::PublicKeyParts, RsaPrivateKey, }; -use secrecy::SecretString; +use secrecy::{ExposeSecret, SecretString}; use serde::Serialize; use crate::db::models::Settings; @@ -235,8 +235,10 @@ impl DefGuardConfig { #[must_use] pub fn new() -> Self { let config = Self::parse(); - // TODO(jck) - // config.validate_secret_key(); + if let Some(secret_key) = &config.secret_key { + Settings::validate_secret_key(secret_key.expose_secret()) + .expect("Invalid DEFGUARD_SECRET_KEY"); + } config } diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 2c30217e90..b528e3d578 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -291,7 +291,7 @@ impl fmt::Debug for Settings { } impl Settings { - fn validate_secret_key(secret_key: &str) -> Result<(), SettingsRequiredValueError> { + pub(crate) fn validate_secret_key(secret_key: &str) -> Result<(), SettingsRequiredValueError> { if secret_key.trim().len() != secret_key.len() { return Err(SettingsRequiredValueError::Invalid( "secret_key", From 38e7b2408e09e811976bb8fb6b05edeea92c501d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Mar 2026 16:14:25 +0100 Subject: [PATCH 20/20] cargo fmt --- crates/defguard_common/src/config.rs | 4 ++-- crates/defguard_common/src/db/models/settings.rs | 12 ++++++++---- crates/defguard_core/src/appstate.rs | 2 +- crates/defguard_core/src/handlers/network_devices.rs | 4 ++-- crates/defguard_core/src/handlers/user.rs | 6 +++--- .../tests/integration/api/common/mod.rs | 2 +- crates/defguard_proxy_manager/src/handler.rs | 9 ++++----- .../src/servers/password_reset.rs | 2 +- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 56ed048635..b05f859d5d 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -3,13 +3,13 @@ use std::{net::IpAddr, sync::OnceLock}; use clap::{Args, Parser, Subcommand}; use humantime::Duration; use ipnetwork::IpNetwork; -use openidconnect::{core::CoreRsaPrivateSigningKey, JsonWebKeyId}; +use openidconnect::{JsonWebKeyId, core::CoreRsaPrivateSigningKey}; use reqwest::Url; use rsa::{ + RsaPrivateKey, pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey}, pkcs8::{DecodePrivateKey, LineEnding}, traits::PublicKeyParts, - RsaPrivateKey, }; use secrecy::{ExposeSecret, SecretString}; use serde::Serialize; diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index b528e3d578..616cef6c39 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -320,10 +320,13 @@ impl Settings { secret_key } - pub async fn ensure_secret_key(pool: &PgPool, config: &DefGuardConfig) -> Result<(), anyhow::Error> { + pub async fn ensure_secret_key( + pool: &PgPool, + config: &DefGuardConfig, + ) -> Result<(), anyhow::Error> { let mut settings = Settings::get_current_settings(); - #[allow(deprecated)] + #[allow(deprecated)] if let Some(secret_key) = &config.secret_key { let secret_key = secret_key.expose_secret(); Settings::validate_secret_key(secret_key)?; @@ -677,7 +680,7 @@ impl Settings { Url::parse(&self.public_proxy_url) } - #[allow(deprecated)] + #[allow(deprecated)] pub async fn update_from_config<'e, E>( &mut self, executor: E, @@ -720,7 +723,8 @@ impl Settings { self.stats_purge_threshold_days = (stats_purge_threshold.as_secs() / day) as i32; } if let Some(enrollment_token_timeout) = config.enrollment_token_timeout { - self.enrollment_token_timeout_hours = (enrollment_token_timeout.as_secs() / hour) as i32; + self.enrollment_token_timeout_hours = + (enrollment_token_timeout.as_secs() / hour) as i32; } if let Some(password_reset_token_timeout) = config.password_reset_token_timeout { self.password_reset_token_timeout_hours = diff --git a/crates/defguard_core/src/appstate.rs b/crates/defguard_core/src/appstate.rs index 06e6ed2113..6e06111b70 100644 --- a/crates/defguard_core/src/appstate.rs +++ b/crates/defguard_core/src/appstate.rs @@ -119,7 +119,7 @@ impl AppState { spawn(Self::handle_triggers(pool.clone(), rx)); let url = Settings::url().expect("Invalid Defguard URL configuration"); - let settings = Settings::get_current_settings(); + let settings = Settings::get_current_settings(); let webauthn_builder = WebauthnBuilder::new( settings .webauthn_rp_id diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index f076d096c9..8e2c78ee4e 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -453,7 +453,7 @@ pub(crate) async fn start_network_device_setup( &mut transaction, &user, None, - settings.enrollment_token_timeout().as_secs(), + settings.enrollment_token_timeout().as_secs(), settings.proxy_public_url()?.clone(), false, Some(result.device.id), @@ -518,7 +518,7 @@ pub(crate) async fn start_network_device_setup_for_device( &mut transaction, &user, None, - settings.enrollment_token_timeout().as_secs(), + settings.enrollment_token_timeout().as_secs(), settings.proxy_public_url()?, false, Some(device.id), diff --git a/crates/defguard_core/src/handlers/user.rs b/crates/defguard_core/src/handlers/user.rs index f1211b452e..884eed08f7 100644 --- a/crates/defguard_core/src/handlers/user.rs +++ b/crates/defguard_core/src/handlers/user.rs @@ -586,7 +586,7 @@ pub async fn start_remote_desktop_configuration( &mut transaction, &session.user, Some(email), - settings.enrollment_token_timeout().as_secs(), + settings.enrollment_token_timeout().as_secs(), public_proxy_url.clone(), data.send_enrollment_notification, None, @@ -1103,12 +1103,12 @@ pub async fn reset_password( Token::delete_unused_user_password_reset_tokens(&mut transaction, user.id).await?; - let settings = Settings::get_current_settings(); + let settings = Settings::get_current_settings(); let enrollment = Token::new( user.id, Some(session.user.id), Some(user.email.clone()), - settings.password_reset_token_timeout().as_secs(), + settings.password_reset_token_timeout().as_secs(), Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), ); enrollment.save(&mut *transaction).await?; diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index eb73c11454..372ff5d72c 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -5,6 +5,7 @@ use std::{ sync::{Arc, Mutex}, }; +use axum_extra::extract::cookie::Key; pub use defguard_common::db::setup_pool; use defguard_common::{ VERSION, @@ -23,7 +24,6 @@ use defguard_core::{ grpc::{GatewayEvent, WorkerState}, handlers::{Auth, user::UserDetails}, }; -use axum_extra::extract::cookie::Key; use reqwest::{StatusCode, header::HeaderName}; use semver::Version; use serde_json::json; diff --git a/crates/defguard_proxy_manager/src/handler.rs b/crates/defguard_proxy_manager/src/handler.rs index 984dbe8f89..02111cbcf8 100644 --- a/crates/defguard_proxy_manager/src/handler.rs +++ b/crates/defguard_proxy_manager/src/handler.rs @@ -90,7 +90,7 @@ pub(super) struct ProxyHandler { } impl ProxyHandler { - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments)] pub(super) fn new( pool: PgPool, url: Url, @@ -205,9 +205,8 @@ 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() @@ -727,7 +726,7 @@ impl ProxyHandler { user.id, Some(user.id), Some(user.email), - settings.enrollment_token_timeout().as_secs(), + settings.enrollment_token_timeout().as_secs(), Some(ENROLLMENT_TOKEN_TYPE.to_string()), ); debug!("Saving a new desktop configuration token..."); diff --git a/crates/defguard_proxy_manager/src/servers/password_reset.rs b/crates/defguard_proxy_manager/src/servers/password_reset.rs index f063ee2c9f..84ad4a81a6 100644 --- a/crates/defguard_proxy_manager/src/servers/password_reset.rs +++ b/crates/defguard_proxy_manager/src/servers/password_reset.rs @@ -135,7 +135,7 @@ impl PasswordResetServer { user.id, None, Some(email.clone()), - settings.password_reset_token_timeout().as_secs(), + settings.password_reset_token_timeout().as_secs(), Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), ); enrollment.save(&mut *transaction).await?;