From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Wolfgang Bumiller <w.bumiller@proxmox.com>
Subject: [pve-devel] [PATCH proxmox-firewall v3 31/39] firewall: add ruleset generation logic
Date: Thu, 18 Apr 2024 18:14:26 +0200 [thread overview]
Message-ID: <20240418161434.709473-32-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20240418161434.709473-1-s.hanreich@proxmox.com>
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.
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Co-authored-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
proxmox-firewall/Cargo.toml | 5 +
proxmox-firewall/src/firewall.rs | 899 +++++++++++++++++++++++++++++++
proxmox-firewall/src/main.rs | 1 +
3 files changed, 905 insertions(+)
create mode 100644 proxmox-firewall/src/firewall.rs
diff --git a/proxmox-firewall/Cargo.toml b/proxmox-firewall/Cargo.toml
index 431e71a..bec7552 100644
--- a/proxmox-firewall/Cargo.toml
+++ b/proxmox-firewall/Cargo.toml
@@ -15,5 +15,10 @@ log = "0.4"
env_logger = "0.10"
anyhow = "1"
+serde = { version = "1", features = [ "derive" ] }
+serde_json = "1"
+
+signal-hook = "0.3"
+
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..2195a07
--- /dev/null
+++ b/proxmox-firewall/src/firewall.rs
@@ -0,0 +1,899 @@
+use std::collections::BTreeMap;
+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, TableName,
+ 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::address::Ipv6Cidr;
+use proxmox_ve_config::firewall::types::ipset::{
+ Ipfilter, Ipset, IpsetEntry, IpsetName, 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(Default)]
+pub struct Firewall {
+ config: FirewallConfig,
+}
+
+impl From<FirewallConfig> for Firewall {
+ fn from(config: FirewallConfig) -> Self {
+ Self { config }
+ }
+}
+
+impl Firewall {
+ pub fn new() -> Self {
+ Self {
+ ..Default::default()
+ }
+ }
+
+ pub fn is_enabled(&self) -> bool {
+ self.config.is_enabled()
+ }
+
+ fn cluster_table() -> TablePart {
+ TablePart::new(TableFamily::Inet, CLUSTER_TABLE_NAME)
+ }
+
+ fn host_table() -> TablePart {
+ TablePart::new(TableFamily::Inet, HOST_TABLE_NAME)
+ }
+
+ fn guest_table() -> TablePart {
+ TablePart::new(TableFamily::Bridge, GUEST_TABLE_NAME)
+ }
+
+ fn guest_vmap(dir: Direction) -> SetName {
+ SetName::new(Self::guest_table(), format!("vm-map-{dir}"))
+ }
+
+ fn cluster_chain(dir: Direction) -> ChainPart {
+ ChainPart::new(Self::cluster_table(), format!("cluster-{dir}"))
+ }
+
+ fn host_chain(dir: Direction) -> ChainPart {
+ ChainPart::new(Self::host_table(), format!("host-{dir}"))
+ }
+
+ fn guest_chain(dir: Direction, vmid: Vmid) -> ChainPart {
+ ChainPart::new(Self::guest_table(), format!("guest-{vmid}-{dir}"))
+ }
+
+ fn group_chain(table: TablePart, name: &str, dir: Direction) -> ChainPart {
+ ChainPart::new(table, format!("group-{name}-{dir}"))
+ }
+
+ fn host_conntrack_chain() -> ChainPart {
+ ChainPart::new(Self::host_table(), "ct-in".to_string())
+ }
+
+ fn host_option_chain(dir: Direction) -> ChainPart {
+ ChainPart::new(Self::host_table(), format!("option-{dir}"))
+ }
+
+ fn synflood_limit_chain() -> ChainPart {
+ ChainPart::new(Self::host_table(), "ratelimit-synflood")
+ }
+
+ fn log_invalid_tcp_chain() -> ChainPart {
+ ChainPart::new(Self::host_table(), "log-invalid-tcp")
+ }
+
+ fn log_smurfs_chain() -> ChainPart {
+ ChainPart::new(Self::host_table(), "log-smurfs")
+ }
+
+ fn default_log_limit(&self) -> Option<LogRateLimit> {
+ 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()),
+ ]);
+
+ /*
+ for prefix in ["v4-guest-", "v6-guest-", "v4-dc/", "v6-dc/"] {
+ for (name, set) in &self.config.nft().sets {
+ if name.starts_with(prefix) {
+ commands.push(Delete::set(set.name().clone()))
+ }
+ }
+ }
+ */
+
+ // 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_commands() -> 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() {
+ log::trace!("auto-generating management ipset");
+
+ 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<Commands, Error> {
+ let mut commands = Commands::default();
+
+ if !self.config.is_enabled() {
+ log::info!("firewall is disabled - doing nothing!");
+ return Ok(commands);
+ }
+
+ self.reset_firewall(&mut commands);
+
+ let cluster_host_table = Self::cluster_table();
+
+ if self.config.host().is_enabled() {
+ log::info!("creating cluster / host configuration");
+
+ self.create_management_ipset(&mut commands)?;
+
+ self.create_ipsets(
+ &mut commands,
+ self.config.cluster().ipsets(),
+ &cluster_host_table,
+ None,
+ )?;
+
+ for (name, group) in self.config.cluster().groups() {
+ self.create_group_chain(
+ &mut commands,
+ &cluster_host_table,
+ group,
+ name,
+ Direction::In,
+ )?;
+ self.create_group_chain(
+ &mut commands,
+ &cluster_host_table,
+ group,
+ name,
+ Direction::Out,
+ )?;
+ }
+
+ self.create_cluster_rules(&mut commands, Direction::In)?;
+ self.create_cluster_rules(&mut commands, Direction::Out)?;
+
+ 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)?;
+ } else {
+ commands.push(Delete::table(TableName::from(Self::cluster_table())));
+ }
+
+ let guest_table = Self::guest_table();
+ let enabled_guests: BTreeMap<&Vmid, &GuestConfig> = self
+ .config
+ .guests()
+ .iter()
+ .filter(|(_, config)| config.is_enabled())
+ .collect();
+
+ if !enabled_guests.is_empty() {
+ log::info!("creating guest configuration");
+
+ self.create_ipsets(
+ &mut commands,
+ self.config.cluster().ipsets(),
+ &guest_table,
+ None,
+ )?;
+
+ 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)?;
+ }
+ } else {
+ commands.push(Delete::table(TableName::from(Self::guest_table())));
+ }
+
+ for (vmid, config) in enabled_guests {
+ log::debug!("Generating firewall config for VM #{vmid}");
+
+ self.create_guest_chain(&mut commands, *vmid, Direction::In)?;
+ self.create_guest_chain(&mut commands, *vmid, Direction::Out)?;
+
+ self.create_ipsets(&mut commands, config.ipsets(), &guest_table, config)?;
+
+ 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::info!("setting host options");
+
+ let chain_in = Self::host_option_chain(Direction::In);
+ let chain_out = Self::host_option_chain(Direction::Out);
+
+ let ndp_chains = if self.config.host().allow_ndp() {
+ ("allow-ndp-in", "allow-ndp-out")
+ } else {
+ ("block-ndp-in", "block-ndp-out")
+ };
+
+ commands.append(&mut vec![
+ Add::rule(AddRule::from_statement(
+ chain_in.clone(),
+ Statement::jump(ndp_chains.0),
+ )),
+ Add::rule(AddRule::from_statement(
+ chain_out,
+ Statement::jump(ndp_chains.1),
+ )),
+ ]);
+
+ 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())
+ .unwrap_or_else(|_| log::warn!("cannot set nf_conntrack_max"));
+ }
+
+ 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())
+ .unwrap_or_else(|_| log::warn!("cannot set nf_conntrack_tcp_timeout_established"));
+ }
+
+ 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())
+ .unwrap_or_else(|_| log::warn!("cannot set nf_conntrack_tcp_timeout_syn_recv"));
+ }
+
+ let value = (self.config.host().log_nf_conntrack() as u8).to_string();
+ fs::write(LOG_CONNTRACK_FILE, value)
+ .unwrap_or_else(|_| log::warn!("cannot set conntrack_log_file"));
+
+ /*
+ 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() {
+ log::debug!("setting macfilter for guest #{vmid}");
+ let mac_address_set: Vec<Expression> = 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()),
+ ])
+ })
+ .collect();
+
+ if !mac_address_set.is_empty() {
+ 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.clone()),
+ )
+ .into(),
+ Statement::make_drop(),
+ ],
+ );
+
+ let macfilter_arp_rule = AddRule::from_statements(
+ chain_out.clone(),
+ [
+ Match::new_ne(
+ Expression::concat([
+ Expression::from(Meta::new("iifname")),
+ Expression::from(Payload::field("arp", "saddr ether")),
+ ]),
+ Expression::set(mac_address_set),
+ )
+ .into(),
+ Statement::make_drop(),
+ ],
+ );
+
+ commands.push(Add::rule(macfilter_rule));
+ commands.push(Add::rule(macfilter_arp_rule));
+ }
+ }
+
+ let dhcp_chains = if config.allow_dhcp() {
+ ("allow-dhcp-in", "allow-dhcp-out")
+ } else {
+ ("block-dhcp-in", "block-dhcp-out")
+ };
+
+ commands.append(&mut vec![
+ Add::rule(AddRule::from_statement(
+ chain_in.clone(),
+ Statement::jump(dhcp_chains.0),
+ )),
+ Add::rule(AddRule::from_statement(
+ chain_out.clone(),
+ Statement::jump(dhcp_chains.1),
+ )),
+ ]);
+
+ let ndp_chains = if config.allow_ndp() {
+ ("allow-ndp-in", "allow-ndp-out")
+ } else {
+ ("block-ndp-in", "block-ndp-out")
+ };
+
+ commands.append(&mut vec![
+ Add::rule(AddRule::from_statement(
+ chain_in.clone(),
+ Statement::jump(ndp_chains.0),
+ )),
+ Add::rule(AddRule::from_statement(
+ chain_out.clone(),
+ Statement::jump(ndp_chains.1),
+ )),
+ ]);
+
+ let ra_chain_out = if config.allow_ra() {
+ "allow-ra-out"
+ } else {
+ "block-ra-out"
+ };
+
+ commands.push(Add::rule(AddRule::from_statement(
+ chain_out,
+ Statement::jump(ra_chain_out),
+ )));
+
+ // we allow incoming ARP by default, except if blocked by any option above
+ let arp_rule = vec![
+ Match::new_eq(Payload::field("ether", "type"), Expression::from("arp")).into(),
+ Statement::make_accept(),
+ ];
+
+ commands.push(Add::rule(AddRule::from_statements(chain_in, arp_rule)));
+
+ 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() {
+ 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 {
+ log::debug!("adding conntrack helper: {helper:?}");
+
+ 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_ipfilter_rules(
+ &self,
+ commands: &mut Commands,
+ vmid: Vmid,
+ ipfilter: &Ipfilter,
+ ) -> Result<(), Error> {
+ for direction in [Direction::In, Direction::Out] {
+ let chain = Self::guest_chain(direction, vmid);
+
+ let rule_env = NftRuleEnv {
+ chain: chain.clone(),
+ direction,
+ 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_ipsets<'a>(
+ &self,
+ commands: &mut Commands,
+ ipsets: &BTreeMap<String, Ipset>,
+ table: &TablePart,
+ guest_config: impl Into<Option<&'a GuestConfig>>,
+ ) -> Result<(), Error> {
+ let config = guest_config.into();
+ let vmid = config.map(|cfg| cfg.vmid());
+
+ let env = NftObjectEnv {
+ table,
+ vmid,
+ firewall_config: &self.config,
+ };
+
+ for (name, ipset) in ipsets {
+ if ipset.ipfilter().is_some() {
+ continue;
+ }
+
+ log::info!("creating ipset {name} in table {}", table.table());
+ commands.append(&mut ipset.to_nft_objects(&env)?);
+ }
+
+ if let (Some(cfg), Some(vmid)) = (config, vmid) {
+ let network_devices = cfg.network_config().network_devices();
+
+ for (index, network_device) in network_devices {
+ let ipfilter_name = Ipfilter::name_for_index(*index);
+
+ if let Some(ipset) = ipsets.get(&ipfilter_name) {
+ log::debug!("creating ipfilter for guest #{vmid} net{index}");
+
+ commands.append(&mut ipset.to_nft_objects(&env)?);
+ // safe due to constructing the name above
+ let ipfilter = ipset.ipfilter().expect("is an ip filter");
+ self.create_ipfilter_rules(commands, vmid, &ipfilter)?;
+ } else if cfg.ipfilter() {
+ log::debug!("generating default ipfilter for guest #{vmid} net{index}");
+ let ipset_name = IpsetName::new(IpsetScope::Guest, ipfilter_name);
+ let mut ipset = Ipset::new(ipset_name);
+
+ let cidr =
+ Ipv6Cidr::from(network_device.mac_address().eui64_link_local_address());
+
+ ipset.push(cidr.into());
+
+ if let Some(ip_address) = network_device.ip() {
+ ipset.push(IpsetEntry::from(*ip_address));
+ }
+
+ if let Some(ip6_address) = network_device.ip6() {
+ ipset.push(IpsetEntry::from(*ip6_address));
+ }
+
+ commands.append(&mut ipset.to_nft_objects(&env)?);
+ // safe due to constructing the name above
+ let ipfilter = ipset.ipfilter().expect("is an ip filter");
+ self.create_ipfilter_rules(commands, vmid, &ipfilter)?;
+ };
+ }
+ }
+
+ Ok(())
+ }
+
+ fn create_cluster_rules(
+ &self,
+ commands: &mut Commands,
+ direction: Direction,
+ ) -> Result<(), Error> {
+ log::info!("creating cluster chain {direction}");
+
+ 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}");
+
+ 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} {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} {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,
+ )));
+ }
+
+ if direction == Direction::In {
+ commands.push(Add::rule(AddRule::from_statement(
+ chain.clone(),
+ Statement::jump("after-vm-in"),
+ )));
+ }
+
+ 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 {name} in table {} {direction}",
+ table.table()
+ );
+
+ 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<Option<Vmid>>,
+ ) -> 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
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2024-04-19 7:31 UTC|newest]
Thread overview: 42+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-18 16:13 [pve-devel] [PATCH container/docs/firewall/manager/proxmox-firewall/qemu-server v3 00/39] proxmox firewall nftables implementation Stefan Hanreich
2024-04-18 16:13 ` [pve-devel] [PATCH proxmox-firewall v3 01/39] config: add proxmox-ve-config crate Stefan Hanreich
2024-04-18 16:13 ` [pve-devel] [PATCH proxmox-firewall v3 02/39] config: firewall: add types for ip addresses Stefan Hanreich
2024-04-18 16:13 ` [pve-devel] [PATCH proxmox-firewall v3 03/39] config: firewall: add types for ports Stefan Hanreich
2024-04-18 16:13 ` [pve-devel] [PATCH proxmox-firewall v3 04/39] config: firewall: add types for log level and rate limit Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 05/39] config: firewall: add types for aliases Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 06/39] config: host: add helpers for host network configuration Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 07/39] config: guest: add helpers for parsing guest network config Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 08/39] config: firewall: add types for ipsets Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 09/39] config: firewall: add types for rules Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 10/39] config: firewall: add types for security groups Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 11/39] config: firewall: add generic parser for firewall configs Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 12/39] config: firewall: add cluster-specific config + option types Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 13/39] config: firewall: add host specific " Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 14/39] config: firewall: add guest-specific " Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 15/39] config: firewall: add firewall macros Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 16/39] config: firewall: add conntrack helper types Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 17/39] nftables: add crate for libnftables bindings Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 18/39] nftables: add helpers Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 19/39] nftables: expression: add types Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 20/39] nftables: expression: implement conversion traits for firewall config Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 21/39] nftables: statement: add types Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 22/39] nftables: statement: add conversion traits for config types Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 23/39] nftables: commands: add types Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 24/39] nftables: types: add conversion traits Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 25/39] nftables: add nft client Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 26/39] firewall: add firewall crate Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 27/39] firewall: add base ruleset Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 28/39] firewall: add config loader Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 29/39] firewall: add rule generation logic Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 30/39] firewall: add object " Stefan Hanreich
2024-04-18 16:14 ` Stefan Hanreich [this message]
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 32/39] firewall: add proxmox-firewall binary and move existing code into lib Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 33/39] firewall: add files for debian packaging Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH proxmox-firewall v3 34/39] firewall: add integration test Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH qemu-server v3 35/39] firewall: add handling for new nft firewall Stefan Hanreich
2024-04-18 21:08 ` Thomas Lamprecht
2024-04-18 16:14 ` [pve-devel] [PATCH pve-container v3 36/39] " Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH pve-firewall v3 37/39] add configuration option for new nftables firewall Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH pve-manager v3 38/39] firewall: expose " Stefan Hanreich
2024-04-18 16:14 ` [pve-devel] [PATCH pve-docs v3 39/39] firewall: add documentation for proxmox-firewall Stefan Hanreich
2024-04-18 20:05 ` [pve-devel] partially-applied-series: [PATCH container/docs/firewall/manager/proxmox-firewall/qemu-server v3 00/39] proxmox firewall nftables implementation Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240418161434.709473-32-s.hanreich@proxmox.com \
--to=s.hanreich@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
--cc=w.bumiller@proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox