use std::net::SocketAddr; use std::sync::{atomic::Ordering, Arc}; use anyhow::Result; use log::*; use futures::FutureExt; use http::header::{HeaderName, HeaderValue}; use hyper::server::conn::Http; use hyper::service::service_fn; use hyper::{Body, Request, Response, StatusCode}; use tokio::net::TcpListener; use tokio::sync::watch; use tokio_rustls::TlsAcceptor; use crate::cert_store::{CertStore, StoreResolver}; use crate::proxy_config::ProxyConfig; use crate::reverse_proxy; pub async fn serve_https( bind_addr: SocketAddr, cert_store: Arc, proxy_config: watch::Receiver>, ) -> Result<()> { let mut cfg = rustls::ServerConfig::builder() .with_safe_defaults() .with_no_client_auth() .with_cert_resolver(Arc::new(StoreResolver(cert_store))); cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let tls_cfg = Arc::new(cfg); let tls_acceptor = Arc::new(TlsAcceptor::from(tls_cfg)); info!("Starting to serve on https://{}.", bind_addr); let tcp = TcpListener::bind(bind_addr).await?; loop { let (socket, remote_addr) = tcp.accept().await?; let proxy_config = proxy_config.clone(); let tls_acceptor = tls_acceptor.clone(); tokio::spawn(async move { match tls_acceptor.accept(socket).await { Ok(stream) => { debug!("TLS handshake was successfull"); let http_result = Http::new() .serve_connection( stream, service_fn(move |req: Request| { let proxy_config: Arc = proxy_config.borrow().clone(); handle(remote_addr, req, proxy_config).map(|res| match res { Err(e) => { warn!("Handler error: {}", e); Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body(Body::from(format!("{}", e))) .map_err(Into::into) } x => x, }) }), ) .await; if let Err(http_err) = http_result { warn!("HTTP error: {}", http_err); } } Err(e) => warn!("Error in TLS connection: {}", e), } }); } } // Custom echo service, handling two different routes and a // catch-all 404 responder. async fn handle( remote_addr: SocketAddr, 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 { req.headers() .get("host") .ok_or_else(|| anyhow!("Missing host header"))? .to_str()? }; let path = req.uri().path(); let best_match = proxy_config .entries .iter() .filter(|ent| { ent.host.matches(host) && ent .path_prefix .as_ref() .map(|prefix| path.starts_with(prefix)) .unwrap_or(true) }) .max_by_key(|ent| { ( ent.priority, ent.path_prefix .as_ref() .map(|x| x.len() as i32) .unwrap_or(0), -ent.calls.load(Ordering::SeqCst), ) }); if let Some(proxy_to) = best_match { proxy_to.calls.fetch_add(1, Ordering::SeqCst); debug!("{}{} -> {}", host, path, proxy_to); trace!("Request: {:?}", req); 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( HeaderName::from_bytes(header.as_bytes())?, HeaderValue::from_str(value)?, ); } trace!("Response: {:?}", response); info!("{} {} {}", method, response.status().as_u16(), uri); Ok(response) } else { debug!("{}{} -> NOT FOUND", host, path); info!("{} 404 {}", method, uri); Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from("No matching proxy entry"))?) } }