mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-28 02:18:50 +00:00
Fix/firezone id handling (#2958)
fixes #2651 Wip because firezone portal doesn't handle names longer than 8 characters yet cc @AndrewDryga
This commit is contained in:
2
rust/Cargo.lock
generated
2
rust/Cargo.lock
generated
@@ -1162,6 +1162,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hickory-resolver",
|
||||
"ip_network",
|
||||
"libc",
|
||||
"log",
|
||||
"os_info",
|
||||
"parking_lot",
|
||||
@@ -1883,6 +1884,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"uuid",
|
||||
"webrtc",
|
||||
]
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ where
|
||||
) {
|
||||
runtime.spawn(async move {
|
||||
let (connect_url, private_key) = fatal_error!(
|
||||
login_url(Mode::Client, api_url, token, device_id),
|
||||
login_url(Mode::Client, api_url, token, device_id, None),
|
||||
runtime_stopper,
|
||||
&callbacks
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ webrtc = { workspace = true }
|
||||
ring = "0.17"
|
||||
hickory-resolver = { workspace = true }
|
||||
domain = { workspace = true }
|
||||
libc = "0.2"
|
||||
|
||||
# Needed for Android logging until tracing is working
|
||||
log = "0.4"
|
||||
|
||||
@@ -16,8 +16,6 @@ pub use error::Result;
|
||||
|
||||
use boringtun::x25519::{PublicKey, StaticSecret};
|
||||
use messages::Key;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::{thread_rng, Rng};
|
||||
use ring::digest::{Context, SHA256};
|
||||
use secrecy::{ExposeSecret, SecretString};
|
||||
use std::net::Ipv4Addr;
|
||||
@@ -30,20 +28,30 @@ pub type Dname = domain::base::Dname<Vec<u8>>;
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const LIB_NAME: &str = "connlib";
|
||||
|
||||
// From https://man7.org/linux/man-pages/man2/gethostname.2.html
|
||||
// SUSv2 guarantees that "Host names are limited to 255 bytes".
|
||||
// POSIX.1 guarantees that "Host names (not including the
|
||||
// terminating null byte) are limited to HOST_NAME_MAX bytes". On
|
||||
// Linux, HOST_NAME_MAX is defined with the value 64, which has been
|
||||
// the limit since Linux 1.0 (earlier kernels imposed a limit of 8
|
||||
// bytes)
|
||||
//
|
||||
// We are counting the nul-byte
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const HOST_NAME_MAX: usize = 256;
|
||||
|
||||
/// Creates a new login URL to use with the portal.
|
||||
pub fn login_url(
|
||||
mode: Mode,
|
||||
api_url: Url,
|
||||
token: SecretString,
|
||||
device_id: String,
|
||||
firezone_name: Option<String>,
|
||||
) -> Result<(Url, StaticSecret)> {
|
||||
let private_key = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
||||
// FIXME: read FIREZONE_NAME from env (eg. for gateways) and use system hostname by default
|
||||
let name: String = thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(8)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
let name = firezone_name
|
||||
.or(get_host_name())
|
||||
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
||||
let external_id = sha256(device_id);
|
||||
|
||||
let url = get_websocket_path(
|
||||
@@ -76,6 +84,23 @@ pub fn get_user_agent() -> String {
|
||||
format!("{os_type}/{os_version} {lib_name}/{lib_version}")
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn get_host_name() -> Option<String> {
|
||||
let mut buf = [0; HOST_NAME_MAX];
|
||||
// SAFETY: we just allocated a buffer with that size
|
||||
if unsafe { libc::gethostname(buf.as_mut_ptr() as *mut _, HOST_NAME_MAX) } != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
String::from_utf8(buf.split(|c| *c == 0).next()?.to_vec()).ok()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_host_name() -> Option<String> {
|
||||
// FIXME: windows
|
||||
None
|
||||
}
|
||||
|
||||
fn set_ws_scheme(url: &mut Url) -> Result<()> {
|
||||
let scheme = match url.scheme() {
|
||||
"http" | "ws" => "ws",
|
||||
|
||||
@@ -34,10 +34,10 @@ pub struct CommonArgs {
|
||||
default_value = "wss://api.firezone.dev"
|
||||
)]
|
||||
pub api_url: Url,
|
||||
/// Identifier generated by the portal to identify and display the device.
|
||||
#[arg(short = 'i', long, env = "FIREZONE_ID")]
|
||||
pub firezone_id: String,
|
||||
/// Token generated by the portal to authorize websocket connection.
|
||||
#[arg(env = "FIREZONE_TOKEN")]
|
||||
pub token: String,
|
||||
/// Friendly name to display in the UI
|
||||
#[arg(short = 'n', long, env = "FIREZONE_NAME")]
|
||||
pub firezone_name: Option<String>,
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ tracing-subscriber = "0.3.17"
|
||||
url = { version = "2.4.1", default-features = false }
|
||||
webrtc = { workspace = true }
|
||||
domain = { workspace = true }
|
||||
uuid = { version = "1.6.1", features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = { version = "1.0", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -13,39 +13,74 @@ use messages::{EgressMessages, IngressMessages};
|
||||
use phoenix_channel::{PhoenixChannel, SecureUrl};
|
||||
use secrecy::{Secret, SecretString};
|
||||
use std::convert::Infallible;
|
||||
use std::path::Path;
|
||||
use std::pin::pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::signal::ctrl_c;
|
||||
use tokio_tungstenite::tungstenite;
|
||||
use tracing_subscriber::layer;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod eventloop;
|
||||
mod messages;
|
||||
|
||||
const ID_PATH: &str = "/var/lib/firezone/gateway_id";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
setup_global_subscriber(layer::Identity::new());
|
||||
|
||||
let firezone_id = get_firezone_id(cli.firezone_id).await
|
||||
.context("Couldn't read FIREZONE_ID or write it to disk: Please provide it through the env variable or provide rw access to /var/lib/firezone/")?;
|
||||
let (connect_url, private_key) = login_url(
|
||||
Mode::Gateway,
|
||||
cli.common.api_url,
|
||||
SecretString::new(cli.common.token),
|
||||
cli.common.firezone_id,
|
||||
firezone_id,
|
||||
cli.common.firezone_name,
|
||||
)?;
|
||||
|
||||
let task = tokio::spawn(async move { run(connect_url, private_key).await }).map_err(Into::into);
|
||||
let task = tokio::spawn(run(connect_url, private_key)).err_into();
|
||||
|
||||
let ctrl_c = pin!(ctrl_c().map_err(anyhow::Error::new));
|
||||
|
||||
future::try_select(task, ctrl_c)
|
||||
match future::try_select(task, ctrl_c)
|
||||
.await
|
||||
.map_err(|e| e.factor_first().0)?;
|
||||
.map_err(|e| e.factor_first().0)?
|
||||
{
|
||||
future::Either::Left((res, _)) => {
|
||||
res?;
|
||||
}
|
||||
future::Either::Right(_) => {}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_firezone_id(env_id: Option<String>) -> Result<String> {
|
||||
if let Some(id) = env_id {
|
||||
if !id.is_empty() {
|
||||
return Ok(id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(id) = tokio::fs::read_to_string(ID_PATH).await {
|
||||
if !id.is_empty() {
|
||||
return Ok(id);
|
||||
}
|
||||
}
|
||||
|
||||
let id_path = Path::new(ID_PATH);
|
||||
tokio::fs::create_dir_all(id_path.parent().unwrap()).await?;
|
||||
let mut id_file = tokio::fs::File::create(id_path).await?;
|
||||
let id = Uuid::new_v4().to_string();
|
||||
id_file.write_all(id.as_bytes()).await?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn run(connect_url: Url, private_key: StaticSecret) -> Result<Infallible> {
|
||||
let tunnel: Arc<Tunnel<_, GatewayState>> =
|
||||
Arc::new(Tunnel::new(private_key, CallbackHandler).await?);
|
||||
@@ -83,9 +118,10 @@ async fn run(connect_url: Url, private_key: StaticSecret) -> Result<Infallible>
|
||||
.context("Eventloop failed")
|
||||
});
|
||||
|
||||
future::try_select(portal_task, eventloop_task)
|
||||
let res = future::try_select(portal_task, eventloop_task)
|
||||
.await
|
||||
.map_err(|e| e.factor_first().0)?;
|
||||
res.factor_first().0?;
|
||||
|
||||
unreachable!("should never exit without error");
|
||||
}
|
||||
@@ -183,6 +219,9 @@ impl Callbacks for CallbackHandler {
|
||||
struct Cli {
|
||||
#[command(flatten)]
|
||||
common: CommonArgs,
|
||||
/// Identifier generated by the portal to identify and display the device.
|
||||
#[arg(short = 'i', long, env = "FIREZONE_ID")]
|
||||
pub firezone_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Checks whether the given [`std::error::Error`] is in-fact an HTTP error with a 4xx status code.
|
||||
|
||||
@@ -14,7 +14,7 @@ fn main() -> Result<()> {
|
||||
let mut session = Session::connect(
|
||||
cli.common.api_url,
|
||||
SecretString::from(cli.common.token),
|
||||
cli.common.firezone_id,
|
||||
cli.firezone_id,
|
||||
CallbackHandler { handle },
|
||||
)
|
||||
.unwrap();
|
||||
@@ -53,6 +53,10 @@ struct Cli {
|
||||
#[command(flatten)]
|
||||
common: CommonArgs,
|
||||
|
||||
/// Identifier generated by the portal to identify and display the device.
|
||||
#[arg(short = 'i', long, env = "FIREZONE_ID")]
|
||||
pub firezone_id: String,
|
||||
|
||||
/// File logging directory. Should be a path that's writeable by the current user.
|
||||
#[arg(short, long, env = "LOG_DIR")]
|
||||
log_dir: Option<PathBuf>,
|
||||
|
||||
Reference in New Issue
Block a user