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 634C81FF37F for ; Thu, 18 Apr 2024 18:16:41 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id CEDB9314A5; Thu, 18 Apr 2024 18:15:09 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Thu, 18 Apr 2024 18:13:59 +0200 Message-Id: <20240418161434.709473-5-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240418161434.709473-1-s.hanreich@proxmox.com> References: <20240418161434.709473-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.351 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 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [parse.rs, log.rs, mod.rs] Subject: [pve-devel] [PATCH proxmox-firewall v3 04/39] config: firewall: add types for log level and rate limit 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 Cc: Wolfgang Bumiller Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" Adds types for log and (log-)rate-limiting firewall config options as well as FromStr implementations for parsing them from the config. Reviewed-by: Lukas Wagner Reviewed-by: Max Carrara Co-authored-by: Wolfgang Bumiller Signed-off-by: Stefan Hanreich --- proxmox-ve-config/Cargo.toml | 1 + proxmox-ve-config/src/firewall/mod.rs | 2 + proxmox-ve-config/src/firewall/parse.rs | 21 ++ proxmox-ve-config/src/firewall/types/log.rs | 222 ++++++++++++++++++++ proxmox-ve-config/src/firewall/types/mod.rs | 1 + 5 files changed, 247 insertions(+) create mode 100644 proxmox-ve-config/src/firewall/parse.rs create mode 100644 proxmox-ve-config/src/firewall/types/log.rs diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml index 80b336a..7bb391e 100644 --- a/proxmox-ve-config/Cargo.toml +++ b/proxmox-ve-config/Cargo.toml @@ -16,4 +16,5 @@ anyhow = "1" serde = { version = "1", features = [ "derive" ] } serde_json = "1" +serde_plain = "1" serde_with = "2.3.3" diff --git a/proxmox-ve-config/src/firewall/mod.rs b/proxmox-ve-config/src/firewall/mod.rs index a9f65bf..2e0f31e 100644 --- a/proxmox-ve-config/src/firewall/mod.rs +++ b/proxmox-ve-config/src/firewall/mod.rs @@ -1,2 +1,4 @@ pub mod ports; pub mod types; + +pub(crate) mod parse; diff --git a/proxmox-ve-config/src/firewall/parse.rs b/proxmox-ve-config/src/firewall/parse.rs new file mode 100644 index 0000000..a75daee --- /dev/null +++ b/proxmox-ve-config/src/firewall/parse.rs @@ -0,0 +1,21 @@ +use anyhow::{bail, format_err, Error}; + +pub fn parse_bool(value: &str) -> Result { + Ok( + if value == "0" + || value.eq_ignore_ascii_case("false") + || value.eq_ignore_ascii_case("off") + || value.eq_ignore_ascii_case("no") + { + false + } else if value == "1" + || value.eq_ignore_ascii_case("true") + || value.eq_ignore_ascii_case("on") + || value.eq_ignore_ascii_case("yes") + { + true + } else { + bail!("not a boolean: {value:?}"); + }, + ) +} diff --git a/proxmox-ve-config/src/firewall/types/log.rs b/proxmox-ve-config/src/firewall/types/log.rs new file mode 100644 index 0000000..72344e4 --- /dev/null +++ b/proxmox-ve-config/src/firewall/types/log.rs @@ -0,0 +1,222 @@ +use std::fmt; +use std::str::FromStr; + +use crate::firewall::parse::parse_bool; +use anyhow::{bail, Error}; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, Deserialize, Serialize, Default)] +#[cfg_attr(test, derive(Eq, PartialEq))] +#[serde(rename_all = "lowercase")] +pub enum LogRateLimitTimescale { + #[default] + Second, + Minute, + Hour, + Day, +} + +impl FromStr for LogRateLimitTimescale { + type Err = Error; + + fn from_str(str: &str) -> Result { + match str { + "second" => Ok(LogRateLimitTimescale::Second), + "minute" => Ok(LogRateLimitTimescale::Minute), + "hour" => Ok(LogRateLimitTimescale::Hour), + "day" => Ok(LogRateLimitTimescale::Day), + _ => bail!("Invalid time scale provided"), + } + } +} + +#[derive(Debug, Deserialize, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct LogRateLimit { + enabled: bool, + rate: i64, // in packets + per: LogRateLimitTimescale, + burst: i64, // in packets +} + +impl LogRateLimit { + pub fn new(enabled: bool, rate: i64, per: LogRateLimitTimescale, burst: i64) -> Self { + Self { + enabled, + rate, + per, + burst, + } + } + + pub fn enabled(&self) -> bool { + self.enabled + } + + pub fn rate(&self) -> i64 { + self.rate + } + + pub fn burst(&self) -> i64 { + self.burst + } + + pub fn per(&self) -> LogRateLimitTimescale { + self.per + } +} + +impl Default for LogRateLimit { + fn default() -> Self { + Self { + enabled: true, + rate: 1, + burst: 5, + per: LogRateLimitTimescale::Second, + } + } +} + +impl FromStr for LogRateLimit { + type Err = Error; + + fn from_str(str: &str) -> Result { + let mut limit = Self::default(); + + for element in str.split(',') { + match element.split_once('=') { + None => { + limit.enabled = parse_bool(element)?; + } + Some((key, value)) if !key.is_empty() && !value.is_empty() => match key { + "enable" => limit.enabled = parse_bool(value)?, + "burst" => limit.burst = i64::from_str(value)?, + "rate" => match value.split_once('/') { + None => { + limit.rate = i64::from_str(value)?; + } + Some((rate, unit)) => { + if unit.is_empty() { + bail!("empty unit specification") + } + + limit.rate = i64::from_str(rate)?; + limit.per = LogRateLimitTimescale::from_str(unit)?; + } + }, + _ => bail!("Invalid value for Key found in log_ratelimit!"), + }, + _ => bail!("invalid value in log_ratelimit"), + } + } + + Ok(limit) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +pub enum LogLevel { + #[default] + Nolog, + Emergency, + Alert, + Critical, + Error, + Warning, + Notice, + Info, + Debug, +} + +impl std::str::FromStr for LogLevel { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "nolog" => LogLevel::Nolog, + "emerg" => LogLevel::Emergency, + "alert" => LogLevel::Alert, + "crit" => LogLevel::Critical, + "err" => LogLevel::Error, + "warn" => LogLevel::Warning, + "warning" => LogLevel::Warning, + "notice" => LogLevel::Notice, + "info" => LogLevel::Info, + "debug" => LogLevel::Debug, + _ => bail!("invalid log level {s:?}"), + }) + } +} + +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + LogLevel::Nolog => "nolog", + LogLevel::Emergency => "emerg", + LogLevel::Alert => "alert", + LogLevel::Critical => "crit", + LogLevel::Error => "err", + LogLevel::Warning => "warn", + LogLevel::Notice => "notice", + LogLevel::Info => "info", + LogLevel::Debug => "debug", + }) + } +} + +serde_plain::derive_deserialize_from_fromstr!(LogLevel, "valid log level"); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_rate_limit() { + let mut parsed_rate_limit = "1,burst=123,rate=44" + .parse::() + .expect("valid rate limit"); + + assert_eq!( + parsed_rate_limit, + LogRateLimit { + enabled: true, + burst: 123, + rate: 44, + per: LogRateLimitTimescale::Second, + } + ); + + parsed_rate_limit = "1".parse::().expect("valid rate limit"); + + assert_eq!(parsed_rate_limit, LogRateLimit::default()); + + parsed_rate_limit = "enable=0,rate=123/hour" + .parse::() + .expect("valid rate limit"); + + assert_eq!( + parsed_rate_limit, + LogRateLimit { + enabled: false, + burst: 5, + rate: 123, + per: LogRateLimitTimescale::Hour, + } + ); + + "2".parse::() + .expect_err("invalid value for enable"); + + "enabled=0,rate=123" + .parse::() + .expect_err("invalid key in log ratelimit"); + + "enable=0,rate=123," + .parse::() + .expect_err("trailing comma in log rate limit specification"); + + "enable=0,rate=123/proxmox," + .parse::() + .expect_err("invalid unit for rate"); + } +} diff --git a/proxmox-ve-config/src/firewall/types/mod.rs b/proxmox-ve-config/src/firewall/types/mod.rs index b740e5d..8bf31b8 100644 --- a/proxmox-ve-config/src/firewall/types/mod.rs +++ b/proxmox-ve-config/src/firewall/types/mod.rs @@ -1,4 +1,5 @@ pub mod address; +pub mod log; pub mod port; pub use address::Cidr; -- 2.39.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel