diff --git a/Cargo.lock b/Cargo.lock index 75554ab85..585fedb52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4083,8 +4083,7 @@ dependencies = [ [[package]] name = "rustls" version = "0.23.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +source = "git+https://github.com/selfhoster1312/rustls?branch=more-public-types#3b0d897225194ab78965031adfd9c6d65c136f76" dependencies = [ "aws-lc-rs", "log", diff --git a/Cargo.toml b/Cargo.toml index 209886507..40284153c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ ferron-modules-builtin = { path = "./ferron-modules-builtin", default-features = ferron-observability-builtin = { path = "./ferron-observability-builtin", default-features = false } [patch.crates-io] +rustls = { git = "https://github.com/selfhoster1312/rustls", branch = "more-public-types" } monoio = { git = "https://github.com/DorianNiemiecSVRJS/monoio.git" } # This is needed for the web server to be able to accept more than one connection on Windows, and to be able to be compiled correctly on FreeBSD cache_control = { git = "https://github.com/DorianNiemiecSVRJS/rust-cache-control.git" } # This is a fork of "cache-control" crate, with a bugfix related to "s-maxage" directive desec_api = { git = "https://github.com/DorianNiemiecSVRJS/desec_api.git" } # This is a fork of "desec_api" crate, with updated dependencies and crypto provider requirement removed diff --git a/ferron/src/handler.rs b/ferron/src/handler.rs index a567c908a..b8bd91b0a 100644 --- a/ferron/src/handler.rs +++ b/ferron/src/handler.rs @@ -24,6 +24,8 @@ use monoio_compat::hyper::{MonoioExecutor, MonoioIo, MonoioTimer}; use rustls::server::Acceptor; use rustls::ServerConfig; #[cfg(feature = "runtime-tokio")] +use tokio::io::AsyncWriteExt; +#[cfg(feature = "runtime-tokio")] use tokio::net::TcpStream; use tokio_rustls::server::TlsStream; use tokio_rustls::LazyConfigAcceptor; @@ -66,6 +68,7 @@ pub fn create_http_handler( acme_tls_alpn_01_configs: HashMap>, acme_http_01_resolvers: Arc>>, enable_proxy_protocol: bool, + proxied_tls_domains: HashMap, ) -> Result> { let shutdown_tx = CancellationToken::new(); let shutdown_rx = shutdown_tx.clone(); @@ -85,6 +88,7 @@ pub fn create_http_handler( acme_tls_alpn_01_configs, acme_http_01_resolvers, enable_proxy_protocol, + proxied_tls_domains, ) .await .err() @@ -116,6 +120,7 @@ async fn http_handler_fn( acme_tls_alpn_01_configs: HashMap>, acme_http_01_resolvers: Arc>>, enable_proxy_protocol: bool, + proxied_tls_domains: HashMap, ) -> Result<(), Box> { handler_init_tx.send(None).await.unwrap_or_default(); @@ -154,6 +159,7 @@ async fn http_handler_fn( let acme_http_01_resolvers = acme_http_01_resolvers.clone(); let connections_references_cloned = connections_references.clone(); let shutdown_rx_clone = shutdown_rx.clone(); + let proxied_tls_domains_clone = proxied_tls_domains.clone(); crate::runtime::spawn(async move { match conn_data.connection { crate::listener_handler_communication::Connection::Tcp(tcp_stream) => { @@ -187,6 +193,7 @@ async fn http_handler_fn( acme_http_01_resolvers, enable_proxy_protocol, shutdown_rx_clone, + proxied_tls_domains_clone, ) .await; } @@ -247,6 +254,7 @@ async fn http_tcp_handler_fn( acme_http_01_resolvers: Arc>>, enable_proxy_protocol: bool, shutdown_rx: CancellationToken, + tls_proxied_domains: HashMap, ) { let _connection_reference = Arc::downgrade(&connection_reference); #[cfg(feature = "runtime-monoio")] @@ -293,6 +301,83 @@ async fn http_tcp_handler_fn( (tcp_stream, None, None) }; + let (mut client, server) = tokio::io::duplex(4096); + let mut buf = [0; 4096]; + match tokio::time::timeout(Duration::from_secs(60), tcp_stream.peek(&mut buf)).await { + Ok(Ok(len)) => client.write_all(&buf[0..len]).await.unwrap(), + _ => panic!(), + } + + let sni: Option = match LazyConfigAcceptor::new(Acceptor::default(), server).await { + Ok(start_handshake) => start_handshake.client_hello().server_name().map(|s| s.to_string()), + Err(_e) => None, + }; + + if let Some(tls_proxy_domain) = sni.and_then(|sni| tls_proxied_domains.get(&sni)) { + println!("Found matching TLS proxy: {}", tls_proxy_domain); + use std::str::FromStr; + let dest = SocketAddr::from_str(&tls_proxy_domain).unwrap(); + let dest_stream = TcpStream::connect(dest).await.unwrap(); + + // First read all client data + loop { + println!("READ CLIENT"); + // let mut buf = Vec::with_capacity(4096); + let mut buf = [0; 4096]; + tcp_stream.readable().await.unwrap(); + match tcp_stream.try_read(&mut buf) { + Ok(0) => { + println!("END READ CLIENT"); + break; + } + Ok(n) => loop { + println!("WRITE BACKEND"); + dest_stream.writable().await.unwrap(); + match dest_stream.try_write(&mut buf[0..n]) { + Ok(_n) => break, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, + Err(_e) => panic!("This is a real writing error to the backend!"), + } + }, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // Is there a way to know the client is done with the request? + // Or do we just end up here? + break; + } + Err(_e) => { + panic!("This is a real error reading from the client!"); + } + } + } + + // Now read the response + loop { + println!("READ BACKEND"); + let mut buf = [0; 4096]; + dest_stream.readable().await.unwrap(); + match dest_stream.try_read(&mut buf) { + Ok(0) => break, + Ok(n) => loop { + println!("WRITE CLIENT"); + tcp_stream.writable().await.unwrap(); + match tcp_stream.try_write(&mut buf[0..n]) { + Ok(_n) => break, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, + Err(_e) => panic!("This is a real writing error to the client!"), + } + }, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + continue; + } + Err(_e) => { + panic!("This is a real error reading from the backend!"); + } + } + } + + return; + } + let maybe_tls_stream = if let Some(tls_config) = tls_config { let start_handshake = match LazyConfigAcceptor::new(Acceptor::default(), tcp_stream).await { Ok(start_handshake) => start_handshake, diff --git a/ferron/src/main.rs b/ferron/src/main.rs index 3cd58b6f9..9347a84da 100644 --- a/ferron/src/main.rs +++ b/ferron/src/main.rs @@ -397,6 +397,7 @@ fn before_starting_server( } }); + let mut proxied_tls_domains: HashMap = HashMap::new(); let mut tls_ports: HashMap = HashMap::new(); #[allow(clippy::type_complexity)] let mut tls_port_locks: HashMap)>>>> = @@ -454,6 +455,17 @@ fn before_starting_server( let https_port = server_configuration.filters.port.or(default_https_port); + // Check for a `proxy_tls` directive in the server configuration block + if let Some(proxied_tls_domain) = get_value!("proxy_tls", server_configuration) { + // Woops apparently this automatically adds 443 to the * block listened ports which is definitely not what we want + // but it works with globals so hey this is just a PoC + proxied_tls_domains.insert( + server_configuration.filters.hostname.clone().unwrap(), + proxied_tls_domain.as_str().unwrap().to_string(), + ); + tls_ports.insert(https_port.unwrap_or(443), CustomSniResolver::new()); + } + let sni_hostname = server_configuration.filters.hostname.clone().or_else(|| { // !!! UNTESTED, many clients don't send SNI hostname when accessing via IP address anyway match server_configuration.filters.ip { @@ -1444,6 +1456,7 @@ fn before_starting_server( acme_tls_alpn_01_configs.clone(), acme_http_01_resolvers.clone(), enable_proxy_protocol, + proxied_tls_domains.clone(), )?); }