When mTLS is used force path to match client certificate CN (#272)

This change makes the server verify the client's path prefix matches the common name (CN) in the certificate the client presented when mTLS is used. This makes it impossible for the client to spoof the path prefix specified in the `restrictions.yaml` file.
This commit is contained in:
Jasper Siepkes 2024-05-16 08:39:30 +02:00 committed by GitHub
parent 562c78187b
commit ddebdfd3d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 35 deletions

View file

@ -37,6 +37,7 @@ use crate::tunnel::tls_reloader::TlsReloader;
use crate::tunnel::transport::http2::{Http2TunnelRead, Http2TunnelWrite};
use crate::tunnel::transport::websocket::{WebsocketTunnelRead, WebsocketTunnelWrite};
use crate::udp::UdpStream;
use crate::tls_utils::{cn_from_certificate, find_leaf_certificate};
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::select;
@ -422,6 +423,7 @@ fn validate_tunnel<'a>(
async fn ws_server_upgrade(
server_config: Arc<WsServerConfig>,
restrictions: Arc<RestrictionsRules>,
restrict_path_prefix: Option<String>,
mut client_addr: SocketAddr,
mut req: Request<Incoming>,
) -> Response<String> {
@ -448,6 +450,16 @@ async fn ws_server_upgrade(
Err(err) => return err,
};
if let Some(restrict_path) = restrict_path_prefix {
if path_prefix != restrict_path {
warn!("Client requested upgrade path '{}' does not match upgrade path restriction '{}' (mTLS, etc.)", path_prefix, restrict_path);
return http::Response::builder()
.status(StatusCode::BAD_REQUEST)
.body("Requested upgrade path does not match upgrade path restriction (mTLS, etc.)".into())
.unwrap();
}
}
let jwt = match extract_tunnel_info(&req) {
Ok(jwt) => jwt,
Err(err) => return err,
@ -547,6 +559,7 @@ async fn ws_server_upgrade(
async fn http_server_upgrade(
server_config: Arc<WsServerConfig>,
restrictions: Arc<RestrictionsRules>,
restrict_path_prefix: Option<String>,
mut client_addr: SocketAddr,
mut req: Request<Incoming>,
) -> Response<Either<String, BoxBody<Bytes, anyhow::Error>>> {
@ -565,6 +578,16 @@ async fn http_server_upgrade(
Err(err) => return err.map(Either::Left),
};
if let Some(restrict_path) = restrict_path_prefix {
if path_prefix != restrict_path {
warn!("Client requested upgrade path '{}' does not match upgrade path restriction '{}' (mTLS, etc.)", path_prefix, restrict_path);
return http::Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Either::Left("Requested upgrade path does not match upgrade path restriction (mTLS, etc.)".to_string()))
.unwrap();
}
}
let jwt = match extract_tunnel_info(&req) {
Ok(jwt) => jwt,
Err(err) => return err.map(Either::Left),
@ -674,34 +697,36 @@ pub async fn run_server(server_config: Arc<WsServerConfig>, restrictions: Restri
// setup upgrade request handler
let mk_websocket_upgrade_fn =
|server_config: Arc<WsServerConfig>, restrictions: Arc<RestrictionsRules>, client_addr: SocketAddr| {
|server_config: Arc<WsServerConfig>, restrictions: Arc<RestrictionsRules>, restrict_path: Option<String>,client_addr: SocketAddr| {
move |req: Request<Incoming>| {
ws_server_upgrade(server_config.clone(), restrictions.clone(), client_addr, req)
ws_server_upgrade(server_config.clone(), restrictions.clone(), restrict_path.clone(), client_addr, req)
.map::<anyhow::Result<_>, _>(Ok)
}
};
let mk_http_upgrade_fn =
|server_config: Arc<WsServerConfig>, restrictions: Arc<RestrictionsRules>, client_addr: SocketAddr| {
|server_config: Arc<WsServerConfig>, restrictions: Arc<RestrictionsRules>, restrict_path: Option<String>, client_addr: SocketAddr| {
move |req: Request<Incoming>| {
http_server_upgrade(server_config.clone(), restrictions.clone(), client_addr, req)
http_server_upgrade(server_config.clone(), restrictions.clone(), restrict_path.clone(), client_addr, req)
.map::<anyhow::Result<_>, _>(Ok)
}
};
let mk_auto_upgrade_fn = |server_config: Arc<WsServerConfig>,
restrictions: Arc<RestrictionsRules>,
restrict_path: Option<String>,
client_addr: SocketAddr| {
move |req: Request<Incoming>| {
let server_config = server_config.clone();
let restrictions = restrictions.clone();
let restrict_path = restrict_path.clone();
async move {
if fastwebsockets::upgrade::is_upgrade_request(&req) {
ws_server_upgrade(server_config.clone(), restrictions.clone(), client_addr, req)
ws_server_upgrade(server_config.clone(), restrictions.clone(), restrict_path, client_addr, req)
.map(|response| Ok::<_, anyhow::Error>(response.map(Either::Left)))
.await
} else if req.version() == Version::HTTP_2 {
http_server_upgrade(server_config.clone(), restrictions.clone(), client_addr, req)
http_server_upgrade(server_config.clone(), restrictions.clone(), restrict_path.clone(), client_addr, req)
.map::<anyhow::Result<_>, _>(Ok)
.await
} else {
@ -786,6 +811,14 @@ pub async fn run_server(server_config: Arc<WsServerConfig>, restrictions: Restri
}
};
let restrict_path = if let Some(client_cert_chain) =
tls_stream.inner().get_ref().1.peer_certificates() {
find_leaf_certificate(&client_cert_chain.to_vec())
.and_then(|leaf_cert| cn_from_certificate(&leaf_cert))
} else {
None
};
match tls_stream.inner().get_ref().1.alpn_protocol() {
// http2
Some(b"h2") => {
@ -794,7 +827,7 @@ pub async fn run_server(server_config: Arc<WsServerConfig>, restrictions: Restri
conn_builder.keep_alive_interval(ping);
}
let http_upgrade_fn = mk_http_upgrade_fn(server_config, restrictions.clone(), peer_addr);
let http_upgrade_fn = mk_http_upgrade_fn(server_config, restrictions.clone(), restrict_path.clone(), peer_addr);
let con_fut = conn_builder.serve_connection(tls_stream, service_fn(http_upgrade_fn));
if let Err(e) = con_fut.await {
error!("Error while upgrading cnx to http: {:?}", e);
@ -803,7 +836,7 @@ pub async fn run_server(server_config: Arc<WsServerConfig>, restrictions: Restri
// websocket
_ => {
let websocket_upgrade_fn =
mk_websocket_upgrade_fn(server_config, restrictions.clone(), peer_addr);
mk_websocket_upgrade_fn(server_config, restrictions.clone(), restrict_path.clone(), peer_addr);
let conn_fut = http1::Builder::new()
.serve_connection(tls_stream, service_fn(websocket_upgrade_fn))
.with_upgrades();
@ -828,7 +861,7 @@ pub async fn run_server(server_config: Arc<WsServerConfig>, restrictions: Restri
conn_fut.http2().keep_alive_interval(ping);
}
let websocket_upgrade_fn = mk_auto_upgrade_fn(server_config, restrictions.clone(), peer_addr);
let websocket_upgrade_fn = mk_auto_upgrade_fn(server_config, restrictions.clone(), None, peer_addr);
let upgradable = conn_fut.serve_connection_with_upgrades(stream, service_fn(websocket_upgrade_fn));
if let Err(e) = upgradable.await {