From dccd4d8cd01d9f83df017981403953a12251e90f Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:22:00 +0200 Subject: [PATCH 01/12] service locations 1 --- crates/defguard_core/src/db/models/device.rs | 15 +- .../defguard_core/src/db/models/wireguard.rs | 222 +++++++++++++++++- .../src/enterprise/db/models/acl.rs | 8 +- .../src/enterprise/db/models/acl/tests.rs | 2 + .../src/enterprise/directory_sync/tests.rs | 6 +- crates/defguard_core/src/grpc/enrollment.rs | 23 +- crates/defguard_core/src/grpc/gateway/mod.rs | 17 +- crates/defguard_core/src/grpc/utils.rs | 88 +++++-- .../defguard_core/src/handlers/wireguard.rs | 17 +- crates/defguard_core/src/lib.rs | 52 ++-- crates/defguard_core/src/utility_thread.rs | 37 ++- crates/defguard_core/src/wg_config.rs | 6 +- .../src/wireguard_peer_disconnect.rs | 5 +- .../tests/integration/api/acl.rs | 7 +- .../tests/integration/api/wireguard.rs | 4 + .../api/wireguard_network_import.rs | 2 + .../tests/integration/grpc/gateway.rs | 6 +- .../20251015080719_service_locations.down.sql | 2 + .../20251015080719_service_locations.up.sql | 7 + proto | 2 +- web/src/i18n/en/index.ts | 25 ++ web/src/i18n/i18n-types.ts | 108 +++++++++ web/src/i18n/pl/index.ts | 57 +++++ .../NetworkEditForm/NetworkEditForm.tsx | 67 +++++- .../pages/network/NetworkEditForm/style.scss | 13 + .../FormLocationMfaModeSelect.tsx | 6 +- .../FormServiceLocationModeSelect.tsx | 84 +++++++ .../FormServiceLocationModeSelect/style.scss | 59 +++++ web/src/shared/types.ts | 7 + 29 files changed, 860 insertions(+), 94 deletions(-) create mode 100644 migrations/20251015080719_service_locations.down.sql create mode 100644 migrations/20251015080719_service_locations.up.sql create mode 100644 web/src/shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx create mode 100644 web/src/shared/components/Form/FormServiceLocationModeSelect/style.scss diff --git a/crates/defguard_core/src/db/models/device.rs b/crates/defguard_core/src/db/models/device.rs index 2c6433ca2e..385faa8aa3 100644 --- a/crates/defguard_core/src/db/models/device.rs +++ b/crates/defguard_core/src/db/models/device.rs @@ -26,7 +26,10 @@ use utoipa::ToSchema; use super::wireguard::{ LocationMfaMode, NetworkAddressError, WIREGUARD_MAX_HANDSHAKE, WireguardNetwork, }; -use crate::{KEY_LENGTH, db::User}; +use crate::{ + KEY_LENGTH, + db::{User, models::wireguard::ServiceLocationMode}, +}; #[derive(Serialize, ToSchema)] pub struct DeviceConfig { @@ -42,6 +45,7 @@ pub struct DeviceConfig { pub(crate) dns: Option, pub(crate) keepalive_interval: i32, pub(crate) location_mfa_mode: LocationMfaMode, + pub(crate) service_location_mode: ServiceLocationMode, } // The type of a device: @@ -501,7 +505,8 @@ impl WireguardNetworkDevice { WireguardNetwork, "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, keepalive_interval, peer_disconnect_threshold, \ - acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" \ + acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", \ + service_location_mode \"service_location_mode: ServiceLocationMode\" \ FROM wireguard_network WHERE id = $1", self.wireguard_network_id ) @@ -703,6 +708,7 @@ impl Device { dns: network.dns.clone(), keepalive_interval: network.keepalive_interval, location_mfa_mode: network.location_mfa_mode.clone(), + service_location_mode: network.service_location_mode.clone(), }; Ok((device_network_info, device_config)) @@ -736,6 +742,7 @@ impl Device { dns: network.dns.clone(), keepalive_interval: network.keepalive_interval, location_mfa_mode: network.location_mfa_mode.clone(), + service_location_mode: network.service_location_mode.clone(), }; Ok((device_network_info, device_config)) @@ -798,6 +805,7 @@ impl Device { dns: network.dns, keepalive_interval: network.keepalive_interval, location_mfa_mode: network.location_mfa_mode.clone(), + service_location_mode: network.service_location_mode.clone(), }); } } @@ -944,7 +952,8 @@ impl Device { WireguardNetwork, "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, keepalive_interval, peer_disconnect_threshold, \ - acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" \ + acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", \ + service_location_mode \"service_location_mode: ServiceLocationMode\" \ FROM wireguard_network WHERE id IN \ (SELECT wireguard_network_id FROM wireguard_network_device WHERE device_id = $1 ORDER BY id LIMIT 1)", self.id diff --git a/crates/defguard_core/src/db/models/wireguard.rs b/crates/defguard_core/src/db/models/wireguard.rs index fef77250f3..94c9940118 100644 --- a/crates/defguard_core/src/db/models/wireguard.rs +++ b/crates/defguard_core/src/db/models/wireguard.rs @@ -12,6 +12,13 @@ use defguard_common::{ csv::AsCsv, db::{Id, NoId, models::ModelError}, }; +use defguard_proto::{ + enterprise::firewall::FirewallConfig, + gateway::Peer, + proxy::{ + LocationMfaMode as ProtoLocationMfaMode, ServiceLocationMode as ProtoServiceLocationMode, + }, +}; use ipnetwork::{IpNetwork, IpNetworkError, NetworkSize}; use model_derive::Model; use rand::rngs::OsRng; @@ -37,10 +44,6 @@ use crate::{ grpc::gateway::{send_multiple_wireguard_events, state::GatewayState}, wg_config::ImportedDevice, }; -use defguard_proto::{ - enterprise::firewall::FirewallConfig, gateway::Peer, - proxy::LocationMfaMode as ProtoLocationMfaMode, -}; pub const DEFAULT_KEEPALIVE_INTERVAL: i32 = 25; pub const DEFAULT_DISCONNECT_THRESHOLD: i32 = 300; @@ -126,6 +129,38 @@ impl From for ProtoLocationMfaMode { } } +#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, ToSchema, Type)] +#[sqlx(type_name = "service_location_mode", rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum ServiceLocationMode { + #[default] + Disabled, + PreLogon, + AlwaysOn, +} + +impl From for ServiceLocationMode { + fn from(value: ProtoServiceLocationMode) -> Self { + match value { + ProtoServiceLocationMode::Unspecified | ProtoServiceLocationMode::Disabled => { + ServiceLocationMode::Disabled + } + ProtoServiceLocationMode::Prelogon => ServiceLocationMode::PreLogon, + ProtoServiceLocationMode::Alwayson => ServiceLocationMode::AlwaysOn, + } + } +} + +impl From for ProtoServiceLocationMode { + fn from(value: ServiceLocationMode) -> Self { + match value { + ServiceLocationMode::Disabled => ProtoServiceLocationMode::Disabled, + ServiceLocationMode::PreLogon => ProtoServiceLocationMode::Prelogon, + ServiceLocationMode::AlwaysOn => ProtoServiceLocationMode::Alwayson, + } + } +} + /// Stores configuration required to setup a WireGuard network #[derive(Clone, Debug, Deserialize, Eq, Hash, Model, PartialEq, Serialize, ToSchema)] #[table(wireguard_network)] @@ -151,6 +186,8 @@ pub struct WireguardNetwork { pub peer_disconnect_threshold: i32, #[model(enum)] pub location_mfa_mode: LocationMfaMode, + #[model(enum)] + pub service_location_mode: ServiceLocationMode, } pub struct WireguardKey { @@ -189,6 +226,7 @@ impl Default for WireguardNetwork { acl_default_allow: false, acl_enabled: false, location_mfa_mode: LocationMfaMode::default(), + service_location_mode: ServiceLocationMode::default(), } } } @@ -249,6 +287,7 @@ impl WireguardNetwork { acl_enabled: bool, acl_default_allow: bool, location_mfa_mode: LocationMfaMode, + service_location_mode: ServiceLocationMode, ) -> Self { let prvkey = StaticSecret::random_from_rng(OsRng); let pubkey = PublicKey::from(&prvkey); @@ -269,6 +308,7 @@ impl WireguardNetwork { acl_enabled, acl_default_allow, location_mfa_mode, + service_location_mode, } } @@ -299,7 +339,8 @@ impl WireguardNetwork { WireguardNetwork, "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, keepalive_interval, peer_disconnect_threshold, \ - acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" \ + acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", \ + service_location_mode \"service_location_mode: ServiceLocationMode\" \ FROM wireguard_network WHERE name = $1", name ) @@ -1238,7 +1279,8 @@ impl WireguardNetwork { WireguardNetwork, "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, \ - acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" \ + acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", \ + service_location_mode \"service_location_mode: ServiceLocationMode\" \ FROM wireguard_network WHERE location_mfa_mode = 'external'::location_mfa_mode", ) .fetch_all(executor) @@ -1282,6 +1324,7 @@ impl Default for WireguardNetwork { acl_enabled: false, acl_default_allow: false, location_mfa_mode: LocationMfaMode::default(), + service_location_mode: ServiceLocationMode::default(), } } } @@ -1418,7 +1461,7 @@ pub(crate) async fn networks_stats( mod test { use std::str::FromStr; - use chrono::{SubsecRound, TimeDelta}; + use chrono::{SubsecRound, TimeDelta, Utc}; use defguard_common::db::setup_pool; use matches::assert_matches; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; @@ -1995,6 +2038,7 @@ mod test { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await @@ -2126,6 +2170,7 @@ mod test { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await @@ -2245,4 +2290,167 @@ mod test { Err(NetworkAddressError::IsBroadcastAddress(..)) ); } + + #[sqlx::test] + async fn test_get_peers_service_location_modes(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + + let user = User::new( + "testuser", + Some("password123"), + "Test", + "User", + "test@example.com", + None, + ) + .save(&pool) + .await + .unwrap(); + + let device1 = Device::new( + "device1".into(), + "pubkey1".into(), + user.id, + DeviceType::User, + None, + true, + ) + .save(&pool) + .await + .unwrap(); + + let device2 = Device::new( + "device2".into(), + "pubkey2".into(), + user.id, + DeviceType::User, + None, + true, + ) + .save(&pool) + .await + .unwrap(); + + // Normal location (service_location_mode = Disabled) should return peers + let mut network_normal = WireguardNetwork { + name: "normal-location".to_string(), + service_location_mode: ServiceLocationMode::Disabled, + location_mfa_mode: LocationMfaMode::Disabled, + ..Default::default() + }; + network_normal.try_set_address("10.1.1.1/24").unwrap(); + let network_normal = network_normal.save(&pool).await.unwrap(); + + WireguardNetworkDevice::new( + network_normal.id, + device1.id, + vec![IpAddr::from_str("10.1.1.2").unwrap()], + ) + .insert(&pool) + .await + .unwrap(); + + let peers_normal = network_normal.get_peers(&pool).await.unwrap(); + assert_eq!(peers_normal.len(), 1, "Normal location should return peers"); + assert_eq!(peers_normal[0].pubkey, "pubkey1"); + + // Service location with PreLogon mode returns peers when enterprise is enabled (test env default) + let mut network_prelogon = WireguardNetwork { + name: "prelogon-service-location".to_string(), + service_location_mode: ServiceLocationMode::PreLogon, + location_mfa_mode: LocationMfaMode::Disabled, + ..Default::default() + }; + network_prelogon.try_set_address("10.2.1.1/24").unwrap(); + let network_prelogon = network_prelogon.save(&pool).await.unwrap(); + + WireguardNetworkDevice::new( + network_prelogon.id, + device2.id, + vec![IpAddr::from_str("10.2.1.2").unwrap()], + ) + .insert(&pool) + .await + .unwrap(); + + // PreLogon service location should return peers when enterprise is enabled + let peers_prelogon = network_prelogon.get_peers(&pool).await.unwrap(); + assert_eq!( + peers_prelogon.len(), + 1, + "PreLogon service location should return peers when enterprise is enabled" + ); + assert_eq!(peers_prelogon[0].pubkey, "pubkey2"); + + // Service location with AlwaysOn mode also returns peers when enterprise is enabled + let mut network_alwayson = WireguardNetwork { + name: "alwayson-service-location".to_string(), + service_location_mode: ServiceLocationMode::AlwaysOn, + location_mfa_mode: LocationMfaMode::Disabled, + ..Default::default() + }; + network_alwayson.try_set_address("10.3.1.1/24").unwrap(); + let network_alwayson = network_alwayson.save(&pool).await.unwrap(); + + let device3 = Device::new( + "device3".into(), + "pubkey3".into(), + user.id, + DeviceType::User, + None, + true, + ) + .save(&pool) + .await + .unwrap(); + + WireguardNetworkDevice::new( + network_alwayson.id, + device3.id, + vec![IpAddr::from_str("10.3.1.2").unwrap()], + ) + .insert(&pool) + .await + .unwrap(); + + // AlwaysOn service location should return peers when enterprise is enabled + let peers_alwayson = network_alwayson.get_peers(&pool).await.unwrap(); + assert_eq!( + peers_alwayson.len(), + 1, + "AlwaysOn service location should return peers when enterprise is enabled" + ); + assert_eq!(peers_alwayson[0].pubkey, "pubkey3"); + + // Now test the negative case: service locations with enterprise disabled + // Exceed the enterprise limits to disable enterprise features + use crate::enterprise::limits::{Counts, DEFAULT_LOCATIONS_LIMIT, set_counts}; + let over_limit_counts = Counts::new(1, 1, DEFAULT_LOCATIONS_LIMIT + 1, 0); + set_counts(over_limit_counts); + + // Test that normal location still returns peers even without enterprise + let peers_normal_no_ent = network_normal.get_peers(&pool).await.unwrap(); + assert_eq!( + peers_normal_no_ent.len(), + 1, + "Normal location should still return peers without enterprise" + ); + + // Test that PreLogon service location returns NO peers without enterprise + let peers_prelogon_no_ent = network_prelogon.get_peers(&pool).await.unwrap(); + assert!( + peers_prelogon_no_ent.is_empty(), + "PreLogon service location should return NO peers when enterprise is disabled" + ); + + // Test that AlwaysOn service location returns NO peers without enterprise + let peers_alwayson_no_ent = network_alwayson.get_peers(&pool).await.unwrap(); + assert!( + peers_alwayson_no_ent.is_empty(), + "AlwaysOn service location should return NO peers when enterprise is disabled" + ); + + let normal_counts = Counts::new(0, 0, 0, 0); + set_counts(normal_counts); + } } diff --git a/crates/defguard_core/src/enterprise/db/models/acl.rs b/crates/defguard_core/src/enterprise/db/models/acl.rs index faddd5c08f..22bb80774c 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl.rs @@ -18,7 +18,10 @@ use thiserror::Error; use crate::{ DeviceType, appstate::AppState, - db::{Device, GatewayEvent, Group, User, WireguardNetwork, models::wireguard::LocationMfaMode}, + db::{ + Device, GatewayEvent, Group, User, WireguardNetwork, + models::wireguard::{LocationMfaMode, ServiceLocationMode}, + }, enterprise::{ firewall::FirewallError, handlers::acl::{ApiAclAlias, ApiAclRule, EditAclAlias, EditAclRule}, @@ -906,7 +909,8 @@ impl AclRule { WireguardNetwork, "SELECT n.id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, keepalive_interval, peer_disconnect_threshold, \ - acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" \ + acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", \ + service_location_mode \"service_location_mode: ServiceLocationMode\" \ FROM aclrulenetwork r \ JOIN wireguard_network n \ ON n.id = r.network_id \ diff --git a/crates/defguard_core/src/enterprise/db/models/acl/tests.rs b/crates/defguard_core/src/enterprise/db/models/acl/tests.rs index becdc26481..85cdd42d82 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl/tests.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl/tests.rs @@ -183,6 +183,7 @@ async fn test_rule_relations(_: PgPoolOptions, options: PgConnectOptions) { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await @@ -199,6 +200,7 @@ async fn test_rule_relations(_: PgPoolOptions, options: PgConnectOptions) { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await diff --git a/crates/defguard_core/src/enterprise/directory_sync/tests.rs b/crates/defguard_core/src/enterprise/directory_sync/tests.rs index d6ae4f4beb..e0d1a5496c 100644 --- a/crates/defguard_core/src/enterprise/directory_sync/tests.rs +++ b/crates/defguard_core/src/enterprise/directory_sync/tests.rs @@ -18,7 +18,10 @@ mod test { use crate::{ db::{ Device, Session, SessionState, WireguardNetwork, - models::{device::DeviceType, wireguard::LocationMfaMode}, + models::{ + device::DeviceType, + wireguard::{LocationMfaMode, ServiceLocationMode}, + }, }, enterprise::db::models::openid_provider::DirectorySyncTarget, }; @@ -59,6 +62,7 @@ mod test { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(pool) .await diff --git a/crates/defguard_core/src/grpc/enrollment.rs b/crates/defguard_core/src/grpc/enrollment.rs index 2a744913e8..ddf124c597 100644 --- a/crates/defguard_core/src/grpc/enrollment.rs +++ b/crates/defguard_core/src/grpc/enrollment.rs @@ -11,6 +11,14 @@ use defguard_mail::{ Mail, templates::{self, TemplateLocation}, }; +use defguard_proto::proxy::{ + ActivateUserRequest, AdminInfo, CodeMfaSetupFinishRequest, CodeMfaSetupFinishResponse, + CodeMfaSetupStartRequest, CodeMfaSetupStartResponse, Device as ProtoDevice, + DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, EnrollmentStartRequest, + EnrollmentStartResponse, ExistingDevice, InitialUserInfo, + LocationMfaMode as ProtoLocationMfaMode, MfaMethod, NewDevice, RegisterMobileAuthRequest, + ServiceLocationMode as ProtoServiceLocationMode, +}; use sqlx::{PgPool, Transaction, query_scalar}; use tokio::sync::{ broadcast::Sender, @@ -26,7 +34,7 @@ use crate::{ device::{DeviceConfig, DeviceInfo, DeviceType}, enrollment::{ENROLLMENT_TOKEN_TYPE, Token, TokenError}, polling_token::PollingToken, - wireguard::LocationMfaMode, + wireguard::{LocationMfaMode, ServiceLocationMode}, }, }, enterprise::{ @@ -45,13 +53,6 @@ use crate::{ headers::get_device_info, is_valid_phone_number, server_config, }; -use defguard_proto::proxy::{ - ActivateUserRequest, AdminInfo, CodeMfaSetupFinishRequest, CodeMfaSetupFinishResponse, - CodeMfaSetupStartRequest, CodeMfaSetupStartResponse, Device as ProtoDevice, - DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, EnrollmentStartRequest, - EnrollmentStartResponse, ExistingDevice, InitialUserInfo, - LocationMfaMode as ProtoLocationMfaMode, MfaMethod, NewDevice, RegisterMobileAuthRequest, -}; pub(super) struct EnrollmentServer { pool: PgPool, @@ -1078,6 +1079,12 @@ impl From for ProtoDeviceConfig { >::into(config.location_mfa_mode) .into(), ), + service_location_mode: Some( + >::into( + config.service_location_mode, + ) + .into(), + ), } } } diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index ca3d668283..f25b7cba4d 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -35,8 +35,12 @@ use self::map::GatewayMap; use crate::{ db::{ Device, GatewayEvent, User, - models::{wireguard::WireguardNetwork, wireguard_peer_stats::WireguardPeerStats}, + models::{ + wireguard::{ServiceLocationMode, WireguardNetwork}, + wireguard_peer_stats::WireguardPeerStats, + }, }, + enterprise::is_enterprise_enabled, events::{GrpcEvent, GrpcRequestContext}, }; @@ -95,11 +99,22 @@ impl WireguardNetwork { /// /// Each device is marked as allowed or not allowed in a given network, /// which enables enforcing peer disconnect in MFA-protected networks. + /// + /// If the location is a service location, only returns peers if enterprise features are enabled. pub async fn get_peers<'e, E>(&self, executor: E) -> Result, SqlxError> where E: PgExecutor<'e>, { debug!("Fetching all peers for network {}", self.id); + + if self.service_location_mode != ServiceLocationMode::Disabled && !is_enterprise_enabled() { + warn!( + "Tried to use service location {} with disabled enterprise features. No clients will be allowed to connect.", + self.name + ); + return Ok(Vec::new()); + } + let rows = query!( "SELECT d.wireguard_pubkey pubkey, preshared_key, \ -- TODO possible to not use ARRAY-unnest here? diff --git a/crates/defguard_core/src/grpc/utils.rs b/crates/defguard_core/src/grpc/utils.rs index 757b9d9490..99549f9cb7 100644 --- a/crates/defguard_core/src/grpc/utils.rs +++ b/crates/defguard_core/src/grpc/utils.rs @@ -4,6 +4,10 @@ use defguard_common::{ csv::AsCsv, db::{Id, models::Settings}, }; +use defguard_proto::proxy::{ + DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, DeviceInfo, + LocationMfaMode as ProtoLocationMfaMode, +}; use sqlx::PgPool; use tonic::Status; @@ -14,17 +18,14 @@ use crate::{ models::{ device::{DeviceType, WireguardNetworkDevice}, polling_token::PollingToken, - wireguard::{LocationMfaMode, WireguardNetwork}, + wireguard::{LocationMfaMode, ServiceLocationMode, WireguardNetwork}, }, }, - enterprise::db::models::{ - enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider, + enterprise::{ + db::models::{enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider}, + is_enterprise_enabled, }, }; -use defguard_proto::proxy::{ - DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, DeviceInfo, - LocationMfaMode as ProtoLocationMfaMode, -}; // Create a new token for configuration polling. pub(crate) async fn new_polling_token( @@ -122,27 +123,46 @@ pub(crate) async fn build_device_config_response( ); Status::internal(format!("unexpected error: {err}")) })?; + if !is_enterprise_enabled() + && network.service_location_mode != ServiceLocationMode::Disabled + { + error!( + "Tried to use service location {} with disabled enterprise features.", + network.name + ); + return Err(Status::permission_denied( + "service location mode is not available", + )); + } // DEPRECATED(1.5): superseeded by location_mfa_mode let mfa_enabled = network.location_mfa_mode == LocationMfaMode::Internal; - let config = ProtoDeviceConfig { - config: Device::create_config(&network, &wireguard_network_device), - network_id: network.id, - network_name: network.name, - assigned_ip: wireguard_network_device.wireguard_ips.as_csv(), - endpoint: format!("{}:{}", network.endpoint, network.port), - pubkey: network.pubkey, - allowed_ips: network.allowed_ips.as_csv(), - dns: network.dns, - keepalive_interval: network.keepalive_interval, - #[allow(deprecated)] - mfa_enabled, - location_mfa_mode: Some( - >::into( - network.location_mfa_mode, - ) - .into(), - ), - }; + let config = + ProtoDeviceConfig { + config: Device::create_config(&network, &wireguard_network_device), + network_id: network.id, + network_name: network.name, + assigned_ip: wireguard_network_device.wireguard_ips.as_csv(), + endpoint: format!("{}:{}", network.endpoint, network.port), + pubkey: network.pubkey, + allowed_ips: network.allowed_ips.as_csv(), + dns: network.dns, + keepalive_interval: network.keepalive_interval, + #[allow(deprecated)] + mfa_enabled, + location_mfa_mode: Some( + >::into( + network.location_mfa_mode, + ) + .into(), + ), + service_location_mode: + Some( + >::into(network.service_location_mode) + .into(), + ), + }; configs.push(config); } } else { @@ -158,6 +178,15 @@ pub(crate) async fn build_device_config_response( ); Status::internal(format!("unexpected error: {err}")) })?; + if !is_enterprise_enabled() + && network.service_location_mode != ServiceLocationMode::Disabled + { + warn!( + "Tried to use service location {} with disabled enterprise features.", + network.name + ); + continue; + } // DEPRECATED(1.5): superseeded by location_mfa_mode let mfa_enabled = network.location_mfa_mode == LocationMfaMode::Internal; if let Some(wireguard_network_device) = wireguard_network_device { @@ -179,6 +208,13 @@ pub(crate) async fn build_device_config_response( ) .into(), ), + service_location_mode: + Some( + >::into(network.service_location_mode) + .into(), + ), }; configs.push(config); } diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 466cc7cb62..40186203b1 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -31,8 +31,9 @@ use crate::{ WireguardNetworkDevice, }, wireguard::{ - DateTimeAggregation, LocationMfaMode, MappedDevice, WireguardDeviceStatsRow, - WireguardNetworkInfo, WireguardNetworkStats, WireguardUserStatsRow, networks_stats, + DateTimeAggregation, LocationMfaMode, MappedDevice, ServiceLocationMode, + WireguardDeviceStatsRow, WireguardNetworkInfo, WireguardNetworkStats, + WireguardUserStatsRow, networks_stats, }, }, }, @@ -85,6 +86,7 @@ pub struct WireguardNetworkData { pub acl_enabled: bool, pub acl_default_allow: bool, pub location_mfa_mode: LocationMfaMode, + pub service_location_mode: ServiceLocationMode, } impl WireguardNetworkData { @@ -196,6 +198,7 @@ pub(crate) async fn create_network( data.acl_enabled, data.acl_default_allow, data.location_mfa_mode, + data.service_location_mode, ); let mut transaction = appstate.pool.begin().await?; @@ -292,6 +295,16 @@ pub(crate) async fn modify_network( network.peer_disconnect_threshold = data.peer_disconnect_threshold; network.acl_enabled = data.acl_enabled; network.acl_default_allow = data.acl_default_allow; + network.service_location_mode = match data.location_mfa_mode { + LocationMfaMode::Disabled => data.service_location_mode, + _ => { + warn!( + "Disabling service location mode for location {} because location MFA is enabled", + network.name + ); + ServiceLocationMode::Disabled + } + }; network.location_mfa_mode = data.location_mfa_mode; network.save(&mut *transaction).await?; diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 5e200f2725..0c4f17c3c4 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -6,7 +6,6 @@ use std::{ sync::{Arc, LazyLock, Mutex, RwLock}, }; -use crate::version::IncompatibleComponents; use anyhow::anyhow; use axum::{ Extension, Json, Router, @@ -89,31 +88,19 @@ use utoipa::{ }; use utoipa_swagger_ui::SwaggerUi; -use self::handlers::wireguard::{ - add_device, add_user_devices, create_network, create_network_token, delete_device, - delete_network, devices_stats, download_config, gateway_status, get_device, import_network, - list_devices, list_networks, list_user_devices, modify_device, modify_network, network_details, - network_stats, remove_gateway, -}; -use self::handlers::worker::{ - create_job, create_worker_token, job_status, list_workers, remove_worker, -}; -use self::handlers::{ - openid_clients::{ - add_openid_client, change_openid_client, change_openid_client_state, delete_openid_client, - get_openid_client, list_openid_clients, - }, - openid_flow::{ - authorization, discovery_keys, openid_configuration, secure_authorization, token, userinfo, - }, -}; use self::{ appstate::AppState, + auth::failed_login::FailedLoginMap, db::{ AppEvent, Device, GatewayEvent, User, WireguardNetwork, - models::wireguard::{DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL}, + models::{ + oauth2client::OAuth2Client, + wireguard::{DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL}, + }, }, + grpc::{WorkerState, gateway::map::GatewayMap}, handlers::{ + app_info::get_app_info, auth::{ authenticate, email_mfa_code, email_mfa_disable, email_mfa_enable, email_mfa_init, logout, mfa_disable, mfa_enable, recovery_code, request_email_mfa_code, totp_code, @@ -126,6 +113,14 @@ use self::{ remove_group_member, }, mail::{send_support_data, test_mail}, + openid_clients::{ + add_openid_client, change_openid_client, change_openid_client_state, + delete_openid_client, get_openid_client, list_openid_clients, + }, + openid_flow::{ + authorization, discovery_keys, openid_configuration, secure_authorization, token, + userinfo, + }, settings::{ get_settings, get_settings_essentials, patch_settings, set_default_branding, test_ldap_settings, update_settings, @@ -142,14 +137,16 @@ use self::{ webhooks::{ add_webhook, change_enabled, change_webhook, delete_webhook, get_webhook, list_webhooks, }, + wireguard::{ + add_device, add_user_devices, create_network, create_network_token, delete_device, + delete_network, devices_stats, download_config, gateway_status, get_device, + import_network, list_devices, list_networks, list_user_devices, modify_device, + modify_network, network_details, network_stats, remove_gateway, + }, + worker::{create_job, create_worker_token, job_status, list_workers, remove_worker}, }, }; -use self::{ - auth::failed_login::FailedLoginMap, - db::models::oauth2client::OAuth2Client, - grpc::{WorkerState, gateway::map::GatewayMap}, - handlers::app_info::get_app_info, -}; +use crate::{db::models::wireguard::ServiceLocationMode, version::IncompatibleComponents}; pub mod appstate; pub mod auth; @@ -777,6 +774,7 @@ pub async fn init_dev_env(config: &DefGuardConfig) { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ); network.pubkey = "zGMeVGm9HV9I4wSKF9AXmYnnAIhDySyqLMuKpcfIaQo=".to_string(); network.prvkey = "MAk3d5KuB167G88HM7nGYR6ksnPMAOguAg2s5EcPp1M=".to_string(); @@ -874,6 +872,7 @@ pub async fn init_vpn_location( false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&mut *transaction) .await?; @@ -913,6 +912,7 @@ pub async fn init_vpn_location( false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(pool) .await? diff --git a/crates/defguard_core/src/utility_thread.rs b/crates/defguard_core/src/utility_thread.rs index 6533ff71c5..366dc83c4a 100644 --- a/crates/defguard_core/src/utility_thread.rs +++ b/crates/defguard_core/src/utility_thread.rs @@ -9,7 +9,7 @@ use tokio::{ use tracing::Instrument; use crate::{ - db::{GatewayEvent, WireguardNetwork}, + db::{GatewayEvent, WireguardNetwork, models::wireguard::ServiceLocationMode}, enterprise::{ db::models::acl::{AclRule, RuleState}, directory_sync::{do_directory_sync, get_directory_sync_interval}, @@ -172,25 +172,48 @@ async fn enterprise_status_check( if new_enterprise_enabled { // handle switch from disabled -> enabled debug!("Re-enabling gateway firewall configuration for ACL-enabled locations"); - let mut conn = pool.acquire().await?; + let mut transaction = pool.begin().await?; for location in locations { debug!("Re-enabling gateway firewall configuration for location {location:?}"); let firewall_config = location - .try_get_firewall_config(&mut conn) + .try_get_firewall_config(&mut transaction) .await? .expect("ACL-enabled location must have firewall config"); - wireguard_tx.send(GatewayEvent::FirewallConfigChanged( - location.id, - firewall_config, - ))?; + // Handle service location update or just update the firewall + if location.service_location_mode != ServiceLocationMode::Disabled { + let new_peers = location.get_peers(&mut *transaction).await?; + wireguard_tx.send(GatewayEvent::NetworkModified( + location.id, + location, + new_peers, + Some(firewall_config), + ))?; + } else { + wireguard_tx.send(GatewayEvent::FirewallConfigChanged( + location.id, + firewall_config, + ))?; + } } + transaction.commit().await?; } else { // handle switch from enabled -> disabled debug!("Disabling gateway firewall configuration for ACL-enabled locations"); for location in locations { debug!("Disabling gateway firewall configuration for location {location:?}"); wireguard_tx.send(GatewayEvent::FirewallDisabled(location.id))?; + + // Handle service location update + if location.service_location_mode != ServiceLocationMode::Disabled { + let new_peers = location.get_peers(pool).await?; + wireguard_tx.send(GatewayEvent::NetworkModified( + location.id, + location, + new_peers, + None, + ))?; + } } } } diff --git a/crates/defguard_core/src/wg_config.rs b/crates/defguard_core/src/wg_config.rs index 2a42ea68dd..38d40c6f0c 100644 --- a/crates/defguard_core/src/wg_config.rs +++ b/crates/defguard_core/src/wg_config.rs @@ -11,7 +11,7 @@ use crate::{ Device, WireguardNetwork, models::wireguard::{ DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL, LocationMfaMode, - WireguardNetworkError, + ServiceLocationMode, WireguardNetworkError, }, }, }; @@ -112,6 +112,7 @@ pub(crate) fn parse_wireguard_config( false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ); network.pubkey = pubkey; network.prvkey = prvkey.to_string(); @@ -170,9 +171,10 @@ pub(crate) fn parse_wireguard_config( #[cfg(test)] mod test { - use super::*; use defguard_common::db::NoId; + use super::*; + #[test] fn test_parse_config() { let config = " diff --git a/crates/defguard_core/src/wireguard_peer_disconnect.rs b/crates/defguard_core/src/wireguard_peer_disconnect.rs index 537db19a30..12a5d02acb 100644 --- a/crates/defguard_core/src/wireguard_peer_disconnect.rs +++ b/crates/defguard_core/src/wireguard_peer_disconnect.rs @@ -27,7 +27,7 @@ use crate::{ Device, GatewayEvent, WireguardNetwork, models::{ device::{DeviceInfo, DeviceNetworkInfo, DeviceType, WireguardNetworkDevice}, - wireguard::{LocationMfaMode, WireguardNetworkError}, + wireguard::{LocationMfaMode, ServiceLocationMode, WireguardNetworkError}, }, }, events::{InternalEvent, InternalEventContext}, @@ -97,7 +97,8 @@ pub async fn run_periodic_peer_disconnect( "SELECT \ id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, keepalive_interval, peer_disconnect_threshold, \ - acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" \ + acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", \ + service_location_mode \"service_location_mode: ServiceLocationMode\" \ FROM wireguard_network WHERE location_mfa_mode != 'disabled'::location_mfa_mode", ) .fetch_all(&pool) diff --git a/crates/defguard_core/tests/integration/api/acl.rs b/crates/defguard_core/tests/integration/api/acl.rs index 352c92b54b..1272d5d8d9 100644 --- a/crates/defguard_core/tests/integration/api/acl.rs +++ b/crates/defguard_core/tests/integration/api/acl.rs @@ -5,7 +5,10 @@ use defguard_common::{ use defguard_core::{ db::{ Device, Group, User, WireguardNetwork, - models::{device::DeviceType, wireguard::LocationMfaMode}, + models::{ + device::DeviceType, + wireguard::{LocationMfaMode, ServiceLocationMode}, + }, }, enterprise::{ db::models::acl::{AclAlias, AclRule, AliasKind, AliasState, RuleState}, @@ -427,6 +430,7 @@ async fn test_related_objects(_: PgPoolOptions, options: PgConnectOptions) { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await @@ -767,6 +771,7 @@ async fn test_rule_delete_state_applied(_: PgPoolOptions, options: PgConnectOpti false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await diff --git a/crates/defguard_core/tests/integration/api/wireguard.rs b/crates/defguard_core/tests/integration/api/wireguard.rs index 468e2a8cbd..436ba629fe 100644 --- a/crates/defguard_core/tests/integration/api/wireguard.rs +++ b/crates/defguard_core/tests/integration/api/wireguard.rs @@ -8,6 +8,7 @@ use defguard_core::{ device::WireguardNetworkDevice, wireguard::{ DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL, LocationMfaMode, + ServiceLocationMode, }, }, }, @@ -71,6 +72,7 @@ async fn test_network(_: PgPoolOptions, options: PgConnectOptions) { acl_enabled: false, acl_default_allow: false, location_mfa_mode: LocationMfaMode::Disabled, + service_location_mode: ServiceLocationMode::Disabled, }; let response = client .put(format!("/api/v1/network/{}", network.id)) @@ -150,6 +152,7 @@ async fn test_location_mfa_mode_validation_create(_: PgPoolOptions, options: PgC acl_enabled: false, acl_default_allow: false, location_mfa_mode: LocationMfaMode::External, + service_location_mode: ServiceLocationMode::Disabled, }; // create network @@ -229,6 +232,7 @@ async fn test_location_mfa_mode_validation_modify(_: PgPoolOptions, options: PgC acl_enabled: false, acl_default_allow: false, location_mfa_mode: LocationMfaMode::Disabled, + service_location_mode: ServiceLocationMode::Disabled, }; // create network diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_import.rs b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs index aad4c8d62c..9db3ce63eb 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_import.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs @@ -7,6 +7,7 @@ use defguard_core::{ device::{DeviceType, UserDevice}, wireguard::{ DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL, LocationMfaMode, + ServiceLocationMode, }, }, }, @@ -62,6 +63,7 @@ async fn test_config_import(_: PgPoolOptions, options: PgConnectOptions) { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ); initial_network.save(&pool).await.unwrap(); diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index 8be4c88c31..3f35c2007c 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -10,7 +10,8 @@ use defguard_core::{ db::{ Device, User, WireguardNetwork, models::{ - device::DeviceType, wireguard::LocationMfaMode, + device::DeviceType, + wireguard::{LocationMfaMode, ServiceLocationMode}, wireguard_peer_stats::WireguardPeerStats, }, }, @@ -50,6 +51,7 @@ async fn setup_test_server( false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await @@ -399,6 +401,7 @@ async fn test_gateway_update_routing(_: PgPoolOptions, options: PgConnectOptions false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await @@ -520,6 +523,7 @@ async fn test_gateway_config(_: PgPoolOptions, options: PgConnectOptions) { false, false, LocationMfaMode::Disabled, + ServiceLocationMode::Disabled, ) .save(&pool) .await diff --git a/migrations/20251015080719_service_locations.down.sql b/migrations/20251015080719_service_locations.down.sql new file mode 100644 index 0000000000..2cc3003aa7 --- /dev/null +++ b/migrations/20251015080719_service_locations.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE wireguard_network DROP COLUMN "service_location_mode"; +DROP TYPE service_location_mode; diff --git a/migrations/20251015080719_service_locations.up.sql b/migrations/20251015080719_service_locations.up.sql new file mode 100644 index 0000000000..4347d98b12 --- /dev/null +++ b/migrations/20251015080719_service_locations.up.sql @@ -0,0 +1,7 @@ +CREATE TYPE service_location_mode AS ENUM ( + 'disabled', + 'prelogon', + 'alwayson' +); + +ALTER TABLE wireguard_network ADD COLUMN "service_location_mode" service_location_mode NOT NULL DEFAULT 'disabled'; diff --git a/proto b/proto index 883487df67..3fd150c024 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 883487df67d90fd14fae900737cd8b5ea6c10de3 +Subproject commit 3fd150c0245f5ed088ed57ad780a9376e3377ce3 diff --git a/web/src/i18n/en/index.ts b/web/src/i18n/en/index.ts index b8a7a44721..76f8484653 100644 --- a/web/src/i18n/en/index.ts +++ b/web/src/i18n/en/index.ts @@ -1162,6 +1162,14 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do external: 'External MFA', }, }, + serviceLocationModeSelect: { + label: 'Service Location Mode', + options: { + disabled: 'Disabled', + prelogon: 'Pre-logon', + alwayson: 'Always-on', + }, + }, }, settingsPage: { title: 'Settings', @@ -2030,6 +2038,17 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do "Internal MFA - MFA is enforced using Defguard's built-in MFA (e.g. TOTP, WebAuthn) with internal identity", external: 'External MFA - If configured (see [OpenID settings](settings)) this option uses external identity provider for MFA', + serviceLocationWarning: + "Location MFA can't be used when service location mode is enabled.", + }, + serviceLocation: { + description: + 'Choose if this location should work as a service location. This feature is currently not supported on every platform. Consult our [documentation](https://docs.defguard.net/features/service-locations) for more details.', + preLogon: + 'Pre-logon - A VPN connection to this location will be active only before the user logs in on their device. When the user completes the login, the VPN connection will be terminated.', + alwaysOn: + 'Always-on - A VPN connection will always be active when the user device is on.', + mfaWarning: "Service locations can't be used while location MFA is enabled.", }, }, sections: { @@ -2039,6 +2058,9 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do mfa: { header: 'Multi-Factor Authentication', }, + serviceLocation: { + header: 'Service location', + }, }, messages: { networkModified: 'Location modified.', @@ -2082,6 +2104,9 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do location_mfa_mode: { label: 'MFA requirement', }, + service_location_mode: { + label: 'Service location mode', + }, }, controls: { submit: 'Save changes', diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts index 2bd9e20581..e934c44085 100644 --- a/web/src/i18n/i18n-types.ts +++ b/web/src/i18n/i18n-types.ts @@ -2851,6 +2851,26 @@ type RootTranslation = { external: string } } + serviceLocationModeSelect: { + /** + * S​e​r​v​i​c​e​ ​L​o​c​a​t​i​o​n​ ​M​o​d​e + */ + label: string + options: { + /** + * D​i​s​a​b​l​e​d + */ + disabled: string + /** + * P​r​e​-​l​o​g​o​n + */ + prelogon: string + /** + * A​l​w​a​y​s​-​o​n + */ + alwayson: string + } + } } settingsPage: { /** @@ -4871,6 +4891,28 @@ type RootTranslation = { * E​x​t​e​r​n​a​l​ ​M​F​A​ ​-​ ​I​f​ ​c​o​n​f​i​g​u​r​e​d​ ​(​s​e​e​ ​[​O​p​e​n​I​D​ ​s​e​t​t​i​n​g​s​]​(​s​e​t​t​i​n​g​s​)​)​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​u​s​e​s​ ​e​x​t​e​r​n​a​l​ ​i​d​e​n​t​i​t​y​ ​p​r​o​v​i​d​e​r​ ​f​o​r​ ​M​F​A */ external: string + /** + * L​o​c​a​t​i​o​n​ ​M​F​A​ ​c​a​n​'​t​ ​b​e​ ​u​s​e​d​ ​w​h​e​n​ ​s​e​r​v​i​c​e​ ​l​o​c​a​t​i​o​n​ ​m​o​d​e​ ​i​s​ ​e​n​a​b​l​e​d​. + */ + serviceLocationWarning: string + } + serviceLocation: { + /** + * C​h​o​o​s​e​ ​i​f​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n​ ​s​h​o​u​l​d​ ​w​o​r​k​ ​a​s​ ​a​ ​s​e​r​v​i​c​e​ ​l​o​c​a​t​i​o​n​.​ ​T​h​i​s​ ​f​e​a​t​u​r​e​ ​i​s​ ​c​u​r​r​e​n​t​l​y​ ​n​o​t​ ​s​u​p​p​o​r​t​e​d​ ​o​n​ ​e​v​e​r​y​ ​p​l​a​t​f​o​r​m​.​ ​C​o​n​s​u​l​t​ ​o​u​r​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​f​e​a​t​u​r​e​s​/​s​e​r​v​i​c​e​-​l​o​c​a​t​i​o​n​s​)​ ​f​o​r​ ​m​o​r​e​ ​d​e​t​a​i​l​s​. + */ + description: string + /** + * P​r​e​-​l​o​g​o​n​ ​-​ ​A​ ​V​P​N​ ​c​o​n​n​e​c​t​i​o​n​ ​t​o​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​a​c​t​i​v​e​ ​o​n​l​y​ ​b​e​f​o​r​e​ ​t​h​e​ ​u​s​e​r​ ​l​o​g​s​ ​i​n​ ​o​n​ ​t​h​e​i​r​ ​d​e​v​i​c​e​.​ ​W​h​e​n​ ​t​h​e​ ​u​s​e​r​ ​c​o​m​p​l​e​t​e​s​ ​t​h​e​ ​l​o​g​i​n​,​ ​t​h​e​ ​V​P​N​ ​c​o​n​n​e​c​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​t​e​r​m​i​n​a​t​e​d​. + */ + preLogon: string + /** + * A​l​w​a​y​s​-​o​n​ ​-​ ​A​ ​V​P​N​ ​c​o​n​n​e​c​t​i​o​n​ ​w​i​l​l​ ​a​l​w​a​y​s​ ​b​e​ ​a​c​t​i​v​e​ ​w​h​e​n​ ​t​h​e​ ​u​s​e​r​ ​d​e​v​i​c​e​ ​i​s​ ​o​n​. + */ + alwaysOn: string + /** + * S​e​r​v​i​c​e​ ​l​o​c​a​t​i​o​n​s​ ​c​a​n​'​t​ ​b​e​ ​u​s​e​d​ ​w​h​i​l​e​ ​l​o​c​a​t​i​o​n​ ​M​F​A​ ​i​s​ ​e​n​a​b​l​e​d​. + */ + mfaWarning: string } } sections: { @@ -4886,6 +4928,12 @@ type RootTranslation = { */ header: string } + serviceLocation: { + /** + * S​e​r​v​i​c​e​ ​l​o​c​a​t​i​o​n + */ + header: string + } } messages: { /** @@ -4974,6 +5022,12 @@ type RootTranslation = { */ label: string } + service_location_mode: { + /** + * S​e​r​v​i​c​e​ ​l​o​c​a​t​i​o​n​ ​m​o​d​e + */ + label: string + } } controls: { /** @@ -9525,6 +9579,26 @@ export type TranslationFunctions = { external: () => LocalizedString } } + serviceLocationModeSelect: { + /** + * Service Location Mode + */ + label: () => LocalizedString + options: { + /** + * Disabled + */ + disabled: () => LocalizedString + /** + * Pre-logon + */ + prelogon: () => LocalizedString + /** + * Always-on + */ + alwayson: () => LocalizedString + } + } } settingsPage: { /** @@ -11526,6 +11600,28 @@ export type TranslationFunctions = { * External MFA - If configured (see [OpenID settings](settings)) this option uses external identity provider for MFA */ external: () => LocalizedString + /** + * Location MFA can't be used when service location mode is enabled. + */ + serviceLocationWarning: () => LocalizedString + } + serviceLocation: { + /** + * Choose if this location should work as a service location. This feature is currently not supported on every platform. Consult our [documentation](https://docs.defguard.net/features/service-locations) for more details. + */ + description: () => LocalizedString + /** + * Pre-logon - A VPN connection to this location will be active only before the user logs in on their device. When the user completes the login, the VPN connection will be terminated. + */ + preLogon: () => LocalizedString + /** + * Always-on - A VPN connection will always be active when the user device is on. + */ + alwaysOn: () => LocalizedString + /** + * Service locations can't be used while location MFA is enabled. + */ + mfaWarning: () => LocalizedString } } sections: { @@ -11541,6 +11637,12 @@ export type TranslationFunctions = { */ header: () => LocalizedString } + serviceLocation: { + /** + * Service location + */ + header: () => LocalizedString + } } messages: { /** @@ -11629,6 +11731,12 @@ export type TranslationFunctions = { */ label: () => LocalizedString } + service_location_mode: { + /** + * Service location mode + */ + label: () => LocalizedString + } } controls: { /** diff --git a/web/src/i18n/pl/index.ts b/web/src/i18n/pl/index.ts index 588a4a60e7..6525901519 100644 --- a/web/src/i18n/pl/index.ts +++ b/web/src/i18n/pl/index.ts @@ -1032,6 +1032,22 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe contact: 'poprzez kontakt:', }, }, + locationMfaModeSelect: { + label: 'Wymaganie MFA', + options: { + disabled: 'Nie wymuszaj MFA', + internal: 'Wewnętrzne MFA', + external: 'Zewnętrzne MFA', + }, + }, + serviceLocationModeSelect: { + label: 'Tryb lokalizacji usługi', + options: { + disabled: 'Wyłączone', + prelogon: 'Pre-logon', + alwayson: 'Always-on', + }, + }, }, settingsPage: { title: 'Ustawienia', @@ -1784,11 +1800,46 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe allowedIps: 'Lista adresów/masek, które powinny być routowane przez sieć VPN.', allowedGroups: 'Domyślnie wszyscy użytkownicy będą mogli połączyć się z tą lokalizacją. Jeżeli chcesz ogranicznyć dostęp do tej lokalizacji do wybranej grupy użytkowników, wybierz ją poniżej.', + aclFeatureDisabled: + 'Funkcjonalność ACL jest funkcją enterprise i przekroczyłeś limity użytkowników, urządzeń lub sieciź. Aby korzystać z tej funkcji, kup licencję enterprise lub zaktualizuj istniejącą.', + peerDisconnectThreshold: + 'Klienci autoryzowani za pomocą MFA zostaną rozłączeni z lokalizacji, gdy nie zostanie wykryta żadna aktywność sieciowa między nimi a bramą VPN przez czas skonfigurowany poniżej.', + locationMfaMode: { + description: + 'Wybierz, w jaki sposób wymuszane jest MFA podczas łączenia się z tą lokalizacją:', + internal: + 'Wewnętrzne MFA - MFA jest wymuszane przy użyciu wbudowanego MFA Defguard (np. TOTP, WebAuthn) z wewnętrzną tożsamością', + external: + 'Zewnętrzne MFA - Jeśli skonfigurowane (zobacz [ustawienia OpenID](settings)), ta opcja używa zewnętrznego dostawcy tożsamości do MFA', + serviceLocationWarning: + 'Nie można używać MFA lokalizacji, gdy włączony jest tryb lokalizacji serwisowej.', + }, + serviceLocation: { + description: + 'Wybierz, czy ta lokalizacja ma działać jako lokalizacja serwisowa. Ta funkcja nie jest obecnie obsługiwana na każdej platformie. Zapoznaj się z naszą [dokumentacją](https://docs.defguard.net/features/service-locations), aby uzyskać więcej szczegółów.', + preLogon: + 'Pre-logon - Połączenie VPN z tą lokalizacją będzie aktywne tylko przed zalogowaniem użytkownika na jego urządzeniu. Połączenie VPN zostanie zakończone po zalogowaniu się użytkownika.', + alwaysOn: + 'Always-on - Połączenie VPN będzie zawsze aktywne, gdy urządzenie użytkownika jest włączone.', + mfaWarning: + 'Nie można używać lokalizacji serwisowej, gdy włączone jest MFA lokalizacji.', + }, }, messages: { networkModified: 'Lokalizacja zmodyfikowana', networkCreated: 'Lokalizacja utworzona', }, + sections: { + accessControl: { + header: 'Kontrola dostępu i firewall', + }, + mfa: { + header: 'Uwierzytelnianie wieloskładnikowe', + }, + serviceLocation: { + header: 'Lokalizacja serwisowa', + }, + }, fields: { name: { label: 'Nazwa lokalizacji', @@ -1827,6 +1878,12 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe acl_default_allow: { label: 'Domyślna polityka ACL', }, + location_mfa_mode: { + label: 'Tryb MFA lokalizacji', + }, + service_location_mode: { + label: 'Tryb lokalizacji serwisowej', + }, }, controls: { submit: 'Zapisz zmiany', diff --git a/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx b/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx index f73bcecd00..1daadaedaf 100644 --- a/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx +++ b/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx @@ -23,7 +23,11 @@ import { useAppStore } from '../../../shared/hooks/store/useAppStore.ts'; import useApi from '../../../shared/hooks/useApi'; import { useToaster } from '../../../shared/hooks/useToaster'; import { QueryKeys } from '../../../shared/queries'; -import { LocationMfaMode, type Network } from '../../../shared/types'; +import { + LocationMfaMode, + ServiceLocationMode, + type Network, +} from '../../../shared/types'; import { titleCase } from '../../../shared/utils/titleCase'; import { trimObjectStrings } from '../../../shared/utils/trimObjectStrings.ts'; import { @@ -33,6 +37,7 @@ import { } from '../../../shared/validators'; import { useNetworkPageStore } from '../hooks/useNetworkPageStore'; import { DividerHeader } from './components/DividerHeader.tsx'; +import { FormServiceLocationModeSelect } from '../../../shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx'; export const NetworkEditForm = () => { const toaster = useToaster(); @@ -161,6 +166,7 @@ export const NetworkEditForm = () => { acl_enabled: z.boolean(), acl_default_allow: z.boolean(), location_mfa_mode: z.nativeEnum(LocationMfaMode), + service_location_mode: z.nativeEnum(ServiceLocationMode), }), [LL.form.error], ); @@ -181,6 +187,7 @@ export const NetworkEditForm = () => { acl_enabled: false, acl_default_allow: false, location_mfa_mode: LocationMfaMode.DISABLED, + service_location_mode: ServiceLocationMode.DISABLED, }), [], ); @@ -237,7 +244,7 @@ export const NetworkEditForm = () => { return defaultValues; }, [defaultValues, networkToForm, networks, selectedNetworkId]); - const { control, handleSubmit, reset } = useForm({ + const { control, handleSubmit, reset, setValue } = useForm({ defaultValues: defaultFormValues, resolver: zodResolver(zodSchema), mode: 'all', @@ -257,6 +264,25 @@ export const NetworkEditForm = () => { () => locationMfaMode === LocationMfaMode.DISABLED, [locationMfaMode], ); + const serviceLocationMode = useWatch({ + control, + name: 'service_location_mode', + defaultValue: defaultFormValues.service_location_mode, + }); + const serviceLocationEnabled = useMemo( + () => serviceLocationMode !== ServiceLocationMode.DISABLED, + [serviceLocationMode], + ); + + useEffect(() => { + if (!mfaDisabled && serviceLocationMode !== ServiceLocationMode.DISABLED) { + setValue('service_location_mode', ServiceLocationMode.DISABLED, { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }); + } + }, [mfaDisabled, serviceLocationMode, setValue]); const onValidSubmit: SubmitHandler = (values) => { if (selectedNetworkId) { @@ -387,7 +413,17 @@ export const NetworkEditForm = () => { - + {serviceLocationEnabled && ( + +

