[pve-devel] [PATCH proxmox-firewall v4 07/18] sdn: create forward firewall rules
Stefan Hanreich
s.hanreich at proxmox.com
Fri Nov 15 13:10:58 CET 2024
Signed-off-by: Stefan Hanreich <s.hanreich at proxmox.com>
Reviewed-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
Tested-by: Hannes Dürr <h.duerr at proxmox.com>
---
.../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<LogRateLimit> {
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 e56a15c..e9ef94f 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<AddMapElement> for AddElement {
+ fn from(value: AddMapElement) -> Self {
+ AddElement::Map(value)
+ }
+}
+
impl From<AddSetElement> for AddElement {
fn from(value: AddSetElement) -> Self {
AddElement::Set(value)
--
2.39.5
More information about the pve-devel
mailing list