mirror of
https://codeberg.org/icewind/haze.git
synced 2026-10-01 08:44:09 +02:00
feat(proxy): Support direct TLS connection
This allows runing haze proxy without a reverse proxy while still having the possiblity to use HTTPS. Signed-off-by: Louis Chmn <louis@chmn.me>
This commit is contained in:
parent
7121474198
commit
048bbe8de5
5 changed files with 87 additions and 11 deletions
62
src/proxy.rs
62
src/proxy.rs
|
|
@ -32,6 +32,10 @@ use tokio::net::UnixListener;
|
|||
use tokio::signal::ctrl_c;
|
||||
use tokio::spawn;
|
||||
use tokio::time::sleep;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
use tokio_rustls::rustls::ServerConfig;
|
||||
use tokio_rustls::rustls::pki_types::pem::PemObject;
|
||||
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
||||
use tokio_stream::wrappers::{TcpListenerStream, UnixListenerStream};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
|
|
@ -147,9 +151,34 @@ pub async fn proxy(docker: Docker, config: HazeConfig) -> Result<()> {
|
|||
}
|
||||
let listen = config.proxy.listen.clone();
|
||||
|
||||
let acceptor = match (&config.proxy.cert, &config.proxy.key) {
|
||||
(None, None) => None,
|
||||
(Some(_), None) => return Err(miette!("`cert` is set without `key`")),
|
||||
(None, Some(_)) => return Err(miette!("`key` is set without `cert`")),
|
||||
(Some(cert), Some(key)) => Some(tls_acceptor(cert, key)?),
|
||||
};
|
||||
|
||||
let base_address = config.proxy.address.clone();
|
||||
let instances = ActiveInstances::new(docker, config);
|
||||
serve(instances, listen, base_address).await
|
||||
serve(instances, listen, base_address, acceptor).await
|
||||
}
|
||||
|
||||
/// Build a TLS acceptor from a PEM encoded certificate chain and private key on disk
|
||||
fn tls_acceptor(cert: &str, key: &str) -> Result<TlsAcceptor> {
|
||||
let certs = CertificateDer::pem_file_iter(cert)
|
||||
.map_err(|e| miette!("failed to load certificate from {cert}: {e}"))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| miette!("failed to load certificate from {cert}: {e}"))?;
|
||||
let key = PrivateKeyDer::from_pem_file(key)
|
||||
.map_err(|e| miette!("failed to load private key from {key}: {e}"))?;
|
||||
|
||||
let mut server_config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key)
|
||||
.into_diagnostic()?;
|
||||
server_config.alpn_protocols = vec![b"http/1.1".to_vec()];
|
||||
|
||||
Ok(TlsAcceptor::from(Arc::new(server_config)))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -159,7 +188,12 @@ struct AppState {
|
|||
proxy_client: Arc<Client>,
|
||||
}
|
||||
|
||||
async fn serve(instances: ActiveInstances, listen: String, base_address: String) -> Result<()> {
|
||||
async fn serve(
|
||||
instances: ActiveInstances,
|
||||
listen: String,
|
||||
base_address: String,
|
||||
acceptor: Option<TlsAcceptor>,
|
||||
) -> Result<()> {
|
||||
let instances = Arc::new(instances);
|
||||
let base_address = Arc::new(base_address);
|
||||
let last_instances = instances.clone();
|
||||
|
|
@ -188,12 +222,13 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
|
|||
if !listen.starts_with('/') {
|
||||
let addr: SocketAddr = listen.parse().into_diagnostic()?;
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
println!("listening on {}", listener.local_addr().unwrap());
|
||||
let scheme = if acceptor.is_some() { "https" } else { "http" };
|
||||
println!("Listening on {scheme}://{}", listener.local_addr().unwrap());
|
||||
let mut connections = pin!(TcpListenerStream::new(listener).take_until(cancel));
|
||||
|
||||
while let Some(stream) = connections.next().await {
|
||||
match stream {
|
||||
Ok(stream) => handle_connection(state.clone(), stream),
|
||||
Ok(stream) => handle_connection(state.clone(), stream, acceptor.clone()).await,
|
||||
Err(error) => {
|
||||
error!(%error, "connection failed");
|
||||
}
|
||||
|
|
@ -216,7 +251,7 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
|
|||
|
||||
while let Some(stream) = connections.next().await {
|
||||
match stream {
|
||||
Ok(stream) => handle_connection(state.clone(), stream),
|
||||
Ok(stream) => handle_connection(state.clone(), stream, None).await,
|
||||
Err(error) => {
|
||||
error!(%error, "connection failed");
|
||||
}
|
||||
|
|
@ -227,7 +262,22 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connection<I: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
|
||||
async fn handle_connection<I: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
|
||||
state: AppState,
|
||||
stream: I,
|
||||
acceptor: Option<TlsAcceptor>,
|
||||
) {
|
||||
// Spawn a tokio task to serve multiple connections concurrently
|
||||
match acceptor {
|
||||
Some(acceptor) => match acceptor.accept(stream).await {
|
||||
Ok(stream) => serve_connection(state, stream).await,
|
||||
Err(error) => error!(%error, "tls handshake failed"),
|
||||
},
|
||||
None => serve_connection(state, stream).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_connection<I: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
|
||||
state: AppState,
|
||||
stream: I,
|
||||
) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue