aboutsummaryrefslogblamecommitdiff
path: root/src/https.rs
blob: c80d51cff44b0be58e518eb39135b2d437debfba (plain) (tree)





























































































































                                                                                                                                   
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"))?)
	}
}