mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
chore(linux-client): allow custom token path (#4666)
```[tasklist] # Before merging - [x] Remove file extension `.txt` - [x] Wait for `linux-group` test to go green on `main` (#4692) - [x] *all* compatibility tests must be green on this branch ``` Closes #4664 Closes #4665 ~~The compatibility tests are expected to fail until the next release is cut, for the same reasons as in #4686~~ The compatibility test must be handled somehow, otherwise it'll turn main red. `linux-group` was moved out of integration / compatibility testing, but the DNS tests do need the whole Docker + portal setup, so that one can't move. --------- Signed-off-by: Reactor Scram <ReactorScram@users.noreply.github.com> Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
2
.github/workflows/_rust.yml
vendored
2
.github/workflows/_rust.yml
vendored
@@ -125,7 +125,7 @@ jobs:
|
||||
matrix:
|
||||
# TODO: Add Windows as part of issue #3782
|
||||
runs-on: [ubuntu-20.04, ubuntu-22.04]
|
||||
test: [linux-group]
|
||||
test: [linux-group, token-path]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
1
rust/Cargo.lock
generated
1
rust/Cargo.lock
generated
@@ -1952,6 +1952,7 @@ dependencies = [
|
||||
"dirs",
|
||||
"firezone-cli-utils",
|
||||
"futures",
|
||||
"git-version",
|
||||
"humantime",
|
||||
"nix 0.28.0",
|
||||
"resolv-conf",
|
||||
|
||||
@@ -10,11 +10,13 @@ authors = ["Firezone, Inc."]
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0" }
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
git-version = "0.3.9"
|
||||
humantime = "2.1"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
# This actually relies on many other features in Tokio, so this will probably
|
||||
# fail to build outside the workspace. <https://github.com/firezone/firezone/pull/4328#discussion_r1540342142>
|
||||
tokio = { version = "1.36.0", features = ["macros", "signal"] }
|
||||
tracing = { workspace = true }
|
||||
url = { version = "2.3.1", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
@@ -29,7 +31,6 @@ sd-notify = "0.4.1" # This is a pure Rust re-implementation, so it isn't vulnera
|
||||
serde_json = "1.0.115"
|
||||
secrecy = { workspace = true }
|
||||
tokio-util = { version = "0.7.10", features = ["codec"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Implementation, Linux-specific
|
||||
|
||||
use super::{Cli, Cmd};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use clap::Parser;
|
||||
@@ -29,11 +31,19 @@ const ROOT_USER: u32 = 0;
|
||||
/// on some systems, `/run` should be the newer version.
|
||||
const SOCK_PATH: &str = "/run/firezone-client.sock";
|
||||
|
||||
pub fn default_token_path() -> PathBuf {
|
||||
PathBuf::from("/etc")
|
||||
.join(connlib_shared::BUNDLE_ID)
|
||||
.join("token")
|
||||
}
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let (layer, _handle) = cli.log_dir.as_deref().map(file_logger::layer).unzip();
|
||||
setup_global_subscriber(layer);
|
||||
|
||||
tracing::info!(git_version = crate::GIT_VERSION);
|
||||
|
||||
match cli.command() {
|
||||
Cmd::Auto => {
|
||||
if let Some(token) = token(&cli)? {
|
||||
@@ -44,7 +54,12 @@ pub async fn run() -> Result<()> {
|
||||
}
|
||||
Cmd::IpcService => run_ipc_service(cli).await,
|
||||
Cmd::Standalone => {
|
||||
let token = token(&cli)?.context("Need a token to run as standalone Client")?;
|
||||
let token = token(&cli)?.with_context(|| {
|
||||
format!(
|
||||
"Can't find the Firezone token in $FIREZONE_TOKEN or in `{}`",
|
||||
cli.token_path
|
||||
)
|
||||
})?;
|
||||
run_standalone(cli, &token).await
|
||||
}
|
||||
Cmd::StubIpcClient => run_debug_ipc_client(cli).await,
|
||||
@@ -55,9 +70,7 @@ pub async fn run() -> Result<()> {
|
||||
///
|
||||
/// Sync because we do blocking file I/O
|
||||
fn token(cli: &Cli) -> Result<Option<SecretString>> {
|
||||
let path = PathBuf::from("/etc")
|
||||
.join(connlib_shared::BUNDLE_ID)
|
||||
.join("token.txt");
|
||||
let path = PathBuf::from(&cli.token_path);
|
||||
|
||||
if let Some(token) = &cli.token {
|
||||
// Token was provided in CLI args or env var
|
||||
@@ -124,6 +137,11 @@ async fn run_standalone(cli: Cli, token: &SecretString) -> Result<()> {
|
||||
let (private_key, public_key) = keypair();
|
||||
let login = LoginUrl::client(cli.api_url, token, firezone_id, None, public_key.to_bytes())?;
|
||||
|
||||
if cli.check {
|
||||
tracing::info!("Check passed");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let session = Session::connect(
|
||||
login,
|
||||
Sockets::new(),
|
||||
@@ -10,25 +10,43 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
pub use imp::{default_token_path, run};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::run;
|
||||
mod imp_linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
use imp_linux as imp;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows {
|
||||
mod imp_windows {
|
||||
use clap::Parser;
|
||||
|
||||
pub fn default_token_path() -> std::path::PathBuf {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn run() -> anyhow::Result<()> {
|
||||
let cli = super::Cli::parse();
|
||||
let _cmd = cli.command();
|
||||
tracing::info!(git_version = crate::GIT_VERSION);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::run;
|
||||
use imp_windows as imp;
|
||||
|
||||
/// Output of `git describe` at compile time
|
||||
/// e.g. `1.0.0-pre.4-20-ged5437c88-modified` where:
|
||||
///
|
||||
/// * `1.0.0-pre.4` is the most recent ancestor tag
|
||||
/// * `20` is the number of commits since then
|
||||
/// * `g` doesn't mean anything
|
||||
/// * `ed5437c88` is the Git commit hash
|
||||
/// * `-modified` is present if the working dir has any changes from that commit number
|
||||
pub const GIT_VERSION: &str = git_version::git_version!(
|
||||
args = ["--always", "--dirty=-modified", "--tags"],
|
||||
fallback = "unknown"
|
||||
);
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
@@ -45,6 +63,13 @@ struct Cli {
|
||||
)]
|
||||
pub api_url: url::Url,
|
||||
|
||||
/// Check the configuration and return 0 before connecting to the API
|
||||
///
|
||||
/// Returns 1 if the configuration is wrong. Mostly non-destructive but may
|
||||
/// write a device ID to disk if one is not found.
|
||||
#[arg(long)]
|
||||
check: bool,
|
||||
|
||||
/// Token generated by the portal to authorize websocket connection.
|
||||
|
||||
// TODO: It isn't good for security to pass the token as a CLI arg.
|
||||
@@ -53,6 +78,10 @@ struct Cli {
|
||||
#[arg(env = "FIREZONE_TOKEN", hide = true, long)]
|
||||
pub token: Option<String>,
|
||||
|
||||
/// A filesystem path where the token can be found
|
||||
#[arg(default_value_t = default_token_path().display().to_string(), env = "FIREZONE_TOKEN_PATH", long)]
|
||||
token_path: String,
|
||||
|
||||
/// Identifier used by the portal to identify and display the device.
|
||||
|
||||
// AKA `device_id` in the Windows and Linux GUI clients
|
||||
@@ -79,7 +108,7 @@ impl Cli {
|
||||
|
||||
#[derive(clap::Subcommand, Clone, Copy)]
|
||||
enum Cmd {
|
||||
/// If there is a token on disk, run in standalone mode. Otherwise, run as an IPC daemon. This will be removed in a future version.
|
||||
/// If there is a token on disk, run in standalone mode. Otherwise, run as an IPC service. This will be removed in a future version.
|
||||
#[command(hide = true)]
|
||||
Auto,
|
||||
/// Listen for IPC connections and act as a privileged tunnel process for a GUI client
|
||||
|
||||
@@ -65,10 +65,14 @@ function assert_process_state {
|
||||
|
||||
function create_token_file {
|
||||
CONFIG_DIR=/etc/dev.firezone.client
|
||||
TOKEN_PATH="$CONFIG_DIR/token.txt"
|
||||
TOKEN_PATH="$CONFIG_DIR/token"
|
||||
|
||||
sudo mkdir "$CONFIG_DIR"
|
||||
sudo touch "$TOKEN_PATH"
|
||||
sudo chmod 600 "$TOKEN_PATH"
|
||||
echo "n.SFMyNTY.g2gDaANtAAAAJGM4OWJjYzhjLTkzOTItNGRhZS1hNDBkLTg4OGFlZjZkMjhlMG0AAAAkN2RhN2QxY2QtMTExYy00NGE3LWI1YWMtNDAyN2I5ZDIzMGU1bQAAACtBaUl5XzZwQmstV0xlUkFQenprQ0ZYTnFJWktXQnMyRGR3XzJ2Z0lRdkZnbgYAGUmu74wBYgABUYA.UN3vSLLcAMkHeEh5VHumPOutkuue8JA6wlxM9JxJEPE" | sudo tee "$TOKEN_PATH" > /dev/null
|
||||
|
||||
# Also put it in `token.txt` for backwards compat, until pull #4666 merges and is
|
||||
# cut into a release.
|
||||
sudo cp "$TOKEN_PATH" "$TOKEN_PATH.txt"
|
||||
}
|
||||
|
||||
@@ -10,12 +10,6 @@ FZ_GROUP="firezone"
|
||||
SERVICE_NAME=firezone-client
|
||||
export RUST_LOG=info
|
||||
|
||||
function print_debug_info {
|
||||
systemctl status "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
trap print_debug_info EXIT
|
||||
|
||||
# Copy the Linux Client out of the build dir
|
||||
ls . ./rust ./rust/target ./rust/target/debug
|
||||
sudo cp "rust/target/debug/firezone-headless-client" "/usr/bin/$BINARY_NAME"
|
||||
@@ -35,5 +29,8 @@ sudo su --login "$USER" --command RUST_LOG="$RUST_LOG" "$BINARY_NAME" stub-ipc-c
|
||||
echo "# Expect Firezone to reject our command if we run without 'su --login'"
|
||||
"$BINARY_NAME" stub-ipc-client && exit 1
|
||||
|
||||
# Stop the service in case other tests run on the same VM
|
||||
sudo systemctl stop "$SERVICE_NAME"
|
||||
|
||||
# Explicitly exiting is needed when we're intentionally having commands fail
|
||||
exit 0
|
||||
|
||||
@@ -30,7 +30,7 @@ curl --interface "$FZ_IFACE" $HTTPBIN/get && exit 1
|
||||
# Start Firezone
|
||||
resolvectl dns tun-firezone && exit 1
|
||||
stat /usr/bin/firezone-linux-client
|
||||
sudo systemctl start "$SERVICE_NAME"
|
||||
sudo systemctl start "$SERVICE_NAME" || systemctl status "$SERVICE_NAME"
|
||||
resolvectl dns tun-firezone
|
||||
resolvectl query "$HTTPBIN"
|
||||
|
||||
|
||||
32
scripts/tests/token-path.sh
Executable file
32
scripts/tests/token-path.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "./scripts/tests/lib.sh"
|
||||
|
||||
BINARY_NAME=firezone-linux-client
|
||||
TOKEN_PATH="token"
|
||||
|
||||
sudo cp "rust/target/debug/firezone-headless-client" "/usr/bin/$BINARY_NAME"
|
||||
|
||||
# Check should fail because there's no token yet
|
||||
sudo "$BINARY_NAME" standalone --check && exit 1
|
||||
|
||||
touch "$TOKEN_PATH"
|
||||
chmod 600 "$TOKEN_PATH"
|
||||
sudo chown root:root "$TOKEN_PATH"
|
||||
echo "n.SFMyNTY.g2gDaANtAAAAJGM4OWJjYzhjLTkzOTItNGRhZS1hNDBkLTg4OGFlZjZkMjhlMG0AAAAkN2RhN2QxY2QtMTExYy00NGE3LWI1YWMtNDAyN2I5ZDIzMGU1bQAAACtBaUl5XzZwQmstV0xlUkFQenprQ0ZYTnFJWktXQnMyRGR3XzJ2Z0lRdkZnbgYAGUmu74wBYgABUYA.UN3vSLLcAMkHeEh5VHumPOutkuue8JA6wlxM9JxJEPE" | sudo tee "$TOKEN_PATH" > /dev/null
|
||||
|
||||
# Check should fail because the token is not in the default path
|
||||
sudo "$BINARY_NAME" --check standalone && exit 1
|
||||
|
||||
# Check should pass if we tell it where to look
|
||||
sudo "$BINARY_NAME" --check --token-path "$TOKEN_PATH" standalone
|
||||
|
||||
# Move the token to the default path
|
||||
sudo mkdir /etc/dev.firezone.client
|
||||
sudo mv "$TOKEN_PATH" /etc/dev.firezone.client/token
|
||||
|
||||
# Check should now pass with the default path
|
||||
sudo "$BINARY_NAME" --check standalone
|
||||
|
||||
# Redundant, but helps if the last command has an `&& exit 1`
|
||||
exit 0
|
||||
@@ -66,6 +66,8 @@ Commands:
|
||||
Options:
|
||||
--token <TOKEN>
|
||||
Token generated by the portal to authorize websocket connection [env: FIREZONE_TOKEN=]
|
||||
--token-path <TOKEN_PATH>
|
||||
A filesystem path where the token can be found [env: FIREZONE_TOKEN_PATH=] [default: /etc/dev.firezone.client/token]
|
||||
-i, --firezone-id <FIREZONE_ID>
|
||||
Identifier used by the portal to identify and display the device [env: FIREZONE_ID=]
|
||||
-l, --log-dir <LOG_DIR>
|
||||
|
||||
Reference in New Issue
Block a user