fix(gui-client): ensure GUI client can access firezone-id.json (#9764)

I believe some of the recent changes around how we load the
`firezone-id.json` from the GUI client surfaced that we in fact don't
always have access to it. Previously, this was silenced because we would
only optionally add it as context to the Sentry client.

Now, we need it to initialise telemetry so we know whether or not to
send logs to Sentry.

In order to be able to access the file, we need to change the config's
directory and the file to be owned by the `firezone-client` group.
This commit is contained in:
Thomas Eizinger
2025-07-01 16:11:29 +02:00
committed by GitHub
parent 9bff0bc8d3
commit 899f5ea5e8
4 changed files with 35 additions and 13 deletions

View File

@@ -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<PathBuf> {
pub fn path() -> Result<PathBuf> {
let path = crate::known_dirs::tunnel_service_config()
.context("Failed to compute path for firezone-id file")?
.join("firezone-id.json");

View File

@@ -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<nix::unistd::Group> {
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:#}"),
}

View File

@@ -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<nix::unistd::Group> {
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)
}

View File

@@ -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 {