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 C1E1C90BA0 for ; Tue, 2 Apr 2024 19:17:14 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 25665A801 for ; Tue, 2 Apr 2024 19:16:40 +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:16:35 +0200 (CEST) Received: by lana.proxmox.com (Postfix, from userid 10043) id 312702C349B; 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:06 +0200 Message-Id: <20240402171629.536804-15-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.330 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 14/37] config: firewall: add guest-specific config + option types 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:17:14 -0000 Co-authored-by: Wolfgang Bumiller Signed-off-by: Stefan Hanreich --- proxmox-ve-config/src/firewall/guest.rs | 194 ++++++++++++++++++++++++ proxmox-ve-config/src/firewall/mod.rs | 1 + 2 files changed, 195 insertions(+) create mode 100644 proxmox-ve-config/src/firewall/guest.rs diff --git a/proxmox-ve-config/src/firewall/guest.rs b/proxmox-ve-config/src/firewall/guest.rs new file mode 100644 index 0000000..6ca446c --- /dev/null +++ b/proxmox-ve-config/src/firewall/guest.rs @@ -0,0 +1,194 @@ +use std::collections::HashMap; +use std::io; + +use crate::guest::types::Vmid; +use crate::guest::vm::NetworkConfig; + +use crate::firewall::types::alias::{Alias, AliasName}; +use crate::firewall::types::ipset::IpsetScope; +use crate::firewall::types::log::LogLevel; +use crate::firewall::types::rule::{Direction, Rule, Verdict}; +use crate::firewall::types::Ipset; + +use anyhow::{bail, Error}; +use serde::Deserialize; + +use crate::firewall::parse::serde_option_bool; + +#[derive(Debug, Default, Deserialize)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct Options { + #[serde(default, with = "serde_option_bool")] + dhcp: Option, + + #[serde(default, with = "serde_option_bool")] + enable: Option, + + #[serde(default, with = "serde_option_bool")] + ipfilter: Option, + + #[serde(default, with = "serde_option_bool")] + ndp: Option, + + #[serde(default, with = "serde_option_bool")] + radv: Option, + + log_level_in: Option, + log_level_out: Option, + + #[serde(default, with = "serde_option_bool")] + macfilter: Option, + + #[serde(rename = "policy_in")] + policy_in: Option, + + #[serde(rename = "policy_out")] + policy_out: Option, +} + +#[derive(Debug)] +pub struct Config { + vmid: Vmid, + + /// The interface prefix: "veth" for containers, "tap" for VMs. + iface_prefix: &'static str, + + network_config: NetworkConfig, + config: super::common::Config, +} + +impl Config { + pub fn parse( + vmid: &Vmid, + iface_prefix: &'static str, + firewall_input: T, + network_input: U, + ) -> Result { + let parser_cfg = super::common::ParserConfig { + guest_iface_names: true, + ipset_scope: Some(IpsetScope::Guest), + }; + + let config = super::common::Config::parse(firewall_input, &parser_cfg)?; + if !config.groups.is_empty() { + bail!("guest firewall config cannot declare groups"); + } + + let network_config = NetworkConfig::parse(network_input)?; + + Ok(Self { + vmid: *vmid, + iface_prefix, + config, + network_config, + }) + } + + pub fn alias(&self, name: &AliasName) -> Option<&Alias> { + self.config.alias(name.name()) + } + + pub fn iface_name_by_key(&self, key: &str) -> Result { + let index = NetworkConfig::index_from_net_key(key)?; + Ok(format!("{}{}i{index}", self.iface_prefix, self.vmid)) + } + + pub fn iface_name_by_index(&self, index: i64) -> String { + format!("{}{}i{index}", self.iface_prefix, self.vmid) + } + + pub fn is_enabled(&self) -> bool { + self.config.options.enable.unwrap_or(false) + } + + pub fn rules(&self) -> &[Rule] { + &self.config.rules + } + + pub fn log_level(&self, dir: Direction) -> LogLevel { + match dir { + Direction::In => self.config.options.log_level_in.unwrap_or_default(), + Direction::Out => self.config.options.log_level_out.unwrap_or_default(), + } + } + + pub fn allow_ndp(&self) -> bool { + self.config.options.ndp.unwrap_or(true) + } + + pub fn allow_dhcp(&self) -> bool { + self.config.options.dhcp.unwrap_or(true) + } + + pub fn allow_ra(&self) -> bool { + self.config.options.radv.unwrap_or(false) + } + + pub fn macfilter(&self) -> bool { + self.config.options.macfilter.unwrap_or(true) + } + + pub fn ipfilter(&self) -> bool { + self.config.options.ipfilter.unwrap_or(false) + } + + pub fn default_policy(&self, dir: Direction) -> Verdict { + match dir { + Direction::In => self.config.options.policy_in.unwrap_or(Verdict::Drop), + Direction::Out => self.config.options.policy_out.unwrap_or(Verdict::Accept), + } + } + + pub fn network_config(&self) -> &NetworkConfig { + &self.network_config + } + + pub fn ipsets(&self) -> &HashMap { + self.config.ipsets() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_config() { + // most of the stuff is already tested in cluster parsing, only testing + // guest specific options here + const CONFIG: &str = r#" +[OPTIONS] +enable: 1 +dhcp: 1 +ipfilter: 0 +log_level_in: emerg +log_level_out: crit +macfilter: 0 +ndp:1 +radv:1 +policy_in: REJECT +policy_out: REJECT +"#; + + let config = CONFIG.as_bytes(); + let network_config: Vec = Vec::new(); + let config = + Config::parse(&Vmid::new(100), "tap", config, network_config.as_slice()).unwrap(); + + assert_eq!( + config.config.options, + Options { + dhcp: Some(true), + enable: Some(true), + ipfilter: Some(false), + ndp: Some(true), + radv: Some(true), + log_level_in: Some(LogLevel::Emergency), + log_level_out: Some(LogLevel::Critical), + macfilter: Some(false), + policy_in: Some(Verdict::Reject), + policy_out: Some(Verdict::Reject), + } + ); + } +} diff --git a/proxmox-ve-config/src/firewall/mod.rs b/proxmox-ve-config/src/firewall/mod.rs index 85fe6c4..afc3dcc 100644 --- a/proxmox-ve-config/src/firewall/mod.rs +++ b/proxmox-ve-config/src/firewall/mod.rs @@ -1,5 +1,6 @@ pub mod cluster; pub mod common; +pub mod guest; pub mod host; pub mod ports; pub mod types; -- 2.39.2