From 28ec9390184b2d216269e9b660b4afed6a8e6507 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 10 Aug 2026 07:51:10 +0200 Subject: [PATCH 1/6] disconnect connected service-location tunnels after posture rejection --- .../enterprise/service-locations/src/linux.rs | 83 +++++++------ .../service-locations/src/reconciler.rs | 99 ++++++++++++--- .../service-locations/src/windows.rs | 113 +++++++++--------- 3 files changed, 180 insertions(+), 115 deletions(-) diff --git a/src-tauri/enterprise/service-locations/src/linux.rs b/src-tauri/enterprise/service-locations/src/linux.rs index 8e11177e..dbbf5f4f 100644 --- a/src-tauri/enterprise/service-locations/src/linux.rs +++ b/src-tauri/enterprise/service-locations/src/linux.rs @@ -241,17 +241,17 @@ impl ServiceLocationManager { None } - fn remove_tracked_interface(&mut self, ifname: &str) { + fn remove_tracked_interface(&mut self, ifname: &str) -> Result<(), ServiceLocationError> { debug!("Tearing down Linux service location interface: {ifname}"); - if let Some(wgapi) = self.wgapis.remove(ifname) { - if let Err(err) = wgapi.remove_interface() { - error!("Failed to remove Linux service location interface {ifname}: {err}"); - } else { - debug!("Linux service location interface {ifname} removed successfully"); - } - } else { - debug!("Linux service location interface {ifname} was not tracked as connected"); - } + let Some(wgapi) = self.wgapis.get(ifname) else { + return Err(ServiceLocationError::InterfaceError(format!( + "Linux service location interface {ifname} is not tracked" + ))); + }; + wgapi.remove_interface()?; + self.wgapis.remove(ifname); + info!("Linux service location interface {ifname} removed successfully"); + Ok(()) } pub fn disconnect_service_locations_by_instance( @@ -260,22 +260,17 @@ impl ServiceLocationManager { ) -> Result<(), ServiceLocationError> { debug!("Disconnecting Linux service locations for instance {instance_id}"); - let Some(locations) = self.connected_service_locations.remove(instance_id) else { + let Some(locations) = self.connected_service_locations.get(instance_id) else { debug!("No connected Linux service locations found for instance {instance_id}"); return Ok(()); }; + let location_pubkeys = locations + .iter() + .map(|connected| connected.location.pubkey.clone()) + .collect::>(); - for connected in locations { - let location = connected.location; - if let Some(ifname) = self.find_interface_by_peer_pubkey(&location.pubkey) { - self.remove_tracked_interface(&ifname); - } else { - debug!( - "No Linux service location interface found for instance {instance_id}, \ - location '{}'", - location.name - ); - } + for location_pubkey in location_pubkeys { + self.disconnect_service_location(instance_id, &location_pubkey)?; } Ok(()) @@ -299,27 +294,27 @@ impl ServiceLocationManager { return Ok(()); }; - let ifname = self.find_interface_by_peer_pubkey(location_pubkey); + let location = self.connected_service_locations[instance_id][position] + .location + .clone(); + let Some(ifname) = self.find_interface_by_peer_pubkey(location_pubkey) else { + return Err(ServiceLocationError::InterfaceError(format!( + "No service location interface found for location '{}' and peer \ + {location_pubkey}", + location.name + ))); + }; + self.remove_tracked_interface(&ifname)?; let Some(locations) = self.connected_service_locations.get_mut(instance_id) else { warn!("Linux service location for instance {instance_id} disappeared before removal"); return Ok(()); }; - let location = locations.remove(position).location; + locations.remove(position); if locations.is_empty() { self.connected_service_locations.remove(instance_id); } - if let Some(ifname) = ifname { - self.remove_tracked_interface(&ifname); - } else { - debug!( - "No Linux service location interface found for instance {instance_id}, location \ - '{}'", - location.name - ); - } - Ok(()) } @@ -528,7 +523,7 @@ impl ServiceLocationManager { /// /// Returns `Ok(true)` when every supported location is connected or already connected, and /// `Ok(false)` when at least one supported location failed so the caller can retry later. - pub fn connect_to_service_locations( + pub(crate) fn connect_to_service_locations( &mut self, authorizations: &PostureAuthorizations, ) -> Result { @@ -548,8 +543,7 @@ impl ServiceLocationManager { } let authorization = authorizations - .get(&(instance_data.instance_id.clone(), location.pubkey.clone())) - .map(|preshared_key| preshared_key.as_deref()); + .get(&(instance_data.instance_id.clone(), location.pubkey.clone())); let action = reconcile_action( self.is_service_location_connected( &instance_data.instance_id, @@ -567,6 +561,7 @@ impl ServiceLocationManager { ); continue; } + ReconcileAction::LeaveDisconnected => continue, ReconcileAction::WaitForAuthorization => { debug!( "Leaving Linux service location '{}' disconnected: no posture check \ @@ -576,6 +571,20 @@ impl ServiceLocationManager { all_connected = false; continue; } + ReconcileAction::Disconnect => { + if let Err(err) = self.disconnect_service_location( + &instance_data.instance_id, + &location.pubkey, + ) { + error!( + "Failed to disconnect rejected Linux service location '{}': \ + {err}", + location.name + ); + all_connected = false; + } + continue; + } ReconcileAction::Renew(preshared_key) => { if let Err(err) = self.reapply_preshared_key( &instance_data.instance_id, diff --git a/src-tauri/enterprise/service-locations/src/reconciler.rs b/src-tauri/enterprise/service-locations/src/reconciler.rs index cc9b6008..ec241f4b 100644 --- a/src-tauri/enterprise/service-locations/src/reconciler.rs +++ b/src-tauri/enterprise/service-locations/src/reconciler.rs @@ -12,6 +12,7 @@ use std::{ time::{Duration, SystemTime}, }; +use defguard_client_core::error::Error as CoreError; use defguard_client_posture::{ inspector::{device_posture_data, DiskEncryptionTarget}, request_posture_authorization, @@ -137,40 +138,62 @@ impl ServiceLocationManager { /// What one reconcile pass should do with a persisted service location. /// -/// An authorization is present only when posture authorization succeeded during this pass. Its key -/// may be absent when posture checks were removed from the location. +/// An authorization records a definitive posture outcome from this pass. An absent outcome means +/// the request failed transiently, while an approval key may be absent when posture checks were +/// removed from the location. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum ReconcileAction<'a> { LeaveConnected, + LeaveDisconnected, WaitForAuthorization, + Disconnect, Renew(Option<&'a str>), Connect(Option<&'a str>), } +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum PostureAuthorization { + Approved(Option), + Rejected, +} + #[must_use] pub(crate) fn reconcile_action( is_connected: bool, posture_check_required: bool, - authorization: Option>, + authorization: Option<&PostureAuthorization>, ) -> ReconcileAction<'_> { + if posture_check_required && matches!(authorization, Some(PostureAuthorization::Rejected)) { + return if is_connected { + ReconcileAction::Disconnect + } else { + ReconcileAction::LeaveDisconnected + }; + } + + let approved_key = match authorization { + Some(PostureAuthorization::Approved(preshared_key)) => Some(preshared_key.as_deref()), + Some(PostureAuthorization::Rejected) | None => None, + }; + if is_connected { - return authorization.map_or(ReconcileAction::LeaveConnected, ReconcileAction::Renew); + return approved_key.map_or(ReconcileAction::LeaveConnected, ReconcileAction::Renew); } - if posture_check_required && authorization.is_none() { + if posture_check_required && approved_key.is_none() { ReconcileAction::WaitForAuthorization } else { - ReconcileAction::Connect(authorization.flatten()) + ReconcileAction::Connect(approved_key.flatten()) } } -/// Posture approvals obtained this pass, keyed by (instance id, location public key). +/// Posture outcomes obtained this pass, keyed by (instance id, location public key). /// -/// An absent map entry means authorization failed and leaves the location alone. A present entry -/// with no key means core approved connecting without a PSK because posture checks were removed. -pub(crate) type PostureAuthorizations = HashMap<(String, String), Option>; +/// An absent map entry means a transient failure and leaves an existing location alone. An approval +/// with no key means core removed posture checks; a rejection tears an existing location down. +pub(crate) type PostureAuthorizations = HashMap<(String, String), PostureAuthorization>; -/// Obtains a preshared key for each location that needs one. +/// Obtains a definitive posture outcome for each location that needs one. async fn authorize_pending(pending: Vec) -> PostureAuthorizations { let mut authorizations = PostureAuthorizations::new(); if pending.is_empty() { @@ -212,13 +235,24 @@ async fn authorize_pending(pending: Vec) -> Posture ); Some(( (request.instance_id, request.location_pubkey), - preshared_key, + PostureAuthorization::Approved(preshared_key), + )) + } + Err(CoreError::PostureCheckFailed(reason)) => { + error!( + "Posture check rejected for service location '{}': {reason}. Any existing \ + tunnel will be disconnected.", + request.location_name + ); + Some(( + (request.instance_id, request.location_pubkey), + PostureAuthorization::Rejected, )) } Err(err) => { error!( - "Posture check failed for service location '{}': {err}. It will stay \ - disconnected and be retried.", + "Posture check could not be completed for service location '{}': {err}. \ + Existing tunnel state will be preserved and the check retried.", request.location_name ); None @@ -230,8 +264,8 @@ async fn authorize_pending(pending: Vec) -> Posture futures_util::pin_mut!(requests); while let Some(authorization) = requests.next().await { - if let Some((location, preshared_key)) = authorization { - authorizations.insert(location, preshared_key); + if let Some((location, outcome)) = authorization { + authorizations.insert(location, outcome); } } @@ -382,28 +416,55 @@ mod tests { #[test] fn test_connected_location_with_fresh_key_is_renewed() { + let authorization = PostureAuthorization::Approved(Some("fresh-key".to_string())); assert_eq!( - reconcile_action(true, true, Some(Some("fresh-key"))), + reconcile_action(true, true, Some(&authorization)), ReconcileAction::Renew(Some("fresh-key")) ); } #[test] fn test_approval_without_a_key_connects_without_a_key() { + let authorization = PostureAuthorization::Approved(None); assert_eq!( - reconcile_action(false, true, Some(None)), + reconcile_action(false, true, Some(&authorization)), ReconcileAction::Connect(None) ); } #[test] fn test_approval_without_a_key_removes_the_old_key_from_a_connected_location() { + let authorization = PostureAuthorization::Approved(None); assert_eq!( - reconcile_action(true, true, Some(None)), + reconcile_action(true, true, Some(&authorization)), ReconcileAction::Renew(None) ); } + #[test] + fn test_posture_rejection_disconnects_a_connected_location() { + assert_eq!( + reconcile_action(true, true, Some(&PostureAuthorization::Rejected)), + ReconcileAction::Disconnect + ); + } + + #[test] + fn test_transient_failure_leaves_a_connected_location_unchanged() { + assert_eq!( + reconcile_action(true, true, None), + ReconcileAction::LeaveConnected + ); + } + + #[test] + fn test_posture_rejection_keeps_a_disconnected_location_down() { + assert_eq!( + reconcile_action(false, true, Some(&PostureAuthorization::Rejected)), + ReconcileAction::LeaveDisconnected + ); + } + #[test] fn test_authorization_failure_keeps_a_posture_location_disconnected() { assert_eq!( diff --git a/src-tauri/enterprise/service-locations/src/windows.rs b/src-tauri/enterprise/service-locations/src/windows.rs index f7f0ad99..3471a2b1 100644 --- a/src-tauri/enterprise/service-locations/src/windows.rs +++ b/src-tauri/enterprise/service-locations/src/windows.rs @@ -483,42 +483,20 @@ impl ServiceLocationManager { ) -> Result<(), ServiceLocationError> { debug!("Disconnecting all service locations for instance_id: {instance_id}"); - if let Some(locations) = self.connected_service_locations.get(instance_id) { - // Collect locations to disconnect to avoid borrowing issues - let locations_to_disconnect = locations.to_vec(); - - for connected in locations_to_disconnect { - let location = connected.location; - let ifname = get_interface_name(&location.name); - debug!("Tearing down interface: {ifname}"); - if let Some(mut wgapi) = self.wgapis.remove(&ifname) { - if let Err(err) = wgapi.remove_interface() { - error!("Failed to remove interface {ifname}: {err}"); - } else { - debug!("Interface {ifname} removed successfully"); - } - debug!( - "Removing connected service location for instance_id: {instance_id}, \ - location_pubkey: {}", - location.pubkey - ); - debug!( - "Disconnected service location for instance_id: {instance_id}, \ - location_pubkey: {}", - location.pubkey - ); - } else { - error!("Failed to find WireGuard API for interface {ifname}"); - } - } - - self.connected_service_locations.remove(instance_id); - } else { + let Some(locations) = self.connected_service_locations.get(instance_id) else { debug!( "No connected service locations found for instance_id: {instance_id}. Skipping \ disconnect" ); return Ok(()); + }; + let location_pubkeys = locations + .iter() + .map(|connected| connected.location.pubkey.clone()) + .collect::>(); + + for location_pubkey in location_pubkeys { + self.disconnect_service_location(instance_id, &location_pubkey)?; } debug!("Disconnected all service locations for instance_id: {instance_id}"); @@ -536,36 +514,40 @@ impl ServiceLocationManager { {location_pubkey}" ); - if let Some(locations) = self.connected_service_locations.get_mut(instance_id) { - if let Some(pos) = locations - .iter() - .position(|connected| connected.location.pubkey == location_pubkey) - { - let location = locations.remove(pos).location; - let ifname = get_interface_name(&location.name); - debug!("Tearing down interface: {ifname}"); - if let Some(mut wgapi) = self.wgapis.remove(&ifname) { - if let Err(err) = wgapi.remove_interface() { - error!("Failed to remove interface {ifname}: {err}"); - } else { - debug!("Interface {ifname} removed successfully."); - } - } else { - error!("Failed to find WireGuard API for interface {ifname}. "); - } - } else { - debug!( - "Service location with pubkey {location_pubkey} for instance {instance_id} is \ - not connected, skipping disconnect" - ); - return Ok(()); - } - } else { + let Some((position, location)) = self + .connected_service_locations + .get(instance_id) + .and_then(|locations| { + locations + .iter() + .enumerate() + .find(|(_, connected)| connected.location.pubkey == location_pubkey) + .map(|(position, connected)| (position, connected.location.clone())) + }) + else { debug!( "No connected service locations found for instance_id: {instance_id}, skipping \ disconnect" ); return Ok(()); + }; + + let ifname = get_interface_name(&location.name); + debug!("Tearing down interface: {ifname}"); + let Some(wgapi) = self.wgapis.get_mut(&ifname) else { + return Err(ServiceLocationError::InterfaceError(format!( + "Failed to find WireGuard API for interface {ifname}" + ))); + }; + wgapi.remove_interface()?; + self.wgapis.remove(&ifname); + + let Some(locations) = self.connected_service_locations.get_mut(instance_id) else { + return Ok(()); + }; + locations.remove(position); + if locations.is_empty() { + self.connected_service_locations.remove(instance_id); } debug!( @@ -808,7 +790,7 @@ impl ServiceLocationManager { /// /// Returns `Ok(true)` when every eligible location is connected or already connected, and /// `Ok(false)` when at least one eligible location failed so the caller can retry later. - pub fn connect_to_service_locations( + pub(crate) fn connect_to_service_locations( &mut self, authorizations: &PostureAuthorizations, ) -> Result { @@ -847,8 +829,7 @@ impl ServiceLocationManager { } let authorization = authorizations - .get(&(instance_data.instance_id.clone(), location.pubkey.clone())) - .map(|preshared_key| preshared_key.as_deref()); + .get(&(instance_data.instance_id.clone(), location.pubkey.clone())); let action = reconcile_action( self.is_service_location_connected( &instance_data.instance_id, @@ -866,6 +847,7 @@ impl ServiceLocationManager { ); continue; } + ReconcileAction::LeaveDisconnected => continue, ReconcileAction::WaitForAuthorization => { debug!( "Leaving service location '{}' disconnected: no posture check has \ @@ -875,6 +857,19 @@ impl ServiceLocationManager { all_connected = false; continue; } + ReconcileAction::Disconnect => { + if let Err(err) = self.disconnect_service_location( + &instance_data.instance_id, + &location.pubkey, + ) { + warn!( + "Failed to disconnect rejected service location '{}': {err}", + location.name + ); + all_connected = false; + } + continue; + } ReconcileAction::Renew(preshared_key) => { if let Err(err) = self.reapply_preshared_key( &instance_data.instance_id, From 0af44278e1a64e056c305f925013d3d68a291583 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 10 Aug 2026 08:45:57 +0200 Subject: [PATCH 2/6] fix file permissions during creation --- src-tauri/enterprise/service-locations/src/linux.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src-tauri/enterprise/service-locations/src/linux.rs b/src-tauri/enterprise/service-locations/src/linux.rs index dbbf5f4f..87c4da27 100644 --- a/src-tauri/enterprise/service-locations/src/linux.rs +++ b/src-tauri/enterprise/service-locations/src/linux.rs @@ -1,7 +1,8 @@ use std::{ collections::HashSet, - fs::{self, create_dir_all, set_permissions}, - os::unix::fs::PermissionsExt, + fs::{self, create_dir_all, set_permissions, OpenOptions}, + io::Write, + os::unix::fs::{OpenOptionsExt, PermissionsExt}, path::PathBuf, str::FromStr, time::SystemTime, @@ -142,7 +143,13 @@ impl ServiceLocationManager { "Writing service location data to file: {}", instance_file_path.display() ); - fs::write(&instance_file_path, json)?; + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .mode(SERVICE_LOCATION_FILE_PERMS) + .open(&instance_file_path)? + .write_all(json.as_bytes())?; set_permissions( &instance_file_path, fs::Permissions::from_mode(SERVICE_LOCATION_FILE_PERMS), From 415598ebc5679d8c68ae25fb4b608d71f05a70b5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 10 Aug 2026 08:54:44 +0200 Subject: [PATCH 3/6] fix CVE --- new-ui/package.json | 2 +- new-ui/pnpm-lock.yaml | 22 +++++++++++----------- new-ui/pnpm-workspace.yaml | 2 ++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/new-ui/package.json b/new-ui/package.json index ab70334c..b8eefb24 100644 --- a/new-ui/package.json +++ b/new-ui/package.json @@ -20,7 +20,6 @@ "@tanstack/react-form": "^1.33.2", "@tanstack/react-query": "^5.101.2", "@tanstack/react-router": "^1.170.18", - "@tanstack/router-plugin": "^1.168.20", "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.7.1", @@ -54,6 +53,7 @@ }, "devDependencies": { "@tanstack/devtools-vite": "^0.8.1", + "@tanstack/router-plugin": "^1.168.20", "@types/byte-size": "^8.1.2", "@types/node": "^26.1.1", "@types/react": "^19.2.17", diff --git a/new-ui/pnpm-lock.yaml b/new-ui/pnpm-lock.yaml index 3d6835bf..eae441c1 100644 --- a/new-ui/pnpm-lock.yaml +++ b/new-ui/pnpm-lock.yaml @@ -29,9 +29,6 @@ importers: '@tanstack/react-router': specifier: ^1.170.18 version: 1.170.18(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tanstack/router-plugin': - specifier: ^1.168.20 - version: 1.168.20(@tanstack/react-router@1.170.18(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(rolldown@1.1.5)(supports-color@10.2.2)(vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0)(sass@1.101.0)) '@tauri-apps/api': specifier: ^2.11.1 version: 2.11.1 @@ -126,6 +123,9 @@ importers: '@tanstack/devtools-vite': specifier: ^0.8.1 version: 0.8.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0)(sass@1.101.0)) + '@tanstack/router-plugin': + specifier: ^1.168.20 + version: 1.168.20(@tanstack/react-router@1.170.18(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(rolldown@1.1.5)(supports-color@10.2.2)(vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0)(sass@1.101.0)) '@types/byte-size': specifier: ^8.1.2 version: 8.1.2 @@ -1180,8 +1180,8 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-uri@3.1.3: - resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} @@ -1390,8 +1390,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true jsesc@3.1.0: @@ -2897,7 +2897,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.3 + fast-uri: 3.1.5 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -3003,7 +3003,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 parse-json: 5.2.0 optionalDependencies: typescript: 6.0.3 @@ -3071,7 +3071,7 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-uri@3.1.3: {} + fast-uri@3.1.5: {} fastest-levenshtein@1.0.16: {} @@ -3291,7 +3291,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.3.0: + js-yaml@4.3.1: dependencies: argparse: 2.0.1 diff --git a/new-ui/pnpm-workspace.yaml b/new-ui/pnpm-workspace.yaml index 6e2543d3..bf441ee6 100644 --- a/new-ui/pnpm-workspace.yaml +++ b/new-ui/pnpm-workspace.yaml @@ -6,3 +6,5 @@ minimumReleaseAgeExclude: - react-dom@19.2.7 - react@19.2.7 - vite@8.0.16 + - fast-uri@3.1.4 || 3.1.5 + - js-yaml@4.3.1 From ca81e38aebf3c1564810545dfd0555e1a0775bd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 06:56:05 +0000 Subject: [PATCH 4/6] chore(nix): update new-ui pnpm deps hash --- nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index d801112b..fae5ed6c 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -90,7 +90,7 @@ inherit version pnpm; src = ../new-ui; fetcherVersion = 4; - hash = "sha256-UfOXEXdeCsx5gmdcmXG30db8fndQWKs6/0IzQFvRR5U="; + hash = "sha256-xEB3HmkcTaf+rxCmhZ7QWfjf13TPTN1Fvr0KU2yJliA="; }; # Pre-build the new UI frontend so Tauri can serve it as WebviewUrl::App("compact/") and "full/". From 62743d6ccc739be46cb5a4f61f44fb9e63bf2c11 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 10 Aug 2026 09:19:51 +0200 Subject: [PATCH 5/6] no fail-fast when disconnecting --- .../enterprise/service-locations/src/linux.rs | 16 +++++++++++++++- .../enterprise/service-locations/src/windows.rs | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src-tauri/enterprise/service-locations/src/linux.rs b/src-tauri/enterprise/service-locations/src/linux.rs index 87c4da27..4e8860dc 100644 --- a/src-tauri/enterprise/service-locations/src/linux.rs +++ b/src-tauri/enterprise/service-locations/src/linux.rs @@ -276,8 +276,22 @@ impl ServiceLocationManager { .map(|connected| connected.location.pubkey.clone()) .collect::>(); + let mut disconnect_failed = false; for location_pubkey in location_pubkeys { - self.disconnect_service_location(instance_id, &location_pubkey)?; + if let Err(err) = self.disconnect_service_location(instance_id, &location_pubkey) { + error!( + "Failed to disconnect Linux service location peer {location_pubkey} for \ + instance {instance_id}: {err}" + ); + disconnect_failed = true; + } + } + + if disconnect_failed { + return Err(ServiceLocationError::InterfaceError(format!( + "Failed to disconnect one or more Linux service locations for instance \ + {instance_id}" + ))); } Ok(()) diff --git a/src-tauri/enterprise/service-locations/src/windows.rs b/src-tauri/enterprise/service-locations/src/windows.rs index 3471a2b1..57b2f2bd 100644 --- a/src-tauri/enterprise/service-locations/src/windows.rs +++ b/src-tauri/enterprise/service-locations/src/windows.rs @@ -495,8 +495,21 @@ impl ServiceLocationManager { .map(|connected| connected.location.pubkey.clone()) .collect::>(); + let mut disconnect_failed = false; for location_pubkey in location_pubkeys { - self.disconnect_service_location(instance_id, &location_pubkey)?; + if let Err(err) = self.disconnect_service_location(instance_id, &location_pubkey) { + error!( + "Failed to disconnect service location peer {location_pubkey} for instance \ + {instance_id}: {err}" + ); + disconnect_failed = true; + } + } + + if disconnect_failed { + return Err(ServiceLocationError::InterfaceError(format!( + "Failed to disconnect one or more service locations for instance {instance_id}" + ))); } debug!("Disconnected all service locations for instance_id: {instance_id}"); From d83e82fd0caf038c6591425342421cfa1c411376 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 10 Aug 2026 10:16:41 +0200 Subject: [PATCH 6/6] fix windows build - cli was merged into client --- src-tauri/daemon/src/daemon.rs | 7 +++---- src-tauri/resources-windows/fragments/cli.wxs | 10 ---------- src-tauri/tauri.conf.json | 18 ++++++++---------- 3 files changed, 11 insertions(+), 24 deletions(-) delete mode 100644 src-tauri/resources-windows/fragments/cli.wxs diff --git a/src-tauri/daemon/src/daemon.rs b/src-tauri/daemon/src/daemon.rs index 25b5641f..464cddd7 100644 --- a/src-tauri/daemon/src/daemon.rs +++ b/src-tauri/daemon/src/daemon.rs @@ -21,12 +21,11 @@ use defguard_client_proto::defguard::{ }, enterprise::posture::v2::DevicePostureData, }; +#[cfg(target_os = "linux")] +use defguard_client_service_locations::reconciler::{run_reconciler, ReconcileSignal}; use defguard_client_service_locations::ServiceLocationError; #[cfg(any(windows, target_os = "linux"))] -use defguard_client_service_locations::{ - reconciler::{run_reconciler, ReconcileSignal}, - ServiceLocationManager, -}; +use defguard_client_service_locations::ServiceLocationManager; #[cfg(not(target_os = "macos"))] use defguard_wireguard_rs::Kernel; #[cfg(target_os = "macos")] diff --git a/src-tauri/resources-windows/fragments/cli.wxs b/src-tauri/resources-windows/fragments/cli.wxs deleted file mode 100644 index a77d0b48..00000000 --- a/src-tauri/resources-windows/fragments/cli.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2035458c..fde6c1b2 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -24,16 +24,14 @@ "upgradeCode": "923b21f5-7d3f-4f5e-8dcb-43fe1c65fb43", "bannerPath": "./resources-windows/msi/top_banner.png", "dialogImagePath": "./resources-windows/msi/side_banner.png", - "fragmentPaths": [ - "./resources-windows/fragments/service.wxs", - "./resources-windows/fragments/cli.wxs", - "./resources-windows/fragments/provisioning.wxs" - ], - "componentRefs": [ - "DefguardServiceFragment", - "DefguardCliFragment", - "ProvisioningScriptFragment" - ], + "fragmentPaths": [ + "./resources-windows/fragments/service.wxs", + "./resources-windows/fragments/provisioning.wxs" + ], + "componentRefs": [ + "DefguardServiceFragment", + "ProvisioningScriptFragment" + ], "template": "./resources-windows/msi/main.wxs" } },