mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(gateway): support systemd credentials (#10538)
For more permanent Gateway installations, or ones that are managed through something else other than our install script, it is useful to define the Gateway's token outside the systemd unit file. Systemd provides support for credentials via the `LoadCredential` and `LoadCredentialEncrypted` instructions. We just need a tiny bit of glue code in the Gateway to actually use that if it is set. --------- Signed-off-by: Thomas Eizinger <thomas@eizinger.io> Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
1
rust/Cargo.lock
generated
1
rust/Cargo.lock
generated
@@ -2428,6 +2428,7 @@ dependencies = [
|
||||
"snownet",
|
||||
"socket-factory",
|
||||
"static_assertions",
|
||||
"tempfile",
|
||||
"thiserror 2.0.16",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
||||
@@ -56,6 +56,7 @@ dns-lookup = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = { workspace = true, features = ["std"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -15,9 +15,9 @@ Linux host:
|
||||
|
||||
1. Generate a new Gateway token from the "Gateways" section of the admin portal
|
||||
and save it in your secrets manager.
|
||||
1. Ensure the `FIREZONE_TOKEN=<gateway_token>` environment variable is set
|
||||
securely in your Gateway's shell environment. The Gateway requires this
|
||||
variable at startup.
|
||||
1. Provide the token to the Gateway using one of these methods:
|
||||
- Set the `FIREZONE_TOKEN=<gateway_token>` environment variable
|
||||
- Set a [systemd credential](https://systemd.io/CREDENTIALS) named `FIREZONE_TOKEN`.
|
||||
1. Set `FIREZONE_ID` to a unique string to identify this gateway in the portal,
|
||||
e.g. `export FIREZONE_ID=$(head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1)`. The Gateway requires this variable at
|
||||
startup. We recommend this to be a 64 character hex string.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#![cfg_attr(test, allow(clippy::unwrap_used))]
|
||||
|
||||
use crate::eventloop::{Eventloop, PHOENIX_TOPIC};
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use backoff::ExponentialBackoffBuilder;
|
||||
use clap::Parser;
|
||||
use firezone_bin_shared::{
|
||||
@@ -19,9 +21,9 @@ use phoenix_channel::LoginUrl;
|
||||
use phoenix_channel::get_user_agent;
|
||||
|
||||
use phoenix_channel::PhoenixChannel;
|
||||
use secrecy::Secret;
|
||||
use std::process::ExitCode;
|
||||
use secrecy::{Secret, SecretString};
|
||||
use std::{collections::BTreeSet, path::Path};
|
||||
use std::{path::PathBuf, process::ExitCode};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tracing_subscriber::layer;
|
||||
use tun::Tun;
|
||||
@@ -119,6 +121,13 @@ async fn try_main(cli: Cli, telemetry: &mut Telemetry) -> Result<()> {
|
||||
let firezone_id = get_firezone_id(cli.firezone_id.clone()).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 token = match cli.token.clone() {
|
||||
Some(token) => token,
|
||||
None => read_systemd_credential("FIREZONE_TOKEN")
|
||||
.await
|
||||
.context("Failed to read `FIREZONE_TOKEN` systemd credential")?,
|
||||
};
|
||||
|
||||
if cli.is_telemetry_allowed() {
|
||||
telemetry
|
||||
.start(
|
||||
@@ -162,7 +171,7 @@ async fn try_main(cli: Cli, telemetry: &mut Telemetry) -> Result<()> {
|
||||
opentelemetry::global::set_meter_provider(provider);
|
||||
}
|
||||
|
||||
let login = LoginUrl::gateway(cli.api_url, &cli.token, firezone_id, cli.firezone_name)
|
||||
let login = LoginUrl::gateway(cli.api_url, &token, firezone_id, cli.firezone_name)
|
||||
.context("Failed to construct URL for logging into portal")?;
|
||||
|
||||
let resolv_conf = resolv_conf::Config::parse(
|
||||
@@ -254,6 +263,16 @@ async fn get_firezone_id(env_id: Option<String>) -> Result<String> {
|
||||
Ok(device_id.id)
|
||||
}
|
||||
|
||||
async fn read_systemd_credential(name: &str) -> Result<SecretString> {
|
||||
let Ok(creds_dir) = std::env::var("CREDENTIALS_DIRECTORY") else {
|
||||
bail!("`CREDENTIALS_DIRECTORY` not provided")
|
||||
};
|
||||
let path = PathBuf::from(creds_dir).join(name);
|
||||
let content = tokio::fs::read_to_string(&path).await?;
|
||||
|
||||
Ok(SecretString::new(content))
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
@@ -267,7 +286,7 @@ struct Cli {
|
||||
api_url: Url,
|
||||
/// Token generated by the portal to authorize websocket connection.
|
||||
#[arg(env = "FIREZONE_TOKEN")]
|
||||
token: Secret<String>,
|
||||
token: Option<SecretString>,
|
||||
/// Friendly name to display in the UI
|
||||
#[arg(short = 'n', long, env = "FIREZONE_NAME")]
|
||||
firezone_name: Option<String>,
|
||||
@@ -400,3 +419,33 @@ impl ValidateChecksumAdapter {
|
||||
Box::new(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use secrecy::ExposeSecret as _;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_firezone_token_from_systemd_credential_with_credentials_directory() {
|
||||
// Create a temporary directory to simulate CREDENTIALS_DIRECTORY
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let cred_path = temp_dir.path().join("FIREZONE_TOKEN");
|
||||
|
||||
// Write token to credential file
|
||||
std::fs::write(cred_path, "systemd-token").unwrap();
|
||||
|
||||
// Set CREDENTIALS_DIRECTORY environment variable
|
||||
unsafe {
|
||||
std::env::set_var("CREDENTIALS_DIRECTORY", temp_dir.path());
|
||||
}
|
||||
|
||||
let result = read_systemd_credential("FIREZONE_TOKEN").await.unwrap();
|
||||
assert_eq!(result.expose_secret(), "systemd-token");
|
||||
|
||||
// Clean up
|
||||
unsafe {
|
||||
std::env::remove_var("CREDENTIALS_DIRECTORY");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user