From ca8c5aad2378dd9f8ec525b3b0779f5c53cfe9eb Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 8 Dec 2021 22:58:19 +0100 Subject: [PATCH] Handle HTTPS targets --- Cargo.lock | 28 ++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/https.rs | 23 +++++++++++++++-------- src/proxy_config.rs | 8 +++++++- src/reverse_proxy.rs | 23 +++++++++++++++++++++++ 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38e8c70..9f5b314 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -671,6 +671,21 @@ dependencies = [ "unicase", ] +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http 0.2.5", + "hyper 0.14.15", + "log", + "rustls 0.20.2", + "rustls-native-certs", + "tokio 1.14.0", + "tokio-rustls", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1294,6 +1309,18 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "0.2.1" @@ -1913,6 +1940,7 @@ dependencies = [ "http 0.2.5", "hyper 0.14.15", "hyper-reverse-proxy", + "hyper-rustls", "lazy_static", "log", "pretty_env_logger", diff --git a/Cargo.toml b/Cargo.toml index 5dca10f..aa1b4fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ chrono = { version = "0.4", features = [ "serde" ] } hyper = { version = "0.14", features = [ "full" ] } futures-util = "0.3" tokio-rustls = "0.23" -#hyper-rustls = "0.23" +hyper-rustls = "0.23" http = "0.2" hyper-reverse-proxy = "0.4" unicase = "2" diff --git a/src/https.rs b/src/https.rs index e1db42e..b0d452b 100644 --- a/src/https.rs +++ b/src/https.rs @@ -79,6 +79,9 @@ async fn handle( req: Request, proxy_config: Arc, ) -> Result, anyhow::Error> { + let method = req.method().clone(); + let uri = req.uri().to_string(); + let host = if let Some(auth) = req.uri().authority() { auth.as_str() } else { @@ -89,7 +92,7 @@ async fn handle( }; let path = req.uri().path(); - let ent = proxy_config + let best_match = proxy_config .entries .iter() .filter(|ent| { @@ -111,17 +114,20 @@ async fn handle( ) }); - if let Some(proxy_to) = ent { + + if let Some(proxy_to) = best_match { proxy_to.calls.fetch_add(1, Ordering::SeqCst); - let to_addr = format!("http://{}", proxy_to.target_addr); - let method = req.method().clone(); - - let uri = req.uri().to_string(); debug!("{}{} -> {}", host, path, proxy_to); trace!("Request: {:?}", req); - let mut response = reverse_proxy::call(remote_addr.ip(), &to_addr, req).await?; + let mut response = if proxy_to.https_target { + let to_addr = format!("https://{}", proxy_to.target_addr); + reverse_proxy::call_https(remote_addr.ip(), &to_addr, req).await? + } else { + let to_addr = format!("http://{}", proxy_to.target_addr); + reverse_proxy::call(remote_addr.ip(), &to_addr, req).await? + }; for (header, value) in proxy_to.add_headers.iter() { response.headers_mut().insert( @@ -134,7 +140,8 @@ async fn handle( Ok(response) } else { - info!("Proxying {} {} -> NOT FOUND", host, path); + debug!("{}{} -> NOT FOUND", host, path); + info!("{} 404 {}", method, uri); Ok(Response::builder() .status(StatusCode::NOT_FOUND) diff --git a/src/proxy_config.rs b/src/proxy_config.rs index 8a64f53..2c55eb5 100644 --- a/src/proxy_config.rs +++ b/src/proxy_config.rs @@ -41,6 +41,7 @@ impl HostDescription { #[derive(Debug)] pub struct ProxyEntry { pub target_addr: SocketAddr, + pub https_target: bool, pub host: HostDescription, pub path_prefix: Option, @@ -55,6 +56,9 @@ pub struct ProxyEntry { impl std::fmt::Display for ProxyEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.https_target { + write!(f, "https://")?; + } write!(f, "{} ", self.target_addr)?; match &self.host { HostDescription::Hostname(h) => write!(f, "{}", h)?, @@ -94,7 +98,8 @@ fn parse_tricot_tag( add_headers: &[(String, String)], ) -> Option { let splits = tag.split(' ').collect::>(); - if (splits.len() != 2 && splits.len() != 3) || splits[0] != "tricot" { + if (splits.len() != 2 && splits.len() != 3) + || (splits[0] != "tricot" && splits[0] != "tricot-https") { return None; } @@ -121,6 +126,7 @@ fn parse_tricot_tag( Some(ProxyEntry { target_addr, + https_target: (splits[0] == "tricot-https"), host, path_prefix, priority, diff --git a/src/reverse_proxy.rs b/src/reverse_proxy.rs index 401f4b1..9d7780c 100644 --- a/src/reverse_proxy.rs +++ b/src/reverse_proxy.rs @@ -139,3 +139,26 @@ pub async fn call( let proxied_response = create_proxied_response(response); Ok(proxied_response) } + +pub async fn call_https( + client_ip: IpAddr, + forward_uri: &str, + request: Request, +) -> Result> { + let proxied_request = create_proxied_request(client_ip, &forward_uri, request)?; + + trace!("Proxied request (HTTPS): {:?}", proxied_request); + + let https = hyper_rustls::HttpsConnectorBuilder::new() + .with_native_roots() + .https_only() + .enable_http1() + .build(); + let client: Client<_, hyper::Body> = Client::builder().build(https); + let response = client.request(proxied_request).await?; + + trace!("Inner response (HTTPS): {:?}", response); + + let proxied_response = create_proxied_response(response); + Ok(proxied_response) +}