parent
5c784482f6
commit
bfaff6067a
@ -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