use std::collections::HashMap;
use std::net::IpAddr;
use std::ops::RangeInclusive;
use ipnet::IpNet;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{error::AddError, utils};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HttpAcl {
allow_http: bool,
allow_https: bool,
allowed_methods: Vec<HttpRequestMethod>,
denied_methods: Vec<HttpRequestMethod>,
allowed_hosts: Vec<String>,
denied_hosts: Vec<String>,
allowed_port_ranges: Vec<RangeInclusive<u16>>,
denied_port_ranges: Vec<RangeInclusive<u16>>,
allowed_ip_ranges: Vec<IpNet>,
denied_ip_ranges: Vec<IpNet>,
allow_private_ip_ranges: bool,
static_dns_mapping: HashMap<String, IpAddr>,
method_acl_default: bool,
host_acl_default: bool,
port_acl_default: bool,
ip_acl_default: bool,
}
impl std::default::Default for HttpAcl {
fn default() -> Self {
Self {
allow_http: true,
allow_https: true,
allowed_methods: vec![
HttpRequestMethod::CONNECT,
HttpRequestMethod::DELETE,
HttpRequestMethod::GET,
HttpRequestMethod::HEAD,
HttpRequestMethod::OPTIONS,
HttpRequestMethod::PATCH,
HttpRequestMethod::POST,
HttpRequestMethod::PUT,
HttpRequestMethod::TRACE,
],
denied_methods: Vec::new(),
allowed_hosts: Vec::new(),
denied_hosts: Vec::new(),
allowed_port_ranges: vec![80..=80, 443..=443],
denied_port_ranges: Vec::new(),
allowed_ip_ranges: Vec::new(),
denied_ip_ranges: Vec::new(),
allow_private_ip_ranges: false,
static_dns_mapping: HashMap::new(),
method_acl_default: false,
host_acl_default: false,
port_acl_default: false,
ip_acl_default: false,
}
}
}
impl HttpAcl {
pub fn builder() -> HttpAclBuilder {
HttpAclBuilder::new()
}
pub fn allow_http(&self) -> bool {
self.allow_http
}
pub fn allow_https(&self) -> bool {
self.allow_https
}
pub fn allow_private_ip_ranges(&self) -> bool {
self.allow_private_ip_ranges
}
pub fn method_acl_default(&self) -> bool {
self.method_acl_default
}
pub fn host_acl_default(&self) -> bool {
self.host_acl_default
}
pub fn port_acl_default(&self) -> bool {
self.port_acl_default
}
pub fn ip_acl_default(&self) -> bool {
self.ip_acl_default
}
pub fn allowed_methods(&self) -> &[HttpRequestMethod] {
&self.allowed_methods
}
pub fn denied_methods(&self) -> &[HttpRequestMethod] {
&self.denied_methods
}
pub fn is_scheme_allowed(&self, scheme: &str) -> AclClassification {
if scheme == "http" && self.allow_http || scheme == "https" && self.allow_https {
AclClassification::AllowedUserAcl
} else {
AclClassification::DeniedUserAcl
}
}
pub fn is_method_allowed(&self, method: impl Into<HttpRequestMethod>) -> AclClassification {
let method = method.into();
if self.allowed_methods.contains(&method) {
AclClassification::AllowedUserAcl
} else if self.denied_methods.contains(&method) {
AclClassification::DeniedUserAcl
} else if self.method_acl_default {
AclClassification::AllowedDefault
} else {
AclClassification::DeniedDefault
}
}
pub fn is_host_allowed(&self, host: &str) -> AclClassification {
if self.denied_hosts.contains(&host.to_string()) {
AclClassification::DeniedUserAcl
} else if self.allowed_hosts.contains(&host.to_string()) {
AclClassification::AllowedUserAcl
} else if self.host_acl_default {
AclClassification::AllowedDefault
} else {
AclClassification::DeniedDefault
}
}
pub fn is_port_allowed(&self, port: u16) -> AclClassification {
if Self::is_port_in_ranges(port, &self.denied_port_ranges) {
AclClassification::DeniedUserAcl
} else if Self::is_port_in_ranges(port, &self.allowed_port_ranges) {
AclClassification::AllowedUserAcl
} else if self.port_acl_default {
AclClassification::AllowedDefault
} else {
AclClassification::DeniedDefault
}
}
pub fn is_ip_allowed(&self, ip: &IpAddr) -> AclClassification {
if (!utils::ip::is_global_ip(ip) || ip.is_loopback()) && !utils::ip::is_private_ip(ip) {
if Self::is_ip_in_ranges(ip, &self.allowed_ip_ranges) {
return AclClassification::AllowedUserAcl;
} else {
return AclClassification::DeniedNotGlobal;
}
}
if Self::is_ip_in_ranges(ip, &self.allowed_ip_ranges) {
AclClassification::AllowedUserAcl
} else if Self::is_ip_in_ranges(ip, &self.denied_ip_ranges) {
AclClassification::DeniedUserAcl
} else if utils::ip::is_private_ip(ip) && !self.allow_private_ip_ranges {
AclClassification::DeniedPrivateRange
} else if self.ip_acl_default {
AclClassification::AllowedDefault
} else {
AclClassification::DeniedDefault
}
}
pub fn resolve_static_dns_mapping(&self, host: &str) -> Option<IpAddr> {
self.static_dns_mapping.get(host).copied()
}
fn is_ip_in_ranges(ip: &IpAddr, ranges: &[IpNet]) -> bool {
ranges.iter().any(|range| range.contains(ip))
}
fn is_port_in_ranges(port: u16, ranges: &[RangeInclusive<u16>]) -> bool {
ranges.iter().any(|range| range.contains(&port))
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum AclClassification {
AllowedUserAcl,
AllowedDefault,
DeniedUserAcl,
DeniedDefault,
Denied(String),
DeniedNotGlobal,
DeniedPrivateRange,
}
impl std::fmt::Display for AclClassification {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AclClassification::AllowedUserAcl => {
write!(f, "The entiy is allowed according to the allowed ACL.")
}
AclClassification::AllowedDefault => write!(
f,
"The entity is allowed because the default is to allow if no ACL match is found."
),
AclClassification::DeniedUserAcl => {
write!(f, "The entiy is denied according to the denied ACL.")
}
AclClassification::DeniedNotGlobal => {
write!(f, "The ip is denied because it is not global.")
}
AclClassification::DeniedPrivateRange => {
write!(f, "The ip is denied because it is in a private range.")
}
AclClassification::DeniedDefault => write!(
f,
"The entity is denied because the default is to deny if no ACL match is found."
),
AclClassification::Denied(reason) => {
write!(f, "The entiy is denied because {}.", reason)
}
}
}
}
impl AclClassification {
pub fn is_allowed(&self) -> bool {
matches!(
self,
AclClassification::AllowedUserAcl | AclClassification::AllowedDefault
)
}
pub fn is_denied(&self) -> bool {
matches!(
self,
AclClassification::DeniedUserAcl
| AclClassification::Denied(_)
| AclClassification::DeniedDefault
| AclClassification::DeniedNotGlobal
| AclClassification::DeniedPrivateRange
)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum HttpRequestMethod {
CONNECT,
DELETE,
GET,
HEAD,
OPTIONS,
PATCH,
POST,
PUT,
TRACE,
OTHER(String),
}
impl From<&str> for HttpRequestMethod {
fn from(method: &str) -> Self {
match method {
"CONNECT" => HttpRequestMethod::CONNECT,
"DELETE" => HttpRequestMethod::DELETE,
"GET" => HttpRequestMethod::GET,
"HEAD" => HttpRequestMethod::HEAD,
"OPTIONS" => HttpRequestMethod::OPTIONS,
"PATCH" => HttpRequestMethod::PATCH,
"POST" => HttpRequestMethod::POST,
"PUT" => HttpRequestMethod::PUT,
"TRACE" => HttpRequestMethod::TRACE,
_ => HttpRequestMethod::OTHER(method.to_string()),
}
}
}
#[derive(Default, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HttpAclBuilder {
allow_http: bool,
allow_https: bool,
allowed_methods: Vec<HttpRequestMethod>,
denied_methods: Vec<HttpRequestMethod>,
allowed_hosts: Vec<String>,
denied_hosts: Vec<String>,
allowed_port_ranges: Vec<RangeInclusive<u16>>,
denied_port_ranges: Vec<RangeInclusive<u16>>,
allowed_ip_ranges: Vec<IpNet>,
denied_ip_ranges: Vec<IpNet>,
allow_private_ip_ranges: bool,
static_dns_mapping: HashMap<String, IpAddr>,
method_acl_default: bool,
host_acl_default: bool,
port_acl_default: bool,
ip_acl_default: bool,
}
impl HttpAclBuilder {
pub fn new() -> Self {
Self {
allow_http: true,
allow_https: true,
allowed_methods: vec![
HttpRequestMethod::CONNECT,
HttpRequestMethod::DELETE,
HttpRequestMethod::GET,
HttpRequestMethod::HEAD,
HttpRequestMethod::OPTIONS,
HttpRequestMethod::PATCH,
HttpRequestMethod::POST,
HttpRequestMethod::PUT,
HttpRequestMethod::TRACE,
],
denied_methods: Vec::new(),
allowed_hosts: Vec::new(),
denied_hosts: Vec::new(),
allowed_port_ranges: vec![80..=80, 443..=443],
denied_port_ranges: Vec::new(),
allowed_ip_ranges: Vec::new(),
denied_ip_ranges: Vec::new(),
allow_private_ip_ranges: false,
static_dns_mapping: HashMap::new(),
method_acl_default: false,
host_acl_default: false,
port_acl_default: false,
ip_acl_default: false,
}
}
pub fn http(mut self, allow: bool) -> Self {
self.allow_http = allow;
self
}
pub fn https(mut self, allow: bool) -> Self {
self.allow_https = allow;
self
}
pub fn private_ip_ranges(mut self, allow: bool) -> Self {
self.allow_private_ip_ranges = allow;
self
}
pub fn method_acl_default(mut self, allow: bool) -> Self {
self.method_acl_default = allow;
self
}
pub fn host_acl_default(mut self, allow: bool) -> Self {
self.host_acl_default = allow;
self
}
pub fn port_acl_default(mut self, allow: bool) -> Self {
self.port_acl_default = allow;
self
}
pub fn ip_acl_default(mut self, allow: bool) -> Self {
self.ip_acl_default = allow;
self
}
pub fn add_allowed_method(
mut self,
method: impl Into<HttpRequestMethod>,
) -> Result<Self, AddError> {
let method = method.into();
if self.denied_methods.contains(&method) {
Err(AddError::AlreadyDenied)
} else if self.allowed_methods.contains(&method) {
Err(AddError::AlreadyAllowed)
} else {
self.allowed_methods.push(method);
Ok(self)
}
}
pub fn remove_allowed_method(mut self, method: impl Into<HttpRequestMethod>) -> Self {
let method = method.into();
self.allowed_methods.retain(|m| m != &method);
self
}
pub fn allowed_methods(
mut self,
methods: Vec<impl Into<HttpRequestMethod>>,
) -> Result<Self, AddError> {
let methods = methods.into_iter().map(|m| m.into()).collect::<Vec<_>>();
for method in &methods {
if self.denied_methods.contains(method) {
return Err(AddError::AlreadyDenied);
} else if self.allowed_methods.contains(method) {
return Err(AddError::AlreadyAllowed);
}
}
self.allowed_methods = methods;
Ok(self)
}
pub fn clear_allowed_methods(mut self) -> Self {
self.allowed_methods.clear();
self
}
pub fn add_denied_method(
mut self,
method: impl Into<HttpRequestMethod>,
) -> Result<Self, AddError> {
let method = method.into();
if self.allowed_methods.contains(&method) {
Err(AddError::AlreadyAllowed)
} else if self.denied_methods.contains(&method) {
Err(AddError::AlreadyDenied)
} else {
self.denied_methods.push(method);
Ok(self)
}
}
pub fn remove_denied_method(mut self, method: impl Into<HttpRequestMethod>) -> Self {
let method = method.into();
self.denied_methods.retain(|m| m != &method);
self
}
pub fn denied_methods(
mut self,
methods: Vec<impl Into<HttpRequestMethod>>,
) -> Result<Self, AddError> {
let methods = methods.into_iter().map(|m| m.into()).collect::<Vec<_>>();
for method in &methods {
if self.allowed_methods.contains(method) {
return Err(AddError::AlreadyAllowed);
} else if self.denied_methods.contains(method) {
return Err(AddError::AlreadyDenied);
}
}
self.denied_methods = methods;
Ok(self)
}
pub fn clear_denied_methods(mut self) -> Self {
self.denied_methods.clear();
self
}
pub fn add_allowed_host(mut self, host: String) -> Result<Self, AddError> {
if utils::authority::is_valid_host(&host) {
if self.denied_hosts.contains(&host) {
Err(AddError::AlreadyDenied)
} else if self.allowed_hosts.contains(&host) {
Err(AddError::AlreadyAllowed)
} else {
self.allowed_hosts.push(host);
Ok(self)
}
} else {
Err(AddError::Invalid)
}
}
pub fn remove_allowed_host(mut self, host: String) -> Self {
self.allowed_hosts.retain(|h| h != &host);
self
}
pub fn allowed_hosts(mut self, hosts: Vec<String>) -> Result<Self, AddError> {
for host in &hosts {
if utils::authority::is_valid_host(host) {
if self.denied_hosts.contains(host) {
return Err(AddError::AlreadyDenied);
} else if self.allowed_hosts.contains(host) {
return Err(AddError::AlreadyAllowed);
}
} else {
return Err(AddError::Invalid);
}
}
self.allowed_hosts = hosts;
Ok(self)
}
pub fn clear_allowed_hosts(mut self) -> Self {
self.allowed_hosts.clear();
self
}
pub fn add_denied_host(mut self, host: String) -> Result<Self, AddError> {
if utils::authority::is_valid_host(&host) {
if self.allowed_hosts.contains(&host) {
Err(AddError::AlreadyAllowed)
} else if self.denied_hosts.contains(&host) {
Err(AddError::AlreadyDenied)
} else {
self.denied_hosts.push(host);
Ok(self)
}
} else {
Err(AddError::Invalid)
}
}
pub fn remove_denied_host(mut self, host: String) -> Self {
self.denied_hosts.retain(|h| h != &host);
self
}
pub fn denied_hosts(mut self, hosts: Vec<String>) -> Result<Self, AddError> {
for host in &hosts {
if utils::authority::is_valid_host(host) {
if self.allowed_hosts.contains(host) {
return Err(AddError::AlreadyAllowed);
} else if self.denied_hosts.contains(host) {
return Err(AddError::AlreadyDenied);
}
} else {
return Err(AddError::Invalid);
}
}
self.denied_hosts = hosts;
Ok(self)
}
pub fn clear_denied_hosts(mut self) -> Self {
self.denied_hosts.clear();
self
}
pub fn add_allowed_port_range(
mut self,
port_range: RangeInclusive<u16>,
) -> Result<Self, AddError> {
if self.denied_port_ranges.contains(&port_range) {
Err(AddError::AlreadyDenied)
} else if self.allowed_port_ranges.contains(&port_range) {
Err(AddError::AlreadyAllowed)
} else {
self.allowed_port_ranges.push(port_range);
Ok(self)
}
}
pub fn remove_allowed_port_range(mut self, port_range: RangeInclusive<u16>) -> Self {
self.allowed_port_ranges.retain(|p| p != &port_range);
self
}
pub fn allowed_port_ranges(
mut self,
port_ranges: Vec<RangeInclusive<u16>>,
) -> Result<Self, AddError> {
for port_range in &port_ranges {
if self.denied_port_ranges.contains(port_range) {
return Err(AddError::AlreadyDenied);
} else if self.allowed_port_ranges.contains(port_range) {
return Err(AddError::AlreadyAllowed);
}
}
self.allowed_port_ranges = port_ranges;
Ok(self)
}
pub fn clear_allowed_port_ranges(mut self) -> Self {
self.allowed_port_ranges.clear();
self
}
pub fn add_denied_port_range(
mut self,
port_range: RangeInclusive<u16>,
) -> Result<Self, AddError> {
if self.allowed_port_ranges.contains(&port_range) {
Err(AddError::AlreadyAllowed)
} else if self.denied_port_ranges.contains(&port_range) {
Err(AddError::AlreadyDenied)
} else {
self.denied_port_ranges.push(port_range);
Ok(self)
}
}
pub fn remove_denied_port_range(mut self, port_range: RangeInclusive<u16>) -> Self {
self.denied_port_ranges.retain(|p| p != &port_range);
self
}
pub fn denied_port_ranges(
mut self,
port_ranges: Vec<RangeInclusive<u16>>,
) -> Result<Self, AddError> {
for port_range in &port_ranges {
if self.allowed_port_ranges.contains(port_range) {
return Err(AddError::AlreadyAllowed);
} else if self.denied_port_ranges.contains(port_range) {
return Err(AddError::AlreadyDenied);
}
}
self.denied_port_ranges = port_ranges;
Ok(self)
}
pub fn clear_denied_port_ranges(mut self) -> Self {
self.denied_port_ranges.clear();
self
}
pub fn add_allowed_ip_range(mut self, ip_range: IpNet) -> Result<Self, AddError> {
if self.denied_ip_ranges.contains(&ip_range) {
return Err(AddError::AlreadyDenied);
} else if self.allowed_ip_ranges.contains(&ip_range) {
return Err(AddError::AlreadyAllowed);
}
self.allowed_ip_ranges.push(ip_range);
Ok(self)
}
pub fn remove_allowed_ip_range(mut self, ip_range: IpNet) -> Self {
self.allowed_ip_ranges.retain(|ip| ip != &ip_range);
self
}
pub fn allowed_ip_ranges(mut self, ip_ranges: Vec<IpNet>) -> Result<Self, AddError> {
for ip_range in &ip_ranges {
if self.denied_ip_ranges.contains(ip_range) {
return Err(AddError::AlreadyDenied);
} else if self.allowed_ip_ranges.contains(ip_range) {
return Err(AddError::AlreadyAllowed);
}
}
self.allowed_ip_ranges = ip_ranges;
Ok(self)
}
pub fn clear_allowed_ip_ranges(mut self) -> Self {
self.allowed_ip_ranges.clear();
self
}
pub fn add_denied_ip_range(mut self, ip_range: IpNet) -> Result<Self, AddError> {
if self.allowed_ip_ranges.contains(&ip_range) {
return Err(AddError::AlreadyAllowed);
} else if self.denied_ip_ranges.contains(&ip_range) {
return Err(AddError::AlreadyDenied);
}
self.denied_ip_ranges.push(ip_range);
Ok(self)
}
pub fn remove_denied_ip_range(mut self, ip_range: IpNet) -> Self {
self.denied_ip_ranges.retain(|ip| ip != &ip_range);
self
}
pub fn denied_ip_ranges(mut self, ip_ranges: Vec<IpNet>) -> Result<Self, AddError> {
for ip_range in &ip_ranges {
if self.allowed_ip_ranges.contains(ip_range) {
return Err(AddError::AlreadyAllowed);
} else if self.denied_ip_ranges.contains(ip_range) {
return Err(AddError::AlreadyDenied);
}
}
self.denied_ip_ranges = ip_ranges;
Ok(self)
}
pub fn clear_denied_ip_ranges(mut self) -> Self {
self.denied_ip_ranges.clear();
self
}
pub fn add_static_dns_mapping(mut self, host: String, ip: IpAddr) -> Result<Self, AddError> {
if utils::authority::is_valid_host(&host) {
self.static_dns_mapping.insert(host, ip);
Ok(self)
} else {
Err(AddError::Invalid)
}
}
pub fn remove_static_dns_mapping(mut self, host: &str) -> Self {
self.static_dns_mapping.remove(host);
self
}
pub fn static_dns_mappings(
mut self,
mappings: HashMap<String, IpAddr>,
) -> Result<Self, AddError> {
for (host, ip) in &mappings {
if utils::authority::is_valid_host(host) {
self.static_dns_mapping.insert(host.to_string(), *ip);
} else {
return Err(AddError::Invalid);
}
}
Ok(self)
}
pub fn build(self) -> HttpAcl {
HttpAcl {
allow_http: self.allow_http,
allow_https: self.allow_https,
allowed_methods: self.allowed_methods,
denied_methods: self.denied_methods,
allowed_hosts: self.allowed_hosts,
denied_hosts: self.denied_hosts,
allowed_port_ranges: self.allowed_port_ranges,
denied_port_ranges: self.denied_port_ranges,
allowed_ip_ranges: self.allowed_ip_ranges,
denied_ip_ranges: self.denied_ip_ranges,
allow_private_ip_ranges: self.allow_private_ip_ranges,
static_dns_mapping: self.static_dns_mapping,
method_acl_default: self.method_acl_default,
host_acl_default: self.host_acl_default,
port_acl_default: self.port_acl_default,
ip_acl_default: self.ip_acl_default,
}
}
pub fn try_build(self) -> Result<HttpAcl, AddError> {
for method in &self.allowed_methods {
if self.denied_methods.contains(method) {
return Err(AddError::AlreadyDenied);
}
}
for method in &self.denied_methods {
if self.allowed_methods.contains(method) {
return Err(AddError::AlreadyAllowed);
}
}
for host in &self.allowed_hosts {
if self.denied_hosts.contains(host) {
return Err(AddError::AlreadyDenied);
}
}
for host in &self.denied_hosts {
if self.allowed_hosts.contains(host) {
return Err(AddError::AlreadyAllowed);
}
}
for port_range in &self.allowed_port_ranges {
if self.denied_port_ranges.contains(port_range) {
return Err(AddError::AlreadyDenied);
}
}
for port_range in &self.denied_port_ranges {
if self.allowed_port_ranges.contains(port_range) {
return Err(AddError::AlreadyAllowed);
}
}
for ip_range in &self.allowed_ip_ranges {
if self.denied_ip_ranges.contains(ip_range) {
return Err(AddError::AlreadyDenied);
}
}
for ip_range in &self.denied_ip_ranges {
if self.allowed_ip_ranges.contains(ip_range) {
return Err(AddError::AlreadyAllowed);
}
}
Ok(self.build())
}
}