Turn match in restriction config into a list
This commit is contained in:
parent
1e07eb7b2a
commit
368f6657fd
5 changed files with 105 additions and 39 deletions
|
@ -3,21 +3,28 @@
|
||||||
restrictions:
|
restrictions:
|
||||||
- name: "Allow all"
|
- name: "Allow all"
|
||||||
description: "This restriction allows all requests"
|
description: "This restriction allows all requests"
|
||||||
# This restriction apply only if it matches the prefix that match the given regex
|
# This restriction apply only and only if all matchers match/are evaluated to true
|
||||||
# The regex does a match, so if you want to match exactly you need to bound the pattern with ^ $
|
# It is a logical AND
|
||||||
# I.e: "tesotron" is going to match "XXXtesotronXXX", but "^tesotron$" is going to match only "tesotron"
|
match:
|
||||||
match: !PathPrefix "^.*$"
|
# This match apply only if it succeeds to match the path prefix with the given regex
|
||||||
|
# The regex does a match, so if you want to match exactly you need to bound the pattern with ^ $
|
||||||
|
# I.e: "tesotron" is going to match "XXXtesotronXXX", but "^tesotron$" is going to match only "tesotron"
|
||||||
|
- !PathPrefix "^.*$"
|
||||||
|
# The only other possible match type for now is !Any, that match everything/any request
|
||||||
|
# - !Any
|
||||||
|
|
||||||
# This is th list of tunnels your restriction is going to allow
|
# This is the list of tunnels your restriction is going to allow
|
||||||
# The list is going to be checked in order, the first match is going to allow the request
|
# The list is checked in order, the first match is going to allow the request
|
||||||
allow:
|
allow:
|
||||||
# !Tunnel allows forward tunnels
|
# !Tunnel allows forward tunnels
|
||||||
- !Tunnel
|
- !Tunnel
|
||||||
# Protocol that are allowed. Empty list means all protocols are allowed
|
# Protocol that are allowed. Empty list means all protocols are allowed
|
||||||
|
# Logical OR
|
||||||
protocol:
|
protocol:
|
||||||
- Tcp
|
- Tcp
|
||||||
- Udp
|
- Udp
|
||||||
# Port that are allowed. Can be a single port or an inclusive range (i.e. 80..90)
|
# Port that are allowed. Can be a single port or an inclusive range (i.e. 80..90)
|
||||||
|
# Logical OR
|
||||||
port:
|
port:
|
||||||
- 80
|
- 80
|
||||||
- 443
|
- 443
|
||||||
|
@ -25,7 +32,8 @@ restrictions:
|
||||||
|
|
||||||
# if the tunnel wants to connect to a specific host, this regex must match
|
# if the tunnel wants to connect to a specific host, this regex must match
|
||||||
host: ^.*$
|
host: ^.*$
|
||||||
# if the tunnel wants to connect to a specific IP, it must match one of the network cidr
|
# if the tunnel wants to connect to a specific IP, it must be included in one of the network cidr
|
||||||
|
# Logical OR
|
||||||
cidr:
|
cidr:
|
||||||
- 0.0.0.0/0
|
- 0.0.0.0/0
|
||||||
- ::/0
|
- ::/0
|
||||||
|
@ -49,7 +57,8 @@ restrictions:
|
||||||
restrictions:
|
restrictions:
|
||||||
- name: "example 1"
|
- name: "example 1"
|
||||||
description: "Only allow forward tunnels to port 443 and forbid reverse tunnels"
|
description: "Only allow forward tunnels to port 443 and forbid reverse tunnels"
|
||||||
match: !PathPrefix "^.*$"
|
match:
|
||||||
|
- !PathPrefix "^.*$"
|
||||||
allow:
|
allow:
|
||||||
- !Tunnel
|
- !Tunnel
|
||||||
port:
|
port:
|
||||||
|
@ -58,7 +67,8 @@ restrictions:
|
||||||
restrictions:
|
restrictions:
|
||||||
- name: "example 2"
|
- name: "example 2"
|
||||||
description: "Only allow forward tunnels to local ssh and forbid reverse tunnels"
|
description: "Only allow forward tunnels to local ssh and forbid reverse tunnels"
|
||||||
match: !PathPrefix "^.*$"
|
match:
|
||||||
|
- !PathPrefix "^.*$"
|
||||||
allow:
|
allow:
|
||||||
- !Tunnel
|
- !Tunnel
|
||||||
protocol:
|
protocol:
|
||||||
|
@ -72,7 +82,8 @@ restrictions:
|
||||||
restrictions:
|
restrictions:
|
||||||
- name: "example 3"
|
- name: "example 3"
|
||||||
description: "Only allow socks5 reverse tunnels listening on port between 1080..1443 on lan network"
|
description: "Only allow socks5 reverse tunnels listening on port between 1080..1443 on lan network"
|
||||||
match: !PathPrefix "^.*$"
|
match:
|
||||||
|
- !PathPrefix "^.*$"
|
||||||
allow:
|
allow:
|
||||||
- !ReverseTunnel
|
- !ReverseTunnel
|
||||||
protocol:
|
protocol:
|
||||||
|
@ -85,7 +96,15 @@ restrictions:
|
||||||
restrictions:
|
restrictions:
|
||||||
- name: "example 4"
|
- name: "example 4"
|
||||||
description: "Allow everything for client using path prefix my-super-secret-path"
|
description: "Allow everything for client using path prefix my-super-secret-path"
|
||||||
match: !PathPrefix "^my-super-secret-path$"
|
match:
|
||||||
|
- !PathPrefix "^my-super-secret-path$"
|
||||||
allow:
|
allow:
|
||||||
- !Tunnel
|
- !Tunnel
|
||||||
- !ReverseTunnel
|
- !ReverseTunnel
|
||||||
|
---
|
||||||
|
restrictions:
|
||||||
|
- name: "example 5"
|
||||||
|
description: "Forbid everything ..."
|
||||||
|
match:
|
||||||
|
- !Any
|
||||||
|
allow: []
|
||||||
|
|
|
@ -1278,9 +1278,10 @@ async fn main() {
|
||||||
args.restrict_http_upgrade_path_prefix.as_deref().unwrap_or(&[]),
|
args.restrict_http_upgrade_path_prefix.as_deref().unwrap_or(&[]),
|
||||||
&restrict_to,
|
&restrict_to,
|
||||||
)
|
)
|
||||||
.expect("Cannot covertion restriction rules from path-prefix and restric-to");
|
.expect("Cannot convert restriction rules from path-prefix and restric-to");
|
||||||
restriction_cfg
|
restriction_cfg
|
||||||
};
|
};
|
||||||
|
debug!("Restriction rules: {:?}", restrictions);
|
||||||
|
|
||||||
let server_config = WsServerConfig {
|
let server_config = WsServerConfig {
|
||||||
socket_so_mark: args.socket_so_mark,
|
socket_so_mark: args.socket_so_mark,
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
use crate::restrictions::types::{default_cidr, default_host};
|
use ipnet::IpNet;
|
||||||
use regex::Regex;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use types::RestrictionsRules;
|
use types::RestrictionsRules;
|
||||||
|
|
||||||
|
use crate::restrictions::types::{default_cidr, default_host};
|
||||||
|
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
impl RestrictionsRules {
|
impl RestrictionsRules {
|
||||||
|
@ -18,41 +25,68 @@ impl RestrictionsRules {
|
||||||
path_prefixes: &[String],
|
path_prefixes: &[String],
|
||||||
restrict_to: &[(String, u16)],
|
restrict_to: &[(String, u16)],
|
||||||
) -> anyhow::Result<RestrictionsRules> {
|
) -> anyhow::Result<RestrictionsRules> {
|
||||||
let mut tunnels_restrictions = if restrict_to.is_empty() {
|
let tunnels_restrictions = if restrict_to.is_empty() {
|
||||||
let r = types::AllowConfig::Tunnel(types::AllowTunnelConfig {
|
let r = types::AllowConfig::Tunnel(types::AllowTunnelConfig {
|
||||||
protocol: vec![],
|
protocol: vec![],
|
||||||
port: vec![],
|
port: vec![],
|
||||||
host: default_host(),
|
host: default_host(),
|
||||||
cidr: default_cidr(),
|
cidr: default_cidr(),
|
||||||
});
|
});
|
||||||
vec![r]
|
let reverse_tunnel = types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
|
||||||
|
protocol: vec![],
|
||||||
|
port: vec![],
|
||||||
|
cidr: default_cidr(),
|
||||||
|
});
|
||||||
|
|
||||||
|
vec![r, reverse_tunnel]
|
||||||
} else {
|
} else {
|
||||||
restrict_to
|
restrict_to
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(host, port)| {
|
.map(|(host, port)| {
|
||||||
let reg = Regex::new(&format!("^{}$", regex::escape(host)))?;
|
let reg = Regex::new(&format!("^{}$", regex::escape(host)))?;
|
||||||
Ok(types::AllowConfig::Tunnel(types::AllowTunnelConfig {
|
let tunnels = if let Ok(ip) = IpAddr::from_str(host) {
|
||||||
protocol: vec![],
|
vec![
|
||||||
port: vec![RangeInclusive::new(*port, *port)],
|
types::AllowConfig::Tunnel(types::AllowTunnelConfig {
|
||||||
host: reg,
|
protocol: vec![],
|
||||||
cidr: default_cidr(),
|
port: vec![RangeInclusive::new(*port, *port)],
|
||||||
}))
|
host: reg,
|
||||||
|
cidr: default_cidr(),
|
||||||
|
}),
|
||||||
|
types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
|
||||||
|
protocol: vec![],
|
||||||
|
port: vec![RangeInclusive::new(*port, *port)],
|
||||||
|
cidr: vec![IpNet::new(ip, if ip.is_ipv4() { 32 } else { 128 })?],
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
types::AllowConfig::Tunnel(types::AllowTunnelConfig {
|
||||||
|
protocol: vec![],
|
||||||
|
port: vec![RangeInclusive::new(*port, *port)],
|
||||||
|
host: reg,
|
||||||
|
cidr: default_cidr(),
|
||||||
|
}),
|
||||||
|
types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
|
||||||
|
protocol: vec![],
|
||||||
|
port: vec![],
|
||||||
|
cidr: default_cidr(),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(tunnels)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, anyhow::Error>>()?
|
.collect::<Result<Vec<_>, anyhow::Error>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
tunnels_restrictions.push(types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
|
|
||||||
protocol: vec![],
|
|
||||||
port: vec![],
|
|
||||||
cidr: default_cidr(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
let restrictions = if path_prefixes.is_empty() {
|
let restrictions = if path_prefixes.is_empty() {
|
||||||
// if no path prefixes are provided, we allow all
|
// if no path prefixes are provided, we allow all
|
||||||
let reg = Regex::new(".").unwrap();
|
|
||||||
let r = types::RestrictionConfig {
|
let r = types::RestrictionConfig {
|
||||||
name: "Allow All".to_string(),
|
name: "Allow All".to_string(),
|
||||||
r#match: types::MatchConfig::PathPrefix(reg),
|
r#match: vec![types::MatchConfig::Any],
|
||||||
allow: tunnels_restrictions,
|
allow: tunnels_restrictions,
|
||||||
};
|
};
|
||||||
vec![r]
|
vec![r]
|
||||||
|
@ -63,7 +97,7 @@ impl RestrictionsRules {
|
||||||
let reg = Regex::new(&format!("^{}$", regex::escape(path_prefix)))?;
|
let reg = Regex::new(&format!("^{}$", regex::escape(path_prefix)))?;
|
||||||
Ok(types::RestrictionConfig {
|
Ok(types::RestrictionConfig {
|
||||||
name: format!("Allow path prefix {}", path_prefix),
|
name: format!("Allow path prefix {}", path_prefix),
|
||||||
r#match: types::MatchConfig::PathPrefix(reg),
|
r#match: vec![types::MatchConfig::PathPrefix(reg)],
|
||||||
allow: tunnels_restrictions.clone(),
|
allow: tunnels_restrictions.clone(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,8 @@ pub struct RestrictionsRules {
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct RestrictionConfig {
|
pub struct RestrictionConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub r#match: MatchConfig,
|
#[serde(deserialize_with = "deserialize_non_empty_vec")]
|
||||||
|
pub r#match: Vec<MatchConfig>,
|
||||||
pub allow: Vec<AllowConfig>,
|
pub allow: Vec<AllowConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +110,19 @@ where
|
||||||
Ok(ranges)
|
Ok(ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_non_empty_vec<'de, D, T>(d: D) -> Result<Vec<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
T: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
let vec = <Vec<T>>::deserialize(d)?;
|
||||||
|
if vec.is_empty() {
|
||||||
|
Err(serde::de::Error::custom("List must not be empty"))
|
||||||
|
} else {
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&LocalProtocol> for ReverseTunnelConfigProtocol {
|
impl From<&LocalProtocol> for ReverseTunnelConfigProtocol {
|
||||||
fn from(value: &LocalProtocol) -> Self {
|
fn from(value: &LocalProtocol) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
|
|
@ -319,13 +319,11 @@ fn validate_tunnel<'a>(
|
||||||
restrictions: &'a RestrictionsRules,
|
restrictions: &'a RestrictionsRules,
|
||||||
) -> Result<&'a RestrictionConfig, Response<String>> {
|
) -> Result<&'a RestrictionConfig, Response<String>> {
|
||||||
for restriction in &restrictions.restrictions {
|
for restriction in &restrictions.restrictions {
|
||||||
match &restriction.r#match {
|
if !restriction.r#match.iter().all(|m| match m {
|
||||||
MatchConfig::Any => {}
|
MatchConfig::Any => true,
|
||||||
MatchConfig::PathPrefix(path) => {
|
MatchConfig::PathPrefix(path) => path.is_match(path_prefix),
|
||||||
if !path.is_match(path_prefix) {
|
}) {
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for allow in &restriction.allow {
|
for allow in &restriction.allow {
|
||||||
|
|
Loading…
Reference in a new issue