From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 8549C90CF1 for ; Tue, 2 Apr 2024 19:26:48 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 88102B479 for ; Tue, 2 Apr 2024 19:25:50 +0200 (CEST) Received: from lana.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP for ; Tue, 2 Apr 2024 19:25:46 +0200 (CEST) Received: by lana.proxmox.com (Postfix, from userid 10043) id D826E2C3A98; Tue, 2 Apr 2024 19:16:31 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Cc: Stefan Hanreich , Wolfgang Bumiller Date: Tue, 2 Apr 2024 19:16:23 +0200 Message-Id: <20240402171629.536804-32-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240402171629.536804-1-s.hanreich@proxmox.com> References: <20240402171629.536804-1-s.hanreich@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.306 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [main.rs, firewall.rs] Subject: [pve-devel] [PATCH proxmox-firewall 31/37] firewall: add ruleset generation logic X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 02 Apr 2024 17:26:48 -0000 We create the rules from the firewall config by utilizing the ToNftRules and ToNftObjects traits to convert the firewall config structs to nftables objects/chains/rules. Co-authored-by: Wolfgang Bumiller Signed-off-by: Stefan Hanreich --- proxmox-firewall/Cargo.toml | 3 + proxmox-firewall/src/firewall.rs | 726 +++++++++++++++++++++++++++++++ proxmox-firewall/src/main.rs | 1 + 3 files changed, 730 insertions(+) create mode 100644 proxmox-firewall/src/firewall.rs diff --git a/proxmox-firewall/Cargo.toml b/proxmox-firewall/Cargo.toml index 431e71a..1e6a4b8 100644 --- a/proxmox-firewall/Cargo.toml +++ b/proxmox-firewall/Cargo.toml @@ -15,5 +15,8 @@ log = "0.4" env_logger = "0.10" anyhow = "1" +serde = { version = "1", features = [ "derive" ] } +serde_json = "1" + proxmox-nftables = { path = "../proxmox-nftables", features = ["config-ext"] } proxmox-ve-config = { path = "../proxmox-ve-config" } diff --git a/proxmox-firewall/src/firewall.rs b/proxmox-firewall/src/firewall.rs new file mode 100644 index 0000000..9947bee --- /dev/null +++ b/proxmox-firewall/src/firewall.rs @@ -0,0 +1,726 @@ +use std::collections::HashMap; +use std::fs; + +use anyhow::Error; + +use proxmox_nftables::command::{Add, Commands, Delete, Flush}; +use proxmox_nftables::expression::{Meta, Payload}; +use proxmox_nftables::helper::NfVec; +use proxmox_nftables::statement::{AnonymousLimit, Log, LogLevel, Match, Set, SetOperation}; +use proxmox_nftables::types::{ + AddElement, AddRule, ChainPart, MapValue, RateTimescale, SetName, TableFamily, TablePart, + Verdict, +}; +use proxmox_nftables::{Expression, Statement}; + +use proxmox_ve_config::firewall::ct_helper::get_cthelper; +use proxmox_ve_config::firewall::guest::Config as GuestConfig; +use proxmox_ve_config::firewall::host::Config as HostConfig; + +use proxmox_ve_config::firewall::types::ipset::{Ipset, IpsetEntry, IpsetScope}; +use proxmox_ve_config::firewall::types::log::{LogLevel as ConfigLogLevel, LogRateLimit}; +use proxmox_ve_config::firewall::types::rule::{Direction, Verdict as ConfigVerdict}; +use proxmox_ve_config::firewall::types::Group; +use proxmox_ve_config::guest::types::Vmid; + +use crate::config::FirewallConfig; +use crate::object::{NftObjectEnv, ToNftObjects}; +use crate::rule::{NftRule, NftRuleEnv}; + +static CLUSTER_TABLE_NAME: &str = "proxmox-firewall"; +static HOST_TABLE_NAME: &str = "proxmox-firewall"; +static GUEST_TABLE_NAME: &str = "proxmox-firewall-guests"; + +static NF_CONNTRACK_MAX_FILE: &str = "/proc/sys/net/netfilter/nf_conntrack_max"; +static NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED: &str = + "/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established"; +static NF_CONNTRACK_TCP_TIMEOUT_SYN_RECV: &str = + "/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_recv"; +static LOG_CONNTRACK_FILE: &str = "/var/lib/pve-firewall/log_nf_conntrack"; + +#[derive(Debug)] +pub struct Firewall { + config: FirewallConfig, +} + +impl Firewall { + pub fn new() -> Result { + Ok(Self { + config: FirewallConfig::load()?, + }) + } + + pub fn is_enabled(&self) -> bool { + self.config.is_enabled() + } + + fn cluster_table(&self) -> TablePart { + TablePart::new(TableFamily::Inet, CLUSTER_TABLE_NAME) + } + + fn host_table(&self) -> TablePart { + TablePart::new(TableFamily::Inet, HOST_TABLE_NAME) + } + + fn guest_table(&self) -> TablePart { + TablePart::new(TableFamily::Bridge, GUEST_TABLE_NAME) + } + + fn guest_vmap(&self, dir: Direction) -> SetName { + SetName::new(self.guest_table(), format!("vm-map-{dir}")) + } + + fn cluster_chain(&self, dir: Direction) -> ChainPart { + ChainPart::new(self.cluster_table(), format!("cluster-{dir}")) + } + + fn host_chain(&self, dir: Direction) -> ChainPart { + ChainPart::new(self.host_table(), format!("host-{dir}")) + } + + fn guest_chain(&self, dir: Direction, vmid: Vmid) -> ChainPart { + ChainPart::new(self.guest_table(), format!("guest-{vmid}-{dir}")) + } + + fn group_chain(&self, table: TablePart, name: &str, dir: Direction) -> ChainPart { + ChainPart::new(table, format!("group-{name}-{dir}")) + } + + fn host_conntrack_chain(&self) -> ChainPart { + ChainPart::new(self.host_table(), "ct-in".to_string()) + } + + fn host_option_chain(&self, dir: Direction) -> ChainPart { + ChainPart::new(self.host_table(), format!("option-{dir}")) + } + + fn synflood_limit_chain(&self) -> ChainPart { + ChainPart::new(self.host_table(), "ratelimit-synflood") + } + + fn log_invalid_tcp_chain(&self) -> ChainPart { + ChainPart::new(self.host_table(), "log-invalid-tcp") + } + + fn log_smurfs_chain(&self) -> ChainPart { + ChainPart::new(self.host_table(), "log-smurfs") + } + + fn default_log_limit(&self) -> Option { + self.config.cluster().log_ratelimit() + } + + fn reset_firewall(&self, commands: &mut Commands) { + commands.append(&mut vec![ + Flush::chain(self.cluster_chain(Direction::In)), + Flush::chain(self.cluster_chain(Direction::Out)), + Add::chain(self.host_chain(Direction::In)), + Flush::chain(self.host_chain(Direction::In)), + Flush::chain(self.host_option_chain(Direction::In)), + Add::chain(self.host_chain(Direction::Out)), + Flush::chain(self.host_chain(Direction::Out)), + Flush::chain(self.host_option_chain(Direction::Out)), + Flush::map(self.guest_vmap(Direction::In)), + Flush::map(self.guest_vmap(Direction::Out)), + Flush::chain(self.host_conntrack_chain()), + Flush::chain(self.synflood_limit_chain()), + Flush::chain(self.log_invalid_tcp_chain()), + Flush::chain(self.log_smurfs_chain()), + ]); + + // we need to remove guest chains before group chains + for prefix in ["guest-", "group-"] { + for (name, chain) in &self.config.nft().chains { + if name.starts_with(prefix) { + commands.push(Delete::chain(chain.clone())) + } + } + } + } + + pub fn remove_firewall(&self) -> Commands { + Commands::new(vec![ + Delete::table(self.cluster_table()), + Delete::table(self.guest_table()), + ]) + } + + fn create_management_ipset(&self, commands: &mut Commands) -> Result<(), Error> { + if self.config.cluster().ipsets().get("management").is_none() { + let management_ips = HostConfig::management_ips()?; + + let mut ipset = Ipset::from_parts(IpsetScope::Datacenter, "management"); + ipset.reserve(management_ips.len()); + + let entries = management_ips.into_iter().map(IpsetEntry::from); + + ipset.extend(entries); + + let env = NftObjectEnv { + table: &self.cluster_table(), + firewall_config: &self.config, + vmid: None, + }; + + commands.append(&mut ipset.to_nft_objects(&env)?); + } + + Ok(()) + } + + pub fn full_host_fw(&self) -> Result { + let mut commands = Commands::default(); + + if !self.config.is_enabled() { + return Ok(commands); + } + + self.reset_firewall(&mut commands); + + let cluster_host_table = self.cluster_table(); + let guest_table = self.guest_table(); + + self.create_management_ipset(&mut commands)?; + + for table in [&cluster_host_table, &guest_table] { + self.create_ipsets(&mut commands, self.config.cluster().ipsets(), table, None)?; + + for (name, group) in self.config.cluster().groups() { + self.create_group_chain(&mut commands, table, group, name, Direction::In)?; + self.create_group_chain(&mut commands, table, group, name, Direction::Out)?; + } + } + + self.create_cluster_rules(&mut commands, Direction::In)?; + self.create_cluster_rules(&mut commands, Direction::Out)?; + + if self.config.host().is_enabled() { + log::debug!("Generating host firewall config"); + + self.setup_ct_helper(&mut commands)?; + + self.handle_host_options(&mut commands)?; + + self.create_host_rules(&mut commands, Direction::In)?; + self.create_host_rules(&mut commands, Direction::Out)?; + } + + for (vmid, config) in self.config.guests() { + if !config.is_enabled() { + log::debug!("Firewall disabled for VM #{vmid} - not generating config"); + continue; + } + + log::debug!("Generating firewall config for VM #{vmid}"); + + self.create_ipsets(&mut commands, config.ipsets(), &guest_table, *vmid)?; + + self.create_guest_chain(&mut commands, *vmid, Direction::In)?; + self.create_guest_chain(&mut commands, *vmid, Direction::Out)?; + + self.handle_guest_options(&mut commands, *vmid, config)?; + + self.create_guest_rules(&mut commands, *vmid, config, Direction::In)?; + self.create_guest_rules(&mut commands, *vmid, config, Direction::Out)?; + } + + Ok(commands) + } + + fn handle_host_options(&self, commands: &mut Commands) -> Result<(), Error> { + log::debug!("setting host options"); + + let chain_in = self.host_option_chain(Direction::In); + let chain_out = self.host_option_chain(Direction::Out); + + if self.config.host().allow_ndp() { + log::debug!("set allow_ndp"); + let statement = Statement::jump("allow-ndp"); + + commands.append(&mut vec![ + Add::rule(AddRule::from_statement(chain_in.clone(), statement.clone())), + Add::rule(AddRule::from_statement(chain_out, statement)), + ]); + } + + if self.config.host().block_synflood() { + log::debug!("set block_synflood"); + + let rate_limit = Statement::from(AnonymousLimit { + rate: self.config.host().synflood_rate(), + per: RateTimescale::Second, + burst: Some(self.config.host().synflood_burst()), + inv: Some(true), + ..Default::default() + }); + + let synflood_limit_chain = self.synflood_limit_chain(); + + let v4_rule = AddRule::from_statements( + synflood_limit_chain.clone(), + [ + Statement::Set(Set { + op: SetOperation::Update, + elem: Expression::from(Payload::field("ip", "saddr")), + stmt: Some(NfVec::one(rate_limit.clone())), + set: "@v4-synflood-limit".to_string(), + }), + Statement::make_drop(), + ], + ); + + let v6_rule = AddRule::from_statements( + synflood_limit_chain, + [ + Statement::Set(Set { + op: SetOperation::Update, + elem: Expression::from(Payload::field("ip6", "saddr")), + stmt: Some(NfVec::one(rate_limit)), + set: "@v6-synflood-limit".to_string(), + }), + Statement::make_drop(), + ], + ); + + commands.append(&mut vec![ + Add::rule(AddRule::from_statement( + chain_in.clone(), + Statement::jump("block-synflood"), + )), + Add::rule(v4_rule), + Add::rule(v6_rule), + ]) + } + + if self.config.host().block_invalid_tcp() { + log::debug!("set block_invalid_tcp"); + + commands.push(Add::rule(AddRule::from_statement( + chain_in.clone(), + Statement::jump("block-invalid-tcp"), + ))); + + self.create_log_rule( + commands, + self.config.host().block_invalid_tcp_log_level(), + self.log_invalid_tcp_chain(), + ConfigVerdict::Drop, + None, + )?; + } + + if self.config.host().block_smurfs() { + log::debug!("set block_smurfs"); + + commands.push(Add::rule(AddRule::from_statement( + chain_in.clone(), + Statement::jump("block-smurfs"), + ))); + + self.create_log_rule( + commands, + self.config.host().block_smurfs_log_level(), + self.log_smurfs_chain(), + ConfigVerdict::Drop, + None, + )?; + } + + if self.config.host().block_invalid_conntrack() { + log::debug!("set block_invalid_conntrack"); + + commands.push(Add::rule(AddRule::from_statement( + chain_in, + Statement::jump("block-conntrack-invalid"), + ))); + } + + if let Some(value) = self.config.host().nf_conntrack_max() { + log::debug!("set nf_conntrack_max"); + fs::write(NF_CONNTRACK_MAX_FILE, value.to_string()).map_err(anyhow::Error::msg)?; + } + + if let Some(value) = self.config.host().nf_conntrack_tcp_timeout_established() { + log::debug!("set nf_conntrack_tcp_timeout_established"); + fs::write(NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED, value.to_string()) + .map_err(anyhow::Error::msg)?; + } + + if let Some(value) = self.config.host().nf_conntrack_tcp_timeout_syn_recv() { + log::debug!("set nf_conntrack_tcp_timeout_syn_recv"); + fs::write(NF_CONNTRACK_TCP_TIMEOUT_SYN_RECV, value.to_string()) + .map_err(anyhow::Error::msg)?; + } + + let value = (self.config.host().log_nf_conntrack() as u8).to_string(); + fs::write(LOG_CONNTRACK_FILE, value).map_err(anyhow::Error::msg)?; + + /* + CliCommand::new("systemctl") + .args(["try-reload-or-restart", "pvefw-logger.service"]) + .output() + .map_err(anyhow::Error::msg)?; + */ + + Ok(()) + } + + fn handle_guest_options( + &self, + commands: &mut Commands, + vmid: Vmid, + config: &GuestConfig, + ) -> Result<(), Error> { + let chain_in = self.guest_chain(Direction::In, vmid); + let chain_out = self.guest_chain(Direction::Out, vmid); + + if config.macfilter() { + let mac_address_set = + config + .network_config() + .network_devices() + .iter() + .map(|(index, device)| { + Expression::concat([ + Expression::from(config.iface_name_by_index(*index)), + Expression::from(device.mac_address().to_string()), + ]) + }); + + let macfilter_rule = AddRule::from_statements( + chain_out.clone(), + [ + Match::new_ne( + Expression::concat([ + Expression::from(Meta::new("iifname")), + Expression::from(Payload::field("ether", "saddr")), + ]), + Expression::set(mac_address_set), + ) + .into(), + Statement::make_drop(), + ], + ); + + commands.push(Add::rule(macfilter_rule)); + } + + if config.allow_dhcp() { + commands.append(&mut vec![ + Add::rule(AddRule::from_statement( + chain_in.clone(), + Statement::jump("allow-dhcp-in"), + )), + Add::rule(AddRule::from_statement( + chain_out.clone(), + Statement::jump("allow-dhcp-out"), + )), + ]); + } + + if config.allow_ndp() { + let statement = Statement::jump("allow-ndp"); + + commands.append(&mut vec![ + Add::rule(AddRule::from_statement(chain_in.clone(), statement.clone())), + Add::rule(AddRule::from_statement(chain_out.clone(), statement)), + ]); + } + + if config.allow_ra() { + let statement = Statement::jump("allow-ra"); + + commands.append(&mut vec![ + Add::rule(AddRule::from_statement(chain_in, statement.clone())), + Add::rule(AddRule::from_statement(chain_out, statement)), + ]); + } + + Ok(()) + } + + fn setup_ct_helper(&self, commands: &mut Commands) -> Result<(), Error> { + let chain_in = self.host_conntrack_chain(); + + if let Some(helpers) = self.config.host().conntrack_helpers() { + log::trace!("adding conntrack helpers: {helpers:?}"); + + let object_env = NftObjectEnv { + table: chain_in.table(), + firewall_config: &self.config, + vmid: None, + }; + + let rule_env = NftRuleEnv { + chain: chain_in.clone(), + direction: Direction::In, + firewall_config: &self.config, + vmid: None, + }; + + for helper in helpers { + let helper_macro = get_cthelper(&helper.to_string()); + + if let Some(helper_macro) = helper_macro { + commands.append(&mut helper_macro.to_nft_objects(&object_env)?); + + // todo: use vmap + for rule in NftRule::from_ct_helper(helper_macro, &rule_env)? { + commands.push(Add::rule(rule.into_add_rule(chain_in.clone()))); + } + } else { + log::warn!("provided invalid helper macro name: {:?}", helper); + } + } + } + + Ok(()) + } + + fn create_ipsets( + &self, + commands: &mut Commands, + ipsets: &HashMap, + table: &TablePart, + vmid: impl Into>, + ) -> Result<(), Error> { + let env = NftObjectEnv { + table, + vmid: vmid.into(), + firewall_config: &self.config, + }; + + for (name, ipset) in ipsets { + log::debug!("Creating ipset {name} in table {table:?}"); + + commands.append(&mut ipset.to_nft_objects(&env)?); + + if let (Some(vmid), Some(ipfilter)) = (env.vmid, ipset.ipfilter()) { + log::debug!("Creating IP filter rules for VM {vmid}"); + + let chain = self.guest_chain(Direction::Out, vmid); + + let rule_env = NftRuleEnv { + chain: chain.clone(), + direction: Direction::Out, + firewall_config: &self.config, + vmid: Some(vmid), + }; + + for rule in NftRule::from_ipfilter(&ipfilter, &rule_env)? { + commands.push(Add::rule(rule.into_add_rule(chain.clone()))); + } + } + } + + Ok(()) + } + + fn create_cluster_rules( + &self, + commands: &mut Commands, + direction: Direction, + ) -> Result<(), Error> { + log::info!("Creating cluster chain {direction} family"); + + let chain = self.cluster_chain(direction); + + let env = NftRuleEnv { + chain: chain.clone(), + direction, + firewall_config: &self.config, + vmid: None, + }; + + let rules = self.config.cluster().rules(); + + commands.reserve(rules.len()); + + for config_rule in rules { + for rule in NftRule::from_config_rule(config_rule, &env)? { + commands.push(Add::rule(rule.into_add_rule(chain.clone()))); + } + } + + let default_policy = self.config.cluster().default_policy(direction); + + self.create_log_rule( + commands, + self.config.host().log_level(direction), + chain.clone(), + default_policy, + None, + )?; + + commands.push(Add::rule(AddRule::from_statement(chain, default_policy))); + + Ok(()) + } + + fn create_host_rules( + &self, + commands: &mut Commands, + direction: Direction, + ) -> Result<(), Error> { + log::info!("Creating host chain {direction} family"); + + let chain = self.host_chain(direction); + + let env = NftRuleEnv { + chain: chain.clone(), + direction, + firewall_config: &self.config, + vmid: None, + }; + + let rules = self.config.host().rules(); + commands.reserve(rules.len()); + + for config_rule in rules { + for rule in NftRule::from_config_rule(config_rule, &env)? { + commands.push(Add::rule(rule.into_add_rule(chain.clone()))); + } + } + + Ok(()) + } + + fn create_guest_chain( + &self, + commands: &mut Commands, + vmid: Vmid, + direction: Direction, + ) -> Result<(), Error> { + log::info!("Creating guest chain (vmid {vmid}) {direction}"); + + let chain = self.guest_chain(direction, vmid); + + commands.append(&mut vec![Add::chain(chain.clone()), Flush::chain(chain)]); + + Ok(()) + } + + fn create_guest_rules( + &self, + commands: &mut Commands, + vmid: Vmid, + config: &GuestConfig, + direction: Direction, + ) -> Result<(), Error> { + log::info!("Creating guest rules (vmid {vmid}) {direction}"); + + let chain = self.guest_chain(direction, vmid); + + let env = NftRuleEnv { + chain: chain.clone(), + direction, + firewall_config: &self.config, + vmid: Some(vmid), + }; + + commands.reserve(config.rules().len()); + + for config_rule in config.rules() { + for rule in NftRule::from_config_rule(config_rule, &env)? { + commands.push(Add::rule(rule.into_add_rule(chain.clone()))) + } + } + + let network_devices = config.network_config().network_devices(); + + if !network_devices.is_empty() { + let map_elements = network_devices + .iter() + .filter(|(_, device)| device.has_firewall()) + .map(|(index, _)| { + ( + Expression::from(config.iface_name_by_index(*index)), + MapValue::from(Verdict::Goto { + target: chain.name().to_string(), + }), + ) + }); + + commands.push(Add::element(AddElement::map_from_expressions( + self.guest_vmap(direction), + map_elements, + ))); + } + + self.create_log_rule( + commands, + config.log_level(direction), + chain.clone(), + config.default_policy(direction), + vmid, + )?; + + commands.push(Add::rule(AddRule::from_statement( + chain, + config.default_policy(direction), + ))); + + Ok(()) + } + + fn create_group_chain( + &self, + commands: &mut Commands, + table: &TablePart, + group: &Group, + name: &str, + direction: Direction, + ) -> Result<(), Error> { + log::info!("Creating group chain (table {table:?}) {direction}"); + + let chain = self.group_chain(table.clone(), name, direction); + + let env = NftRuleEnv { + chain: chain.clone(), + direction, + firewall_config: &self.config, + vmid: None, + }; + + commands.append(&mut vec![ + Add::chain(chain.clone()), + Flush::chain(chain.clone()), + ]); + + for rule in group.rules() { + for firewall_rule in NftRule::from_config_rule(rule, &env)? { + commands.push(Add::rule(firewall_rule.into_add_rule(chain.clone()))) + } + } + + Ok(()) + } + + fn create_log_rule( + &self, + commands: &mut Commands, + log_level: ConfigLogLevel, + chain: ChainPart, + verdict: ConfigVerdict, + vmid: impl Into>, + ) -> Result<(), Error> { + if let Ok(log_level) = LogLevel::try_from(log_level) { + let mut log_rule = AddRule::new(chain.clone()); + + if let Some(limit) = self.default_log_limit() { + log_rule.push(Statement::from(limit)); + } + + let log_statement = Log::new_nflog( + Log::generate_prefix(vmid, log_level, chain.name(), verdict), + 0, + ); + + log_rule.push(Statement::from(log_statement)); + + commands.push(Add::rule(log_rule)); + } + + Ok(()) + } +} diff --git a/proxmox-firewall/src/main.rs b/proxmox-firewall/src/main.rs index a4979a7..53c1289 100644 --- a/proxmox-firewall/src/main.rs +++ b/proxmox-firewall/src/main.rs @@ -1,6 +1,7 @@ use anyhow::Error; mod config; +mod firewall; mod object; mod rule; -- 2.39.2