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 4993F1FF142 for ; Mon, 16 Feb 2026 11:45:09 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 64B8FD0FC; Mon, 16 Feb 2026 11:45:00 +0100 (CET) From: Dietmar Maurer To: pve-devel@lists.proxmox.com Subject: [RFC proxmox 03/22] firewall-api-types: add firewall policy types Date: Mon, 16 Feb 2026 11:43:41 +0100 Message-ID: <20260216104401.3959270-4-dietmar@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260216104401.3959270-1-dietmar@proxmox.com> References: <20260216104401.3959270-1-dietmar@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.576 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 Message-ID-Hash: KYARB2NBL3FZ6TVAJZECLFG7RZUSBTT4 X-Message-ID-Hash: KYARB2NBL3FZ6TVAJZECLFG7RZUSBTT4 X-MailFrom: dietmar@zilli.proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Generated from perl api. Signed-off-by: Dietmar Maurer --- proxmox-firewall-api-types/Cargo.toml | 5 +- proxmox-firewall-api-types/src/lib.rs | 3 +- proxmox-firewall-api-types/src/policy.rs | 151 +++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 proxmox-firewall-api-types/src/policy.rs diff --git a/proxmox-firewall-api-types/Cargo.toml b/proxmox-firewall-api-types/Cargo.toml index 515d1efc..3122d813 100644 --- a/proxmox-firewall-api-types/Cargo.toml +++ b/proxmox-firewall-api-types/Cargo.toml @@ -9,12 +9,15 @@ license.workspace = true repository.workspace = true exclude.workspace = true +[features] +enum-fallback = ["dep:proxmox-fixed-string"] + [dependencies] anyhow.workspace = true regex.workspace = true -proxmox-fixed-string.workspace = true serde = { workspace = true, features = [ "derive" ] } serde_plain = { workspace = true } proxmox-schema = { workspace = true, features = ["api-macro"] } proxmox-serde = { workspace = true, features = ["perl"] } +proxmox-fixed-string = { workspace = true, optional = true } diff --git a/proxmox-firewall-api-types/src/lib.rs b/proxmox-firewall-api-types/src/lib.rs index 7c4a64a7..b8004c76 100644 --- a/proxmox-firewall-api-types/src/lib.rs +++ b/proxmox-firewall-api-types/src/lib.rs @@ -1 +1,2 @@ -// TODO: add code here +mod policy; +pub use policy::{FirewallFWPolicy, FirewallIOPolicy}; diff --git a/proxmox-firewall-api-types/src/policy.rs b/proxmox-firewall-api-types/src/policy.rs new file mode 100644 index 00000000..274fbe20 --- /dev/null +++ b/proxmox-firewall-api-types/src/policy.rs @@ -0,0 +1,151 @@ +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "enum-fallback")] +use proxmox_fixed_string::FixedString; +use proxmox_schema::api; + +#[api] +/// Firewall forward policy. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +pub enum FirewallFWPolicy { + #[serde(rename = "ACCEPT")] + #[default] + /// ACCEPT. + Accept, + #[serde(rename = "DROP")] + /// DROP. + Drop, + #[cfg(feature = "enum-fallback")] + #[serde(untagged)] + /// Unknwon + UnknownEnumValue(FixedString), +} +serde_plain::derive_display_from_serialize!(FirewallFWPolicy); +serde_plain::derive_fromstr_from_deserialize!(FirewallFWPolicy); + +#[api] +/// Firewall IO policy. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub enum FirewallIOPolicy { + #[serde(rename = "ACCEPT")] + /// ACCEPT. + Accept, + #[serde(rename = "DROP")] + /// DROP. + Drop, + #[serde(rename = "REJECT")] + /// REJECT. + Reject, + #[cfg(feature = "enum-fallback")] + #[serde(untagged)] + /// Unknwon + UnknownEnumValue(FixedString), +} +serde_plain::derive_display_from_serialize!(FirewallIOPolicy); +serde_plain::derive_fromstr_from_deserialize!(FirewallIOPolicy); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_firewall_fw_policy_default() { + assert_eq!(FirewallFWPolicy::default(), FirewallFWPolicy::Accept); + } + + #[test] + #[cfg(not(feature = "enum-fallback"))] + fn test_firewall_fw_policy_serde_invalid() { + serde_plain::from_str::("REJECT") + .expect_err("REJECT is not valid for FWPolicy"); + serde_plain::from_str::("accept") + .expect_err("lowercase should be invalid"); + serde_plain::from_str::("").expect_err("empty string should be invalid"); + } + + #[test] + fn test_firewall_fw_policy_roundtrip() { + for policy in [FirewallFWPolicy::Accept, FirewallFWPolicy::Drop] { + let serialized = serde_plain::to_string(&policy).expect("serialize"); + let parsed: FirewallFWPolicy = + serde_plain::from_str(&serialized).expect("roundtrip parse"); + assert_eq!(policy, parsed); + } + } + + #[test] + fn test_firewall_fw_policy_serde() { + let accept = FirewallFWPolicy::Accept; + let serialized = serde_plain::to_string(&accept).expect("serialize"); + assert_eq!(serialized, "ACCEPT"); + + let parsed: FirewallFWPolicy = serde_plain::from_str(&serialized).expect("deserialize"); + assert_eq!(parsed, accept); + + let drop = FirewallFWPolicy::Drop; + let serialized = serde_plain::to_string(&drop).expect("serialize"); + assert_eq!(serialized, "DROP"); + + let parsed: FirewallFWPolicy = serde_plain::from_str(&serialized).expect("deserialize"); + assert_eq!(parsed, drop); + } + + #[test] + #[cfg(feature = "enum-fallback")] + fn test_firewall_fw_policy_serde_enum_fallback() { + let unknown = FirewallFWPolicy::UnknownEnumValue(FixedString::new("TEST").unwrap()); + let serialized = serde_plain::to_string(&unknown).expect("serialize"); + assert_eq!(serialized, "TEST"); + + let parsed: FirewallFWPolicy = serde_plain::from_str(&serialized).expect("deserialize"); + assert_eq!(parsed, unknown); + } + + #[test] + #[cfg(not(feature = "enum-fallback"))] + fn test_firewall_io_policy_serde_invalid() { + serde_plain::from_str::("ALLOW").expect_err("ALLOW is not valid"); + serde_plain::from_str::("drop").expect_err("lowercase should be invalid"); + serde_plain::from_str::("").expect_err("empty string should be invalid"); + } + + #[test] + fn test_firewall_io_policy_roundtrip() { + for policy in [ + FirewallIOPolicy::Accept, + FirewallIOPolicy::Drop, + FirewallIOPolicy::Reject, + ] { + let serialized = serde_plain::to_string(&policy).expect("serialize"); + let parsed: FirewallIOPolicy = + serde_plain::from_str(&serialized).expect("roundtrip parse"); + assert_eq!(policy, parsed); + } + } + + #[test] + fn test_firewall_io_policy_serde() { + for (policy, expected) in [ + (FirewallIOPolicy::Accept, "ACCEPT"), + (FirewallIOPolicy::Drop, "DROP"), + (FirewallIOPolicy::Reject, "REJECT"), + ] { + let serialized = serde_plain::to_string(&policy).expect("serialize"); + assert_eq!(serialized, expected); + + let parsed: FirewallIOPolicy = serde_plain::from_str(&serialized).expect("deserialize"); + assert_eq!(parsed, policy); + } + } + + #[test] + #[cfg(feature = "enum-fallback")] + fn test_firewall_io_policy_serde_enum_fallback() { + let unknown = FirewallIOPolicy::UnknownEnumValue(FixedString::new("TEST").unwrap()); + let serialized = serde_plain::to_string(&unknown).expect("serialize"); + assert_eq!(serialized, "TEST"); + + let parsed: FirewallIOPolicy = serde_plain::from_str(&serialized).expect("deserialize"); + assert_eq!(parsed, unknown); + } +} -- 2.47.3