[pve-devel] [PATCH proxmox-firewall 2/2] firewall: add subcommands to proxmox-firewall binary

Stefan Hanreich s.hanreich at proxmox.com
Mon Apr 14 17:44:54 CEST 2025


pve-firewall supports several subcommands for starting / stopping the
firewall, as well as debugging. proxmox-firewall currently only ran in
foreground when executed and had no real means of dumping the
generated ruleset other than running it with log level trace and
capturing the log output.

The commands introduced in this patch series are:
* start - runs proxmox-firewall in foreground
* skeleton - prints the rule skeleton, included in the binary
* compile - prints the commands generated from the firewall
  configuration files

For now, start retains the exact same behavior as the binary had
before introducing subcommands, so calling it with start as subcommand
is equivalent to invoking it without the start subcommand before.

The skeleton and compile subcommands can be used to dump the nftables
rules generated by proxmox-firewall. They print the ruleset in the
format expected by nft directly to STDOUT, so it can be piped to the
nft binary directly:

$ proxmox-firewall skeleton | nft -f -
$ proxmox-firewall compile | nft -j -f -

start always prints its logs to the journal, all other commands print
the logs to stderr. Since there isn't really a reason anymore to run
proxmox-firewall in the foreground via start (there are now specific
commands for debugging), this should be fine.

Signed-off-by: Stefan Hanreich <s.hanreich at proxmox.com>
---
 debian/control                               |   1 +
 debian/proxmox-firewall.service              |   2 +-
 proxmox-firewall/Cargo.toml                  |   2 +
 proxmox-firewall/src/bin/proxmox-firewall.rs | 103 +++++++++++++++++--
 4 files changed, 97 insertions(+), 11 deletions(-)

diff --git a/debian/control b/debian/control
index be6e584..15d9cad 100644
--- a/debian/control
+++ b/debian/control
@@ -7,6 +7,7 @@ Build-Depends: cargo:native,
                librust-anyhow-1+default-dev,
                librust-insta-1+default-dev (>= 1.21-~~),
                librust-insta-1+json-dev (>= 1.21-~~),
+               librust-pico-args-0.5+default-dev,
                librust-proxmox-log-0.2+default-dev (>= 0.2.9-~~),
                librust-proxmox-sys-0.6+default-dev,
                librust-proxmox-ve-config-dev (>= 0.2.3-~~),
diff --git a/debian/proxmox-firewall.service b/debian/proxmox-firewall.service
index ececa75..398670f 100644
--- a/debian/proxmox-firewall.service
+++ b/debian/proxmox-firewall.service
@@ -4,7 +4,7 @@ Wants=pve-cluster.service pvefw-logger.service
 After=pvefw-logger.service pve-cluster.service network.target systemd-modules-load.service
 
 [Service]
-ExecStart=/usr/libexec/proxmox/proxmox-firewall
+ExecStart=/usr/libexec/proxmox/proxmox-firewall start
 Type=simple
 
 [Install]
diff --git a/proxmox-firewall/Cargo.toml b/proxmox-firewall/Cargo.toml
index a7031a3..3302060 100644
--- a/proxmox-firewall/Cargo.toml
+++ b/proxmox-firewall/Cargo.toml
@@ -13,6 +13,8 @@ license = "AGPL-3"
 [dependencies]
 anyhow = "1"
 
+pico-args = "0.5"
+
 serde = { version = "1", features = [ "derive" ] }
 serde_json = "1"
 
diff --git a/proxmox-firewall/src/bin/proxmox-firewall.rs b/proxmox-firewall/src/bin/proxmox-firewall.rs
index 70dca73..273daec 100644
--- a/proxmox-firewall/src/bin/proxmox-firewall.rs
+++ b/proxmox-firewall/src/bin/proxmox-firewall.rs
@@ -2,7 +2,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 use std::time::{Duration, Instant};
 
-use anyhow::{Context, Error};
+use anyhow::{bail, format_err, Context, Error};
+use pico_args::Arguments;
 
 use proxmox_firewall::config::{FirewallConfig, PveFirewallConfigLoader, PveNftConfigLoader};
 use proxmox_firewall::firewall::Firewall;
