aboutsummaryrefslogtreecommitdiff
path: root/src/https.rs
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2021-12-07 15:20:45 +0100
committerAlex Auvolat <alex@adnab.me>2021-12-07 15:20:45 +0100
commitcd7e5ad034b75d659d4d87a752ab7b11cf75de12 (patch)
tree32773f9758b33188402e137d435bdd61ce01b280 /src/https.rs
parent5535c4951a832d65755afa53822a36e96681320f (diff)
downloadtricot-cd7e5ad034b75d659d4d87a752ab7b11cf75de12.tar.gz
tricot-cd7e5ad034b75d659d4d87a752ab7b11cf75de12.zip
Got a reverse proxy
Diffstat (limited to 'src/https.rs')
-rw-r--r--src/https.rs126
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"))?)
+ }
+}