mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
refactor(rust): move device_id to bin-shared (#9040)
Both `device_id` and `device_info` are used by the headless-client and the GUI client / IPC service. They should therefore be defined in the `bin-shared` crate.
This commit is contained in:
@@ -9,7 +9,7 @@ license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
atomicwrites = { workspace = true } # Needed to safely backup `/etc/resolv.conf` and write the device ID on behalf of `gui-client`
|
||||
atomicwrites = { workspace = true } # Needed to safely backup `/etc/resolv.conf`
|
||||
backoff = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env", "string"] }
|
||||
connlib-client-shared = { workspace = true }
|
||||
@@ -31,7 +31,6 @@ secrecy = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_variant = { workspace = true }
|
||||
smbios-lib = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
# This actually relies on many other features in Tokio, so this will probably
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use atomicwrites::{AtomicFile, OverwriteBehavior};
|
||||
use std::{
|
||||
fs,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub struct DeviceId {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
/// Returns the path of the randomly-generated Firezone device ID
|
||||
///
|
||||
/// 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> {
|
||||
let path = firezone_bin_shared::known_dirs::ipc_service_config()
|
||||
.context("Failed to compute path for firezone-id file")?
|
||||
.join("firezone-id.json");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn device_serial() -> Option<String> {
|
||||
const DEFAULT_SERIAL: &str = "123456789";
|
||||
let data = smbioslib::table_load_from_device().ok()?;
|
||||
|
||||
let serial = data.find_map(|sys_info: smbioslib::SMBiosSystemInformation| {
|
||||
sys_info.serial_number().to_utf8_lossy()
|
||||
})?;
|
||||
|
||||
if serial == DEFAULT_SERIAL {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(serial)
|
||||
}
|
||||
|
||||
fn device_uuid() -> Option<String> {
|
||||
let data = smbioslib::table_load_from_device().ok()?;
|
||||
|
||||
let uuid = data.find_map(|sys_info: smbioslib::SMBiosSystemInformation| sys_info.uuid());
|
||||
|
||||
uuid?.to_string().into()
|
||||
}
|
||||
|
||||
pub fn device_info() -> phoenix_channel::DeviceInfo {
|
||||
phoenix_channel::DeviceInfo {
|
||||
device_serial: device_serial(),
|
||||
device_uuid: device_uuid(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the device ID without generating it
|
||||
pub fn get() -> Result<DeviceId> {
|
||||
let path = path()?;
|
||||
let content = fs::read_to_string(&path).context("Failed to read file")?;
|
||||
let device_id_json = serde_json::from_str::<DeviceIdJson>(&content)
|
||||
.context("Failed to deserialize content as JSON")?;
|
||||
|
||||
Ok(DeviceId {
|
||||
id: device_id_json.device_id(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the device ID, generating it and saving it to disk if needed.
|
||||
///
|
||||
/// Per <https://github.com/firezone/firezone/issues/2697> and <https://github.com/firezone/firezone/issues/2711>,
|
||||
/// clients must generate their own random IDs and persist them to disk, to handle situations like VMs where a hardware ID is not unique or not available.
|
||||
///
|
||||
/// Returns: The UUID as a String, suitable for sending verbatim to `connlib_client_shared::Session::connect`.
|
||||
///
|
||||
/// Errors: If the disk is unwritable when initially generating the ID, or unwritable when re-generating an invalid ID.
|
||||
pub fn get_or_create() -> Result<DeviceId> {
|
||||
let path = path()?;
|
||||
let dir = path
|
||||
.parent()
|
||||
.context("Device ID path should always have a parent")?;
|
||||
// Make sure the dir exists, and fix its permissions so the GUI can write the
|
||||
// log filter file
|
||||
fs::create_dir_all(dir).context("Failed to create dir for firezone-id")?;
|
||||
set_dir_permissions(dir).with_context(|| {
|
||||
format!(
|
||||
"Couldn't set permissions on IPC service config dir `{}`",
|
||||
dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
// Try to read it from the disk
|
||||
if let Some(j) = fs::read_to_string(&path)
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str::<DeviceIdJson>(&s).ok())
|
||||
{
|
||||
let id = j.device_id();
|
||||
tracing::debug!(?id, "Loaded device ID from disk");
|
||||
// Correct permissions for #6989
|
||||
set_id_permissions(&path).context("Couldn't set permissions on Firezone ID file")?;
|
||||
return Ok(DeviceId { id });
|
||||
}
|
||||
|
||||
// Couldn't read, it's missing or invalid, generate a new one and save it.
|
||||
let id = uuid::Uuid::new_v4();
|
||||
let j = DeviceIdJson { id };
|
||||
|
||||
let content =
|
||||
serde_json::to_string(&j).context("Impossible: Failed to serialize firezone-id")?;
|
||||
|
||||
let file = AtomicFile::new(&path, OverwriteBehavior::DisallowOverwrite);
|
||||
file.write(|f| f.write_all(content.as_bytes()))
|
||||
.context("Failed to write firezone-id file")?;
|
||||
|
||||
let id = j.device_id();
|
||||
tracing::debug!(?id, "Saved device ID to disk");
|
||||
set_id_permissions(&path).context("Couldn't set permissions on Firezone ID file")?;
|
||||
Ok(DeviceId { id })
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn set_dir_permissions(dir: &Path) -> Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
// user read/write, group read/write, others nothing
|
||||
// directories need `+x` to work of course
|
||||
let perms = fs::Permissions::from_mode(0o770);
|
||||
std::fs::set_permissions(dir, perms)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Does nothing on non-Linux systems
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[expect(clippy::unnecessary_wraps)]
|
||||
fn set_dir_permissions(_: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn set_id_permissions(path: &Path) -> Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
// user read/write, group read, others nothing
|
||||
let perms = fs::Permissions::from_mode(0o640);
|
||||
std::fs::set_permissions(path, perms)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Does nothing on non-Linux systems
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[expect(clippy::unnecessary_wraps)]
|
||||
fn set_id_permissions(_: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
struct DeviceIdJson {
|
||||
id: uuid::Uuid,
|
||||
}
|
||||
|
||||
impl DeviceIdJson {
|
||||
fn device_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{CallbackHandler, CliCommon, ConnlibMsg, device_id};
|
||||
use crate::{CallbackHandler, CliCommon, ConnlibMsg};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use atomicwrites::{AtomicFile, OverwriteBehavior};
|
||||
use clap::Parser;
|
||||
use connlib_model::ResourceView;
|
||||
use firezone_bin_shared::{
|
||||
DnsControlMethod, DnsController, TOKEN_ENV_KEY, TunDeviceManager, known_dirs,
|
||||
DnsControlMethod, DnsController, TOKEN_ENV_KEY, TunDeviceManager, device_id, device_info,
|
||||
known_dirs,
|
||||
platform::{tcp_socket_factory, udp_socket_factory},
|
||||
signals,
|
||||
};
|
||||
@@ -15,7 +16,7 @@ use futures::{
|
||||
future::poll_fn,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use phoenix_channel::LoginUrl;
|
||||
use phoenix_channel::{DeviceInfo, LoginUrl};
|
||||
use secrecy::SecretString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -566,7 +567,11 @@ impl<'a> Handler<'a> {
|
||||
&token,
|
||||
device_id.id,
|
||||
None,
|
||||
device_id::device_info(),
|
||||
DeviceInfo {
|
||||
device_serial: device_info::serial(),
|
||||
device_uuid: device_info::uuid(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.context("Failed to create `LoginUrl`")?;
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ use tokio::sync::mpsc;
|
||||
use tracing_subscriber::{EnvFilter, Layer as _, Registry, fmt, layer::SubscriberExt as _};
|
||||
|
||||
mod clear_logs;
|
||||
/// Generate a persistent device ID, stores it to disk, and reads it back.
|
||||
pub mod device_id;
|
||||
mod ipc_service;
|
||||
|
||||
pub use clear_logs::clear_logs;
|
||||
|
||||
@@ -7,19 +7,20 @@ use backoff::ExponentialBackoffBuilder;
|
||||
use clap::Parser;
|
||||
use connlib_client_shared::Session;
|
||||
use firezone_bin_shared::{
|
||||
DnsController, TOKEN_ENV_KEY, TunDeviceManager, new_dns_notifier, new_network_notifier,
|
||||
DnsController, TOKEN_ENV_KEY, TunDeviceManager, device_id, device_info, new_dns_notifier,
|
||||
new_network_notifier,
|
||||
platform::{tcp_socket_factory, udp_socket_factory},
|
||||
signals,
|
||||
};
|
||||
use firezone_headless_client::{CallbackHandler, CliCommon, ConnlibMsg, device_id};
|
||||
use firezone_headless_client::{CallbackHandler, CliCommon, ConnlibMsg};
|
||||
use firezone_logging::telemetry_span;
|
||||
use firezone_telemetry::Telemetry;
|
||||
use firezone_telemetry::otel;
|
||||
use futures::StreamExt as _;
|
||||
use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
|
||||
use phoenix_channel::LoginUrl;
|
||||
use phoenix_channel::PhoenixChannel;
|
||||
use phoenix_channel::get_user_agent;
|
||||
use phoenix_channel::{DeviceInfo, LoginUrl};
|
||||
use secrecy::{Secret, SecretString};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
@@ -194,7 +195,11 @@ fn main() -> Result<()> {
|
||||
&token,
|
||||
firezone_id.clone(),
|
||||
cli.firezone_name,
|
||||
device_id::device_info(),
|
||||
DeviceInfo {
|
||||
device_serial: device_info::serial(),
|
||||
device_uuid: device_info::uuid(),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
if cli.check {
|
||||
|
||||
Reference in New Issue
Block a user