+ {LL.networkConfiguration.form.helpers.locationMfaMode.serviceLocationWarning()} +

+
+ )} +

{LL.networkConfiguration.form.helpers.peerDisconnectThreshold()}

@@ -397,6 +433,31 @@ export const NetworkEditForm = () => { type="number" disabled={mfaDisabled} /> + + + +
    +
  • +

    {LL.networkConfiguration.form.helpers.serviceLocation.preLogon()}

    +
  • +
  • +

    {LL.networkConfiguration.form.helpers.serviceLocation.alwaysOn()}

    +
  • +
+
+ {!mfaDisabled && ( + +

{LL.networkConfiguration.form.helpers.serviceLocation.mfaWarning()}

+
+ )} + diff --git a/web/src/pages/network/NetworkEditForm/style.scss b/web/src/pages/network/NetworkEditForm/style.scss index c8840e9d88..b66171214d 100644 --- a/web/src/pages/network/NetworkEditForm/style.scss +++ b/web/src/pages/network/NetworkEditForm/style.scss @@ -43,6 +43,19 @@ } } + #service-location-mode-explain-message-box { + ul { + list-style-position: inside; + margin-top: 8px; + + li { + p { + display: inline; + } + } + } + } + .divider-header { padding-bottom: var(--spacing-s); diff --git a/web/src/shared/components/Form/FormLocationMfaModeSelect/FormLocationMfaModeSelect.tsx b/web/src/shared/components/Form/FormLocationMfaModeSelect/FormLocationMfaModeSelect.tsx index 0c87aae1c7..23e89e11c0 100644 --- a/web/src/shared/components/Form/FormLocationMfaModeSelect/FormLocationMfaModeSelect.tsx +++ b/web/src/shared/components/Form/FormLocationMfaModeSelect/FormLocationMfaModeSelect.tsx @@ -14,10 +14,12 @@ import { LocationMfaMode } from '../../../types'; type Props = { controller: UseControllerProps; + disabled?: boolean; }; export const FormLocationMfaModeSelect = ({ controller, + disabled = false, }: Props) => { const { LL } = useI18nContext(); const { @@ -38,12 +40,13 @@ export const FormLocationMfaModeSelect = ({ key: LocationMfaMode.INTERNAL, value: LocationMfaMode.INTERNAL, label: LL.components.locationMfaModeSelect.options.internal(), + disabled: disabled, }, { key: LocationMfaMode.EXTERNAL, value: LocationMfaMode.EXTERNAL, label: LL.components.locationMfaModeSelect.options.external(), - disabled: externalMfaDisabled, + disabled: externalMfaDisabled || disabled, }, ], [ @@ -51,6 +54,7 @@ export const FormLocationMfaModeSelect = ({ LL.components.locationMfaModeSelect.options.external, LL.components.locationMfaModeSelect.options.internal, externalMfaDisabled, + disabled, ], ); diff --git a/web/src/shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx b/web/src/shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx new file mode 100644 index 0000000000..150c158f1d --- /dev/null +++ b/web/src/shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx @@ -0,0 +1,84 @@ +import './style.scss'; +import clsx from 'clsx'; +import { useMemo } from 'react'; +import { + type FieldValues, + type UseControllerProps, + useController, +} from 'react-hook-form'; +import { useI18nContext } from '../../../../i18n/i18n-react'; +import { RadioButton } from '../../../defguard-ui/components/Layout/RadioButton/Radiobutton'; +import type { SelectOption } from '../../../defguard-ui/components/Layout/Select/types'; +import { useAppStore } from '../../../hooks/store/useAppStore'; +import { ServiceLocationMode } from '../../../types'; + +type Props = { + controller: UseControllerProps; + disabled?: boolean; +}; + +export const FormServiceLocationModeSelect = ({ + controller, + disabled = false, +}: Props) => { + const { LL } = useI18nContext(); + const { + field: { onChange, value: fieldValue }, + } = useController(controller); + const enterpriseEnabled = useAppStore((s) => s.appInfo?.license_info.enterprise); + + const options = useMemo( + (): SelectOption[] => [ + { + key: ServiceLocationMode.DISABLED, + value: ServiceLocationMode.DISABLED, + label: LL.components.serviceLocationModeSelect.options.disabled(), + }, + { + key: ServiceLocationMode.PRELOGON, + value: ServiceLocationMode.PRELOGON, + label: LL.components.serviceLocationModeSelect.options.prelogon(), + disabled: !enterpriseEnabled || disabled, + }, + { + key: ServiceLocationMode.ALWAYSON, + value: ServiceLocationMode.ALWAYSON, + label: LL.components.serviceLocationModeSelect.options.alwayson(), + disabled: !enterpriseEnabled || disabled, + }, + ], + [ + LL.components.serviceLocationModeSelect.options.disabled, + LL.components.serviceLocationModeSelect.options.prelogon, + LL.components.serviceLocationModeSelect.options.alwayson, + disabled, + enterpriseEnabled, + ], + ); + + return ( +
+ + {options.map(({ key, value, label, disabled = false }) => { + const active = fieldValue === value; + return ( +
{ + if (!disabled) { + onChange(value); + } + }} + > +

{label}

+ +
+ ); + })} +
+ ); +}; diff --git a/web/src/shared/components/Form/FormServiceLocationModeSelect/style.scss b/web/src/shared/components/Form/FormServiceLocationModeSelect/style.scss new file mode 100644 index 0000000000..4cc39d601e --- /dev/null +++ b/web/src/shared/components/Form/FormServiceLocationModeSelect/style.scss @@ -0,0 +1,59 @@ +.service-location-mode-select { + display: flex; + flex-flow: column; + row-gap: var(--spacing-s); + margin-bottom: 25px; + + .service-location-mode { + display: flex; + align-items: center; + justify-content: space-between; + column-gap: var(--spacing-xs); + min-height: 30px; + border: 1px solid var(--border-primary); + padding: var(--spacing-xs) var(--spacing-s); + border-radius: 10px; + cursor: pointer; + user-select: none; + transition-property: border-color, opacity; + @include animate-standard; + + &:not(.active) { + &:hover { + border-color: var(--border-separator); + } + } + + &.active { + border-color: var(--surface-main-primary); + } + + &.active, + &:hover { + .label { + color: var(--text-body-primary); + } + } + + &.disabled { + opacity: 0.6; + cursor: not-allowed; + background-color: var(--surface-secondary); + + .label { + color: var(--text-body-disabled); + } + + &:hover { + border-color: var(--border-primary); + } + } + + .label { + color: var(--text-body-secondary); + transition-property: color; + @include typography(app-modal-1); + @include animate-standard; + } + } +} diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts index d423ceed58..3c181cb11e 100644 --- a/web/src/shared/types.ts +++ b/web/src/shared/types.ts @@ -139,6 +139,12 @@ export enum LocationMfaMode { EXTERNAL = 'external', } +export enum ServiceLocationMode { + DISABLED = 'disabled', + PRELOGON = 'prelogon', + ALWAYSON = 'alwayson', +} + export interface Network { id: number; name: string; @@ -156,6 +162,7 @@ export interface Network { acl_enabled: boolean; acl_default_allow: boolean; location_mfa_mode: LocationMfaMode; + service_location_mode: ServiceLocationMode; } export type ModifyNetworkRequest = { From 60af41739043acca9067d11b082907c58381faf5 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:28:59 +0200 Subject: [PATCH 02/12] biome fix --- web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx b/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx index 1daadaedaf..772c97a2a1 100644 --- a/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx +++ b/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx @@ -12,6 +12,7 @@ import { shallow } from 'zustand/shallow'; import { useI18nContext } from '../../../i18n/i18n-react'; import { FormAclDefaultPolicy } from '../../../shared/components/Form/FormAclDefaultPolicySelect/FormAclDefaultPolicy.tsx'; import { FormLocationMfaModeSelect } from '../../../shared/components/Form/FormLocationMfaModeSelect/FormLocationMfaModeSelect.tsx'; +import { FormServiceLocationModeSelect } from '../../../shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx'; import { RenderMarkdown } from '../../../shared/components/Layout/RenderMarkdown/RenderMarkdown.tsx'; import { FormCheckBox } from '../../../shared/defguard-ui/components/Form/FormCheckBox/FormCheckBox.tsx'; import { FormInput } from '../../../shared/defguard-ui/components/Form/FormInput/FormInput'; @@ -25,8 +26,8 @@ import { useToaster } from '../../../shared/hooks/useToaster'; import { QueryKeys } from '../../../shared/queries'; import { LocationMfaMode, - ServiceLocationMode, type Network, + ServiceLocationMode, } from '../../../shared/types'; import { titleCase } from '../../../shared/utils/titleCase'; import { trimObjectStrings } from '../../../shared/utils/trimObjectStrings.ts'; @@ -37,7 +38,6 @@ import { } from '../../../shared/validators'; import { useNetworkPageStore } from '../hooks/useNetworkPageStore'; import { DividerHeader } from './components/DividerHeader.tsx'; -import { FormServiceLocationModeSelect } from '../../../shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx'; export const NetworkEditForm = () => { const toaster = useToaster(); From cc28291981918781b5042972dfcc6b07bd890421 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:35:35 +0200 Subject: [PATCH 03/12] attempt 3 --- .../WizardNetworkConfiguration.tsx | 34 +++++++++++++++++-- web/src/pages/wizard/hooks/useWizardStore.ts | 3 ++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/web/src/pages/wizard/components/WizardNetworkConfiguration/WizardNetworkConfiguration.tsx b/web/src/pages/wizard/components/WizardNetworkConfiguration/WizardNetworkConfiguration.tsx index caa6ef8530..2df1039ee9 100644 --- a/web/src/pages/wizard/components/WizardNetworkConfiguration/WizardNetworkConfiguration.tsx +++ b/web/src/pages/wizard/components/WizardNetworkConfiguration/WizardNetworkConfiguration.tsx @@ -10,6 +10,7 @@ import { shallow } from 'zustand/shallow'; import { useI18nContext } from '../../../../i18n/i18n-react'; import { FormAclDefaultPolicy } from '../../../../shared/components/Form/FormAclDefaultPolicySelect/FormAclDefaultPolicy.tsx'; import { FormLocationMfaModeSelect } from '../../../../shared/components/Form/FormLocationMfaModeSelect/FormLocationMfaModeSelect.tsx'; +import { FormServiceLocationModeSelect } from '../../../../shared/components/Form/FormServiceLocationModeSelect/FormServiceLocationModeSelect.tsx'; import { RenderMarkdown } from '../../../../shared/components/Layout/RenderMarkdown/RenderMarkdown.tsx'; import { FormCheckBox } from '../../../../shared/defguard-ui/components/Form/FormCheckBox/FormCheckBox.tsx'; import { FormInput } from '../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; @@ -22,7 +23,7 @@ import { useAppStore } from '../../../../shared/hooks/store/useAppStore.ts'; import useApi from '../../../../shared/hooks/useApi'; import { useToaster } from '../../../../shared/hooks/useToaster'; import { QueryKeys } from '../../../../shared/queries'; -import { LocationMfaMode } from '../../../../shared/types.ts'; +import { LocationMfaMode, ServiceLocationMode } from '../../../../shared/types.ts'; import { titleCase } from '../../../../shared/utils/titleCase'; import { trimObjectStrings } from '../../../../shared/utils/trimObjectStrings.ts'; import { validateIpList, validateIpOrDomainList } from '../../../../shared/validators'; @@ -141,6 +142,7 @@ export const WizardNetworkConfiguration = () => { acl_enabled: z.boolean(), acl_default_allow: z.boolean(), location_mfa_mode: z.nativeEnum(LocationMfaMode), + service_location_mode: z.nativeEnum(ServiceLocationMode), }), [LL.form.error], ); @@ -171,6 +173,15 @@ export const WizardNetworkConfiguration = () => { () => locationMfaMode === LocationMfaMode.DISABLED, [locationMfaMode], ); + const serviceLocationMode = useWatch({ + control, + name: 'service_location_mode', + defaultValue: getDefaultValues.service_location_mode, + }); + const serviceLocationEnabled = useMemo( + () => serviceLocationMode !== ServiceLocationMode.DISABLED, + [serviceLocationMode], + ); const handleValidSubmit: SubmitHandler = (values) => { const trimmed = trimObjectStrings(values); @@ -282,7 +293,17 @@ export const WizardNetworkConfiguration = () => { - + {serviceLocationEnabled && ( + +

+ {LL.networkConfiguration.form.helpers.locationMfaMode.serviceLocationWarning()} +

+
+ )} +

{LL.networkConfiguration.form.helpers.peerDisconnectThreshold()}

@@ -292,6 +313,15 @@ export const WizardNetworkConfiguration = () => { type="number" disabled={mfaDisabled} /> + {!mfaDisabled && ( + +

{LL.networkConfiguration.form.helpers.serviceLocation.mfaWarning()}

+
+ )} + diff --git a/web/src/pages/wizard/hooks/useWizardStore.ts b/web/src/pages/wizard/hooks/useWizardStore.ts index 0206902238..944f9b48f0 100644 --- a/web/src/pages/wizard/hooks/useWizardStore.ts +++ b/web/src/pages/wizard/hooks/useWizardStore.ts @@ -7,6 +7,7 @@ import { type ImportedDevice, LocationMfaMode, type Network, + ServiceLocationMode, } from '../../../shared/types'; export enum WizardSetupType { @@ -34,6 +35,7 @@ const defaultValues: StoreFields = { acl_enabled: false, acl_default_allow: false, location_mfa_mode: LocationMfaMode.DISABLED, + service_location_mode: ServiceLocationMode.DISABLED, }, }; @@ -90,6 +92,7 @@ type StoreFields = { acl_enabled: boolean; acl_default_allow: boolean; location_mfa_mode: LocationMfaMode; + service_location_mode: ServiceLocationMode; }; }; From d42705d4da2759e3e39d72ea42a77c8df4bb68d4 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:35:49 +0200 Subject: [PATCH 04/12] sqlx prepare --- ...74a42e3447f9d2a949b6820e685ddab6561d.json} | 21 +++++++++++++++++-- ...0b1ea9e8149f3bb760d2039b463ef262eb03.json} | 21 +++++++++++++++++-- ...c2deb6e638c229a1929afe35d79dddfbdf67.json} | 21 +++++++++++++++++-- ...42f9e63cf2ebf64a517adeea511ba932e2a0.json} | 21 +++++++++++++++++-- ...d0aea3e87da3082b49d7f6d6dd82630597ac.json} | 21 +++++++++++++++++-- ...7e657dcb71c90e209102b4033888f5c668cd.json} | 21 +++++++++++++++++-- ...c33c6e14d74fb23bd9baf450dc9827d547c6.json} | 21 +++++++++++++++++-- ...5034418d6224e190ce6ae96b8779e2505062.json} | 16 ++++++++++++-- ...f7835e7ef3e0312aef9c56e495f05f11b4dd.json} | 21 +++++++++++++++++-- ...85908407c4f642d3005e36df113a1aee841d.json} | 16 ++++++++++++-- 10 files changed, 180 insertions(+), 20 deletions(-) rename .sqlx/{query-5350e57595e044cea6976a73910210e5106af580e45647ae620850de0b77785b.json => query-03710e2a3e96096e57f95a62543174a42e3447f9d2a949b6820e685ddab6561d.json} (77%) rename .sqlx/{query-0fb053b3b00a1fe78f764d2d1d90375d5674fd59fe3018af120ae2ef5fd10f48.json => query-0c237b235f0455c5f79f2ea4e8210b1ea9e8149f3bb760d2039b463ef262eb03.json} (76%) rename .sqlx/{query-89ec538b614f4ea6440ce15ec58044149463f9a39f673b1f83587980c527c575.json => query-38970835606c0fb0d2cf1d87253cc2deb6e638c229a1929afe35d79dddfbdf67.json} (77%) rename .sqlx/{query-21957027aa29a30a186e87441b86eadc2d27eeb56d18f9debf50d0ba71e01e48.json => query-4628fb49dfe7cb4e9470018f566342f9e63cf2ebf64a517adeea511ba932e2a0.json} (78%) rename .sqlx/{query-d6298964d89e9fdb087f7860a38f2f325d5ec81e6e0ea10660f74084718ac48e.json => query-4ad6e8059b230eee547c234d02f2d0aea3e87da3082b49d7f6d6dd82630597ac.json} (77%) rename .sqlx/{query-6f207a4d39d616b6cfdb10e4e4e7e2bf4a03081f33c0b4e779e5e431b092fbdc.json => query-60f62225c964c16171bfe23148757e657dcb71c90e209102b4033888f5c668cd.json} (78%) rename .sqlx/{query-0c7b5094f1e4dc79a5782b6948aa2527807bd9643f2758c83c728d1d003843ab.json => query-7938837afdae9daee7a22d0ed919c33c6e14d74fb23bd9baf450dc9827d547c6.json} (80%) rename .sqlx/{query-966dd7a3677babebc8e34b6f502e7e1aea7304e054c1241406aa62993d66117a.json => query-7cb2f4e82c2d2cb1aec62bc3ae335034418d6224e190ce6ae96b8779e2505062.json} (68%) rename .sqlx/{query-32187156f93aaff898a4445056a2a332453a9626e56a5f543c2790bff9d2109c.json => query-f7c1feed3561417bc20420558c27f7835e7ef3e0312aef9c56e495f05f11b4dd.json} (79%) rename .sqlx/{query-acb58694a7dc5ac4268c88e2d37a50697d20952973b676d574b01d0836f1aff0.json => query-fe2ad01a923b3d66c74343dbd9c385908407c4f642d3005e36df113a1aee841d.json} (70%) diff --git a/.sqlx/query-5350e57595e044cea6976a73910210e5106af580e45647ae620850de0b77785b.json b/.sqlx/query-03710e2a3e96096e57f95a62543174a42e3447f9d2a949b6820e685ddab6561d.json similarity index 77% rename from .sqlx/query-5350e57595e044cea6976a73910210e5106af580e45647ae620850de0b77785b.json rename to .sqlx/query-03710e2a3e96096e57f95a62543174a42e3447f9d2a949b6820e685ddab6561d.json index 36d70341c3..9bb03daf9c 100644 --- a/.sqlx/query-5350e57595e044cea6976a73910210e5106af580e45647ae620850de0b77785b.json +++ b/.sqlx/query-03710e2a3e96096e57f95a62543174a42e3447f9d2a949b6820e685ddab6561d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT n.id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" FROM aclrulenetwork r JOIN wireguard_network n ON n.id = r.network_id WHERE r.rule_id = $1", + "query": "SELECT n.id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", service_location_mode \"service_location_mode: ServiceLocationMode\" FROM aclrulenetwork r JOIN wireguard_network n ON n.id = r.network_id WHERE r.rule_id = $1", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: ServiceLocationMode", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -110,8 +126,9 @@ false, false, false, + false, false ] }, - "hash": "5350e57595e044cea6976a73910210e5106af580e45647ae620850de0b77785b" + "hash": "03710e2a3e96096e57f95a62543174a42e3447f9d2a949b6820e685ddab6561d" } diff --git a/.sqlx/query-0fb053b3b00a1fe78f764d2d1d90375d5674fd59fe3018af120ae2ef5fd10f48.json b/.sqlx/query-0c237b235f0455c5f79f2ea4e8210b1ea9e8149f3bb760d2039b463ef262eb03.json similarity index 76% rename from .sqlx/query-0fb053b3b00a1fe78f764d2d1d90375d5674fd59fe3018af120ae2ef5fd10f48.json rename to .sqlx/query-0c237b235f0455c5f79f2ea4e8210b1ea9e8149f3bb760d2039b463ef262eb03.json index 4d94977db3..3b4c356fed 100644 --- a/.sqlx/query-0fb053b3b00a1fe78f764d2d1d90375d5674fd59fe3018af120ae2ef5fd10f48.json +++ b/.sqlx/query-0c237b235f0455c5f79f2ea4e8210b1ea9e8149f3bb760d2039b463ef262eb03.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" FROM wireguard_network WHERE id IN (SELECT wireguard_network_id FROM wireguard_network_device WHERE device_id = $1 ORDER BY id LIMIT 1)", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", service_location_mode \"service_location_mode: ServiceLocationMode\" FROM wireguard_network WHERE id IN (SELECT wireguard_network_id FROM wireguard_network_device WHERE device_id = $1 ORDER BY id LIMIT 1)", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: ServiceLocationMode", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -110,8 +126,9 @@ false, false, false, + false, false ] }, - "hash": "0fb053b3b00a1fe78f764d2d1d90375d5674fd59fe3018af120ae2ef5fd10f48" + "hash": "0c237b235f0455c5f79f2ea4e8210b1ea9e8149f3bb760d2039b463ef262eb03" } diff --git a/.sqlx/query-89ec538b614f4ea6440ce15ec58044149463f9a39f673b1f83587980c527c575.json b/.sqlx/query-38970835606c0fb0d2cf1d87253cc2deb6e638c229a1929afe35d79dddfbdf67.json similarity index 77% rename from .sqlx/query-89ec538b614f4ea6440ce15ec58044149463f9a39f673b1f83587980c527c575.json rename to .sqlx/query-38970835606c0fb0d2cf1d87253cc2deb6e638c229a1929afe35d79dddfbdf67.json index 55255fc63f..3fbc28224b 100644 --- a/.sqlx/query-89ec538b614f4ea6440ce15ec58044149463f9a39f673b1f83587980c527c575.json +++ b/.sqlx/query-38970835606c0fb0d2cf1d87253cc2deb6e638c229a1929afe35d79dddfbdf67.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" FROM wireguard_network WHERE location_mfa_mode = 'external'::location_mfa_mode", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", service_location_mode \"service_location_mode: ServiceLocationMode\" FROM wireguard_network WHERE location_mfa_mode != 'disabled'::location_mfa_mode", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: ServiceLocationMode", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -108,8 +124,9 @@ false, false, false, + false, false ] }, - "hash": "89ec538b614f4ea6440ce15ec58044149463f9a39f673b1f83587980c527c575" + "hash": "38970835606c0fb0d2cf1d87253cc2deb6e638c229a1929afe35d79dddfbdf67" } diff --git a/.sqlx/query-21957027aa29a30a186e87441b86eadc2d27eeb56d18f9debf50d0ba71e01e48.json b/.sqlx/query-4628fb49dfe7cb4e9470018f566342f9e63cf2ebf64a517adeea511ba932e2a0.json similarity index 78% rename from .sqlx/query-21957027aa29a30a186e87441b86eadc2d27eeb56d18f9debf50d0ba71e01e48.json rename to .sqlx/query-4628fb49dfe7cb4e9470018f566342f9e63cf2ebf64a517adeea511ba932e2a0.json index 1b397c0991..75b2c85a66 100644 --- a/.sqlx/query-21957027aa29a30a186e87441b86eadc2d27eeb56d18f9debf50d0ba71e01e48.json +++ b/.sqlx/query-4628fb49dfe7cb4e9470018f566342f9e63cf2ebf64a517adeea511ba932e2a0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" FROM wireguard_network WHERE id = $1", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", service_location_mode \"service_location_mode: ServiceLocationMode\" FROM wireguard_network WHERE id = $1", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: ServiceLocationMode", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -110,8 +126,9 @@ false, false, false, + false, false ] }, - "hash": "21957027aa29a30a186e87441b86eadc2d27eeb56d18f9debf50d0ba71e01e48" + "hash": "4628fb49dfe7cb4e9470018f566342f9e63cf2ebf64a517adeea511ba932e2a0" } diff --git a/.sqlx/query-d6298964d89e9fdb087f7860a38f2f325d5ec81e6e0ea10660f74084718ac48e.json b/.sqlx/query-4ad6e8059b230eee547c234d02f2d0aea3e87da3082b49d7f6d6dd82630597ac.json similarity index 77% rename from .sqlx/query-d6298964d89e9fdb087f7860a38f2f325d5ec81e6e0ea10660f74084718ac48e.json rename to .sqlx/query-4ad6e8059b230eee547c234d02f2d0aea3e87da3082b49d7f6d6dd82630597ac.json index cee5190d9a..613c96a482 100644 --- a/.sqlx/query-d6298964d89e9fdb087f7860a38f2f325d5ec81e6e0ea10660f74084718ac48e.json +++ b/.sqlx/query-4ad6e8059b230eee547c234d02f2d0aea3e87da3082b49d7f6d6dd82630597ac.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" FROM wireguard_network WHERE location_mfa_mode != 'disabled'::location_mfa_mode", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", service_location_mode \"service_location_mode: ServiceLocationMode\" FROM wireguard_network WHERE location_mfa_mode = 'external'::location_mfa_mode", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: ServiceLocationMode", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -108,8 +124,9 @@ false, false, false, + false, false ] }, - "hash": "d6298964d89e9fdb087f7860a38f2f325d5ec81e6e0ea10660f74084718ac48e" + "hash": "4ad6e8059b230eee547c234d02f2d0aea3e87da3082b49d7f6d6dd82630597ac" } diff --git a/.sqlx/query-6f207a4d39d616b6cfdb10e4e4e7e2bf4a03081f33c0b4e779e5e431b092fbdc.json b/.sqlx/query-60f62225c964c16171bfe23148757e657dcb71c90e209102b4033888f5c668cd.json similarity index 78% rename from .sqlx/query-6f207a4d39d616b6cfdb10e4e4e7e2bf4a03081f33c0b4e779e5e431b092fbdc.json rename to .sqlx/query-60f62225c964c16171bfe23148757e657dcb71c90e209102b4033888f5c668cd.json index 4b43522b73..e221cd0a9d 100644 --- a/.sqlx/query-6f207a4d39d616b6cfdb10e4e4e7e2bf4a03081f33c0b4e779e5e431b092fbdc.json +++ b/.sqlx/query-60f62225c964c16171bfe23148757e657dcb71c90e209102b4033888f5c668cd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\" FROM wireguard_network WHERE name = $1", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, keepalive_interval, peer_disconnect_threshold, acl_enabled, acl_default_allow, location_mfa_mode \"location_mfa_mode: LocationMfaMode\", service_location_mode \"service_location_mode: ServiceLocationMode\" FROM wireguard_network WHERE name = $1", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: ServiceLocationMode", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -110,8 +126,9 @@ false, false, false, + false, false ] }, - "hash": "6f207a4d39d616b6cfdb10e4e4e7e2bf4a03081f33c0b4e779e5e431b092fbdc" + "hash": "60f62225c964c16171bfe23148757e657dcb71c90e209102b4033888f5c668cd" } diff --git a/.sqlx/query-0c7b5094f1e4dc79a5782b6948aa2527807bd9643f2758c83c728d1d003843ab.json b/.sqlx/query-7938837afdae9daee7a22d0ed919c33c6e14d74fb23bd9baf450dc9827d547c6.json similarity index 80% rename from .sqlx/query-0c7b5094f1e4dc79a5782b6948aa2527807bd9643f2758c83c728d1d003843ab.json rename to .sqlx/query-7938837afdae9daee7a22d0ed919c33c6e14d74fb23bd9baf450dc9827d547c6.json index da1e780cd7..8a209a1cf5 100644 --- a/.sqlx/query-0c7b5094f1e4dc79a5782b6948aa2527807bd9643f2758c83c728d1d003843ab.json +++ b/.sqlx/query-7938837afdae9daee7a22d0ed919c33c6e14d74fb23bd9baf450dc9827d547c6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"acl_enabled\",\"acl_default_allow\",\"keepalive_interval\",\"peer_disconnect_threshold\",\"location_mfa_mode\" \"location_mfa_mode: _\" FROM \"wireguard_network\"", + "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"acl_enabled\",\"acl_default_allow\",\"keepalive_interval\",\"peer_disconnect_threshold\",\"location_mfa_mode\" \"location_mfa_mode: _\",\"service_location_mode\" \"service_location_mode: _\" FROM \"wireguard_network\"", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: _", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -108,8 +124,9 @@ false, false, false, + false, false ] }, - "hash": "0c7b5094f1e4dc79a5782b6948aa2527807bd9643f2758c83c728d1d003843ab" + "hash": "7938837afdae9daee7a22d0ed919c33c6e14d74fb23bd9baf450dc9827d547c6" } diff --git a/.sqlx/query-966dd7a3677babebc8e34b6f502e7e1aea7304e054c1241406aa62993d66117a.json b/.sqlx/query-7cb2f4e82c2d2cb1aec62bc3ae335034418d6224e190ce6ae96b8779e2505062.json similarity index 68% rename from .sqlx/query-966dd7a3677babebc8e34b6f502e7e1aea7304e054c1241406aa62993d66117a.json rename to .sqlx/query-7cb2f4e82c2d2cb1aec62bc3ae335034418d6224e190ce6ae96b8779e2505062.json index 070daed9c5..6315118ca6 100644 --- a/.sqlx/query-966dd7a3677babebc8e34b6f502e7e1aea7304e054c1241406aa62993d66117a.json +++ b/.sqlx/query-7cb2f4e82c2d2cb1aec62bc3ae335034418d6224e190ce6ae96b8779e2505062.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO \"wireguard_network\" (\"name\",\"address\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\",\"connected_at\",\"acl_enabled\",\"acl_default_allow\",\"keepalive_interval\",\"peer_disconnect_threshold\",\"location_mfa_mode\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING id", + "query": "INSERT INTO \"wireguard_network\" (\"name\",\"address\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\",\"connected_at\",\"acl_enabled\",\"acl_default_allow\",\"keepalive_interval\",\"peer_disconnect_threshold\",\"location_mfa_mode\",\"service_location_mode\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING id", "describe": { "columns": [ { @@ -35,6 +35,18 @@ ] } } + }, + { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } } ] }, @@ -42,5 +54,5 @@ false ] }, - "hash": "966dd7a3677babebc8e34b6f502e7e1aea7304e054c1241406aa62993d66117a" + "hash": "7cb2f4e82c2d2cb1aec62bc3ae335034418d6224e190ce6ae96b8779e2505062" } diff --git a/.sqlx/query-32187156f93aaff898a4445056a2a332453a9626e56a5f543c2790bff9d2109c.json b/.sqlx/query-f7c1feed3561417bc20420558c27f7835e7ef3e0312aef9c56e495f05f11b4dd.json similarity index 79% rename from .sqlx/query-32187156f93aaff898a4445056a2a332453a9626e56a5f543c2790bff9d2109c.json rename to .sqlx/query-f7c1feed3561417bc20420558c27f7835e7ef3e0312aef9c56e495f05f11b4dd.json index 37b1fe0d05..f238310936 100644 --- a/.sqlx/query-32187156f93aaff898a4445056a2a332453a9626e56a5f543c2790bff9d2109c.json +++ b/.sqlx/query-f7c1feed3561417bc20420558c27f7835e7ef3e0312aef9c56e495f05f11b4dd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"acl_enabled\",\"acl_default_allow\",\"keepalive_interval\",\"peer_disconnect_threshold\",\"location_mfa_mode\" \"location_mfa_mode: _\" FROM \"wireguard_network\" WHERE id = $1", + "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"acl_enabled\",\"acl_default_allow\",\"keepalive_interval\",\"peer_disconnect_threshold\",\"location_mfa_mode\" \"location_mfa_mode: _\",\"service_location_mode\" \"service_location_mode: _\" FROM \"wireguard_network\" WHERE id = $1", "describe": { "columns": [ { @@ -88,6 +88,22 @@ } } } + }, + { + "ordinal": 15, + "name": "service_location_mode: _", + "type_info": { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } + } } ], "parameters": { @@ -110,8 +126,9 @@ false, false, false, + false, false ] }, - "hash": "32187156f93aaff898a4445056a2a332453a9626e56a5f543c2790bff9d2109c" + "hash": "f7c1feed3561417bc20420558c27f7835e7ef3e0312aef9c56e495f05f11b4dd" } diff --git a/.sqlx/query-acb58694a7dc5ac4268c88e2d37a50697d20952973b676d574b01d0836f1aff0.json b/.sqlx/query-fe2ad01a923b3d66c74343dbd9c385908407c4f642d3005e36df113a1aee841d.json similarity index 70% rename from .sqlx/query-acb58694a7dc5ac4268c88e2d37a50697d20952973b676d574b01d0836f1aff0.json rename to .sqlx/query-fe2ad01a923b3d66c74343dbd9c385908407c4f642d3005e36df113a1aee841d.json index 933ffa8b91..88bf4542cf 100644 --- a/.sqlx/query-acb58694a7dc5ac4268c88e2d37a50697d20952973b676d574b01d0836f1aff0.json +++ b/.sqlx/query-fe2ad01a923b3d66c74343dbd9c385908407c4f642d3005e36df113a1aee841d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"wireguard_network\" SET \"name\" = $2,\"address\" = $3,\"port\" = $4,\"pubkey\" = $5,\"prvkey\" = $6,\"endpoint\" = $7,\"dns\" = $8,\"allowed_ips\" = $9,\"connected_at\" = $10,\"acl_enabled\" = $11,\"acl_default_allow\" = $12,\"keepalive_interval\" = $13,\"peer_disconnect_threshold\" = $14,\"location_mfa_mode\" = $15 WHERE id = $1", + "query": "UPDATE \"wireguard_network\" SET \"name\" = $2,\"address\" = $3,\"port\" = $4,\"pubkey\" = $5,\"prvkey\" = $6,\"endpoint\" = $7,\"dns\" = $8,\"allowed_ips\" = $9,\"connected_at\" = $10,\"acl_enabled\" = $11,\"acl_default_allow\" = $12,\"keepalive_interval\" = $13,\"peer_disconnect_threshold\" = $14,\"location_mfa_mode\" = $15,\"service_location_mode\" = $16 WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -30,10 +30,22 @@ ] } } + }, + { + "Custom": { + "name": "service_location_mode", + "kind": { + "Enum": [ + "disabled", + "prelogon", + "alwayson" + ] + } + } } ] }, "nullable": [] }, - "hash": "acb58694a7dc5ac4268c88e2d37a50697d20952973b676d574b01d0836f1aff0" + "hash": "fe2ad01a923b3d66c74343dbd9c385908407c4f642d3005e36df113a1aee841d" } From 02b6a48603bb0790ec661bec72996584cc43cc72 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:59:47 +0200 Subject: [PATCH 05/12] cleanup, some clippy changes --- .../defguard_core/src/db/models/wireguard.rs | 8 +++++++- crates/defguard_core/src/grpc/gateway/mod.rs | 2 +- crates/defguard_core/src/grpc/utils.rs | 8 ++------ crates/defguard_proto/src/lib.rs | 20 +++++++++---------- crates/defguard_version/src/lib.rs | 8 ++++---- crates/defguard_version/src/tracing.rs | 4 ++-- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/crates/defguard_core/src/db/models/wireguard.rs b/crates/defguard_core/src/db/models/wireguard.rs index 94c9940118..17c594e4b5 100644 --- a/crates/defguard_core/src/db/models/wireguard.rs +++ b/crates/defguard_core/src/db/models/wireguard.rs @@ -40,7 +40,7 @@ use super::{ wireguard_peer_stats::WireguardPeerStats, }; use crate::{ - enterprise::firewall::FirewallError, + enterprise::{firewall::FirewallError, is_enterprise_enabled}, grpc::gateway::{send_multiple_wireguard_events, state::GatewayState}, wg_config::ImportedDevice, }; @@ -1303,6 +1303,12 @@ impl WireguardNetwork { Ok(token) } + + /// If this location is marked as a service location, checks if all requirements are met for it to function: + /// - Enterprise is enabled + pub fn check_service_location_requirements(&self) -> bool { + self.service_location_mode != ServiceLocationMode::Disabled && is_enterprise_enabled() + } } // [`IpNetwork`] does not implement [`Default`] diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index f25b7cba4d..52d6f40a51 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -107,7 +107,7 @@ impl WireguardNetwork { { debug!("Fetching all peers for network {}", self.id); - if self.service_location_mode != ServiceLocationMode::Disabled && !is_enterprise_enabled() { + if !self.check_service_location_requirements() { warn!( "Tried to use service location {} with disabled enterprise features. No clients will be allowed to connect.", self.name diff --git a/crates/defguard_core/src/grpc/utils.rs b/crates/defguard_core/src/grpc/utils.rs index 99549f9cb7..a41cdef7e3 100644 --- a/crates/defguard_core/src/grpc/utils.rs +++ b/crates/defguard_core/src/grpc/utils.rs @@ -123,9 +123,7 @@ pub(crate) async fn build_device_config_response( ); Status::internal(format!("unexpected error: {err}")) })?; - if !is_enterprise_enabled() - && network.service_location_mode != ServiceLocationMode::Disabled - { + if !network.check_service_location_requirements() { error!( "Tried to use service location {} with disabled enterprise features.", network.name @@ -178,9 +176,7 @@ pub(crate) async fn build_device_config_response( ); Status::internal(format!("unexpected error: {err}")) })?; - if !is_enterprise_enabled() - && network.service_location_mode != ServiceLocationMode::Disabled - { + if !network.check_service_location_requirements() { warn!( "Tried to use service location {} with disabled enterprise features.", network.name diff --git a/crates/defguard_proto/src/lib.rs b/crates/defguard_proto/src/lib.rs index b7a38faaaf..0313436669 100644 --- a/crates/defguard_proto/src/lib.rs +++ b/crates/defguard_proto/src/lib.rs @@ -29,11 +29,11 @@ impl fmt::Display for MfaMethod { f, "{}", match self { - MfaMethod::Totp => "TOTP", - MfaMethod::Email => "Email", - MfaMethod::Oidc => "OIDC", - MfaMethod::Biometric => "Biometric", - MfaMethod::MobileApprove => "MobileApprove", + Self::Totp => "TOTP", + Self::Email => "Email", + Self::Oidc => "OIDC", + Self::Biometric => "Biometric", + Self::MobileApprove => "MobileApprove", } ) } @@ -45,11 +45,11 @@ impl Serialize for MfaMethod { S: serde::Serializer, { match *self { - MfaMethod::Totp => serializer.serialize_unit_variant("MfaMethod", 0, "Totp"), - MfaMethod::Email => serializer.serialize_unit_variant("MfaMethod", 1, "Email"), - MfaMethod::Oidc => serializer.serialize_unit_variant("MfaMethod", 2, "Oidc"), - MfaMethod::Biometric => serializer.serialize_unit_variant("MfaMethod", 3, "Biometric"), - MfaMethod::MobileApprove => { + Self::Totp => serializer.serialize_unit_variant("MfaMethod", 0, "Totp"), + Self::Email => serializer.serialize_unit_variant("MfaMethod", 1, "Email"), + Self::Oidc => serializer.serialize_unit_variant("MfaMethod", 2, "Oidc"), + Self::Biometric => serializer.serialize_unit_variant("MfaMethod", 3, "Biometric"), + Self::MobileApprove => { serializer.serialize_unit_variant("MfaMethod", 4, "MobileApprove") } } diff --git a/crates/defguard_version/src/lib.rs b/crates/defguard_version/src/lib.rs index 18c19a1339..05f177b24f 100644 --- a/crates/defguard_version/src/lib.rs +++ b/crates/defguard_version/src/lib.rs @@ -103,9 +103,9 @@ impl FromStr for DefguardComponent { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "core" => Ok(DefguardComponent::Core), - "proxy" => Ok(DefguardComponent::Proxy), - "gateway" => Ok(DefguardComponent::Gateway), + "core" => Ok(Self::Core), + "proxy" => Ok(Self::Proxy), + "gateway" => Ok(Self::Gateway), _ => Err(Self::Err::InvalidDefguardComponent(s.to_string())), } } @@ -217,7 +217,7 @@ pub struct ComponentInfo { } impl ComponentInfo { - /// Creates a new ComponentInfo with the provided version and automatically detects + /// Creates a new `ComponentInfo` with the provided version and automatically detects /// the current system information. /// /// # Arguments diff --git a/crates/defguard_version/src/tracing.rs b/crates/defguard_version/src/tracing.rs index c81793499b..bcf33fecdf 100644 --- a/crates/defguard_version/src/tracing.rs +++ b/crates/defguard_version/src/tracing.rs @@ -209,8 +209,8 @@ pub fn build_version_suffix( /// Custom tracing formatter that conditionally includes version information in log messages. /// /// This formatter wraps the default tracing formatter and adds version suffix to log messages: -/// - For ERROR level logs: includes own_version, own_info and components version and info -/// - For other levels: includes only own_version and component version if available +/// - For ERROR level logs: includes `own_version`, `own_info` and components version and info +/// - For other levels: includes only `own_version` and component version if available /// /// The version information is extracted from tracing span fields. pub struct VersionSuffixFormat { From 8808c865e77f76807c5e3be03a883f9b5fb4f5f7 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:09:59 +0200 Subject: [PATCH 06/12] remove unused imports --- crates/defguard_core/src/grpc/gateway/mod.rs | 5 +---- crates/defguard_core/src/grpc/utils.rs | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 52d6f40a51..ec4aab4db4 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -35,10 +35,7 @@ use self::map::GatewayMap; use crate::{ db::{ Device, GatewayEvent, User, - models::{ - wireguard::{ServiceLocationMode, WireguardNetwork}, - wireguard_peer_stats::WireguardPeerStats, - }, + models::{wireguard::WireguardNetwork, wireguard_peer_stats::WireguardPeerStats}, }, enterprise::is_enterprise_enabled, events::{GrpcEvent, GrpcRequestContext}, diff --git a/crates/defguard_core/src/grpc/utils.rs b/crates/defguard_core/src/grpc/utils.rs index a41cdef7e3..854a875dd6 100644 --- a/crates/defguard_core/src/grpc/utils.rs +++ b/crates/defguard_core/src/grpc/utils.rs @@ -21,9 +21,8 @@ use crate::{ wireguard::{LocationMfaMode, ServiceLocationMode, WireguardNetwork}, }, }, - enterprise::{ - db::models::{enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider}, - is_enterprise_enabled, + enterprise::db::models::{ + enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider, }, }; From 8e14d3706497efdcd451e7c41b50f6d422d666d4 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:26:44 +0200 Subject: [PATCH 07/12] Update deny.toml --- deny.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index d26808d382..b40c3db21b 100644 --- a/deny.toml +++ b/deny.toml @@ -71,7 +71,13 @@ feature-depth = 1 # output a note when they are encountered. ignore = [ { id = "RUSTSEC-2023-0071", reason = "https://github.com/RustCrypto/RSA/issues/19" }, - { id = "RUSTSEC-2024-0436", reason = "Unmaintained" }, + { id = "RUSTSEC-2024-0436", reason = "Unmaintained dependency of tera" }, + { id = "RUSTSEC-2025-0098", reason = "Unmaintained dependency of tera" }, + { id = "RUSTSEC-2025-0104", reason = "Unmaintained dependency of tera" }, + { id = "RUSTSEC-2025-0074", reason = "Unmaintained dependency of tera" }, + { id = "RUSTSEC-2025-0080", reason = "Unmaintained dependency of tera" }, + { id = "RUSTSEC-2025-0075", reason = "Unmaintained dependency of tera" }, + { id = "RUSTSEC-2025-0081", reason = "Unmaintained dependency of tera" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. From 9ad3c1a4d21dabc5535d7d173ce0e9f9f5d93c8d Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:41:54 +0200 Subject: [PATCH 08/12] remove unnecessary operations --- crates/defguard_core/src/utility_thread.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/defguard_core/src/utility_thread.rs b/crates/defguard_core/src/utility_thread.rs index 366dc83c4a..5a0de67b32 100644 --- a/crates/defguard_core/src/utility_thread.rs +++ b/crates/defguard_core/src/utility_thread.rs @@ -201,18 +201,21 @@ async fn enterprise_status_check( // handle switch from enabled -> disabled debug!("Disabling gateway firewall configuration for ACL-enabled locations"); for location in locations { - debug!("Disabling gateway firewall configuration for location {location:?}"); - wireguard_tx.send(GatewayEvent::FirewallDisabled(location.id))?; - - // Handle service location update if location.service_location_mode != ServiceLocationMode::Disabled { - let new_peers = location.get_peers(pool).await?; + debug!( + "Disabling gateway firewall configuration and service location client connections \ + for location {location:?}" + ); wireguard_tx.send(GatewayEvent::NetworkModified( location.id, location, - new_peers, + // Send empty peer list, we are disabling the service location + Vec::new(), None, ))?; + } else { + debug!("Disabling gateway firewall configuration for location {location:?}"); + wireguard_tx.send(GatewayEvent::FirewallDisabled(location.id))?; } } } From 416679ffb12f53300ea9ecb6a84496a2cb6877c2 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:42:47 +0200 Subject: [PATCH 09/12] sort imports --- crates/defguard_common/src/db/models/settings.rs | 3 ++- .../src/db/models/activity_log/mod.rs | 3 +-- .../src/db/models/oauth2authorizedapp.rs | 3 +-- .../defguard_core/src/db/models/oauth2client.rs | 9 +++++---- .../defguard_core/src/db/models/polling_token.rs | 7 ++++--- crates/defguard_core/src/db/models/session.rs | 3 +-- crates/defguard_core/src/db/models/user.rs | 13 ++++++------- crates/defguard_core/src/db/models/webauthn.rs | 3 +-- crates/defguard_core/src/db/models/webhook.rs | 2 +- .../src/db/models/wireguard_peer_stats.rs | 3 +-- crates/defguard_core/src/db/models/yubikey.rs | 3 +-- .../enterprise/db/models/activity_log_stream.rs | 8 ++++---- .../src/enterprise/db/models/api_tokens.rs | 3 +-- .../src/enterprise/db/models/openid_provider.rs | 3 +-- .../src/enterprise/db/models/snat.rs | 2 +- .../defguard_core/src/enterprise/firewall/mod.rs | 10 +++++----- .../src/enterprise/firewall/tests.rs | 8 ++++---- .../src/enterprise/grpc/desktop_client_mfa.rs | 2 +- .../defguard_core/src/enterprise/grpc/polling.rs | 2 +- .../enterprise/handlers/activity_log_stream.rs | 2 +- .../src/enterprise/handlers/api_tokens.rs | 2 +- .../defguard_core/src/enterprise/ldap/client.rs | 2 +- crates/defguard_core/src/enterprise/license.rs | 12 ++++++------ crates/defguard_core/src/events.rs | 2 +- crates/defguard_core/src/grpc/client_mfa.rs | 10 +++++----- crates/defguard_core/src/grpc/gateway/map.rs | 2 +- crates/defguard_core/src/grpc/mod.rs | 15 +++++---------- crates/defguard_core/src/grpc/password_reset.rs | 8 ++++---- crates/defguard_core/src/grpc/worker.rs | 4 ++-- crates/defguard_core/src/handlers/app_info.rs | 2 +- crates/defguard_core/src/handlers/mod.rs | 3 +-- crates/defguard_core/src/support.rs | 8 ++++---- crates/defguard_core/src/version.rs | 3 +-- .../defguard_core/tests/integration/api/auth.rs | 3 +-- .../defguard_core/tests/integration/api/user.rs | 3 +-- 35 files changed, 78 insertions(+), 93 deletions(-) diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index e2ae4fee63..018aa0b913 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, fmt}; -use crate::{global_value, secret::SecretStringWrapper}; use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, PgPool, Type, query, query_as}; use struct_patch::Patch; @@ -8,6 +7,8 @@ use thiserror::Error; use tracing::{debug, info, warn}; use uuid::Uuid; +use crate::{global_value, secret::SecretStringWrapper}; + global_value!(SETTINGS, Option, None, set_settings, get_settings); /// Initializes global `SETTINGS` struct at program startup diff --git a/crates/defguard_core/src/db/models/activity_log/mod.rs b/crates/defguard_core/src/db/models/activity_log/mod.rs index 62cf59154c..cf9314cf42 100644 --- a/crates/defguard_core/src/db/models/activity_log/mod.rs +++ b/crates/defguard_core/src/db/models/activity_log/mod.rs @@ -1,10 +1,9 @@ use chrono::NaiveDateTime; +use defguard_common::db::{Id, NoId}; use ipnetwork::IpNetwork; use model_derive::Model; use sqlx::{FromRow, Type}; -use defguard_common::db::{Id, NoId}; - pub mod metadata; #[derive(Clone, Debug, Deserialize, Serialize, Type)] diff --git a/crates/defguard_core/src/db/models/oauth2authorizedapp.rs b/crates/defguard_core/src/db/models/oauth2authorizedapp.rs index 0039265a65..0b7bb1af90 100644 --- a/crates/defguard_core/src/db/models/oauth2authorizedapp.rs +++ b/crates/defguard_core/src/db/models/oauth2authorizedapp.rs @@ -1,8 +1,7 @@ +use defguard_common::db::{Id, NoId}; use model_derive::Model; use sqlx::{Error as SqlxError, PgPool, query_as}; -use defguard_common::db::{Id, NoId}; - #[derive(Model)] pub struct OAuth2AuthorizedApp { pub id: I, diff --git a/crates/defguard_core/src/db/models/oauth2client.rs b/crates/defguard_core/src/db/models/oauth2client.rs index 6e8fae8428..d7e2cbdb53 100644 --- a/crates/defguard_core/src/db/models/oauth2client.rs +++ b/crates/defguard_core/src/db/models/oauth2client.rs @@ -1,11 +1,12 @@ +use defguard_common::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query_as}; -use crate::db::OAuth2Token; - use super::NewOpenIDClient; -use defguard_common::db::{Id, NoId}; -use defguard_common::random::gen_alphanumeric; +use crate::db::OAuth2Token; #[derive(Clone, Debug, Deserialize, Model, Serialize, PartialEq)] pub struct OAuth2Client { diff --git a/crates/defguard_core/src/db/models/polling_token.rs b/crates/defguard_core/src/db/models/polling_token.rs index 8b19e98dda..b4d911936d 100644 --- a/crates/defguard_core/src/db/models/polling_token.rs +++ b/crates/defguard_core/src/db/models/polling_token.rs @@ -1,10 +1,11 @@ use chrono::{NaiveDateTime, Utc}; +use defguard_common::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query_as}; -use defguard_common::db::{Id, NoId}; -use defguard_common::random::gen_alphanumeric; - // Token used for polling requests. #[derive(Clone, Debug, Model)] pub struct PollingToken { diff --git a/crates/defguard_core/src/db/models/session.rs b/crates/defguard_core/src/db/models/session.rs index 52d7a0fd9b..ee1cda00b0 100644 --- a/crates/defguard_core/src/db/models/session.rs +++ b/crates/defguard_core/src/db/models/session.rs @@ -1,10 +1,9 @@ use chrono::{NaiveDateTime, TimeDelta, Utc}; use defguard_common::{config::server_config, db::Id, random::gen_alphanumeric}; +use defguard_mail::templates::SessionContext; use sqlx::{Error as SqlxError, PgExecutor, PgPool, Type, query, query_as}; use webauthn_rs::prelude::{PasskeyAuthentication, PasskeyRegistration}; -use defguard_mail::templates::SessionContext; - #[derive(Clone, PartialEq, Type)] #[repr(i16)] pub enum SessionState { diff --git a/crates/defguard_core/src/db/models/user.rs b/crates/defguard_core/src/db/models/user.rs index 6ba075b646..df518b8bde 100644 --- a/crates/defguard_core/src/db/models/user.rs +++ b/crates/defguard_core/src/db/models/user.rs @@ -8,6 +8,11 @@ use argon2::{ }, }; use axum::http::StatusCode; +use defguard_common::{ + config::server_config, + db::{Id, NoId, models::MFAMethod}, + random::{gen_alphanumeric, gen_totp_secret}, +}; use defguard_mail::templates::UserContext; use model_derive::Model; #[cfg(test)] @@ -36,11 +41,6 @@ use crate::{ error::WebError, grpc::gateway::{send_multiple_wireguard_events, send_wireguard_event}, }; -use defguard_common::{ - config::server_config, - db::{Id, NoId, models::MFAMethod}, - random::{gen_alphanumeric, gen_totp_secret}, -}; const RECOVERY_CODES_COUNT: usize = 8; @@ -1275,12 +1275,11 @@ impl Distribution> for Standard { mod test { use defguard_common::{ config::{DefGuardConfig, SERVER_CONFIG}, - db::setup_pool, + db::{models::settings::initialize_current_settings, setup_pool}, }; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; - use defguard_common::db::models::settings::initialize_current_settings; #[sqlx::test] async fn test_mfa_code(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/src/db/models/webauthn.rs b/crates/defguard_core/src/db/models/webauthn.rs index 8cf61dba92..bd5a912f70 100644 --- a/crates/defguard_core/src/db/models/webauthn.rs +++ b/crates/defguard_core/src/db/models/webauthn.rs @@ -1,9 +1,8 @@ +use defguard_common::db::{Id, NoId, models::ModelError}; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query, query_as, query_scalar}; use webauthn_rs::prelude::Passkey; -use defguard_common::db::{Id, NoId, models::ModelError}; - #[derive(Model, Clone, Debug, PartialEq)] pub struct WebAuthn { pub id: I, diff --git a/crates/defguard_core/src/db/models/webhook.rs b/crates/defguard_core/src/db/models/webhook.rs index be3e2d26a4..8b2715c46a 100644 --- a/crates/defguard_core/src/db/models/webhook.rs +++ b/crates/defguard_core/src/db/models/webhook.rs @@ -1,8 +1,8 @@ +use defguard_common::db::{Id, NoId}; use model_derive::Model; use sqlx::{Error as SqlxError, FromRow, PgPool, query_as}; use super::UserInfo; -use defguard_common::db::{Id, NoId}; /// App events which triggers webhook action #[derive(Debug)] diff --git a/crates/defguard_core/src/db/models/wireguard_peer_stats.rs b/crates/defguard_core/src/db/models/wireguard_peer_stats.rs index 2323584c7d..350f826fdd 100644 --- a/crates/defguard_core/src/db/models/wireguard_peer_stats.rs +++ b/crates/defguard_core/src/db/models/wireguard_peer_stats.rs @@ -1,13 +1,12 @@ use std::time::Duration; use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc}; +use defguard_common::db::{Id, NoId}; use humantime::format_duration; use ipnetwork::IpNetwork; use model_derive::Model; use sqlx::{PgExecutor, PgPool, query, query_as, query_scalar}; -use defguard_common::db::{Id, NoId}; - #[derive(Debug, Deserialize, Model, Serialize)] #[table(wireguard_peer_stats)] pub struct WireguardPeerStats { diff --git a/crates/defguard_core/src/db/models/yubikey.rs b/crates/defguard_core/src/db/models/yubikey.rs index 0813319334..b12f4b8548 100644 --- a/crates/defguard_core/src/db/models/yubikey.rs +++ b/crates/defguard_core/src/db/models/yubikey.rs @@ -1,8 +1,7 @@ +use defguard_common::db::{Id, NoId}; use model_derive::Model; use sqlx::{PgExecutor, query, query_as}; -use defguard_common::db::{Id, NoId}; - #[derive(Deserialize, Model, Serialize)] pub struct YubiKey { pub id: I, diff --git a/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs b/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs index d6adb11fc4..3548de1883 100644 --- a/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs +++ b/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs @@ -1,13 +1,13 @@ +use defguard_common::{ + db::{Id, NoId}, + secret::SecretStringWrapper, +}; use model_derive::Model; use serde::Serialize; use sqlx::{Error as SqlxError, FromRow, PgExecutor, Type, query_as}; use strum_macros::{Display, EnumString}; use crate::enterprise::activity_log_stream::error::ActivityLogStreamError; -use defguard_common::{ - db::{Id, NoId}, - secret::SecretStringWrapper, -}; #[derive(Debug, Serialize, Deserialize, Type, EnumString, Display, Clone, PartialEq)] #[sqlx(type_name = "text", rename_all = "snake_case")] diff --git a/crates/defguard_core/src/enterprise/db/models/api_tokens.rs b/crates/defguard_core/src/enterprise/db/models/api_tokens.rs index 5b44881fc8..7b65a80e96 100644 --- a/crates/defguard_core/src/enterprise/db/models/api_tokens.rs +++ b/crates/defguard_core/src/enterprise/db/models/api_tokens.rs @@ -1,9 +1,8 @@ use chrono::NaiveDateTime; +use defguard_common::db::{Id, NoId}; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, query_as}; -use defguard_common::db::{Id, NoId}; - #[derive(Clone, Debug, Deserialize, Model, Serialize, PartialEq)] #[table(api_token)] pub struct ApiToken { diff --git a/crates/defguard_core/src/enterprise/db/models/openid_provider.rs b/crates/defguard_core/src/enterprise/db/models/openid_provider.rs index 1ffdde6830..7143b062b3 100644 --- a/crates/defguard_core/src/enterprise/db/models/openid_provider.rs +++ b/crates/defguard_core/src/enterprise/db/models/openid_provider.rs @@ -1,10 +1,9 @@ use std::fmt; +use defguard_common::db::{Id, NoId}; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, Type, query, query_as}; -use defguard_common::db::{Id, NoId}; - // The behavior when a user is deleted from the directory // Keep: Keep the user, despite being deleted from the external provider's directory // Disable: Disable the user diff --git a/crates/defguard_core/src/enterprise/db/models/snat.rs b/crates/defguard_core/src/enterprise/db/models/snat.rs index e8ed98df8e..c4ae69fc8e 100644 --- a/crates/defguard_core/src/enterprise/db/models/snat.rs +++ b/crates/defguard_core/src/enterprise/db/models/snat.rs @@ -1,12 +1,12 @@ use std::net::IpAddr; +use defguard_common::db::{Id, NoId}; use model_derive::Model; use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, query_as}; use utoipa::ToSchema; use crate::enterprise::snat::error::UserSnatBindingError; -use defguard_common::db::{Id, NoId}; #[derive(Clone, Debug, Deserialize, Model, Serialize, ToSchema, PartialEq)] #[table(user_snat_binding)] diff --git a/crates/defguard_core/src/enterprise/firewall/mod.rs b/crates/defguard_core/src/enterprise/firewall/mod.rs index a87f9110ac..5e2b7e8d97 100644 --- a/crates/defguard_core/src/enterprise/firewall/mod.rs +++ b/crates/defguard_core/src/enterprise/firewall/mod.rs @@ -4,6 +4,11 @@ use std::{ }; use defguard_common::db::{Id, models::ModelError}; +use defguard_proto::enterprise::firewall::{ + FirewallConfig, FirewallPolicy, FirewallRule, IpAddress, IpRange, IpVersion, Port, + PortRange as PortRangeProto, SnatBinding as SnatBindingProto, ip_address::Address, + port::Port as PortInner, +}; use ipnetwork::IpNetwork; use sqlx::{Error as SqlxError, PgConnection, query_as, query_scalar}; @@ -21,11 +26,6 @@ use crate::{ is_enterprise_enabled, }, }; -use defguard_proto::enterprise::firewall::{ - FirewallConfig, FirewallPolicy, FirewallRule, IpAddress, IpRange, IpVersion, Port, - PortRange as PortRangeProto, SnatBinding as SnatBindingProto, ip_address::Address, - port::Port as PortInner, -}; #[derive(Debug, thiserror::Error)] pub enum FirewallError { diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index 0785fb85fc..abad1a6910 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -2,6 +2,10 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use chrono::{DateTime, NaiveDateTime}; use defguard_common::db::{Id, NoId, setup_pool}; +use defguard_proto::enterprise::firewall::{ + FirewallPolicy, IpAddress, IpRange, IpVersion, Port, PortRange as PortRangeProto, Protocol, + ip_address::Address, port::Port as PortInner, +}; use ipnetwork::{IpNetwork, Ipv6Network}; use rand::{Rng, thread_rng}; use sqlx::{ @@ -27,10 +31,6 @@ use crate::{ firewall::{get_source_addrs, get_source_network_devices}, }, }; -use defguard_proto::enterprise::firewall::{ - FirewallPolicy, IpAddress, IpRange, IpVersion, Port, PortRange as PortRangeProto, Protocol, - ip_address::Address, port::Port as PortInner, -}; impl Default for AclRuleDestinationRange { fn default() -> Self { diff --git a/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs b/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs index eeb022224f..b03d904df9 100644 --- a/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs +++ b/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs @@ -1,3 +1,4 @@ +use defguard_proto::proxy::{ClientMfaOidcAuthenticateRequest, DeviceInfo, MfaMethod}; use openidconnect::{AuthorizationCode, Nonce}; use reqwest::Url; use tonic::Status; @@ -13,7 +14,6 @@ use crate::{ utils::parse_client_info, }, }; -use defguard_proto::proxy::{ClientMfaOidcAuthenticateRequest, DeviceInfo, MfaMethod}; impl ClientMfaServer { #[instrument(skip_all)] diff --git a/crates/defguard_core/src/enterprise/grpc/polling.rs b/crates/defguard_core/src/enterprise/grpc/polling.rs index 782ca70fab..ecce3ec2f3 100644 --- a/crates/defguard_core/src/enterprise/grpc/polling.rs +++ b/crates/defguard_core/src/enterprise/grpc/polling.rs @@ -1,4 +1,5 @@ use defguard_common::db::Id; +use defguard_proto::proxy::{InstanceInfoRequest, InstanceInfoResponse}; use sqlx::PgPool; use tonic::Status; @@ -7,7 +8,6 @@ use crate::{ enterprise::is_enterprise_enabled, grpc::utils::build_device_config_response, }; -use defguard_proto::proxy::{InstanceInfoRequest, InstanceInfoResponse}; pub struct PollingServer { pool: PgPool, diff --git a/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs b/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs index f125fddf5e..e8bbe7573a 100644 --- a/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs +++ b/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs @@ -2,6 +2,7 @@ use axum::{ Json, extract::{Path, State}, }; +use defguard_common::db::{Id, NoId}; use reqwest::StatusCode; use serde_json::json; @@ -15,7 +16,6 @@ use crate::{ events::{ApiEvent, ApiEventType, ApiRequestContext}, handlers::{ApiResponse, ApiResult}, }; -use defguard_common::db::{Id, NoId}; pub async fn get_activity_log_stream( _admin: AdminRole, diff --git a/crates/defguard_core/src/enterprise/handlers/api_tokens.rs b/crates/defguard_core/src/enterprise/handlers/api_tokens.rs index 12132fbc7e..15a40ecfda 100644 --- a/crates/defguard_core/src/enterprise/handlers/api_tokens.rs +++ b/crates/defguard_core/src/enterprise/handlers/api_tokens.rs @@ -4,6 +4,7 @@ use axum::{ http::StatusCode, }; use chrono::Utc; +use defguard_common::random::gen_alphanumeric; use serde_json::json; use super::LicenseInfo; @@ -16,7 +17,6 @@ use crate::{ events::{ApiEvent, ApiEventType, ApiRequestContext}, handlers::{ApiResponse, ApiResult, user_for_admin_or_self}, }; -use defguard_common::random::gen_alphanumeric; const API_TOKEN_LENGTH: usize = 32; diff --git a/crates/defguard_core/src/enterprise/ldap/client.rs b/crates/defguard_core/src/enterprise/ldap/client.rs index 36a5f1b0d9..0524f3b649 100644 --- a/crates/defguard_core/src/enterprise/ldap/client.rs +++ b/crates/defguard_core/src/enterprise/ldap/client.rs @@ -4,6 +4,7 @@ use std::{ time::Duration, }; +use defguard_common::db::models::Settings; use ldap3::{ LdapConnAsync, LdapConnSettings, Mod, Scope, SearchEntry, adapters::PagedResults, drive, ldap_escape, @@ -11,7 +12,6 @@ use ldap3::{ use super::error::LdapError; use crate::{db::User, enterprise::ldap::model::extract_rdn_value}; -use defguard_common::db::models::Settings; impl super::LDAPConnection { pub(crate) async fn create() -> Result { diff --git a/crates/defguard_core/src/enterprise/license.rs b/crates/defguard_core/src/enterprise/license.rs index 499839d8cd..2bec3c4747 100644 --- a/crates/defguard_core/src/enterprise/license.rs +++ b/crates/defguard_core/src/enterprise/license.rs @@ -3,6 +3,12 @@ use std::time::Duration; use anyhow::Result; use base64::prelude::*; use chrono::{DateTime, TimeDelta, Utc}; +use defguard_common::{ + VERSION, + config::server_config, + db::models::{Settings, settings::update_current_settings}, + global_value, +}; use humantime::format_duration; use pgp::{ composed::{Deserializable, SignedPublicKey, StandaloneSignature}, @@ -15,12 +21,6 @@ use tokio::time::sleep; use super::limits::Counts; use crate::grpc::proto::enterprise::license::{LicenseKey, LicenseLimits, LicenseMetadata}; -use defguard_common::{ - VERSION, - config::server_config, - db::models::{Settings, settings::update_current_settings}, - global_value, -}; const LICENSE_SERVER_URL: &str = "https://pkgs.defguard.net/api/license/renew"; diff --git a/crates/defguard_core/src/events.rs b/crates/defguard_core/src/events.rs index eb763a6c84..dc316c0992 100644 --- a/crates/defguard_core/src/events.rs +++ b/crates/defguard_core/src/events.rs @@ -5,6 +5,7 @@ use defguard_common::db::{ Id, models::{AuthenticationKey, MFAMethod, Settings}, }; +use defguard_proto::proxy::MfaMethod; use crate::{ db::{ @@ -16,7 +17,6 @@ use crate::{ openid_provider::OpenIdProvider, snat::UserSnatBinding, }, }; -use defguard_proto::proxy::MfaMethod; /// Shared context that needs to be added to every API event /// diff --git a/crates/defguard_core/src/grpc/client_mfa.rs b/crates/defguard_core/src/grpc/client_mfa.rs index e2b86685b0..ea1e6482d6 100644 --- a/crates/defguard_core/src/grpc/client_mfa.rs +++ b/crates/defguard_core/src/grpc/client_mfa.rs @@ -9,6 +9,11 @@ use defguard_common::{ }, }; use defguard_mail::Mail; +use defguard_proto::proxy::{ + self, ClientMfaFinishRequest, ClientMfaFinishResponse, ClientMfaStartRequest, + ClientMfaStartResponse, ClientMfaTokenValidationRequest, ClientMfaTokenValidationResponse, + MfaMethod, +}; use sqlx::PgPool; use thiserror::Error; use tokio::sync::{ @@ -30,11 +35,6 @@ use crate::{ grpc::utils::parse_client_info, handlers::mail::send_email_mfa_code_email, }; -use defguard_proto::proxy::{ - self, ClientMfaFinishRequest, ClientMfaFinishResponse, ClientMfaStartRequest, - ClientMfaStartResponse, ClientMfaTokenValidationRequest, ClientMfaTokenValidationResponse, - MfaMethod, -}; const CLIENT_SESSION_TIMEOUT: u64 = 60 * 5; // 10 minutes diff --git a/crates/defguard_core/src/grpc/gateway/map.rs b/crates/defguard_core/src/grpc/gateway/map.rs index 42e665784d..60b5ab49d0 100644 --- a/crates/defguard_core/src/grpc/gateway/map.rs +++ b/crates/defguard_core/src/grpc/gateway/map.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use chrono::Utc; use defguard_common::db::Id; +use defguard_mail::Mail; use defguard_version::tracing::VersionInfo; use semver::Version; use sqlx::PgPool; @@ -10,7 +11,6 @@ use tokio::sync::mpsc::UnboundedSender; use uuid::Uuid; use super::state::GatewayState; -use defguard_mail::Mail; /// Helper struct used to handle gateway state. Gateways are grouped by network. type GatewayHostname = String; diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 2346a8e1ad..85cf800d95 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -1,11 +1,9 @@ use std::{ collections::hash_map::HashMap, fs::read_to_string, - time::{Duration, Instant}, -}; -use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex, RwLock}, + time::{Duration, Instant}, }; use axum::http::Uri; @@ -15,10 +13,9 @@ use defguard_common::{ db::{Id, models::Settings}, }; use defguard_mail::Mail; -use defguard_version::server::DefguardVersionLayer; use defguard_version::{ ComponentInfo, DefguardComponent, Version, client::ClientVersionInterceptor, - get_tracing_variables, + get_tracing_variables, server::DefguardVersionLayer, }; use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; @@ -40,18 +37,16 @@ use tonic::{ }; use tower::ServiceBuilder; -use self::gateway::GatewayServer; use self::{ auth::AuthServer, client_mfa::ClientMfaServer, enrollment::EnrollmentServer, - password_reset::PasswordResetServer, + gateway::GatewayServer, interceptor::JwtInterceptor, password_reset::PasswordResetServer, + worker::WorkerServer, }; -use self::{interceptor::JwtInterceptor, worker::WorkerServer}; -use crate::db::GatewayEvent; pub use crate::version::MIN_GATEWAY_VERSION; use crate::{ auth::failed_login::FailedLoginMap, db::{ - AppEvent, + AppEvent, GatewayEvent, models::enrollment::{ENROLLMENT_TOKEN_TYPE, Token}, }, enterprise::{ diff --git a/crates/defguard_core/src/grpc/password_reset.rs b/crates/defguard_core/src/grpc/password_reset.rs index 9049fb449e..0c1957e713 100644 --- a/crates/defguard_core/src/grpc/password_reset.rs +++ b/crates/defguard_core/src/grpc/password_reset.rs @@ -1,4 +1,8 @@ use defguard_mail::Mail; +use defguard_proto::proxy::{ + DeviceInfo, PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, + PasswordResetStartResponse, +}; use sqlx::PgPool; use tokio::sync::mpsc::{UnboundedSender, error::SendError}; use tonic::Status; @@ -18,10 +22,6 @@ use crate::{ headers::get_device_info, server_config, }; -use defguard_proto::proxy::{ - DeviceInfo, PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, - PasswordResetStartResponse, -}; pub(super) struct PasswordResetServer { pool: PgPool, diff --git a/crates/defguard_core/src/grpc/worker.rs b/crates/defguard_core/src/grpc/worker.rs index c0acfa12ec..069492a687 100644 --- a/crates/defguard_core/src/grpc/worker.rs +++ b/crates/defguard_core/src/grpc/worker.rs @@ -6,14 +6,14 @@ use std::{ }; use defguard_common::db::models::{AuthenticationKey, AuthenticationKeyType}; +pub use defguard_proto::worker::JobStatus; +use defguard_proto::worker::{GetJobResponse, Worker, worker_service_server}; use sqlx::{PgPool, query}; use tokio::sync::mpsc::UnboundedSender; use tonic::{Request, Response, Status}; use super::{Job, JobResponse, WorkerDetail, WorkerInfo, WorkerState}; use crate::db::{AppEvent, HWKeyUserData, User, YubiKey}; -pub use defguard_proto::worker::JobStatus; -use defguard_proto::worker::{GetJobResponse, Worker, worker_service_server}; impl WorkerInfo { /// Create new `Worker` instance. diff --git a/crates/defguard_core/src/handlers/app_info.rs b/crates/defguard_core/src/handlers/app_info.rs index e53592f9c8..344ee41925 100644 --- a/crates/defguard_core/src/handlers/app_info.rs +++ b/crates/defguard_core/src/handlers/app_info.rs @@ -1,4 +1,5 @@ use axum::{extract::State, http::StatusCode}; +use defguard_common::{VERSION, db::models::Settings}; use serde_json::json; use super::{ApiResponse, ApiResult}; @@ -13,7 +14,6 @@ use crate::{ limits::{LimitsExceeded, get_counts}, }, }; -use defguard_common::{VERSION, db::models::Settings}; #[derive(Serialize)] struct LicenseInfo { diff --git a/crates/defguard_core/src/handlers/mod.rs b/crates/defguard_core/src/handlers/mod.rs index cbe8263013..c83fed9a13 100644 --- a/crates/defguard_core/src/handlers/mod.rs +++ b/crates/defguard_core/src/handlers/mod.rs @@ -12,11 +12,10 @@ use sqlx::PgPool; use utoipa::ToSchema; use webauthn_rs::prelude::RegisterPublicKeyCredential; -use crate::db::Device; use crate::{ appstate::AppState, auth::SessionInfo, - db::{User, UserInfo, WebHook}, + db::{Device, User, UserInfo, WebHook}, enterprise::{db::models::acl::AclError, license::LicenseError}, error::WebError, events::ApiRequestContext, diff --git a/crates/defguard_core/src/support.rs b/crates/defguard_core/src/support.rs index 753daa5c5e..e37bf4e770 100644 --- a/crates/defguard_core/src/support.rs +++ b/crates/defguard_core/src/support.rs @@ -1,5 +1,9 @@ use std::{collections::HashMap, fmt::Display}; +use defguard_common::{ + VERSION, + db::{Id, models::Settings}, +}; use serde::Serialize; use serde_json::{Value, json, value::to_value}; use sqlx::PgPool; @@ -8,10 +12,6 @@ use crate::{ db::{User, WireguardNetwork, models::device::WireguardNetworkDevice}, server_config, }; -use defguard_common::{ - VERSION, - db::{Id, models::Settings}, -}; /// Unwraps the result returning a JSON representation of value or error fn unwrap_json(result: Result) -> Value { diff --git a/crates/defguard_core/src/version.rs b/crates/defguard_core/src/version.rs index 5285e2e2eb..e61142618e 100644 --- a/crates/defguard_core/src/version.rs +++ b/crates/defguard_core/src/version.rs @@ -5,11 +5,10 @@ use std::{ }; use chrono::{NaiveDateTime, TimeDelta, Utc}; +use defguard_version::{ComponentInfo, Version, is_version_lower}; use serde::Serialize; use tonic::{Status, service::Interceptor}; -use defguard_version::{ComponentInfo, Version, is_version_lower}; - const MIN_PROXY_VERSION: Version = Version::new(1, 5, 0); pub const MIN_GATEWAY_VERSION: Version = Version::new(1, 5, 0); static OUTDATED_COMPONENT_LIFETIME: TimeDelta = TimeDelta::hours(1); diff --git a/crates/defguard_core/tests/integration/api/auth.rs b/crates/defguard_core/tests/integration/api/auth.rs index 272eeadc49..60d3c29e2b 100644 --- a/crates/defguard_core/tests/integration/api/auth.rs +++ b/crates/defguard_core/tests/integration/api/auth.rs @@ -20,12 +20,11 @@ use totp_lite::{Sha1, totp_custom}; use webauthn_authenticator_rs::{WebauthnAuthenticator, prelude::Url, softpasskey::SoftPasskey}; use webauthn_rs::prelude::{CreationChallengeResponse, RequestChallengeResponse}; -use crate::api::common::client::TestResponse; - use super::common::{ X_FORWARDED_FOR, fetch_user_details, make_client, make_client_with_db, make_test_client, setup_pool, }; +use crate::api::common::client::TestResponse; static SESSION_COOKIE_NAME: &str = "defguard_session"; diff --git a/crates/defguard_core/tests/integration/api/user.rs b/crates/defguard_core/tests/integration/api/user.rs index 936bcb69a5..1028c6093e 100644 --- a/crates/defguard_core/tests/integration/api/user.rs +++ b/crates/defguard_core/tests/integration/api/user.rs @@ -11,12 +11,11 @@ use reqwest::{StatusCode, header::USER_AGENT}; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use tokio_stream::{self as stream, StreamExt}; -use crate::api::common::{get_db_device, get_db_location, get_db_user, make_client_with_db}; - use super::{ TEST_SERVER_URL, common::{fetch_user_details, make_client, make_network, make_test_client, setup_pool}, }; +use crate::api::common::{get_db_device, get_db_location, get_db_user, make_client_with_db}; #[sqlx::test] async fn test_authenticate(_: PgPoolOptions, options: PgConnectOptions) { From c62562b9620724e901ed1f0e157da52b6f63493e Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:43:32 +0200 Subject: [PATCH 10/12] remove import --- crates/defguard_core/src/grpc/gateway/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index ec4aab4db4..2d5329446d 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -37,7 +37,6 @@ use crate::{ Device, GatewayEvent, User, models::{wireguard::WireguardNetwork, wireguard_peer_stats::WireguardPeerStats}, }, - enterprise::is_enterprise_enabled, events::{GrpcEvent, GrpcRequestContext}, }; From b3c50ca2c5fc28e627de3e92d86020d74fb9b8e1 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 24 Oct 2025 00:37:15 +0200 Subject: [PATCH 11/12] fix tests --- .../defguard_core/src/db/models/wireguard.rs | 5 ++-- crates/defguard_core/src/grpc/gateway/mod.rs | 2 +- crates/defguard_core/src/grpc/utils.rs | 4 ++-- .../defguard_core/src/handlers/wireguard.rs | 3 ++- .../tests/integration/api/common/mod.rs | 9 ++++--- .../tests/integration/api/wireguard.rs | 6 +++-- .../api/wireguard_network_allowed_groups.rs | 24 ++++++++++++------- .../api/wireguard_network_devices.rs | 9 ++++--- 8 files changed, 40 insertions(+), 22 deletions(-) diff --git a/crates/defguard_core/src/db/models/wireguard.rs b/crates/defguard_core/src/db/models/wireguard.rs index 17c594e4b5..83f1b9966c 100644 --- a/crates/defguard_core/src/db/models/wireguard.rs +++ b/crates/defguard_core/src/db/models/wireguard.rs @@ -1306,8 +1306,9 @@ impl WireguardNetwork { /// If this location is marked as a service location, checks if all requirements are met for it to function: /// - Enterprise is enabled - pub fn check_service_location_requirements(&self) -> bool { - self.service_location_mode != ServiceLocationMode::Disabled && is_enterprise_enabled() + #[must_use] + pub fn should_prevent_service_location_usage(&self) -> bool { + self.service_location_mode != ServiceLocationMode::Disabled && !is_enterprise_enabled() } } diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 2d5329446d..1fd5ad6f3a 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -103,7 +103,7 @@ impl WireguardNetwork { { debug!("Fetching all peers for network {}", self.id); - if !self.check_service_location_requirements() { + if self.should_prevent_service_location_usage() { warn!( "Tried to use service location {} with disabled enterprise features. No clients will be allowed to connect.", self.name diff --git a/crates/defguard_core/src/grpc/utils.rs b/crates/defguard_core/src/grpc/utils.rs index 854a875dd6..1f7ffec69a 100644 --- a/crates/defguard_core/src/grpc/utils.rs +++ b/crates/defguard_core/src/grpc/utils.rs @@ -122,7 +122,7 @@ pub(crate) async fn build_device_config_response( ); Status::internal(format!("unexpected error: {err}")) })?; - if !network.check_service_location_requirements() { + if network.should_prevent_service_location_usage() { error!( "Tried to use service location {} with disabled enterprise features.", network.name @@ -175,7 +175,7 @@ pub(crate) async fn build_device_config_response( ); Status::internal(format!("unexpected error: {err}")) })?; - if !network.check_service_location_requirements() { + if network.should_prevent_service_location_usage() { warn!( "Tried to use service location {} with disabled enterprise features.", network.name diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 40186203b1..4aa617b107 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -729,7 +729,8 @@ pub struct AddDeviceResult { "pubkey": "pubkey", "dns": "8.8.8.8", "keepalive_interval": 5, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" } ], "device": { diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 233f51f056..1c4e222445 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -181,7 +181,8 @@ pub(crate) async fn exceed_enterprise_limits(client: &TestClient) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -201,7 +202,8 @@ pub(crate) async fn exceed_enterprise_limits(client: &TestClient) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -221,7 +223,8 @@ pub(crate) fn make_network() -> Value { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" }) } diff --git a/crates/defguard_core/tests/integration/api/wireguard.rs b/crates/defguard_core/tests/integration/api/wireguard.rs index 436ba629fe..02d7a73f36 100644 --- a/crates/defguard_core/tests/integration/api/wireguard.rs +++ b/crates/defguard_core/tests/integration/api/wireguard.rs @@ -493,7 +493,8 @@ async fn test_network_address_reassignment(_: PgPoolOptions, options: PgConnectO "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" }); let response = client.post("/api/v1/network").json(&network).send().await; assert_eq!(response.status(), StatusCode::CREATED); @@ -561,7 +562,8 @@ async fn test_network_address_reassignment(_: PgPoolOptions, options: PgConnectO "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" }); let response = client .put(format!("/api/v1/network/{}", network_from_details.id)) diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs index 3a2f9e15a1..d641de589e 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs @@ -151,7 +151,8 @@ async fn test_create_new_network(_: PgPoolOptions, options: PgConnectOptions) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -197,7 +198,8 @@ async fn test_modify_network(_: PgPoolOptions, options: PgConnectOptions) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -230,7 +232,8 @@ async fn test_modify_network(_: PgPoolOptions, options: PgConnectOptions) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -257,7 +260,8 @@ async fn test_modify_network(_: PgPoolOptions, options: PgConnectOptions) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -285,7 +289,8 @@ async fn test_modify_network(_: PgPoolOptions, options: PgConnectOptions) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -312,7 +317,8 @@ async fn test_modify_network(_: PgPoolOptions, options: PgConnectOptions) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -561,7 +567,8 @@ async fn test_modify_user(_: PgPoolOptions, options: PgConnectOptions) { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; @@ -660,7 +667,8 @@ async fn test_delete_only_allowed_group(_: PgPoolOptions, options: PgConnectOpti "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" })) .send() .await; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs index f16ed592bd..225df95bb2 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs @@ -27,7 +27,8 @@ fn make_network() -> Value { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" }) } @@ -44,7 +45,8 @@ fn make_second_network() -> Value { "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" }) } @@ -303,7 +305,8 @@ async fn test_device_ip_validation(_: PgPoolOptions, options: PgConnectOptions) "peer_disconnect_threshold": 300, "acl_enabled": false, "acl_default_allow": false, - "location_mfa_mode": "disabled" + "location_mfa_mode": "disabled", + "service_location_mode": "disabled" }); let response = client.post("/api/v1/network").json(&location).send().await; assert_eq!(response.status(), StatusCode::CREATED); From a7d7d6bf882baa19b81c706b3998820e5e5a4c32 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:08:20 +0100 Subject: [PATCH 12/12] Update proto --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 3fd150c024..fee706013b 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 3fd150c0245f5ed088ed57ad780a9376e3377ce3 +Subproject commit fee706013b3bb5452c3c4dbf35bd973d0637ff25