Turn match in restriction config into a list

This commit is contained in:
Σrebe - Romain GERARD 2024-04-30 22:21:47 +02:00
parent 1e07eb7b2a
commit 368f6657fd
No known key found for this signature in database
GPG key ID: 7A42B4B97E0332F4
5 changed files with 105 additions and 39 deletions

View file

@ -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: []

View file

@ -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,

View file

@ -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(),
}) })
}) })

View file

@ -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 {

View file

@ -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 {