mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(gateway): create debian package (#10537)
With this PR we add `cargo-deb` to our CI pipeline and build a debian package for the Gateway. The debian package comes with several configuration files that make it easy for admins to start and maintain a Gateway installation: - The embedded systemd unit file is essentially the same one as what we currently install with the install script with some minor modifications. - The token is read from `/etc/firezone/gateway-token` and passed as a systemd credential. This allows us to set the permissions for this file to `0400` and have it owned by `root:root`. - The configuration is read from `/etc/firezone/gateway-env`. - Both of these changes basically mean the user should never need to touch the unit file itself. - The `sysusers` configuration file ensures the `firezone` user and group are present on the system. - The `tmpfiles` configuration file ensures the necessary directories are present. All of the above is automatically installed and configured using the post-installation script which is called by `apt` once the package is installed. In addition to the Gateway, we also package a first version of the `firezone-cli`. Right now, `firezone-cli` (installed as `firezone`) has three subcommands: - `gateway authenticate`: Asks for the Gateway's token and installs it at `/etc/firezone/gateway-token`. The user doesn't have to know how we manage this token and can trust that we are using safe defaults. - `gateway enable`: Enables and starts the systemd service. - `gateway disable`: Disables the systemd service. Right now, the `.deb` file is only uploaded to the preview APT repository and not attached to the release. It should therefore not yet be user-visible unless somebody pokes around a lot, meaning we can defer documentation to a later PR and start testing it from the preview repository for our own purposes. Related: #10598 Resolves: #8484 Resolves: #10681
This commit is contained in:
16
rust/cli/Cargo.toml
Normal file
16
rust/cli/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "firezone-cli"
|
||||
version = "1.0.0"
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
description = "CLI for managing Firezone installations"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
nix = { workspace = true, features = ["user"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
165
rust/cli/src/main.rs
Normal file
165
rust/cli/src/main.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
#![expect(clippy::print_stdout, reason = "We are a CLI.")]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
const ETC_FIREZONE_GATEWAY_TOKEN: &str = "/etc/firezone/gateway-token";
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
use Component::*;
|
||||
use GatewayCommand::*;
|
||||
|
||||
match cli.component {
|
||||
Gateway(Authenticate { replace }) => {
|
||||
anyhow::ensure!(cfg!(target_os = "linux"), "Only supported Linux right now");
|
||||
anyhow::ensure!(is_root(), "Must be executed as root");
|
||||
|
||||
if let Ok(existing) = std::fs::read_to_string(ETC_FIREZONE_GATEWAY_TOKEN)
|
||||
&& !existing.trim().is_empty()
|
||||
&& !replace
|
||||
{
|
||||
anyhow::bail!(
|
||||
"Found existing token at {ETC_FIREZONE_GATEWAY_TOKEN}, use --replace to overwrite"
|
||||
);
|
||||
}
|
||||
|
||||
let mut token = String::with_capacity(512); // Our tokens are ~270 characters, grab the next power of 2.
|
||||
|
||||
loop {
|
||||
println!("Paste the token from the portal's deploy page:");
|
||||
|
||||
let num_bytes = std::io::stdin()
|
||||
.read_line(&mut token)
|
||||
.context("Failed to read token from stdin")?;
|
||||
|
||||
if num_bytes == 0 || token.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
install_firezone_gateway_token(token)?;
|
||||
|
||||
println!("Successfully installed token");
|
||||
println!("Tip: You can now start the Gateway with `firezone gateway enable`");
|
||||
}
|
||||
Gateway(Enable) => {
|
||||
anyhow::ensure!(cfg!(target_os = "linux"), "Only supported Linux right now");
|
||||
anyhow::ensure!(is_root(), "Must be executed as root");
|
||||
|
||||
enable_gateway_service().context("Failed to enable `firezone-gateway.service`")?;
|
||||
|
||||
println!("Successfully enabled `firezone-gateway.service`");
|
||||
}
|
||||
Gateway(Disable) => {
|
||||
anyhow::ensure!(cfg!(target_os = "linux"), "Only supported Linux right now");
|
||||
anyhow::ensure!(is_root(), "Must be executed as root");
|
||||
|
||||
disable_gateway_service().context("Failed to disable `firezone-gateway.service`")?;
|
||||
|
||||
println!("Successfully disabled `firezone-gateway.service`");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "firezone", bin_name = "firezone", about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
component: Component,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Component {
|
||||
#[command(subcommand)]
|
||||
Gateway(GatewayCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum GatewayCommand {
|
||||
/// Securely store the Gateway's token on disk.
|
||||
Authenticate {
|
||||
/// If an existing token is found, replace it.
|
||||
#[arg(long, default_value_t = false)]
|
||||
replace: bool,
|
||||
},
|
||||
/// Enable the Gateway's systemd service.
|
||||
Enable,
|
||||
/// Disable the Gateway's systemd service.
|
||||
Disable,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_root() -> bool {
|
||||
nix::unistd::Uid::current().is_root()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn is_root() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn install_firezone_gateway_token(token: String) -> Result<()> {
|
||||
std::fs::write(ETC_FIREZONE_GATEWAY_TOKEN, token)
|
||||
.with_context(|| format!("Failed to write token to `{ETC_FIREZONE_GATEWAY_TOKEN}`"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn install_firezone_gateway_token(token: String) -> Result<()> {
|
||||
anyhow::bail!("Not implemented")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn enable_gateway_service() -> Result<()> {
|
||||
use std::process::Command;
|
||||
|
||||
let output = Command::new("systemctl")
|
||||
.arg("enable")
|
||||
.arg("--now")
|
||||
.arg("firezone-gateway.service")
|
||||
.output()?;
|
||||
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"`systemctl enable` exited with {}",
|
||||
output.status
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn enable_gateway_service() -> Result<()> {
|
||||
anyhow::bail!("Not implemented")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn disable_gateway_service() -> Result<()> {
|
||||
use std::process::Command;
|
||||
|
||||
let output = Command::new("systemctl")
|
||||
.arg("disable")
|
||||
.arg("firezone-gateway.service")
|
||||
.output()?;
|
||||
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"`systemctl disable` exited with {}",
|
||||
output.status
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn disable_gateway_service() -> Result<()> {
|
||||
anyhow::bail!("Not implemented")
|
||||
}
|
||||
Reference in New Issue
Block a user