mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(gateway): check if we run with correct permissions (#7565)
The gateway needs either the `CAP_NET_ADMIN` capability or run as `root` in order to access the TUN device as well as configure routes via `netlink`. Running without either leads to "Permission denied" errors at runtime. It is good to fail early in these kind of situations. By checking for this capability early on during startup, these should no longer surface later. As a bonus, we won't receive (unactionable) Sentry alerts. Resolves: #7559. --------- Signed-off-by: Thomas Eizinger <thomas@eizinger.io> Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
12
rust/Cargo.lock
generated
12
rust/Cargo.lock
generated
@@ -752,6 +752,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "caps"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.8"
|
||||
@@ -1952,6 +1962,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"backoff",
|
||||
"boringtun",
|
||||
"caps",
|
||||
"chrono",
|
||||
"clap",
|
||||
"connlib-model",
|
||||
@@ -1967,6 +1978,7 @@ dependencies = [
|
||||
"ip-packet",
|
||||
"ip_network",
|
||||
"libc",
|
||||
"nix 0.29.0",
|
||||
"phoenix-channel",
|
||||
"rustls",
|
||||
"secrecy",
|
||||
|
||||
@@ -59,6 +59,7 @@ glob = "0.3.1"
|
||||
hex = "0.4.3"
|
||||
hex-display = "0.3.0"
|
||||
hex-literal = "0.4.1"
|
||||
caps = "0.5.5"
|
||||
humantime = "2.1"
|
||||
ip_network = { version = "0.4", default-features = false }
|
||||
ip_network_table = { version = "0.2", default-features = false }
|
||||
|
||||
@@ -65,7 +65,7 @@ impl TunDeviceManager {
|
||||
///
|
||||
/// Panics if called without a Tokio runtime.
|
||||
pub fn new(mtu: usize, num_threads: usize) -> Result<Self> {
|
||||
let (cxn, handle, _) = new_connection()?;
|
||||
let (cxn, handle, _) = new_connection().context("Failed to create netlink connection")?;
|
||||
let task = tokio::spawn(cxn);
|
||||
let connection = Connection { handle, task };
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
backoff = { workspace = true }
|
||||
boringtun = { workspace = true }
|
||||
caps = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
connlib-model = { workspace = true }
|
||||
@@ -26,6 +27,7 @@ futures-bounded = { workspace = true }
|
||||
ip-packet = { workspace = true }
|
||||
ip_network = { workspace = true }
|
||||
libc = { workspace = true, features = ["std", "const-extern-fn", "extra_traits"] }
|
||||
nix = { workspace = true }
|
||||
phoenix-channel = { workspace = true }
|
||||
rustls = { workspace = true }
|
||||
secrecy = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::eventloop::{Eventloop, PHOENIX_TOPIC};
|
||||
use anyhow::{Context, Result};
|
||||
use backoff::ExponentialBackoffBuilder;
|
||||
use caps::{CapSet, Capability};
|
||||
use clap::Parser;
|
||||
use firezone_bin_shared::{
|
||||
http_health_check,
|
||||
@@ -31,11 +32,20 @@ mod eventloop;
|
||||
const ID_PATH: &str = "/var/lib/firezone/gateway_id";
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let cli = Cli::parse();
|
||||
|
||||
#[expect(clippy::print_stderr, reason = "No logger has been set up yet")]
|
||||
if !has_necessary_permissions() && !cli.no_check {
|
||||
eprintln!(
|
||||
"firezone-gateway needs to be executed as `root` or with the `CAP_NET_ADMIN` capability.\nSee https://www.firezone.dev/kb/deploy/gateways#permissions for details."
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
rustls::crypto::ring::default_provider()
|
||||
.install_default()
|
||||
.expect("Calling `install_default` only once per process should always succeed");
|
||||
|
||||
let cli = Cli::parse();
|
||||
let mut telemetry = Telemetry::default();
|
||||
if cli.is_telemetry_allowed() {
|
||||
telemetry.start(
|
||||
@@ -73,6 +83,15 @@ fn main() -> ExitCode {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn has_necessary_permissions() -> bool {
|
||||
let is_root = nix::unistd::Uid::current().is_root();
|
||||
let has_net_admin =
|
||||
caps::has_cap(None, CapSet::Effective, Capability::CAP_NET_ADMIN).is_ok_and(|b| b);
|
||||
|
||||
is_root || has_net_admin
|
||||
}
|
||||
|
||||
async fn try_main(cli: Cli, telemetry: &mut Telemetry) -> Result<ExitCode> {
|
||||
firezone_logging::setup_global_subscriber(layer::Identity::default())
|
||||
.context("Failed to set up logging")?;
|
||||
@@ -184,6 +203,10 @@ struct Cli {
|
||||
#[arg(long, env = "FIREZONE_NO_TELEMETRY", default_value_t = false)]
|
||||
no_telemetry: bool,
|
||||
|
||||
/// Don't preemtively check permissions.
|
||||
#[arg(long, default_value_t = false)]
|
||||
no_check: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
health_check: http_health_check::HealthCheckArgs,
|
||||
|
||||
|
||||
@@ -63,6 +63,23 @@ you'll need to make sure the following outbound traffic is allowed:
|
||||
| github.com, www.firezone.dev | Varies | `443` | HTTPS | Only required for [Gateway upgrades](/kb/administer/upgrading). |
|
||||
| sentry.io | Varies | `443` | HTTPS | Crash-reporting, see [Telemetry](#telemetry) |
|
||||
|
||||
#### Permissions
|
||||
|
||||
In order to function correctly, Gateways need access to several parts of the
|
||||
Linux system:
|
||||
|
||||
1. The TUN device as `/dev/net/tun`
|
||||
1. Permissions to open new UDP sockets
|
||||
1. Permissions to add and remove routes via `netlink`
|
||||
|
||||
Typically, it is enough to run Gateways with the `CAP_NET_ADMIN` capability.
|
||||
Alternatively, you can run them as `root`.
|
||||
|
||||
Gateways will check on startup for these two conditions and fail if neither are
|
||||
met. You can skip these permission checks by passing `--no-check`. This is only
|
||||
advisable in case you have configured access in ways not covered by these
|
||||
checks.
|
||||
|
||||
## Where to deploy Gateways
|
||||
|
||||
Ideally, Gateways should be deployed as close to the Resources they're serving
|
||||
|
||||
@@ -15,6 +15,11 @@ export default function Gateway() {
|
||||
Fixes an issue where ICMPv6's `PacketTooBig' errors were not correctly
|
||||
translated by the NAT64 module.
|
||||
</ChangeItem>
|
||||
<ChangeItem pull="7565">
|
||||
Fails early in case the binary is not started as `root` or with the
|
||||
`CAP_NET_ADMIN` capability. The check can be skipped with
|
||||
`--no-check`.
|
||||
</ChangeItem>
|
||||
</Unreleased>
|
||||
<Entry version="1.4.2" date={new Date("2024-12-13")}>
|
||||
<ChangeItem pull="7210">
|
||||
|
||||
Reference in New Issue
Block a user