@@ -10,6 +11,18 @@ use proxmox_log as log;
 use proxmox_log::{LevelFilter, Logger};
 use proxmox_nftables::{client::NftError, NftClient};
 
+const HELP: &str = r#"
+USAGE:
+  proxmox-firewall <COMMAND>
+
+COMMANDS:
+  help              Prints this help message.
+  skeleton          Prints the firewall rule skeleton as accepted by 'nft -f -'
+  compile           Compile and print firewall rules as accepted by 'nft -j -f -'
+  start             Execute proxmox-firewall service in foreground
+"#;
+
+
 const RULE_BASE: &str = include_str!("../../resources/proxmox-firewall.nft");
 
 const FORCE_DISABLE_FLAG_FILE: &str = "/run/proxmox-nftables-firewall-force-disable";
@@ -27,10 +40,13 @@ fn remove_firewall() -> Result<(), std::io::Error> {
     Ok(())
 }
 
-fn handle_firewall() -> Result<(), Error> {
+fn create_firewall_instance() -> Result<Firewall, Error> {
     let config = FirewallConfig::new(&PveFirewallConfigLoader::new(), &PveNftConfigLoader::new())?;
+    Ok(Firewall::new(config))
+}
 
-    let firewall = Firewall::new(config);
+fn handle_firewall() -> Result<(), Error> {
+    let firewall = create_firewall_instance()?;
 
     if !firewall.is_enabled() {
         return remove_firewall().with_context(|| "could not remove firewall tables".to_string());
@@ -55,15 +71,19 @@ fn handle_firewall() -> Result<(), Error> {
     Ok(())
 }
 
-fn init_logger() -> Result<(), Error> {
-    Logger::from_env("PVE_LOG", LevelFilter::WARN)
-        .journald()
-        .init()
-}
+fn init_logger(command: Command) -> Result<(), Error> {
+    let mut logger = Logger::from_env("PVE_LOG", LevelFilter::WARN);
 
-fn main() -> Result<(), Error> {
-    init_logger()?;
+    if command == Command::Start {
+        logger = logger.journald();
+    } else {
+        logger = logger.stderr_pve();
+    }
+
+    logger.init()
+}
 
+fn run_firewall() -> Result<(), Error> {
     let term = Arc::new(AtomicBool::new(false));
 
     signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term))?;
@@ -97,3 +117,66 @@ fn main() -> Result<(), Error> {
     remove_firewall()
         .with_context(|| "Could not remove firewall rules")
 }
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Command {
+    Compile,
+    Help,
+    Skeleton,
+    Start,
+}
+
+impl std::str::FromStr for Command {
+    type Err = Error;
+
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        Ok(match value {
+            "help" => Command::Help,
+            "compile" => Command::Compile,
+            "skeleton" => Command::Skeleton,
+            "start" => Command::Start,
+            cmd => {
+                bail!("{cmd} is not a valid command")
+            },
+        })
+    }
+}
+
+fn run_command(command: Command) -> Result<(), Error> {
+    init_logger(command)?;
+
+    match command {
+        Command::Help => {
+            println!("{}", HELP);
+            Ok(())
+        },
+        Command::Compile => {
+            let commands = create_firewall_instance()?.full_host_fw()?;
+            let json = serde_json::to_string_pretty(&commands)?;
+
+            println!("{json}");
+            Ok(())
+        },
+        Command::Skeleton => {
+            println!("{}", RULE_BASE);
+            Ok(())
+        },
+        Command::Start => run_firewall(),
+    }
+}
+
+fn main() -> Result<(), Error> {
+    let mut args = Arguments::from_env();
+
+    let parsed_command = args.subcommand()?
+        .ok_or_else(|| format_err!("no subcommand specified"))?
+        .parse();
+
+    if let Ok(command) = parsed_command {
+        run_command(command)
+    } else {
+        eprintln!("Invalid command specified!\n{}", HELP);
+        std::process::exit(1);
+    }
+
+}
-- 
2.39.5




More information about the pve-devel mailing list