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:
Gabi
2023-12-19 18:38:27 -03:00
committed by GitHub
parent aabc06c3c1
commit 73823ecba0
8 changed files with 90 additions and 18 deletions

2
rust/Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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
);

View File

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

View File

@@ -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",

View File

@@ -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>,
}

View File

@@ -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"] }

View File

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

View File

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