diff options
author | Alex Auvolat <alex@adnab.me> | 2021-12-07 15:20:45 +0100 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2021-12-07 15:20:45 +0100 |
commit | cd7e5ad034b75d659d4d87a752ab7b11cf75de12 (patch) | |
tree | 32773f9758b33188402e137d435bdd61ce01b280 /src/https.rs | |
parent | 5535c4951a832d65755afa53822a36e96681320f (diff) | |
download | tricot-cd7e5ad034b75d659d4d87a752ab7b11cf75de12.tar.gz tricot-cd7e5ad034b75d659d4d87a752ab7b11cf75de12.zip |
Got a reverse proxy
Diffstat (limited to 'src/https.rs')
-rw-r--r-- | src/https.rs | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/src/https.rs b/src/https.rs new file mode 100644 index 0000000..c80d51c --- /dev/null +++ b/src/https.rs @@ -0,0 +1,126 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use anyhow::Result; +use log::*; + +use futures::FutureExt; +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( + cert_store: Arc<CertStore>, + proxy_config: watch::Receiver<Arc<ProxyConfig>>, +) -> Result<()> { + let addr = format!("0.0.0.0:1443"); + + 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)); + + println!("Starting to serve on https://{}.", addr); + + let tcp = TcpListener::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<Body>| { + let proxy_config: Arc<ProxyConfig> = 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 { + debug!("HTTP error: {}", http_err); + } + } + Err(e) => debug!("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<Body>, + proxy_config: Arc<ProxyConfig>, +) -> Result<Response<Body>, anyhow::Error> { + 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 ent = proxy_config + .entries + .iter() + .filter(|ent| { + ent.host == host + && ent + .path_prefix + .as_ref() + .map(|prefix| path.starts_with(prefix)) + .unwrap_or(true) + }) + .min_by_key(|ent| { + ( + ent.priority, + -(ent + .path_prefix + .as_ref() + .map(|x| x.len() as i32) + .unwrap_or(0)), + ) + }); + + if let Some(proxy_to) = ent { + let to_addr = format!("http://{}", proxy_to.target_addr); + info!("Proxying {} {} -> {}", host, path, to_addr); + + reverse_proxy::call(remote_addr.ip(), &to_addr, req).await + } else { + info!("Proxying {} {} -> NOT FOUND", host, path); + + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("No matching proxy entry"))?) + } +} |