wip: support rustls-0.23 for http3-quinn

This commit is contained in:
Jun Kurihara 2024-05-28 21:39:38 +09:00
commit 234abae5dd
No known key found for this signature in database
GPG key ID: 48ADFD173ED22B03
10 changed files with 63 additions and 53 deletions

View file

@ -13,8 +13,8 @@ publish.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
# default = ["http3-quinn", "cache", "rustls-backend"] default = ["http3-quinn", "cache", "rustls-backend"]
default = ["cache", "rustls-backend"] # default = ["cache", "rustls-backend"]
http3-quinn = ["rpxy-lib/http3-quinn"] http3-quinn = ["rpxy-lib/http3-quinn"]
http3-s2n = ["rpxy-lib/http3-s2n"] http3-s2n = ["rpxy-lib/http3-s2n"]
native-tls-backend = ["rpxy-lib/native-tls-backend"] native-tls-backend = ["rpxy-lib/native-tls-backend"]

View file

@ -73,7 +73,6 @@ pub fn build_settings(config: &ConfigToml) -> std::result::Result<(ProxyConfig,
// build applications // build applications
let mut app_config_list_inner = Vec::<AppConfig>::new(); let mut app_config_list_inner = Vec::<AppConfig>::new();
// let mut backends = Backends::new();
for (app_name, app) in apps.0.iter() { for (app_name, app) in apps.0.iter() {
let _server_name_string = app.server_name.as_ref().ok_or(anyhow!("No server name"))?; let _server_name_string = app.server_name.as_ref().ok_or(anyhow!("No server name"))?;
let registered_app_name = app_name.to_ascii_lowercase(); let registered_app_name = app_name.to_ascii_lowercase();

View file

@ -230,7 +230,10 @@ impl Application {
tls.https_redirection.unwrap() tls.https_redirection.unwrap()
}; };
Some(TlsConfig { https_redirection }) Some(TlsConfig {
mutual_tls: tls.client_ca_cert_path.is_some(),
https_redirection,
})
} else { } else {
None None
}; };

View file

@ -13,8 +13,8 @@ publish.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["sticky-cookie", "cache", "rustls-backend"] # default = ["sticky-cookie", "cache", "rustls-backend"]
# default = ["http3-quinn", "sticky-cookie", "cache", "rustls-backend"] default = ["http3-quinn", "sticky-cookie", "cache", "rustls-backend"]
http3-quinn = ["socket2", "quinn", "h3", "h3-quinn", "rpxy-certs/http3"] http3-quinn = ["socket2", "quinn", "h3", "h3-quinn", "rpxy-certs/http3"]
http3-s2n = [ http3-s2n = [
"h3", "h3",
@ -83,11 +83,11 @@ x509-parser = "0.16.0"
tracing = { version = "0.1.40" } tracing = { version = "0.1.40" }
# http/3 # http/3
quinn = { version = "0.10.2", optional = true } quinn = { version = "0.11.1", optional = true }
# h3 = { path = "../submodules/h3/h3/", optional = true } # h3 = { path = "../submodules/h3/h3/", optional = true }
# h3-quinn = { path = "../submodules/h3/h3-quinn/", optional = true } # h3-quinn = { path = "../submodules/h3/h3-quinn/", optional = true }
h3 = { version = "0.0.4", optional = true } h3 = { version = "0.0.5", optional = true }
h3-quinn = { version = "0.0.5", optional = true } h3-quinn = { version = "0.0.6", optional = true }
s2n-quic = { version = "1.37.0", default-features = false, features = [ s2n-quic = { version = "1.37.0", default-features = false, features = [
"provider-tls-rustls", "provider-tls-rustls",
], optional = true } ], optional = true }

View file

@ -24,6 +24,9 @@ pub struct BackendApp {
/// tls settings: https redirection with 30x /// tls settings: https redirection with 30x
#[builder(default)] #[builder(default)]
pub https_redirection: Option<bool>, pub https_redirection: Option<bool>,
/// tls settings: mutual TLS is enabled
#[builder(default)]
pub mutual_tls: Option<bool>,
} }
impl<'a> BackendAppBuilder { impl<'a> BackendAppBuilder {
pub fn server_name(&mut self, server_name: impl Into<Cow<'a, str>>) -> &mut Self { pub fn server_name(&mut self, server_name: impl Into<Cow<'a, str>>) -> &mut Self {
@ -56,7 +59,10 @@ impl TryFrom<&AppConfig> for BackendApp {
backend_builder.build()? backend_builder.build()?
} else { } else {
let tls = app_config.tls.as_ref().unwrap(); let tls = app_config.tls.as_ref().unwrap();
backend_builder.https_redirection(Some(tls.https_redirection)).build()? backend_builder
.https_redirection(Some(tls.https_redirection))
.mutual_tls(Some(tls.mutual_tls))
.build()?
}; };
Ok(backend) Ok(backend)
} }

View file

@ -16,10 +16,14 @@ pub enum RpxyError {
NoServerNameInClientHello, NoServerNameInClientHello,
#[error("No TLS serving app: {0}")] #[error("No TLS serving app: {0}")]
NoTlsServingApp(String), NoTlsServingApp(String),
#[error("Failed to update server crypto: {0}")] #[error("No default crypto provider")]
FailedToUpdateServerCrypto(String), NoDefaultCryptoProvider,
#[error("No server crypto: {0}")] #[error("Failed to build server config: {0}")]
NoServerCrypto(String), FailedToBuildServerConfig(String),
// #[error("Failed to update server crypto: {0}")]
// FailedToUpdateServerCrypto(String),
// #[error("No server crypto: {0}")]
// NoServerCrypto(String),
// hyper errors // hyper errors
#[error("hyper body manipulation error: {0}")] #[error("hyper body manipulation error: {0}")]

View file

@ -157,5 +157,6 @@ pub struct UpstreamUri {
/// Configuration parameters on TLS for a single backend application /// Configuration parameters on TLS for a single backend application
#[derive(PartialEq, Eq, Clone)] #[derive(PartialEq, Eq, Clone)]
pub struct TlsConfig { pub struct TlsConfig {
pub mutual_tls: bool,
pub https_redirection: bool, pub https_redirection: bool,
} }

View file

@ -29,15 +29,15 @@ where
{ {
// Manipulate ALT_SVC allowing h3 in response message only when mutual TLS is not enabled // Manipulate ALT_SVC allowing h3 in response message only when mutual TLS is not enabled
// TODO: This is a workaround for avoiding a client authentication in HTTP/3 // TODO: This is a workaround for avoiding a client authentication in HTTP/3
if self.globals.proxy_config.http3 && backend_app.crypto_source.as_ref().is_some_and(|v| !v.is_mutual_tls()) { if self.globals.proxy_config.http3
&& backend_app.https_redirection.is_some()
&& backend_app.mutual_tls.as_ref().is_some_and(|v| !v)
{
if let Some(port) = self.globals.proxy_config.https_port { if let Some(port) = self.globals.proxy_config.https_port {
add_header_entry_overwrite_if_exist( add_header_entry_overwrite_if_exist(
headers, headers,
header::ALT_SVC.as_str(), header::ALT_SVC.as_str(),
format!( format!("h3=\":{}\"; ma={}", port, self.globals.proxy_config.h3_alt_svc_max_age),
"h3=\":{}\"; ma={}, h3-29=\":{}\"; ma={}",
port, self.globals.proxy_config.h3_alt_svc_max_age, port, self.globals.proxy_config.h3_alt_svc_max_age
),
)?; )?;
} }
} else { } else {

View file

@ -1,6 +1,5 @@
use super::proxy_main::Proxy; use super::proxy_main::Proxy;
use crate::{ use crate::{
crypto::CryptoSource,
error::*, error::*,
hyper_ext::body::{IncomingLike, RequestBody}, hyper_ext::body::{IncomingLike, RequestBody},
log::*, log::*,
@ -17,10 +16,9 @@ use h3::{quic::BidiStream, quic::Connection as ConnectionQuic, server::RequestSt
#[cfg(all(feature = "http3-s2n", not(feature = "http3-quinn")))] #[cfg(all(feature = "http3-s2n", not(feature = "http3-quinn")))]
use s2n_quic_h3::h3::{self, quic::BidiStream, quic::Connection as ConnectionQuic, server::RequestStream}; use s2n_quic_h3::h3::{self, quic::BidiStream, quic::Connection as ConnectionQuic, server::RequestStream};
impl<U, T> Proxy<U, T> impl<T> Proxy<T>
where where
T: Connect + Clone + Sync + Send + 'static, T: Connect + Clone + Sync + Send + 'static,
U: CryptoSource + Clone + Sync + Send + 'static,
{ {
pub(super) async fn h3_serve_connection<C>( pub(super) async fn h3_serve_connection<C>(
&self, &self,

View file

@ -1,20 +1,17 @@
use super::proxy_main::Proxy; use super::{proxy_main::Proxy, socket::bind_udp_socket};
use super::socket::bind_udp_socket; use crate::{error::*, log::*, name_exp::ByteName};
use crate::{
crypto::{CryptoSource, ServerCrypto},
error::*,
log::*,
name_exp::ByteName,
};
use hyper_util::client::legacy::connect::Connect; use hyper_util::client::legacy::connect::Connect;
use quinn::{crypto::rustls::HandshakeData, Endpoint, ServerConfig as QuicServerConfig, TransportConfig}; use quinn::{
crypto::rustls::{HandshakeData, QuicServerConfig},
Endpoint, TransportConfig,
};
use rpxy_certs::ServerCrypto;
use rustls::ServerConfig; use rustls::ServerConfig;
use std::sync::Arc; use std::sync::Arc;
impl<U, T> Proxy<U, T> impl<T> Proxy<T>
where where
T: Send + Sync + Connect + Clone + 'static, T: Send + Sync + Connect + Clone + 'static,
U: CryptoSource + Clone + Sync + Send + 'static,
{ {
pub(super) async fn h3_listener_service(&self) -> RpxyResult<()> { pub(super) async fn h3_listener_service(&self) -> RpxyResult<()> {
let Some(mut server_crypto_rx) = self.globals.cert_reloader_rx.clone() else { let Some(mut server_crypto_rx) = self.globals.cert_reloader_rx.clone() else {
@ -22,13 +19,14 @@ where
}; };
info!("Start UDP proxy serving with HTTP/3 request for configured host names [quinn]"); info!("Start UDP proxy serving with HTTP/3 request for configured host names [quinn]");
// first set as null config server // first set as null config server
let rustls_server_config = ServerConfig::builder() // AWS LC provider by default
.with_safe_default_cipher_suites() let provider = rustls::crypto::CryptoProvider::get_default().ok_or(RpxyError::NoDefaultCryptoProvider)?;
.with_safe_default_kx_groups() let rustls_server_config = ServerConfig::builder_with_provider(provider.clone())
.with_protocol_versions(&[&rustls::version::TLS13]) .with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|e| RpxyError::QuinnInvalidTlsProtocolVersion(e.to_string()))? .map_err(|e| RpxyError::FailedToBuildServerConfig(format!("TLS 1.3 server config failed: {e}")))?
.with_no_client_auth() .with_no_client_auth()
.with_cert_resolver(Arc::new(rustls::server::ResolvesServerCertUsingSni::new())); .with_cert_resolver(Arc::new(rustls::server::ResolvesServerCertUsingSni::new()));
let quinn_server_config_crypto = QuicServerConfig::try_from(Arc::new(rustls_server_config)).unwrap();
let mut transport_config_quic = TransportConfig::default(); let mut transport_config_quic = TransportConfig::default();
transport_config_quic transport_config_quic
@ -42,20 +40,15 @@ where
.map(|v| quinn::IdleTimeout::try_from(v).unwrap()), .map(|v| quinn::IdleTimeout::try_from(v).unwrap()),
); );
let mut server_config_h3 = QuicServerConfig::with_crypto(Arc::new(rustls_server_config)); let mut server_config_h3 = quinn::ServerConfig::with_crypto(Arc::new(quinn_server_config_crypto));
server_config_h3.transport = Arc::new(transport_config_quic); server_config_h3.transport = Arc::new(transport_config_quic);
server_config_h3.concurrent_connections(self.globals.proxy_config.h3_max_concurrent_connections); server_config_h3.max_incoming(self.globals.proxy_config.h3_max_concurrent_connections as usize);
// To reuse address // To reuse address
let udp_socket = bind_udp_socket(&self.listening_on)?; let udp_socket = bind_udp_socket(&self.listening_on)?;
let runtime = quinn::default_runtime() let runtime =
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No async runtime found"))?; quinn::default_runtime().ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No async runtime found"))?;
let endpoint = Endpoint::new( let endpoint = Endpoint::new(quinn::EndpointConfig::default(), Some(server_config_h3), udp_socket, runtime)?;
quinn::EndpointConfig::default(),
Some(server_config_h3),
udp_socket,
runtime,
)?;
let mut server_crypto: Option<Arc<ServerCrypto>> = None; let mut server_crypto: Option<Arc<ServerCrypto>> = None;
loop { loop {
@ -64,8 +57,10 @@ where
if server_crypto.is_none() || new_conn.is_none() { if server_crypto.is_none() || new_conn.is_none() {
continue; continue;
} }
let mut conn: quinn::Connecting = new_conn.unwrap(); let Ok(mut incoming) = new_conn.unwrap().accept() else {
let Ok(hsd) = conn.handshake_data().await else { continue
};
let Ok(hsd) = incoming.handshake_data().await else {
continue continue
}; };
@ -84,8 +79,8 @@ where
// TODO: 通常のTLSと同じenumか何かにまとめたい // TODO: 通常のTLSと同じenumか何かにまとめたい
let self_clone = self.clone(); let self_clone = self.clone();
self.globals.runtime_handle.spawn(async move { self.globals.runtime_handle.spawn(async move {
let client_addr = conn.remote_address(); let client_addr = incoming.remote_address();
let quic_connection = match conn.await { let quic_connection = match incoming.await {
Ok(new_conn) => { Ok(new_conn) => {
info!("New connection established"); info!("New connection established");
h3_quinn::Connection::new(new_conn) h3_quinn::Connection::new(new_conn)
@ -114,8 +109,12 @@ where
error!("Failed to update server crypto for h3"); error!("Failed to update server crypto for h3");
break; break;
}; };
endpoint.set_server_config(Some(QuicServerConfig::with_crypto(inner.clone().inner_global_no_client_auth.clone()))); let rustls_server_config = inner.aggregated_config_no_client_auth.clone();
let Ok(quinn_server_config_crypto) = QuicServerConfig::try_from(rustls_server_config) else {
error!("Failed to update server crypto for h3");
break;
};
endpoint.set_server_config(Some(quinn::ServerConfig::with_crypto(Arc::new(quinn_server_config_crypto))));
} }
else => break else => break
} }