changed how to support multiple domains and support client authentication directly by rustls using split server_config
This commit is contained in:
parent
42c0e9474e
commit
512690fce5
11 changed files with 218 additions and 184 deletions
|
|
@ -4,6 +4,7 @@ use rustls::Certificate;
|
|||
use x509_parser::extensions::ParsedExtension;
|
||||
use x509_parser::prelude::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
// TODO: consider move this function to the layer of handle_request (L7) to return 403
|
||||
pub(super) fn check_client_authentication(
|
||||
client_certs: Option<&[Certificate]>,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use super::{proxy_client_cert::check_client_authentication, Proxy};
|
||||
use crate::{backend::SniKeyIdsMap, error::*, log::*, utils::ServerNameBytesExp};
|
||||
use super::Proxy;
|
||||
use crate::{error::*, log::*, utils::ServerNameBytesExp};
|
||||
use bytes::{Buf, Bytes};
|
||||
use h3::{quic::BidiStream, server::RequestStream};
|
||||
use hyper::{client::connect::Connect, Body, Request, Response};
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
impl<T> Proxy<T>
|
||||
|
|
@ -14,28 +14,11 @@ where
|
|||
self,
|
||||
conn: quinn::Connecting,
|
||||
tls_server_name: ServerNameBytesExp,
|
||||
sni_cc_map: Arc<SniKeyIdsMap>,
|
||||
) -> Result<()> {
|
||||
let client_addr = conn.remote_address();
|
||||
|
||||
match conn.await {
|
||||
Ok(new_conn) => {
|
||||
// Check client certificates
|
||||
let cc = {
|
||||
// https://docs.rs/quinn/latest/quinn/struct.Connection.html
|
||||
let client_certs_setting_for_sni = sni_cc_map.get(&tls_server_name);
|
||||
let client_certs = match new_conn.connection.peer_identity() {
|
||||
Some(peer_identity) => peer_identity
|
||||
.downcast::<Vec<rustls::Certificate>>()
|
||||
.ok()
|
||||
.map(|p| p.into_iter().collect::<Vec<_>>()),
|
||||
None => None,
|
||||
};
|
||||
(client_certs, client_certs_setting_for_sni)
|
||||
};
|
||||
// TODO: pass this value to the layer of handle_request (L7) to return 403
|
||||
let tls_client_auth_result = check_client_authentication(cc.0.as_ref().map(AsRef::as_ref), cc.1);
|
||||
|
||||
let mut h3_conn = h3::server::Connection::<_, bytes::Bytes>::new(h3_quinn::Connection::new(new_conn)).await?;
|
||||
info!(
|
||||
"QUIC/HTTP3 connection established from {:?} {:?}",
|
||||
|
|
@ -43,43 +26,46 @@ where
|
|||
);
|
||||
// TODO: Is here enough to fetch server_name from NewConnection?
|
||||
// to avoid deep nested call from listener_service_h3
|
||||
while let Some((req, stream)) = match h3_conn.accept().await {
|
||||
Ok(opt_req) => opt_req,
|
||||
Err(e) => {
|
||||
warn!("HTTP/3 failed to accept incoming connection: {}", e);
|
||||
return Ok(h3_conn.shutdown(0).await?);
|
||||
}
|
||||
} {
|
||||
// We consider the connection count separately from the stream count.
|
||||
// Max clients for h1/h2 = max 'stream' for h3.
|
||||
let request_count = self.globals.request_count.clone();
|
||||
if request_count.increment() > self.globals.max_clients {
|
||||
request_count.decrement();
|
||||
return Ok(h3_conn.shutdown(0).await?);
|
||||
}
|
||||
debug!("Request incoming: current # {}", request_count.current());
|
||||
|
||||
let self_inner = self.clone();
|
||||
let tls_server_name_inner = tls_server_name.clone();
|
||||
let tls_client_auth_result_inner = tls_client_auth_result.clone();
|
||||
self.globals.runtime_handle.spawn(async move {
|
||||
if let Err(e) = timeout(
|
||||
self_inner.globals.proxy_timeout + Duration::from_secs(1), // timeout per stream are considered as same as one in http2
|
||||
self_inner.stream_serve_h3(
|
||||
req,
|
||||
stream,
|
||||
client_addr,
|
||||
tls_server_name_inner,
|
||||
tls_client_auth_result_inner,
|
||||
),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("HTTP/3 failed to process stream: {}", e);
|
||||
loop {
|
||||
// this routine follows hyperium/h3 examples https://github.com/hyperium/h3/blob/master/examples/server.rs
|
||||
match h3_conn.accept().await {
|
||||
Ok(None) => {
|
||||
break;
|
||||
}
|
||||
request_count.decrement();
|
||||
debug!("Request processed: current # {}", request_count.current());
|
||||
});
|
||||
Err(e) => {
|
||||
warn!("HTTP/3 error on accept incoming connection: {}", e);
|
||||
match e.get_error_level() {
|
||||
h3::error::ErrorLevel::ConnectionError => break,
|
||||
h3::error::ErrorLevel::StreamError => continue,
|
||||
}
|
||||
}
|
||||
Ok(Some((req, stream))) => {
|
||||
// We consider the connection count separately from the stream count.
|
||||
// Max clients for h1/h2 = max 'stream' for h3.
|
||||
let request_count = self.globals.request_count.clone();
|
||||
if request_count.increment() > self.globals.max_clients {
|
||||
request_count.decrement();
|
||||
h3_conn.shutdown(0).await?;
|
||||
break;
|
||||
}
|
||||
debug!("Request incoming: current # {}", request_count.current());
|
||||
|
||||
let self_inner = self.clone();
|
||||
let tls_server_name_inner = tls_server_name.clone();
|
||||
self.globals.runtime_handle.spawn(async move {
|
||||
if let Err(e) = timeout(
|
||||
self_inner.globals.proxy_timeout + Duration::from_secs(1), // timeout per stream are considered as same as one in http2
|
||||
self_inner.stream_serve_h3(req, stream, client_addr, tls_server_name_inner),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("HTTP/3 failed to process stream: {}", e);
|
||||
}
|
||||
request_count.decrement();
|
||||
debug!("Request processed: current # {}", request_count.current());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
|
@ -97,7 +83,6 @@ where
|
|||
stream: RequestStream<S, Bytes>,
|
||||
client_addr: SocketAddr,
|
||||
tls_server_name: ServerNameBytesExp,
|
||||
tls_client_auth_result: std::result::Result<(), ClientCertsError>,
|
||||
) -> Result<()>
|
||||
where
|
||||
S: BidiStream<Bytes> + Send + 'static,
|
||||
|
|
@ -149,7 +134,6 @@ where
|
|||
self.listening_on,
|
||||
self.tls_enabled,
|
||||
Some(tls_server_name),
|
||||
Some(tls_client_auth_result),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ where
|
|||
server: Http<LocalExecutor>,
|
||||
peer_addr: SocketAddr,
|
||||
tls_server_name: Option<ServerNameBytesExp>,
|
||||
tls_client_auth_result: Option<std::result::Result<(), ClientCertsError>>,
|
||||
) where
|
||||
I: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
||||
{
|
||||
|
|
@ -75,7 +74,6 @@ where
|
|||
self.listening_on,
|
||||
self.tls_enabled,
|
||||
tls_server_name.clone(),
|
||||
tls_client_auth_result.clone(),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -94,9 +92,7 @@ where
|
|||
let tcp_listener = TcpListener::bind(&self.listening_on).await?;
|
||||
info!("Start TCP proxy serving with HTTP request for configured host names");
|
||||
while let Ok((stream, _client_addr)) = tcp_listener.accept().await {
|
||||
self
|
||||
.clone()
|
||||
.client_serve(stream, server.clone(), _client_addr, None, None);
|
||||
self.clone().client_serve(stream, server.clone(), _client_addr, None);
|
||||
}
|
||||
Ok(()) as Result<()>
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
use super::{
|
||||
proxy_client_cert::check_client_authentication,
|
||||
proxy_main::{LocalExecutor, Proxy},
|
||||
};
|
||||
use super::proxy_main::{LocalExecutor, Proxy};
|
||||
use crate::{
|
||||
backend::{ServerCrypto, SniKeyIdsMap},
|
||||
backend::{ServerCrypto, SniServerCryptoMap},
|
||||
constants::*,
|
||||
error::*,
|
||||
log::*,
|
||||
|
|
@ -17,7 +14,6 @@ use tokio::{
|
|||
sync::watch,
|
||||
time::{sleep, timeout, Duration},
|
||||
};
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
|
||||
#[cfg(feature = "http3")]
|
||||
use futures::StreamExt;
|
||||
|
|
@ -31,7 +27,7 @@ where
|
|||
async fn cert_service(&self, server_crypto_tx: watch::Sender<Option<Arc<ServerCrypto>>>) {
|
||||
info!("Start cert watch service");
|
||||
loop {
|
||||
if let Ok(server_crypto) = self.globals.backends.generate_server_crypto_with_cert_resolver().await {
|
||||
if let Ok(server_crypto) = self.globals.backends.generate_server_crypto().await {
|
||||
if let Err(_e) = server_crypto_tx.send(Some(Arc::new(server_crypto))) {
|
||||
error!("Failed to populate server crypto");
|
||||
break;
|
||||
|
|
@ -52,61 +48,61 @@ where
|
|||
let tcp_listener = TcpListener::bind(&self.listening_on).await?;
|
||||
info!("Start TCP proxy serving with HTTPS request for configured host names");
|
||||
|
||||
// let mut server_crypto: Option<Arc<ServerConfig>> = None;
|
||||
let mut tls_acceptor: Option<TlsAcceptor> = None;
|
||||
let mut sni_client_ca_keyid_map: Option<Arc<SniKeyIdsMap>> = None;
|
||||
let mut server_crypto_map: Option<Arc<SniServerCryptoMap>> = None;
|
||||
loop {
|
||||
tokio::select! {
|
||||
tcp_cnx = tcp_listener.accept() => {
|
||||
if tls_acceptor.is_none() || tcp_cnx.is_err() || sni_client_ca_keyid_map.is_none() {
|
||||
if tcp_cnx.is_err() || server_crypto_map.is_none() {
|
||||
continue;
|
||||
}
|
||||
let (raw_stream, client_addr) = tcp_cnx.unwrap();
|
||||
let acceptor = tls_acceptor.clone().unwrap();
|
||||
let sni_cc_map = sni_client_ca_keyid_map.clone().unwrap();
|
||||
let sc_map_inner = server_crypto_map.clone();
|
||||
let server_clone = server.clone();
|
||||
let self_inner = self.clone();
|
||||
|
||||
// spawns async handshake to avoid blocking thread by sequential handshake.
|
||||
let handshake_fut = async move {
|
||||
// timeout is introduced to avoid get stuck here.
|
||||
let accepted = match timeout(Duration::from_secs(TLS_HANDSHAKE_TIMEOUT_SEC), acceptor.accept(raw_stream)).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
return Err(RpxyError::Proxy(format!("Timeout to handshake TLS: {}", e)));
|
||||
}
|
||||
};
|
||||
let stream = match accepted {
|
||||
let acceptor = tokio_rustls::LazyConfigAcceptor::new(rustls::server::Acceptor::new().unwrap(), raw_stream).await;
|
||||
if let Err(e) = acceptor {
|
||||
return Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e)));
|
||||
}
|
||||
let start = acceptor.unwrap();
|
||||
let client_hello = start.client_hello();
|
||||
let server_name = client_hello.server_name();
|
||||
debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name);
|
||||
let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec()));
|
||||
if server_name.is_none(){
|
||||
return Err(RpxyError::Proxy("No SNI is given".to_string()));
|
||||
}
|
||||
let server_crypto = sc_map_inner.as_ref().unwrap().get(server_name.as_ref().unwrap());
|
||||
if server_crypto.is_none() {
|
||||
return Err(RpxyError::Proxy(format!("No TLS serving app for {:?}", "xx")));
|
||||
}
|
||||
let stream = match start.into_stream(server_crypto.unwrap().clone()).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e)));
|
||||
}
|
||||
};
|
||||
// Retrieve SNI
|
||||
let (_, conn) = stream.get_ref();
|
||||
let server_name = conn.sni_hostname();
|
||||
debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name);
|
||||
let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec()));
|
||||
if server_name.is_none(){
|
||||
Err(RpxyError::Proxy("No SNI is given".to_string()))
|
||||
} else {
|
||||
//////////////////////////////
|
||||
// Check client certificate
|
||||
let client_certs = conn.peer_certificates();
|
||||
let client_ca_keyids_set_for_sni = sni_cc_map.get(&server_name.clone().unwrap());
|
||||
// TODO: pass this value to the layer of handle_request (L7) to return 403
|
||||
let client_certs_auth_result = check_client_authentication(client_certs, client_ca_keyids_set_for_sni);
|
||||
//////////////////////////////
|
||||
// this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake.
|
||||
// TODO: don't want to pass copied value...
|
||||
self_inner.client_serve(stream, server_clone, client_addr, server_name, Some(client_certs_auth_result));
|
||||
Ok(())
|
||||
}
|
||||
self_inner.client_serve(stream, server_clone, client_addr, server_name);
|
||||
Ok(())
|
||||
};
|
||||
|
||||
self.globals.runtime_handle.spawn( async move {
|
||||
if let Err(e) = handshake_fut.await {
|
||||
error!("{}", e);
|
||||
}
|
||||
// timeout is introduced to avoid get stuck here.
|
||||
match timeout(
|
||||
Duration::from_secs(TLS_HANDSHAKE_TIMEOUT_SEC),
|
||||
handshake_fut
|
||||
).await {
|
||||
Ok(a) => {
|
||||
if let Err(e) = a {
|
||||
error!("{}", e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Timeout to handshake TLS: {}", e);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
_ = server_crypto_rx.changed() => {
|
||||
|
|
@ -114,8 +110,7 @@ where
|
|||
break;
|
||||
}
|
||||
let server_crypto = server_crypto_rx.borrow().clone().unwrap();
|
||||
tls_acceptor = Some(TlsAcceptor::from(server_crypto.inner.clone()));
|
||||
sni_client_ca_keyid_map = Some(server_crypto.server_name_client_ca_keyids_map.clone());
|
||||
server_crypto_map = Some(server_crypto.inner_local_map.clone());
|
||||
}
|
||||
else => break
|
||||
}
|
||||
|
|
@ -143,11 +138,10 @@ where
|
|||
let (endpoint, mut incoming) = Endpoint::server(server_config_h3, self.listening_on)?;
|
||||
|
||||
let mut server_crypto: Option<Arc<ServerCrypto>> = None;
|
||||
let mut sni_client_ca_keyid_map: Option<Arc<SniKeyIdsMap>> = None;
|
||||
loop {
|
||||
tokio::select! {
|
||||
new_conn = incoming.next() => {
|
||||
if server_crypto.is_none() || new_conn.is_none() || sni_client_ca_keyid_map.is_none() {
|
||||
if server_crypto.is_none() || new_conn.is_none() {
|
||||
continue;
|
||||
}
|
||||
let mut conn = new_conn.unwrap();
|
||||
|
|
@ -173,7 +167,7 @@ where
|
|||
);
|
||||
// TODO: server_nameをここで出してどんどん深く投げていくのは効率が悪い。connecting -> connectionsの後でいいのでは?
|
||||
// TODO: 通常のTLSと同じenumか何かにまとめたい
|
||||
let fut = self.clone().connection_serve_h3(conn, new_server_name, sni_client_ca_keyid_map.clone().unwrap());
|
||||
let fut = self.clone().connection_serve_h3(conn, new_server_name);
|
||||
self.globals.runtime_handle.spawn(async move {
|
||||
// Timeout is based on underlying quic
|
||||
if let Err(e) = fut.await {
|
||||
|
|
@ -187,8 +181,7 @@ where
|
|||
}
|
||||
server_crypto = server_crypto_rx.borrow().clone();
|
||||
if server_crypto.is_some(){
|
||||
endpoint.set_server_config(Some(QuicServerConfig::with_crypto(server_crypto.clone().unwrap().inner.clone())));
|
||||
sni_client_ca_keyid_map = Some(server_crypto.clone().unwrap().server_name_client_ca_keyids_map.clone());
|
||||
endpoint.set_server_config(Some(QuicServerConfig::with_crypto(server_crypto.clone().unwrap().inner_global_no_client_auth.clone())));
|
||||
}
|
||||
}
|
||||
else => break
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue