diff --git a/rust/bin-shared/src/device_id.rs b/rust/bin-shared/src/device_id.rs index 53186a931..5f3671da7 100644 --- a/rust/bin-shared/src/device_id.rs +++ b/rust/bin-shared/src/device_id.rs @@ -18,7 +18,7 @@ pub struct DeviceId { /// /// e.g. `C:\ProgramData\dev.firezone.client/firezone-id.json` or /// `/var/lib/dev.firezone.client/config/firezone-id.json`. -pub(crate) fn path() -> Result { +pub fn path() -> Result { let path = crate::known_dirs::tunnel_service_config() .context("Failed to compute path for firezone-id file")? .join("firezone-id.json"); diff --git a/rust/gui-client/src-tauri/src/elevation.rs b/rust/gui-client/src-tauri/src/elevation.rs index 57e133b51..de1eb8d80 100644 --- a/rust/gui-client/src-tauri/src/elevation.rs +++ b/rust/gui-client/src-tauri/src/elevation.rs @@ -2,10 +2,9 @@ pub use platform::gui_check; #[cfg(target_os = "linux")] mod platform { + use crate::FIREZONE_CLIENT_GROUP; use anyhow::{Context as _, Result}; - const FIREZONE_GROUP: &str = "firezone-client"; - /// Returns true if all permissions are correct for the GUI to run /// /// Everything that needs root / admin powers happens in the Tunnel services, @@ -17,7 +16,7 @@ mod platform { return Ok(false); } - let fz_gid = firezone_group()?.gid; + let fz_gid = crate::firezone_client_group()?.gid; let groups = nix::unistd::getgroups().context("Unable to read groups of current user")?; if !groups.contains(&fz_gid) { return Err(Error::UserNotInFirezoneGroup); @@ -26,16 +25,9 @@ mod platform { Ok(true) } - fn firezone_group() -> Result { - let group = nix::unistd::Group::from_name(FIREZONE_GROUP) - .context("can't get group by name")? - .with_context(|| format!("`{FIREZONE_GROUP}` group must exist on the system"))?; - Ok(group) - } - #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("User is not part of {FIREZONE_GROUP} group")] + #[error("User is not part of {FIREZONE_CLIENT_GROUP} group")] UserNotInFirezoneGroup, #[error(transparent)] Other(#[from] anyhow::Error), @@ -45,7 +37,7 @@ mod platform { pub fn user_friendly_msg(&self) -> String { match self { Error::UserNotInFirezoneGroup => format!( - "You are not a member of the group `{FIREZONE_GROUP}`. Try `sudo usermod -aG {FIREZONE_GROUP} $USER` and then reboot" + "You are not a member of the group `{FIREZONE_CLIENT_GROUP}`. Try `sudo usermod -aG {FIREZONE_CLIENT_GROUP} $USER` and then reboot" ), Error::Other(e) => format!("Failed to determine group ownership: {e:#}"), } diff --git a/rust/gui-client/src-tauri/src/lib.rs b/rust/gui-client/src-tauri/src/lib.rs index 8b6443295..61876ad8b 100644 --- a/rust/gui-client/src-tauri/src/lib.rs +++ b/rust/gui-client/src-tauri/src/lib.rs @@ -20,3 +20,16 @@ pub mod settings; /// Tunnel service and GUI client are always bundled into a single release. /// Hence, we have a single constant for Tunnel service and GUI client. pub const RELEASE: &str = concat!("gui-client@", env!("CARGO_PKG_VERSION")); + +pub const FIREZONE_CLIENT_GROUP: &str = "firezone-client"; + +#[cfg(target_os = "linux")] +pub fn firezone_client_group() -> anyhow::Result { + use anyhow::Context as _; + + let group = nix::unistd::Group::from_name(FIREZONE_CLIENT_GROUP) + .context("can't get group by name")? + .with_context(|| format!("`{FIREZONE_CLIENT_GROUP}` group must exist on the system"))?; + + Ok(group) +} diff --git a/rust/gui-client/src-tauri/src/service.rs b/rust/gui-client/src-tauri/src/service.rs index 4a634bda1..641ee7dee 100644 --- a/rust/gui-client/src-tauri/src/service.rs +++ b/rust/gui-client/src-tauri/src/service.rs @@ -107,6 +107,23 @@ async fn ipc_listen( // This also gives the GUI a safe place to put the log filter config let device_id = device_id::get_or_create().context("Failed to read / create device ID")?; + // Fix up the group of the device ID file and directory so the GUI client can access it. + #[cfg(target_os = "linux")] + { + let path = device_id::path().context("Failed to access device ID path")?; + let group_id = crate::firezone_client_group() + .context("Failed to get `firezone-client` group")? + .gid + .as_raw(); + + std::os::unix::fs::chown(&path, None, Some(group_id)) + .with_context(|| format!("Failed to change ownership of '{}'", path.display()))?; + + let dir = path.parent().context("No parent path")?; + std::os::unix::fs::chown(dir, None, Some(group_id)) + .with_context(|| format!("Failed to change ownership of '{}'", dir.display()))?; + } + let mut server = ipc::Server::new(SocketId::Tunnel)?; let mut dns_controller = DnsController { dns_control_method }; loop {