Allow multiple ports in restriction file
This commit is contained in:
parent
135fcb5127
commit
3c84c59a11
5 changed files with 72 additions and 58 deletions
|
@ -18,7 +18,10 @@ restrictions:
|
||||||
- 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)
|
||||||
port: 9999
|
port:
|
||||||
|
- 80
|
||||||
|
- 443
|
||||||
|
- 8080..8089
|
||||||
|
|
||||||
# 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: ^.*$
|
||||||
|
@ -35,7 +38,8 @@ restrictions:
|
||||||
- Udp
|
- Udp
|
||||||
- Socks5
|
- Socks5
|
||||||
- Unix
|
- Unix
|
||||||
port: 1..65535
|
port:
|
||||||
|
- 1..65535
|
||||||
cidr:
|
cidr:
|
||||||
- 0.0.0.0/0
|
- 0.0.0.0/0
|
||||||
- ::/0
|
- ::/0
|
||||||
|
@ -48,7 +52,8 @@ restrictions:
|
||||||
match: !PathPrefix "^.*$"
|
match: !PathPrefix "^.*$"
|
||||||
allow:
|
allow:
|
||||||
- !Tunnel
|
- !Tunnel
|
||||||
port: 443
|
port:
|
||||||
|
- 443
|
||||||
---
|
---
|
||||||
restrictions:
|
restrictions:
|
||||||
- name: "example 2"
|
- name: "example 2"
|
||||||
|
@ -58,7 +63,8 @@ restrictions:
|
||||||
- !Tunnel
|
- !Tunnel
|
||||||
protocol:
|
protocol:
|
||||||
- Tcp
|
- Tcp
|
||||||
port: 22
|
port:
|
||||||
|
- 22
|
||||||
host: ^localhost$
|
host: ^localhost$
|
||||||
cidr:
|
cidr:
|
||||||
- 127.0.0.1/32
|
- 127.0.0.1/32
|
||||||
|
@ -71,7 +77,8 @@ restrictions:
|
||||||
- !ReverseTunnel
|
- !ReverseTunnel
|
||||||
protocol:
|
protocol:
|
||||||
- Socks5
|
- Socks5
|
||||||
port: 1080..1443
|
port:
|
||||||
|
- 1080..1443
|
||||||
cidr:
|
cidr:
|
||||||
- 192.168.0.0/16
|
- 192.168.0.0/16
|
||||||
---
|
---
|
||||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -259,12 +259,6 @@ struct Server {
|
||||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||||
websocket_mask_frame: bool,
|
websocket_mask_frame: bool,
|
||||||
|
|
||||||
/// Server will only accept connection from the specified tunnel information.
|
|
||||||
/// Can be specified multiple time
|
|
||||||
/// Example: --restrict-to "google.com:443" --restrict-to "localhost:22"
|
|
||||||
#[arg(long, value_name = "DEST:PORT", verbatim_doc_comment)]
|
|
||||||
restrict_to: Option<Vec<String>>,
|
|
||||||
|
|
||||||
/// Dns resolver to use to lookup ips of domain name
|
/// Dns resolver to use to lookup ips of domain name
|
||||||
/// This option is not going to work if you use transparent proxy
|
/// This option is not going to work if you use transparent proxy
|
||||||
/// Can be specified multiple time
|
/// Can be specified multiple time
|
||||||
|
@ -277,6 +271,12 @@ struct Server {
|
||||||
#[arg(long, verbatim_doc_comment)]
|
#[arg(long, verbatim_doc_comment)]
|
||||||
dns_resolver: Option<Vec<Url>>,
|
dns_resolver: Option<Vec<Url>>,
|
||||||
|
|
||||||
|
/// Server will only accept connection from the specified tunnel information.
|
||||||
|
/// Can be specified multiple time
|
||||||
|
/// Example: --restrict-to "google.com:443" --restrict-to "localhost:22"
|
||||||
|
#[arg(long, value_name = "DEST:PORT", verbatim_doc_comment)]
|
||||||
|
restrict_to: Option<Vec<String>>,
|
||||||
|
|
||||||
/// Server will only accept connection from if this specific path prefix is used during websocket upgrade.
|
/// Server will only accept connection from if this specific path prefix is used during websocket upgrade.
|
||||||
/// Useful if you specify in the client a custom path prefix, and you want the server to only allow this one.
|
/// Useful if you specify in the client a custom path prefix, and you want the server to only allow this one.
|
||||||
/// The path prefix act as a secret to authenticate clients
|
/// The path prefix act as a secret to authenticate clients
|
||||||
|
@ -292,7 +292,7 @@ struct Server {
|
||||||
/// Path to the location of the restriction yaml config file.
|
/// Path to the location of the restriction yaml config file.
|
||||||
/// Restriction file is automatically reloaded if it changes
|
/// Restriction file is automatically reloaded if it changes
|
||||||
#[arg(long, verbatim_doc_comment)]
|
#[arg(long, verbatim_doc_comment)]
|
||||||
restriction_file: Option<PathBuf>,
|
restrict_config: Option<PathBuf>,
|
||||||
|
|
||||||
/// [Optional] Use custom certificate (pem) instead of the default embedded self-signed certificate.
|
/// [Optional] Use custom certificate (pem) instead of the default embedded self-signed certificate.
|
||||||
/// The certificate will be automatically reloaded if it changes
|
/// The certificate will be automatically reloaded if it changes
|
||||||
|
@ -1260,7 +1260,7 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let restrictions = if let Some(path) = &args.restriction_file {
|
let restrictions = if let Some(path) = &args.restrict_config {
|
||||||
RestrictionsRules::from_config_file(path).expect("Cannot parse restriction file")
|
RestrictionsRules::from_config_file(path).expect("Cannot parse restriction file")
|
||||||
} else {
|
} else {
|
||||||
let restrict_to: Vec<(String, u16)> = args
|
let restrict_to: Vec<(String, u16)> = args
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::restrictions::types::{default_cidr, default_host, default_port};
|
use crate::restrictions::types::{default_cidr, default_host};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
@ -21,7 +21,7 @@ impl RestrictionsRules {
|
||||||
let mut tunnels_restrictions = if restrict_to.is_empty() {
|
let mut 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: default_port(),
|
port: vec![],
|
||||||
host: default_host(),
|
host: default_host(),
|
||||||
cidr: default_cidr(),
|
cidr: default_cidr(),
|
||||||
});
|
});
|
||||||
|
@ -30,21 +30,20 @@ impl RestrictionsRules {
|
||||||
restrict_to
|
restrict_to
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(host, port)| {
|
.map(|(host, port)| {
|
||||||
// Fixme: Remove the unwrap
|
let reg = Regex::new(&format!("^{}$", regex::escape(host)))?;
|
||||||
let reg = Regex::new(&format!("^{}$", regex::escape(host))).unwrap();
|
Ok(types::AllowConfig::Tunnel(types::AllowTunnelConfig {
|
||||||
types::AllowConfig::Tunnel(types::AllowTunnelConfig {
|
|
||||||
protocol: vec![],
|
protocol: vec![],
|
||||||
port: RangeInclusive::new(*port, *port),
|
port: vec![RangeInclusive::new(*port, *port)],
|
||||||
host: reg,
|
host: reg,
|
||||||
cidr: default_cidr(),
|
cidr: default_cidr(),
|
||||||
})
|
}))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect::<Result<Vec<_>, anyhow::Error>>()?
|
||||||
};
|
};
|
||||||
|
|
||||||
tunnels_restrictions.push(types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
|
tunnels_restrictions.push(types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
|
||||||
protocol: vec![],
|
protocol: vec![],
|
||||||
port: default_port(),
|
port: vec![],
|
||||||
cidr: default_cidr(),
|
cidr: default_cidr(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -61,19 +60,16 @@ impl RestrictionsRules {
|
||||||
path_prefixes
|
path_prefixes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|path_prefix| {
|
.map(|path_prefix| {
|
||||||
// Fixme: Remove the unwrap
|
let reg = Regex::new(&format!("^{}$", regex::escape(path_prefix)))?;
|
||||||
let reg = Regex::new(&format!("^{}$", regex::escape(path_prefix))).unwrap();
|
Ok(types::RestrictionConfig {
|
||||||
types::RestrictionConfig {
|
|
||||||
name: format!("Allow path prefix {}", path_prefix),
|
name: format!("Allow path prefix {}", path_prefix),
|
||||||
r#match: types::MatchConfig::PathPrefix(reg),
|
r#match: types::MatchConfig::PathPrefix(reg),
|
||||||
allow: tunnels_restrictions.clone(),
|
allow: tunnels_restrictions.clone(),
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect::<Result<Vec<_>, anyhow::Error>>()?
|
||||||
};
|
};
|
||||||
|
|
||||||
let restrictions = RestrictionsRules { restrictions };
|
Ok(RestrictionsRules { restrictions })
|
||||||
|
|
||||||
Ok(restrictions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,8 @@ pub struct AllowTunnelConfig {
|
||||||
pub protocol: Vec<TunnelConfigProtocol>,
|
pub protocol: Vec<TunnelConfigProtocol>,
|
||||||
|
|
||||||
#[serde(deserialize_with = "deserialize_port_range")]
|
#[serde(deserialize_with = "deserialize_port_range")]
|
||||||
#[serde(default = "default_port")]
|
#[serde(default)]
|
||||||
pub port: RangeInclusive<u16>,
|
pub port: Vec<RangeInclusive<u16>>,
|
||||||
|
|
||||||
#[serde(with = "serde_regex")]
|
#[serde(with = "serde_regex")]
|
||||||
#[serde(default = "default_host")]
|
#[serde(default = "default_host")]
|
||||||
|
@ -52,8 +52,8 @@ pub struct AllowReverseTunnelConfig {
|
||||||
pub protocol: Vec<ReverseTunnelConfigProtocol>,
|
pub protocol: Vec<ReverseTunnelConfigProtocol>,
|
||||||
|
|
||||||
#[serde(deserialize_with = "deserialize_port_range")]
|
#[serde(deserialize_with = "deserialize_port_range")]
|
||||||
#[serde(default = "default_port")]
|
#[serde(default)]
|
||||||
pub port: RangeInclusive<u16>,
|
pub port: Vec<RangeInclusive<u16>>,
|
||||||
|
|
||||||
#[serde(default = "default_cidr")]
|
#[serde(default = "default_cidr")]
|
||||||
pub cidr: Vec<IpNet>,
|
pub cidr: Vec<IpNet>,
|
||||||
|
@ -75,10 +75,6 @@ pub enum ReverseTunnelConfigProtocol {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_port() -> RangeInclusive<u16> {
|
|
||||||
RangeInclusive::new(1, 65535)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_host() -> Regex {
|
pub fn default_host() -> Regex {
|
||||||
Regex::new("^.*$").unwrap()
|
Regex::new("^.*$").unwrap()
|
||||||
}
|
}
|
||||||
|
@ -87,22 +83,30 @@ pub fn default_cidr() -> Vec<IpNet> {
|
||||||
vec![IpNet::V4(Ipv4Net::default()), IpNet::V6(Ipv6Net::default())]
|
vec![IpNet::V4(Ipv4Net::default()), IpNet::V6(Ipv6Net::default())]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_port_range<'de, D>(deserializer: D) -> Result<RangeInclusive<u16>, D::Error>
|
fn deserialize_port_range<'de, D>(deserializer: D) -> Result<Vec<RangeInclusive<u16>>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let s = String::deserialize(deserializer)?;
|
let s = Vec::<String>::deserialize(deserializer)?;
|
||||||
let range = if let Some((l, r)) = s.split_once("..") {
|
let ranges = s
|
||||||
RangeInclusive::new(
|
.into_iter()
|
||||||
l.parse().map_err(serde::de::Error::custom)?,
|
.map(|s| {
|
||||||
r.parse().map_err(serde::de::Error::custom)?,
|
let range: Result<RangeInclusive<u16>, D::Error> = if let Some((l, r)) = s.split_once("..") {
|
||||||
)
|
Ok(RangeInclusive::new(
|
||||||
} else {
|
l.parse().map_err(<D::Error as serde::de::Error>::custom)?,
|
||||||
let port = s.parse::<u16>().map_err(serde::de::Error::custom)?;
|
r.parse().map_err(<D::Error as serde::de::Error>::custom)?,
|
||||||
RangeInclusive::new(port, port)
|
))
|
||||||
};
|
} else {
|
||||||
|
let port = s.parse::<u16>().map_err(serde::de::Error::custom)?;
|
||||||
|
Ok(RangeInclusive::new(port, port))
|
||||||
|
};
|
||||||
|
range
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Result<Vec<RangeInclusive<u16>>, D::Error>>()?;
|
||||||
|
|
||||||
Ok(range)
|
Ok(ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&LocalProtocol> for ReverseTunnelConfigProtocol {
|
impl From<&LocalProtocol> for ReverseTunnelConfigProtocol {
|
||||||
|
|
|
@ -211,7 +211,7 @@ where
|
||||||
}
|
}
|
||||||
Some(Ok(cnx)) => {
|
Some(Ok(cnx)) => {
|
||||||
if tx.send_timeout(cnx, send_timeout).await.is_err() {
|
if tx.send_timeout(cnx, send_timeout).await.is_err() {
|
||||||
info!("New remote connection failed to be picked by client after {}s. Closing remote tunnel server", send_timeout.as_secs());
|
info!("New reverse connection failed to be picked by client after {}s. Closing reverse tunnel server", send_timeout.as_secs());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Stopping listening server");
|
info!("Stopping listening reverse server");
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::spawn(fut.instrument(Span::current()));
|
tokio::spawn(fut.instrument(Span::current()));
|
||||||
|
@ -233,7 +233,7 @@ where
|
||||||
let cnx = listening_server
|
let cnx = listening_server
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow!("listening server stopped"))?;
|
.ok_or_else(|| anyhow!("listening reverse server stopped"))?;
|
||||||
servers.lock().insert(local_srv.clone(), listening_server);
|
servers.lock().insert(local_srv.clone(), listening_server);
|
||||||
Ok(cnx)
|
Ok(cnx)
|
||||||
}
|
}
|
||||||
|
@ -314,7 +314,6 @@ fn extract_tunnel_info(req: &Request<Incoming>) -> Result<TokenData<JwtTunnelCon
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn validate_tunnel<'a>(
|
fn validate_tunnel<'a>(
|
||||||
_req: &Request<Incoming>,
|
|
||||||
remote: &RemoteAddr,
|
remote: &RemoteAddr,
|
||||||
path_prefix: &str,
|
path_prefix: &str,
|
||||||
restrictions: &'a RestrictionsRules,
|
restrictions: &'a RestrictionsRules,
|
||||||
|
@ -332,7 +331,11 @@ fn validate_tunnel<'a>(
|
||||||
for allow in &restriction.allow {
|
for allow in &restriction.allow {
|
||||||
match allow {
|
match allow {
|
||||||
AllowConfig::ReverseTunnel(allow) => {
|
AllowConfig::ReverseTunnel(allow) => {
|
||||||
if !remote.protocol.is_reverse_tunnel() || !allow.port.contains(&remote.port) {
|
if !remote.protocol.is_reverse_tunnel() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allow.port.is_empty() && !allow.port.iter().any(|range| range.contains(&remote.port)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,7 +369,11 @@ fn validate_tunnel<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
AllowConfig::Tunnel(allow) => {
|
AllowConfig::Tunnel(allow) => {
|
||||||
if remote.protocol.is_reverse_tunnel() || !allow.port.contains(&remote.port) {
|
if remote.protocol.is_reverse_tunnel() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allow.port.is_empty() && !allow.port.iter().any(|range| range.contains(&remote.port)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +465,7 @@ async fn ws_server_upgrade(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match validate_tunnel(&req, &remote, path_prefix, &server_config.restrictions) {
|
match validate_tunnel(&remote, path_prefix, &server_config.restrictions) {
|
||||||
Ok(matched_restriction) => {
|
Ok(matched_restriction) => {
|
||||||
info!("Tunnel accepted due to matched restriction: {}", matched_restriction.name);
|
info!("Tunnel accepted due to matched restriction: {}", matched_restriction.name);
|
||||||
}
|
}
|
||||||
|
@ -573,7 +580,7 @@ async fn http_server_upgrade(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match validate_tunnel(&req, &remote, path_prefix, &server_config.restrictions) {
|
match validate_tunnel(&remote, path_prefix, &server_config.restrictions) {
|
||||||
Ok(matched_restriction) => {
|
Ok(matched_restriction) => {
|
||||||
info!("Tunnel accepted due to matched restriction: {}", matched_restriction.name);
|
info!("Tunnel accepted due to matched restriction: {}", matched_restriction.name);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue