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 AF4661FF163 for ; Thu, 10 Oct 2024 18:03:53 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B702D1F01E; Thu, 10 Oct 2024 18:03:49 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Thu, 10 Oct 2024 17:56:40 +0200 Message-Id: <20241010155650.255698-8-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20241010155650.255698-1-s.hanreich@proxmox.com> References: <20241010155650.255698-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.303 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 PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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 v2 07/17] sdn: create forward firewall rules 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 --- .../resources/proxmox-firewall.nft | 54 ++++++++ proxmox-firewall/src/firewall.rs | 122 +++++++++++++++++- proxmox-firewall/src/rule.rs | 5 +- .../integration_tests__firewall.snap | 86 ++++++++++++ proxmox-nftables/src/expression.rs | 8 ++ proxmox-nftables/src/types.rs | 6 + 6 files changed, 275 insertions(+), 6 deletions(-) diff --git a/proxmox-firewall/resources/proxmox-firewall.nft b/proxmox-firewall/resources/proxmox-firewall.nft index f42255c..af9454d 100644 --- a/proxmox-firewall/resources/proxmox-firewall.nft +++ b/proxmox-firewall/resources/proxmox-firewall.nft @@ -20,8 +20,12 @@ add chain inet proxmox-firewall allow-icmp add chain inet proxmox-firewall log-drop-smurfs add chain inet proxmox-firewall default-in add chain inet proxmox-firewall default-out +add chain inet proxmox-firewall before-bridge +add chain inet proxmox-firewall host-bridge-input {type filter hook input priority filter - 1; policy accept;} +add chain inet proxmox-firewall host-bridge-output {type filter hook output priority filter + 1; policy accept;} add chain inet proxmox-firewall input {type filter hook input priority filter; policy drop;} add chain inet proxmox-firewall output {type filter hook output priority filter; policy accept;} +add chain inet proxmox-firewall forward {type filter hook forward priority filter; policy accept;} add chain bridge proxmox-firewall-guests allow-dhcp-in add chain bridge proxmox-firewall-guests allow-dhcp-out @@ -39,6 +43,8 @@ add chain bridge proxmox-firewall-guests pre-vm-out add chain bridge proxmox-firewall-guests vm-out {type filter hook prerouting priority 0; policy accept;} add chain bridge proxmox-firewall-guests pre-vm-in add chain bridge proxmox-firewall-guests vm-in {type filter hook postrouting priority 0; policy accept;} +add chain bridge proxmox-firewall-guests before-bridge +add chain bridge proxmox-firewall-guests forward {type filter hook forward priority 0; policy accept;} flush chain inet proxmox-firewall do-reject flush chain inet proxmox-firewall accept-management @@ -55,8 +61,12 @@ flush chain inet proxmox-firewall allow-icmp flush chain inet proxmox-firewall log-drop-smurfs flush chain inet proxmox-firewall default-in flush chain inet proxmox-firewall default-out +flush chain inet proxmox-firewall before-bridge +flush chain inet proxmox-firewall host-bridge-input +flush chain inet proxmox-firewall host-bridge-output flush chain inet proxmox-firewall input flush chain inet proxmox-firewall output +flush chain inet proxmox-firewall forward flush chain bridge proxmox-firewall-guests allow-dhcp-in flush chain bridge proxmox-firewall-guests allow-dhcp-out @@ -74,6 +84,8 @@ flush chain bridge proxmox-firewall-guests pre-vm-out flush chain bridge proxmox-firewall-guests vm-out flush chain bridge proxmox-firewall-guests pre-vm-in flush chain bridge proxmox-firewall-guests vm-in +flush chain bridge proxmox-firewall-guests before-bridge +flush chain bridge proxmox-firewall-guests forward table inet proxmox-firewall { chain do-reject { @@ -223,6 +235,25 @@ table inet proxmox-firewall { chain option-in {} chain option-out {} + map bridge-map { + type ifname : verdict + } + + chain before-bridge { + meta protocol arp accept + meta protocol != arp ct state vmap { established : accept, related : accept, invalid : drop } + } + + chain host-bridge-input { + type filter hook input priority filter - 1; policy accept; + meta iifname vmap @bridge-map + } + + chain host-bridge-output { + type filter hook output priority filter + 1; policy accept; + meta oifname vmap @bridge-map + } + chain input { type filter hook input priority filter; policy accept; jump default-in @@ -240,12 +271,21 @@ table inet proxmox-firewall { jump cluster-out } + chain forward { + type filter hook forward priority filter; policy accept; + jump host-forward + jump cluster-forward + } + chain cluster-in {} chain cluster-out {} chain host-in {} chain host-out {} + chain cluster-forward {} + chain host-forward {} + chain ct-in {} } @@ -335,4 +375,18 @@ table bridge proxmox-firewall-guests { jump allow-icmp oifname vmap @vm-map-in } + + map bridge-map { + type ifname . ifname : verdict + } + + chain before-bridge { + meta protocol arp accept + meta protocol != arp ct state vmap { established : accept, related : accept, invalid : drop } + } + + chain forward { + type filter hook forward priority 0; policy accept; + meta ibrname . meta obrname vmap @bridge-map + } } diff --git a/proxmox-firewall/src/firewall.rs b/proxmox-firewall/src/firewall.rs index 347f3af..bb54023 100644 --- a/proxmox-firewall/src/firewall.rs +++ b/proxmox-firewall/src/firewall.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::fs; -use anyhow::Error; +use anyhow::{bail, Error}; use proxmox_nftables::command::{Add, Commands, Delete, Flush}; use proxmox_nftables::expression::{Meta, Payload}; @@ -13,6 +13,9 @@ use proxmox_nftables::types::{ }; use proxmox_nftables::{Expression, Statement}; +use proxmox_ve_config::host::types::BridgeName; + +use proxmox_ve_config::firewall::bridge::Config as BridgeConfig; 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; @@ -112,6 +115,14 @@ impl Firewall { ChainPart::new(Self::host_table(), "log-smurfs") } + fn bridge_vmap(table: TablePart) -> SetName { + SetName::new(table, "bridge-map") + } + + fn bridge_chain(table: TablePart, bridge_name: &BridgeName) -> ChainPart { + ChainPart::new(table, format!("bridge-{bridge_name}")) + } + fn default_log_limit(&self) -> Option { self.config.cluster().log_ratelimit() } @@ -120,14 +131,18 @@ impl Firewall { commands.append(&mut vec![ Flush::chain(Self::cluster_chain(Direction::In)), Flush::chain(Self::cluster_chain(Direction::Out)), + Flush::chain(Self::cluster_chain(Direction::Forward)), 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::chain(Self::host_chain(Direction::Forward)), Flush::map(Self::guest_vmap(Direction::In)), Flush::map(Self::guest_vmap(Direction::Out)), + Flush::map(Self::bridge_vmap(Self::guest_table())), + Flush::map(Self::bridge_vmap(Self::host_table())), Flush::chain(Self::host_conntrack_chain()), Flush::chain(Self::synflood_limit_chain()), Flush::chain(Self::log_invalid_tcp_chain()), @@ -144,8 +159,8 @@ impl Firewall { } */ - // we need to remove guest chains before group chains - for prefix in ["guest-", "group-"] { + // we need to remove guest & bridge chains before group chains + for prefix in ["guest-", "bridge-", "group-"] { for (name, chain) in self.config.nft_chains() { if name.starts_with(prefix) { commands.push(Delete::chain(chain.clone())) @@ -246,10 +261,18 @@ impl Firewall { name, Direction::Out, )?; + self.create_group_chain( + &mut commands, + &cluster_host_table, + group, + name, + Direction::Forward, + )?; } self.create_cluster_rules(&mut commands, Direction::In)?; self.create_cluster_rules(&mut commands, Direction::Out)?; + self.create_cluster_rules(&mut commands, Direction::Forward)?; log::debug!("Generating host firewall config"); @@ -259,6 +282,7 @@ impl Firewall { self.create_host_rules(&mut commands, Direction::In)?; self.create_host_rules(&mut commands, Direction::Out)?; + self.create_host_rules(&mut commands, Direction::Forward)?; } else { commands.push(Delete::table(TableName::from(Self::cluster_table()))); } @@ -270,7 +294,14 @@ impl Firewall { .filter(|(_, config)| config.is_enabled()) .collect(); - if !enabled_guests.is_empty() { + let enabled_bridges: BTreeMap<&BridgeName, &BridgeConfig> = self + .config + .bridges() + .iter() + .filter(|(_, config)| config.enabled()) + .collect(); + + if !(enabled_guests.is_empty() && enabled_bridges.is_empty()) { log::info!("creating guest configuration"); self.create_ipsets( @@ -283,6 +314,13 @@ impl Firewall { for (name, group) in self.config.cluster().groups() { self.create_group_chain(&mut commands, &guest_table, group, name, Direction::In)?; self.create_group_chain(&mut commands, &guest_table, group, name, Direction::Out)?; + self.create_group_chain( + &mut commands, + &guest_table, + group, + name, + Direction::Forward, + )?; } } else { commands.push(Delete::table(TableName::from(Self::guest_table()))); @@ -302,9 +340,84 @@ impl Firewall { self.create_guest_rules(&mut commands, *vmid, config, Direction::Out)?; } + for (bridge_name, bridge_config) in enabled_bridges { + self.create_bridge_chain(&mut commands, bridge_name, bridge_config)?; + } + Ok(commands) } + fn create_bridge_chain( + &self, + commands: &mut Commands, + name: &BridgeName, + config: &BridgeConfig, + ) -> Result<(), Error> { + for table in [Self::host_table(), Self::guest_table()] { + log::info!("creating bridge chain {name} in table {}", table.table()); + + let chain = Self::bridge_chain(table.clone(), name); + + commands.append(&mut vec![ + Add::chain(chain.clone()), + Flush::chain(chain.clone()), + Add::rule(AddRule::from_statement( + chain.clone(), + Statement::jump("before-bridge"), + )), + ]); + + let env = NftRuleEnv { + chain: chain.clone(), + direction: Direction::Forward, + firewall_config: &self.config, + vmid: None, + }; + + 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 default_policy = config.policy_forward(); + + self.create_log_rule( + commands, + config.log_level_forward(), + chain.clone(), + default_policy, + None, + )?; + + commands.push(Add::rule(AddRule::from_statement( + chain.clone(), + default_policy, + ))); + + let key = if table == Self::host_table() { + name.into() + } else { + Expression::concat([name.into(), name.into()]) + }; + + let map_element = AddElement::map_from_expressions( + Self::bridge_vmap(table), + [( + key, + MapValue::from(Verdict::Jump { + target: chain.name().to_string(), + }), + )] + .to_vec(), + ); + + commands.push(Add::element(map_element)); + } + + Ok(()) + } + fn handle_host_options(&self, commands: &mut Commands) -> Result<(), Error> { log::info!("setting host options"); @@ -781,6 +894,7 @@ impl Firewall { let pre_chain = match direction { Direction::In => "pre-vm-in", Direction::Out => "pre-vm-out", + Direction::Forward => bail!("cannot create guest_chain in direction forward"), }; commands.append(&mut vec![ diff --git a/proxmox-firewall/src/rule.rs b/proxmox-firewall/src/rule.rs index 02f964e..3b947f0 100644 --- a/proxmox-firewall/src/rule.rs +++ b/proxmox-firewall/src/rule.rs @@ -1,6 +1,6 @@ use std::ops::{Deref, DerefMut}; -use anyhow::{format_err, Error}; +use anyhow::{bail, format_err, Error}; use proxmox_nftables::{ expression::{Ct, IpFamily, Meta, Payload, Prefix}, statement::{Log, LogLevel, Match, Operator}, @@ -179,6 +179,7 @@ fn handle_iface(rules: &mut [NftRule], env: &NftRuleEnv, name: &str) -> Result<( (Some(_), Direction::Out) => "iifname", (None, Direction::In) => "iifname", (None, Direction::Out) => "oifname", + (_, Direction::Forward) => bail!("cannot define interfaces for forward direction"), }; let iface_name = env.iface_name(name); @@ -693,8 +694,8 @@ impl ToNftRules for Ipfilter<'_> { rules.push(base_rule); } } + Direction::Forward => bail!("cannot generate IP filter for direction forward"), } - Ok(()) } } diff --git a/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap b/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap index e1b599c..06b6beb 100644 --- a/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap +++ b/proxmox-firewall/tests/snapshots/integration_tests__firewall.snap @@ -22,6 +22,15 @@ expression: "firewall.full_host_fw().expect(\"firewall can be generated\")" } } }, + { + "flush": { + "chain": { + "family": "inet", + "table": "proxmox-firewall", + "name": "cluster-forward" + } + } + }, { "add": { "chain": { @@ -76,6 +85,15 @@ expression: "firewall.full_host_fw().expect(\"firewall can be generated\")" } } }, + { + "flush": { + "chain": { + "family": "inet", + "table": "proxmox-firewall", + "name": "host-forward" + } + } + }, { "flush": { "map": { @@ -94,6 +112,24 @@ expression: "firewall.full_host_fw().expect(\"firewall can be generated\")" } } }, + { + "flush": { + "map": { + "family": "bridge", + "table": "proxmox-firewall-guests", + "name": "bridge-map" + } + } + }, + { + "flush": { + "map": { + "family": "inet", + "table": "proxmox-firewall", + "name": "bridge-map" + } + } + }, { "flush": { "chain": { @@ -1784,6 +1820,24 @@ expression: "firewall.full_host_fw().expect(\"firewall can be generated\")" } } }, + { + "add": { + "chain": { + "family": "inet", + "table": "proxmox-firewall", + "name": "group-network1-forward" + } + } + }, + { + "flush": { + "chain": { + "family": "inet", + "table": "proxmox-firewall", + "name": "group-network1-forward" + } + } + }, { "add": { "rule": { @@ -1874,6 +1928,20 @@ expression: "firewall.full_host_fw().expect(\"firewall can be generated\")" } } }, + { + "add": { + "rule": { + "family": "inet", + "table": "proxmox-firewall", + "chain": "cluster-forward", + "expr": [ + { + "accept": null + } + ] + } + } + }, { "add": { "ct helper": { @@ -3813,6 +3881,24 @@ expression: "firewall.full_host_fw().expect(\"firewall can be generated\")" } } }, + { + "add": { + "chain": { + "family": "bridge", + "table": "proxmox-firewall-guests", + "name": "group-network1-forward" + } + } + }, + { + "flush": { + "chain": { + "family": "bridge", + "table": "proxmox-firewall-guests", + "name": "group-network1-forward" + } + } + }, { "add": { "chain": { diff --git a/proxmox-nftables/src/expression.rs b/proxmox-nftables/src/expression.rs index 71a90eb..aefb4c4 100644 --- a/proxmox-nftables/src/expression.rs +++ b/proxmox-nftables/src/expression.rs @@ -1,5 +1,6 @@ use crate::types::{ElemConfig, Verdict}; use proxmox_ve_config::firewall::types::address::IpRange; +use proxmox_ve_config::host::types::BridgeName; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; @@ -259,6 +260,13 @@ impl From<&PortList> for Expression { } } +#[cfg(feature = "config-ext")] +impl From<&BridgeName> for Expression { + fn from(value: &BridgeName) -> Self { + Expression::String(value.name().to_string()) + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Meta { key: String, diff --git a/proxmox-nftables/src/types.rs b/proxmox-nftables/src/types.rs index d8f3b62..320c757 100644 --- a/proxmox-nftables/src/types.rs +++ b/proxmox-nftables/src/types.rs @@ -742,6 +742,12 @@ impl AddElement { } } +impl From for AddElement { + fn from(value: AddMapElement) -> Self { + AddElement::Map(value) + } +} + impl From for AddElement { fn from(value: AddSetElement) -> Self { AddElement::Set(value) -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel