Add option to map (force) port use on the server for reverse tunnels. (#274)

This change adds a `port_mapping` option to the `ReverseTunnel` definition in the (YAML) restriction file.

It maps ports on the server side from X to Y (X:Y). Where X is the originally requested port by the client and Y is the port which will be used to listen on server-side.

For example with `10001:8080` configured and a client which connects using `-R tcp://10001:localhost:80` the server will listen on port 8080 instead of 10001. The originally requested ports (NOT the mapped ports) still needs to be allowed via the `ports` directive.

This is for example useful when dealing with lots of clients and you don't want to coordinate port use on all the clients but centrally on the server.
This commit is contained in:
Jasper Siepkes 2024-05-22 16:04:19 +02:00 committed by Σrebe - Romain GERARD
parent fb74d9cfba
commit c09c349610
No known key found for this signature in database
GPG key ID: 7A42B4B97E0332F4
4 changed files with 76 additions and 10 deletions

View file

@ -36,6 +36,7 @@ impl RestrictionsRules {
let reverse_tunnel = types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
protocol: vec![],
port: vec![],
port_mapping: Default::default(),
cidr: default_cidr(),
});
@ -56,6 +57,7 @@ impl RestrictionsRules {
types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
protocol: vec![],
port: vec![RangeInclusive::new(*port, *port)],
port_mapping: Default::default(),
cidr: vec![IpNet::new(ip, if ip.is_ipv4() { 32 } else { 128 })?],
}),
]
@ -70,6 +72,7 @@ impl RestrictionsRules {
types::AllowConfig::ReverseTunnel(types::AllowReverseTunnelConfig {
protocol: vec![],
port: vec![],
port_mapping: Default::default(),
cidr: default_cidr(),
}),
]

View file

@ -2,6 +2,7 @@ use crate::LocalProtocol;
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use regex::Regex;
use serde::{Deserialize, Deserializer};
use std::collections::HashMap;
use std::ops::RangeInclusive;
#[derive(Debug, Clone, Deserialize)]
@ -56,6 +57,10 @@ pub struct AllowReverseTunnelConfig {
#[serde(default)]
pub port: Vec<RangeInclusive<u16>>,
#[serde(deserialize_with = "deserialize_port_mapping")]
#[serde(default)]
pub port_mapping: HashMap<u16, u16>,
#[serde(default = "default_cidr")]
pub cidr: Vec<IpNet>,
}
@ -110,6 +115,29 @@ where
Ok(ranges)
}
fn deserialize_port_mapping<'de, D>(deserializer: D) -> Result<HashMap<u16, u16>, D::Error>
where
D: Deserializer<'de>,
{
let mappings: Vec<String> = Deserialize::deserialize(deserializer)?;
mappings
.into_iter()
.map(|port_mapping| {
let port_mapping_parts: Vec<&str> = port_mapping.split(':').collect();
if port_mapping_parts.len() != 2 {
Err(serde::de::Error::custom(format!(
"Invalid port_mapping entry: {}",
port_mapping
)))
} else {
let orig_port = port_mapping_parts[0].parse::<u16>().map_err(serde::de::Error::custom)?;
let target_port = port_mapping_parts[1].parse::<u16>().map_err(serde::de::Error::custom)?;
Ok((orig_port, target_port))
}
})
.collect()
}
fn deserialize_non_empty_vec<'de, D, T>(d: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,