Config hot reloading API

This commit is contained in:
mdecimus
2024-03-28 17:21:32 +01:00
parent 16b0465933
commit cb4d2f15ae
64 changed files with 1118 additions and 846 deletions

View File

@@ -23,7 +23,7 @@ webpki-roots = { version = "0.26"}
ring = { version = "0.17" }
base64 = "0.21"
serde_json = "1.0"
rcgen = "0.12"
rcgen = "0.13"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]}
x509-parser = "0.16.0"
pem = "3.0"

View File

@@ -81,52 +81,53 @@ impl SimpleCron {
}
impl ParseValue for SimpleCron {
fn parse_value(key: impl super::utils::AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
let mut hour = 0;
let mut minute = 0;
let key = key.as_key();
for (pos, value) in value.split(' ').enumerate() {
if pos == 0 {
minute = value.parse::<u32>().map_err(|_| {
format!("Invalid cron key {key:?}: failed to parse cron minute")
})?;
minute = value
.parse::<u32>()
.map_err(|_| "Invalid cron key: failed to parse cron minute".to_string())?;
if !(0..=59).contains(&minute) {
return Err(format!(
"Invalid cron key {key:?}: failed to parse minute, invalid value: {minute}"
"Invalid cron key: failed to parse minute, invalid value: {minute}"
));
}
} else if pos == 1 {
if value
.as_bytes()
.first()
.ok_or_else(|| format!("Invalid cron key {key:?}: failed to parse cron hour"))?
.ok_or_else(|| "Invalid cron key: failed to parse cron hour".to_string())?
== &b'*'
{
return Ok(SimpleCron::Hour { minute });
} else {
hour = value.parse::<u32>().map_err(|_| {
format!("Invalid cron key {key:?}: failed to parse cron hour")
})?;
hour = value
.parse::<u32>()
.map_err(|_| "Invalid cron key: failed to parse cron hour".to_string())?;
if !(0..=23).contains(&hour) {
return Err(format!(
"Invalid cron key {key:?}: failed to parse hour, invalid value: {hour}"
"Invalid cron key: failed to parse hour, invalid value: {hour}"
));
}
}
} else if pos == 2 {
if value.as_bytes().first().ok_or_else(|| {
format!("Invalid cron key {key:?}: failed to parse cron weekday")
})? == &b'*'
if value
.as_bytes()
.first()
.ok_or_else(|| "Invalid cron key: failed to parse cron weekday".to_string())?
== &b'*'
{
return Ok(SimpleCron::Day { hour, minute });
} else {
let day = value.parse::<u32>().map_err(|_| {
format!("Invalid cron key {key:?}: failed to parse cron weekday")
"Invalid cron key: failed to parse cron weekday".to_string()
})?;
if !(1..=7).contains(&hour) {
return Err(format!(
"Invalid cron key {key:?}: failed to parse weekday, invalid value: {}, range is 1 (Monday) to 7 (Sunday).",
"Invalid cron key: failed to parse weekday, invalid value: {}, range is 1 (Monday) to 7 (Sunday).",
hour,
));
}
@@ -136,7 +137,7 @@ impl ParseValue for SimpleCron {
}
}
Err(format!("Invalid cron key {key:?}: parse cron expression."))
Err("Invalid cron key: parse cron expression.".to_string())
}
}

View File

@@ -25,7 +25,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use rustls::{crypto::ring::cipher_suite::*, SupportedCipherSuite};
use super::utils::{AsKey, ParseKey, ParseValue};
use super::utils::ParseValue;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpAddrMask {
@@ -91,7 +91,7 @@ impl IpAddrMask {
}
impl ParseValue for IpAddrMask {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
if let Some((addr, mask)) = value.rsplit_once('/') {
if let (Ok(addr), Ok(mask)) =
(addr.trim().parse::<IpAddr>(), mask.trim().parse::<u32>())
@@ -130,38 +130,30 @@ impl ParseValue for IpAddrMask {
}
}
Err(format!(
"Invalid IP address {:?} for property {:?}.",
value,
key.as_key()
))
Err(format!("Invalid IP address {:?}", value,))
}
}
impl ParseValue for IpAddrOrMask {
fn parse_value(key: impl AsKey, ip: &str) -> super::Result<Self> {
fn parse_value(ip: &str) -> super::Result<Self> {
if ip.contains('/') {
ip.parse_key(key).map(IpAddrOrMask::Mask)
IpAddrMask::parse_value(ip).map(IpAddrOrMask::Mask)
} else {
ip.parse_key(key).map(IpAddrOrMask::Ip)
IpAddr::parse_value(ip).map(IpAddrOrMask::Ip)
}
}
}
impl ParseValue for SocketAddr {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid socket address {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid socket address {:?}.", value,))
}
}
impl ParseValue for SupportedCipherSuite {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
Ok(match value {
// TLS1.3 suites
"TLS13_AES_256_GCM_SHA384" => TLS13_AES_256_GCM_SHA384,
@@ -178,13 +170,7 @@ impl ParseValue for SupportedCipherSuite {
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => {
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
}
cipher => {
return Err(format!(
"Unsupported TLS cipher suite {:?} found in key {:?}",
cipher,
key.as_key()
))
}
cipher => return Err(format!("Unsupported TLS cipher suite {:?}", cipher,)),
})
}
}
@@ -200,7 +186,7 @@ mod tests {
("10.0.0.0/8", "10.0.13.73"),
("192.168.1.1", "192.168.1.1"),
] {
let mask = IpAddrMask::parse_value("test", mask).unwrap();
let mask = IpAddrMask::parse_value(mask).unwrap();
let ip = ip.parse::<IpAddr>().unwrap();
assert!(mask.matches(&ip));
}
@@ -209,7 +195,7 @@ mod tests {
("10.0.0.0/8", "11.30.20.11"),
("192.168.1.1", "193.168.1.1"),
] {
let mask = IpAddrMask::parse_value("test", mask).unwrap();
let mask = IpAddrMask::parse_value(mask).unwrap();
let ip = ip.parse::<IpAddr>().unwrap();
assert!(!mask.matches(&ip));
}

View File

@@ -29,15 +29,25 @@ pub mod utils;
use std::{collections::BTreeMap, time::Duration};
use ahash::AHashMap;
use serde::Serialize;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct Config {
#[serde(skip)]
pub keys: BTreeMap<String, String>,
pub missing: AHashMap<String, Option<String>>,
pub warnings: AHashMap<String, ConfigWarning>,
pub errors: AHashMap<String, ConfigError>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "type")]
pub enum ConfigWarning {
Missing,
AppliedDefault(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "type")]
pub enum ConfigError {
Parse(String),
Build(String),
@@ -184,12 +194,12 @@ impl Config {
}
pub fn log_warnings(&self, use_stderr: bool) {
for (key, value) in &self.missing {
let message = match value {
Some(replaced) => {
format!("WARNING: Missing setting {key:?}, applied default {replaced:?}")
for (key, warn) in &self.warnings {
let message = match warn {
ConfigWarning::AppliedDefault(default) => {
format!("WARNING: Missing setting {key:?}, applied default {default:?}")
}
None => {
ConfigWarning::Missing => {
format!("WARNING: Missing setting {key:?}")
}
};

View File

@@ -34,13 +34,13 @@ use mail_auth::{
};
use smtp_proto::MtPriority;
use super::{Config, ConfigError, Rate};
use super::{Config, ConfigError, ConfigWarning, Rate};
impl Config {
pub fn property<T: ParseValue>(&mut self, key: impl AsKey) -> Option<T> {
let key = key.as_key();
if let Some(value) = self.keys.get(&key) {
match T::parse_value(key.as_str(), value) {
match T::parse_value(value) {
Ok(value) => Some(value),
Err(err) => {
self.new_parse_error(key, err);
@@ -61,11 +61,14 @@ impl Config {
let value = match self.keys.get(&key) {
Some(value) => value.as_str(),
None => {
self.missing.insert(key.clone(), default.to_string().into());
self.warnings.insert(
key.clone(),
ConfigWarning::AppliedDefault(default.to_string()),
);
default
}
};
match T::parse_value(key.as_str(), value) {
match T::parse_value(value) {
Ok(value) => Some(value),
Err(err) => {
self.new_parse_error(key, err);
@@ -83,12 +86,13 @@ impl Config {
let value = match self.value_or_else(key.as_str(), default.clone()) {
Some(value) => value,
None => {
self.missing.insert(default.as_key(), None);
self.warnings
.insert(default.as_key(), ConfigWarning::Missing);
return None;
}
};
match T::parse_value(key.as_str(), value) {
match T::parse_value(value) {
Ok(value) => Some(value),
Err(err) => {
self.new_parse_error(key, err);
@@ -100,7 +104,7 @@ impl Config {
pub fn property_require<T: ParseValue>(&mut self, key: impl AsKey) -> Option<T> {
let key = key.as_key();
if let Some(value) = self.keys.get(&key) {
match T::parse_value(key.as_str(), value) {
match T::parse_value(value) {
Ok(value) => Some(value),
Err(err) => {
self.new_parse_error(key, err);
@@ -169,7 +173,7 @@ impl Config {
for (key, value) in &self.keys {
if key.starts_with(&prefix) || key == &full_prefix {
match T::parse_value(key.as_str(), value) {
match T::parse_value(value) {
Ok(value) => {
results.push((key.to_string(), value));
}
@@ -203,7 +207,7 @@ impl Config {
}
pub fn try_parse_value<T: ParseValue>(&mut self, key: impl AsKey, value: &str) -> Option<T> {
match T::parse_value(key.clone(), value) {
match T::parse_value(value) {
Ok(value) => Some(value),
Err(err) => {
self.errors.insert(key.as_key(), ConfigError::Parse(err));
@@ -261,7 +265,7 @@ impl Config {
match self.keys.get(&key) {
Some(value) => Some(value.as_str()),
None => {
self.missing.insert(key, None);
self.warnings.insert(key, ConfigWarning::Missing);
None
}
}
@@ -278,38 +282,16 @@ impl Config {
}
pub fn new_missing_property(&mut self, key: impl AsKey) {
self.missing.insert(key.as_key(), None);
self.warnings.insert(key.as_key(), ConfigWarning::Missing);
}
}
pub trait ParseValue: Sized {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self>;
}
pub trait ParseKey<T: ParseValue> {
fn parse_key(&self, key: impl AsKey) -> super::Result<T>;
}
impl<T: ParseValue> ParseKey<T> for &str {
fn parse_key(&self, key: impl AsKey) -> super::Result<T> {
T::parse_value(key, self)
}
}
impl<T: ParseValue> ParseKey<T> for String {
fn parse_key(&self, key: impl AsKey) -> super::Result<T> {
T::parse_value(key, self.as_str())
}
}
impl<T: ParseValue> ParseKey<T> for &String {
fn parse_key(&self, key: impl AsKey) -> super::Result<T> {
T::parse_value(key, self.as_str())
}
fn parse_value(value: &str) -> super::Result<Self>;
}
impl<T: ParseValue> ParseValue for Option<T> {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
if !value.is_empty()
&& !value.eq_ignore_ascii_case("false")
&& !value.eq_ignore_ascii_case("disable")
@@ -317,7 +299,7 @@ impl<T: ParseValue> ParseValue for Option<T> {
&& !value.eq_ignore_ascii_case("never")
&& !value.eq("0")
{
T::parse_value(key, value).map(Some)
T::parse_value(value).map(Some)
} else {
Ok(None)
}
@@ -325,137 +307,101 @@ impl<T: ParseValue> ParseValue for Option<T> {
}
impl ParseValue for String {
fn parse_value(_key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
Ok(value.to_string())
}
}
impl ParseValue for u64 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid integer value {:?}.", value,))
}
}
impl ParseValue for f64 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid floating point value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid floating point value {:?}.", value))
}
}
impl ParseValue for u16 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid integer value {:?}.", value))
}
}
impl ParseValue for i16 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid integer value {:?}.", value))
}
}
impl ParseValue for u32 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid integer value {:?}.", value))
}
}
impl ParseValue for i32 {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid integer value {:?}.", value))
}
}
impl ParseValue for IpAddr {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid IP address value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid IP address value {:?}.", value))
}
}
impl ParseValue for usize {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid integer value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid integer value {:?}.", value))
}
}
impl ParseValue for bool {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
value.parse().map_err(|_| {
format!(
"Invalid boolean value {:?} for property {:?}.",
value,
key.as_key()
)
})
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid boolean value {:?}.", value))
}
}
impl ParseValue for Ipv4Addr {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid IPv4 value {:?} for key {:?}.", value, key.as_key()))
.map_err(|_| format!("Invalid IPv4 value {:?}.", value))
}
}
impl ParseValue for Ipv6Addr {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
value
.parse()
.map_err(|_| format!("Invalid IPv6 value {:?} for key {:?}.", value, key.as_key()))
.map_err(|_| format!("Invalid IPv6 value {:?}.", value))
}
}
impl ParseValue for PathBuf {
fn parse_value(_key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
let path = PathBuf::from(value);
if path.exists() {
@@ -467,84 +413,62 @@ impl ParseValue for PathBuf {
}
impl ParseValue for MtPriority {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
match value.to_ascii_lowercase().as_str() {
"mixer" => Ok(MtPriority::Mixer),
"stanag4406" => Ok(MtPriority::Stanag4406),
"nsep" => Ok(MtPriority::Nsep),
_ => Err(format!(
"Invalid priority value {:?} for property {:?}.",
value,
key.as_key()
)),
_ => Err(format!("Invalid priority value {:?}.", value)),
}
}
}
impl ParseValue for Canonicalization {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
match value {
"relaxed" => Ok(Canonicalization::Relaxed),
"simple" => Ok(Canonicalization::Simple),
_ => Err(format!(
"Invalid canonicalization value {:?} for key {:?}.",
value,
key.as_key()
)),
_ => Err(format!("Invalid canonicalization value {:?}.", value)),
}
}
}
impl ParseValue for IpLookupStrategy {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
Ok(match value.to_lowercase().as_str() {
"ipv4_only" => IpLookupStrategy::Ipv4Only,
"ipv6_only" => IpLookupStrategy::Ipv6Only,
//"ipv4_and_ipv6" => IpLookupStrategy::Ipv4AndIpv6,
"ipv6_then_ipv4" => IpLookupStrategy::Ipv6thenIpv4,
"ipv4_then_ipv6" => IpLookupStrategy::Ipv4thenIpv6,
_ => {
return Err(format!(
"Invalid IP lookup strategy {:?} for property {:?}.",
value,
key.as_key()
))
}
_ => return Err(format!("Invalid IP lookup strategy {:?}.", value)),
})
}
}
impl ParseValue for Algorithm {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
match value {
"ed25519-sha256" | "ed25519-sha-256" => Ok(Algorithm::Ed25519Sha256),
"rsa-sha-256" | "rsa-sha256" => Ok(Algorithm::RsaSha256),
"rsa-sha-1" | "rsa-sha1" => Ok(Algorithm::RsaSha1),
_ => Err(format!(
"Invalid algorithm {:?} for key {:?}.",
value,
key.as_key()
)),
_ => Err(format!("Invalid algorithm {:?}.", value)),
}
}
}
impl ParseValue for HashAlgorithm {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
match value {
"sha256" | "sha-256" => Ok(HashAlgorithm::Sha256),
"sha-1" | "sha1" => Ok(HashAlgorithm::Sha1),
_ => Err(format!(
"Invalid hash algorithm {:?} for key {:?}.",
value,
key.as_key()
)),
_ => Err(format!("Invalid hash algorithm {:?}.", value)),
}
}
}
impl ParseValue for Duration {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
let mut digits = String::new();
let mut multiplier = String::new();
@@ -562,13 +486,7 @@ impl ParseValue for Duration {
"m" => 60 * 1000,
"s" => 1000,
"ms" | "" => 1,
_ => {
return Err(format!(
"Invalid duration value {:?} for property {:?}.",
value,
key.as_key()
))
}
_ => return Err(format!("Invalid duration value {:?}.", value)),
};
digits
@@ -581,18 +499,12 @@ impl ParseValue for Duration {
None
}
})
.ok_or_else(|| {
format!(
"Invalid duration value {:?} for property {:?}.",
value,
key.as_key()
)
})
.ok_or_else(|| format!("Invalid duration value {:?}.", value))
}
}
impl ParseValue for Rate {
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
fn parse_value(value: &str) -> super::Result<Self> {
if let Some((requests, period)) = value.split_once('/') {
Ok(Rate {
requests: requests
@@ -600,23 +512,13 @@ impl ParseValue for Rate {
.parse::<u64>()
.ok()
.and_then(|r| if r > 0 { Some(r) } else { None })
.ok_or_else(|| {
format!(
"Invalid rate value {:?} for property {:?}.",
value,
key.as_key()
)
})?,
period: std::cmp::max(period.parse_key(key)?, Duration::from_secs(1)),
.ok_or_else(|| format!("Invalid rate value {:?}.", value))?,
period: std::cmp::max(Duration::parse_value(period)?, Duration::from_secs(1)),
})
} else if ["false", "none", "unlimited"].contains(&value) {
Ok(Rate::default())
} else {
Err(format!(
"Invalid rate value {:?} for property {:?}.",
value,
key.as_key()
))
Err(format!("Invalid rate value {:?}.", value))
}
}
}