//! Copied from https://github.com/felipenoris/hyper-reverse-proxy //! See there for original Copyright notice use std::sync::Arc; use std::convert::TryInto; use std::time::SystemTime; use std::net::IpAddr; use std::str::FromStr; use anyhow::Result; use log::*; use http::header::HeaderName; use hyper::header::{HeaderMap, HeaderValue}; use hyper::{Body, Client, Request, Response, Uri}; use rustls::{Certificate, ServerName}; use rustls::client::{ServerCertVerifier, ServerCertVerified}; use lazy_static::lazy_static; use crate::tls_util::HttpsConnectorFixedDnsname; fn is_hop_header(name: &str) -> bool { use unicase::Ascii; // A list of the headers, using `unicase` to help us compare without // worrying about the case, and `lazy_static!` to prevent reallocation // of the vector. lazy_static! { static ref HOP_HEADERS: Vec> = vec![ Ascii::new("Connection"), Ascii::new("Keep-Alive"), Ascii::new("Proxy-Authenticate"), Ascii::new("Proxy-Authorization"), Ascii::new("Te"), Ascii::new("Trailers"), Ascii::new("Transfer-Encoding"), Ascii::new("Upgrade"), ]; } HOP_HEADERS.iter().any(|h| h == &name) } /// Returns a clone of the headers without the [hop-by-hop headers]. /// /// [hop-by-hop headers]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html fn remove_hop_headers(headers: &HeaderMap) -> HeaderMap { let mut result = HeaderMap::new(); for (k, v) in headers.iter() { if !is_hop_header(k.as_str()) { result.insert(k.clone(), v.clone()); } } result } fn create_proxied_response(mut response: Response) -> Response { *response.headers_mut() = remove_hop_headers(response.headers()); response } fn forward_uri(forward_url: &str, req: &Request) -> Result { let forward_uri = match req.uri().query() { Some(query) => format!("{}{}?{}", forward_url, req.uri().path(), query), None => format!("{}{}", forward_url, req.uri().path()), }; Ok(Uri::from_str(forward_uri.as_str())?) } fn create_proxied_request( client_ip: IpAddr, forward_url: &str, request: Request, ) -> Result> { let mut builder = Request::builder() .method(request.method()) .uri(forward_uri(forward_url, &request)?); *builder.headers_mut().unwrap() = remove_hop_headers(request.headers()); // If request does not have host header, add it from original URI authority let host_header_name = "host"; if let Some(authority) = request.uri().authority() { if let hyper::header::Entry::Vacant(entry) = builder.headers_mut().unwrap().entry(host_header_name) { entry.insert(authority.as_str().parse()?); } } // Add forwarding information in the headers let x_forwarded_for_header_name = "x-forwarded-for"; match builder .headers_mut() .unwrap() .entry(x_forwarded_for_header_name) { hyper::header::Entry::Vacant(entry) => { entry.insert(client_ip.to_string().parse()?); } hyper::header::Entry::Occupied(mut entry) => { let addr = format!("{}, {}", entry.get().to_str()?, client_ip); entry.insert(addr.parse()?); } } builder.headers_mut().unwrap().insert( HeaderName::from_bytes(b"x-forwarded-proto")?, "https".try_into()?, ); if let Some(conn) = request.headers().get("connection") { if conn.to_str()?.to_lowercase() == "upgrade" { if let Some(upgrade) = request.headers().get("upgrade") { builder.headers_mut().unwrap().insert( HeaderName::from_bytes(b"connection")?, "Upgrade".try_into()?, ); builder .headers_mut() .unwrap() .insert(HeaderName::from_bytes(b"upgrade")?, upgrade.clone()); } } } Ok(builder.body(request.into_body())?) } pub async fn call( client_ip: IpAddr, forward_uri: &str, request: Request, ) -> Result> { let proxied_request = create_proxied_request(client_ip, &forward_uri, request)?; trace!("Proxied request: {:?}", proxied_request); let client = Client::new(); let response = client.request(proxied_request).await?; trace!("Inner response: {:?}", response); 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 tls_config = rustls::client::ClientConfig::builder() .with_safe_defaults() .with_custom_certificate_verifier(Arc::new(DontVerifyServerCert)) .with_no_client_auth(); let connector = HttpsConnectorFixedDnsname::new(tls_config, "dummy"); let client: Client<_, hyper::Body> = Client::builder().build(connector); let response = client.request(proxied_request).await?; trace!("Inner response (HTTPS): {:?}", response); let proxied_response = create_proxied_response(response); Ok(proxied_response) } struct DontVerifyServerCert; impl ServerCertVerifier for DontVerifyServerCert { fn verify_server_cert( &self, _end_entity: &Certificate, _intermediates: &[Certificate], _server_name: &ServerName, _scts: &mut dyn Iterator, _ocsp_response: &[u8], _now: SystemTime ) -> Result { Ok(ServerCertVerified::assertion()) } }