This commit is contained in:
Mateusz Gruszczyński committed 2026-10-06 15:12:59 +02:00
1 parent 464ff42cfd
commit f07429c267
53 files changed
+1049 -283

No files matched your search

+62 -9
View File
@@ -13,6 +13,18 @@ pub enum Action {
fn deny() -> Action {
Action::Deny
}
fn parse_network(value: &str) -> Result<IpNet> {
if let Ok(network) = value.parse::<IpNet>() {
return Ok(network);
}
let ip: IpAddr = value
.parse()
.context("ACL requires an IPv4/IPv6 address or CIDR")?;
let prefix = if ip.is_ipv4() { 32 } else { 128 };
format!("{ip}/{prefix}")
.parse::<IpNet>()
.context("ACL requires an IPv4/IPv6 address or CIDR")
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Rule {
@@ -38,10 +50,7 @@ impl Acl {
let mut rules = Vec::with_capacity(self.rules.len());
for rule in &self.rules {
ensure!(rule.network.len() <= 64, "ACL network is too long");
let network: IpNet = rule
.network
.parse()
.context("ACL requires an IPv4/IPv6 CIDR")?;
let network = parse_network(&rule.network)?;
if let IpNet::V6(net) = network {
ensure!(
net.addr().to_ipv4_mapped().is_none(),
@@ -57,9 +66,7 @@ impl Acl {
}
}
impl CompiledAcl {
/// Explicit deny beats all matching allow rules. Otherwise, allow or the default applies.
/// Host policy and route policy are both evaluated, so route allow cannot override host deny.
pub fn allows(&self, peer: IpAddr) -> bool {
fn explicit_action(&self, peer: IpAddr) -> Option<Action> {
let peer = match peer {
IpAddr::V6(v6) => v6.to_ipv4_mapped().map(IpAddr::V4).unwrap_or(peer),
_ => peer,
@@ -68,12 +75,58 @@ impl CompiledAcl {
for (network, action) in &self.rules {
if network.contains(&peer) {
if *action == Action::Deny {
return false;
return Some(Action::Deny);
}
allowed = true;
}
}
allowed || self.default == Action::Allow
allowed.then_some(Action::Allow)
}
/// Explicit deny beats all matching allow rules. Otherwise, allow or the default applies.
/// Host policy and route policy are both evaluated, so route allow cannot override host deny.
pub fn allows(&self, peer: IpAddr) -> bool {
match self.explicit_action(peer) {
Some(Action::Deny) => false,
Some(Action::Allow) => true,
None => self.default == Action::Allow,
}
}
}
/// Evaluate the IP and country controls that belong to one access-policy scope.
/// Explicit IP denies and blocked countries always deny. In allow-only country mode,
/// a listed country or an explicit IP allow is sufficient; this lets a default-deny
/// policy express e.g. "private networks OR PL/DE" without weakening other scopes.
pub fn allows_with_country(
acl: Option<&CompiledAcl>,
countries: &crate::rate_limit::CountryAccess,
peer: IpAddr,
country: Option<&str>,
) -> bool {
let ip_action = acl.and_then(|value| value.explicit_action(peer));
if ip_action == Some(Action::Deny) {
return false;
}
let country_listed = country.is_some_and(|code| {
countries
.countries
.iter()
.any(|item| item.eq_ignore_ascii_case(code))
});
match countries.mode {
crate::rate_limit::CountryMode::Off => {
ip_action == Some(Action::Allow)
|| acl.is_none_or(|value| value.default == Action::Allow)
}
crate::rate_limit::CountryMode::Block => {
!country_listed
&& (ip_action == Some(Action::Allow)
|| acl.is_none_or(|value| value.default == Action::Allow))
}
crate::rate_limit::CountryMode::Allow => {
ip_action == Some(Action::Allow) || country_listed
}
}
}
/// Validate the already-decoded routing representation used internally after