commit
29cf9660d0
@ -0,0 +1,181 @@
|
|||||||
|
//! Filter accepts and denies based on cidr masks.
|
||||||
|
use super::*;
|
||||||
|
use cidr::{
|
||||||
|
Cidr,
|
||||||
|
IpCidr,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
net::{
|
||||||
|
IpAddr,
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IpFilterDeniedError(IpAddr, Option<IpCidr>);
|
||||||
|
|
||||||
|
impl warp::reject::Reject for IpFilterDeniedError{}
|
||||||
|
impl error::Error for IpFilterDeniedError{}
|
||||||
|
impl fmt::Display for IpFilterDeniedError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "Denied {} due to ", self.0)?;
|
||||||
|
match &self.1 {
|
||||||
|
Some(cidr) => write!(f, "matching rule {}", cidr),
|
||||||
|
None => write!(f, "non-matching accept rule"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub enum Rule
|
||||||
|
{
|
||||||
|
Accept,
|
||||||
|
Deny,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Rule
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self::Deny
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rule
|
||||||
|
{
|
||||||
|
fn into_result<'a>(self, net: Option<&'a IpCidr>) -> Result<Option<&'a IpCidr>, Option<IpCidr>>
|
||||||
|
{
|
||||||
|
if let Self::Accept = self {
|
||||||
|
Ok(net)
|
||||||
|
} else {
|
||||||
|
Err(net.cloned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct IpFilter
|
||||||
|
{
|
||||||
|
/// The default fallback rule
|
||||||
|
pub default: Rule,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
accept: Vec<IpCidr>,
|
||||||
|
#[serde(default)]
|
||||||
|
deny: Vec<IpCidr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline] fn find_in<'a>(needle: &IpAddr, haystack: &'a [IpCidr]) -> Option<&'a IpCidr>
|
||||||
|
{
|
||||||
|
for x in haystack.iter()
|
||||||
|
{
|
||||||
|
if x.contains(needle) {
|
||||||
|
return Some(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IpFilter
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
default: Rule::Deny,
|
||||||
|
accept: vec![cidr::Cidr::new_host([127,0,0,1].into())],
|
||||||
|
deny: Vec::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpFilter
|
||||||
|
{
|
||||||
|
/// Create a new CIDR filter with thie default rule.
|
||||||
|
///
|
||||||
|
/// Use `default()` to use with default rule.
|
||||||
|
pub fn new(fallback: Rule) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
default: fallback,
|
||||||
|
accept: Vec::new(),
|
||||||
|
deny: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the rule for this IP, returns a result if it should accept or not.
|
||||||
|
///
|
||||||
|
/// If acceptance rule is met, return the CIDR match that caused the acceptance if applicable
|
||||||
|
///
|
||||||
|
/// If acceptance rule is not met, return in the error which CIDR match cause the deny if applicable
|
||||||
|
pub fn check(&self, ip: &IpAddr) -> Result<Option<&'_ IpCidr>, IpFilterDeniedError>
|
||||||
|
{
|
||||||
|
let accept = find_in(ip, &self.accept[..]);
|
||||||
|
let deny = find_in(ip, &self.deny[..]);
|
||||||
|
|
||||||
|
let (rule, cidr) = match (accept, deny) {
|
||||||
|
(None, Some(net)) => (Rule::Deny, Some(net)),
|
||||||
|
(Some(net), None) => (Rule::Accept, Some(net)),
|
||||||
|
(Some(ac), Some(den)) if ac != den => {
|
||||||
|
if ac.network_length() > den.network_length() {
|
||||||
|
(Rule::Accept, Some(ac))
|
||||||
|
} else {
|
||||||
|
(Rule::Deny, Some(den))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (self.default, None)
|
||||||
|
};
|
||||||
|
rule.into_result(cidr)
|
||||||
|
.map_err(|cidr| IpFilterDeniedError(*ip, cidr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept_mask(&self) -> &[IpCidr]
|
||||||
|
{
|
||||||
|
&self.accept[..]
|
||||||
|
}
|
||||||
|
pub fn deny_mask(&self) -> &[IpCidr]
|
||||||
|
{
|
||||||
|
&self.deny[..]
|
||||||
|
}
|
||||||
|
pub fn accept_range(&mut self, items: impl IntoIterator<Item = IpCidr>)
|
||||||
|
{
|
||||||
|
self.accept.extend(items)
|
||||||
|
}
|
||||||
|
pub fn deny_range(&mut self, items: impl IntoIterator<Item = IpCidr>)
|
||||||
|
{
|
||||||
|
self.deny.extend(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept_one(&mut self, item: IpCidr)
|
||||||
|
{
|
||||||
|
self.accept.push(item)
|
||||||
|
}
|
||||||
|
pub fn deny_one(&mut self, items: IpCidr)
|
||||||
|
{
|
||||||
|
self.deny.push(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can any connection ever be accepted?
|
||||||
|
pub fn possible(&self) -> bool
|
||||||
|
{
|
||||||
|
//TODO: Test this
|
||||||
|
!(self.default == Rule::Deny && self.accept.len() == 0) &&
|
||||||
|
!(self.deny.iter().find(|x| x.network_length() == 0).is_some() && self.accept.len() == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rejection>
|
||||||
|
{
|
||||||
|
if let Some(t) = err.find::<IpFilterDeniedError>() {
|
||||||
|
error!("Denying access to {} because of {:?} (403)", t.0, t.1);
|
||||||
|
Ok(warp::http::Response::builder()
|
||||||
|
.status(status!(403))
|
||||||
|
.body(format!("Access denied: {}", t)))
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue