From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 0566B1FF166 for ; Wed, 11 Sep 2024 11:31:51 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 8ECF4A0AC; Wed, 11 Sep 2024 11:31:28 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Wed, 11 Sep 2024 11:31:06 +0200 Message-Id: <20240911093116.112960-6-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240911093116.112960-1-s.hanreich@proxmox.com> References: <20240911093116.112960-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.267 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 Subject: [pve-devel] [PATCH proxmox-firewall 05/15] sdn: add support for loading vnet-level firewall config 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: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" Signed-off-by: Stefan Hanreich --- proxmox-firewall/src/config.rs | 88 ++++++++++++++++++++- proxmox-firewall/tests/integration_tests.rs | 12 +++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/proxmox-firewall/src/config.rs b/proxmox-firewall/src/config.rs index c27aac6..ac60e15 100644 --- a/proxmox-firewall/src/config.rs +++ b/proxmox-firewall/src/config.rs @@ -1,10 +1,11 @@ use std::collections::BTreeMap; use std::default::Default; -use std::fs::File; +use std::fs::{self, DirEntry, File, ReadDir}; use std::io::{self, BufReader}; -use anyhow::{format_err, Context, Error}; +use anyhow::{bail, format_err, Context, Error}; +use proxmox_ve_config::firewall::bridge::Config as BridgeConfig; use proxmox_ve_config::firewall::cluster::Config as ClusterConfig; use proxmox_ve_config::firewall::guest::Config as GuestConfig; use proxmox_ve_config::firewall::host::Config as HostConfig; @@ -12,6 +13,7 @@ use proxmox_ve_config::firewall::types::alias::{Alias, AliasName, AliasScope}; use proxmox_ve_config::guest::types::Vmid; use proxmox_ve_config::guest::{GuestEntry, GuestMap}; +use proxmox_ve_config::host::types::BridgeName; use proxmox_nftables::command::{CommandOutput, Commands, List, ListOutput}; use proxmox_nftables::types::ListChain; @@ -33,6 +35,11 @@ pub trait FirewallConfigLoader { fn guest_firewall_config(&self, vmid: &Vmid) -> Result>, Error>; fn sdn_running_config(&self) -> Result>, Error>; fn ipam(&self) -> Result>, Error>; + fn bridge_list(&self) -> Result, Error>; + fn bridge_firewall_config( + &self, + bridge_name: &BridgeName, + ) -> Result>, Error>; } #[derive(Default)] @@ -61,8 +68,31 @@ fn open_config_file(path: &str) -> Result, Error> { } } +fn open_config_folder(path: &str) -> Result, Error> { + match fs::read_dir(path) { + Ok(paths) => Ok(Some(paths)), + Err(err) if err.kind() == io::ErrorKind::NotFound => { + log::info!("SDN config folder {path} does not exist"); + Ok(None) + } + Err(err) => { + let context = format!("unable to open configuration folder at {BRIDGE_CONFIG_PATH}"); + Err(anyhow::Error::new(err).context(context)) + } + } +} + +fn fw_name(dir_entry: DirEntry) -> Option { + dir_entry + .file_name() + .to_str()? + .strip_suffix(".fw") + .map(str::to_string) +} + const CLUSTER_CONFIG_PATH: &str = "/etc/pve/firewall/cluster.fw"; const HOST_CONFIG_PATH: &str = "/etc/pve/local/host.fw"; +const BRIDGE_CONFIG_PATH: &str = "/etc/pve/sdn/firewall"; const SDN_RUNNING_CONFIG_PATH: &str = "/etc/pve/sdn/.running-config"; const SDN_IPAM_PATH: &str = "/etc/pve/priv/ipam.db"; @@ -154,6 +184,38 @@ impl FirewallConfigLoader for PveFirewallConfigLoader { Ok(None) } + + fn bridge_list(&self) -> Result, Error> { + let mut bridges = Vec::new(); + + if let Some(files) = open_config_folder(BRIDGE_CONFIG_PATH)? { + for file in files { + let bridge_name = fw_name(file?).map(BridgeName::new).transpose()?; + + if let Some(bridge_name) = bridge_name { + bridges.push(bridge_name); + } + } + } + + Ok(bridges) + } + + fn bridge_firewall_config( + &self, + bridge_name: &BridgeName, + ) -> Result>, Error> { + log::info!("loading firewall config for bridge {bridge_name}"); + + let fd = open_config_file(&format!("/etc/pve/sdn/firewall/{bridge_name}.fw"))?; + + if let Some(file) = fd { + let buf_reader = Box::new(BufReader::new(file)) as Box; + return Ok(Some(buf_reader)); + } + + Ok(None) + } } pub trait NftConfigLoader { @@ -184,6 +246,7 @@ pub struct FirewallConfig { cluster_config: ClusterConfig, host_config: HostConfig, guest_config: BTreeMap, + bridge_config: BTreeMap, nft_config: BTreeMap, sdn_config: Option, ipam_config: Option, @@ -284,6 +347,22 @@ impl FirewallConfig { Ok(chains) } + pub fn parse_bridges( + firewall_loader: &dyn FirewallConfigLoader, + ) -> Result, Error> { + let mut bridge_config = BTreeMap::new(); + + for bridge_name in firewall_loader.bridge_list()? { + if let Some(config) = firewall_loader.bridge_firewall_config(&bridge_name)? { + bridge_config.insert(bridge_name, BridgeConfig::parse(config)?); + } else { + bail!("Could not read config for {bridge_name}") + } + } + + Ok(bridge_config) + } + pub fn new( firewall_loader: &dyn FirewallConfigLoader, nft_loader: &dyn NftConfigLoader, @@ -292,6 +371,7 @@ impl FirewallConfig { cluster_config: Self::parse_cluster(firewall_loader)?, host_config: Self::parse_host(firewall_loader)?, guest_config: Self::parse_guests(firewall_loader)?, + bridge_config: Self::parse_bridges(firewall_loader)?, sdn_config: Self::parse_sdn(firewall_loader)?, ipam_config: Self::parse_ipam(firewall_loader)?, nft_config: Self::parse_nft(nft_loader)?, @@ -310,6 +390,10 @@ impl FirewallConfig { &self.guest_config } + pub fn bridges(&self) -> &BTreeMap { + &self.bridge_config + } + pub fn nft_chains(&self) -> &BTreeMap { &self.nft_config } diff --git a/proxmox-firewall/tests/integration_tests.rs b/proxmox-firewall/tests/integration_tests.rs index 5de1a4e..61a8062 100644 --- a/proxmox-firewall/tests/integration_tests.rs +++ b/proxmox-firewall/tests/integration_tests.rs @@ -7,6 +7,7 @@ use proxmox_nftables::command::CommandOutput; use proxmox_sys::nodename; use proxmox_ve_config::guest::types::Vmid; use proxmox_ve_config::guest::{GuestEntry, GuestMap, GuestType}; +use proxmox_ve_config::host::types::BridgeName; struct MockFirewallConfigLoader {} @@ -79,6 +80,17 @@ impl FirewallConfigLoader for MockFirewallConfigLoader { fn ipam(&self) -> Result>, Error> { Ok(Some(Box::new(include_str!("input/ipam.db").as_bytes()))) } + + fn bridge_list(&self) -> Result, Error> { + Ok(Vec::new()) + } + + fn bridge_firewall_config( + &self, + bridge_name: &BridgeName, + ) -> Result>, Error> { + Ok(None) + } } struct MockNftConfigLoader {} -- 2.39.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel