Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions config-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,13 @@ tls = { https_redirection = true, acme = true }
# Experimantal settings #
###################################
[experimental]
# Higly recommend not to be true. If true, you ignore RFC. if not specified, it is always false.
# This might be required to be true when a certificate is used by multiple backend hosts, especially in case where a TLS connection is re-used.
# Higly recommend not to be true. If either is true, you ignore RFC. If not specified, they are always false.
# Either might be required to be true when a certificate is used by multiple backend hosts, especially in case where a TLS connection is re-used.
# The `samecert_sni_consistency` option is a middleground. It only allows to share a connection if the apps of each request share the same tls certficate that was chosen during TLS handshake.
# The `ignore_sni_consistency` options takes precedence if both are true.
# We should note that this strongly depends on the client implementation.
ignore_sni_consistency = false
samecert_sni_consistency = false

# Force connection handling timeout regardless of the connection status, i.e., idle or not.
# 0 represents an infinite timeout. [default: 0]
Expand Down
15 changes: 11 additions & 4 deletions rpxy-bin/src/config/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
error::{anyhow, ensure},
log::warn,
};
use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri};
use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, SniConsistency, TlsConfig, UpstreamUri};
use rustc_hash::FxHashMap as HashMap;
use serde::Deserialize;
use std::{fs, net::SocketAddr};
Expand Down Expand Up @@ -63,6 +63,7 @@ pub struct Experimental {
pub acme: Option<AcmeOption>,

pub ignore_sni_consistency: Option<bool>,
pub samecert_sni_consistency: Option<bool>,
pub connection_handling_timeout: Option<u64>,
}

Expand Down Expand Up @@ -191,9 +192,14 @@ impl TryInto<ProxyConfig> for &ConfigToml {
}
}

if let Some(ignore) = exp.ignore_sni_consistency {
proxy_config.sni_consistency = !ignore;
}
proxy_config.sni_consistency = match (
exp.ignore_sni_consistency.unwrap_or_default(),
exp.samecert_sni_consistency.unwrap_or_default(),
) {
(false, false) => SniConsistency::Full,
(false, true) => SniConsistency::SameCertificate,
(true, _) => SniConsistency::Ignore,
};

if let Some(timeout) = exp.connection_handling_timeout {
if timeout == 0u64 {
Expand Down Expand Up @@ -280,6 +286,7 @@ impl Application {
Some(TlsConfig {
mutual_tls: tls.client_ca_cert_path.is_some(),
https_redirection,
cert: tls.tls_cert_path.clone(),
#[cfg(feature = "acme")]
acme: tls.acme.unwrap_or(false),
})
Expand Down
3 changes: 3 additions & 0 deletions rpxy-lib/src/backend/backend_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub struct BackendApp {
/// tls settings: mutual TLS is enabled
#[builder(default)]
pub mutual_tls: Option<bool>,
#[builder(default)]
pub cert: Option<String>,
}
impl<'a> BackendAppBuilder {
pub fn server_name(&mut self, server_name: impl Into<Cow<'a, str>>) -> &mut Self {
Expand Down Expand Up @@ -61,6 +63,7 @@ impl TryFrom<&AppConfig> for BackendApp {
let tls = app_config.tls.as_ref().unwrap();
backend_builder
.https_redirection(Some(tls.https_redirection))
.cert(tls.cert.clone())
.mutual_tls(Some(tls.mutual_tls))
.build()?
};
Expand Down
24 changes: 21 additions & 3 deletions rpxy-lib/src/globals.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{constants::*, count::RequestCount};
use hot_reload::ReloaderReceiver;
use rpxy_certs::ServerCryptoBase;
use std::{net::SocketAddr, time::Duration};
use std::{fmt::Display, net::SocketAddr, time::Duration};
use tokio_util::sync::CancellationToken;

/// Global object containing proxy configurations and shared object like counters.
Expand Down Expand Up @@ -50,7 +50,7 @@ pub struct ProxyConfig {

// experimentals
/// SNI consistency check
pub sni_consistency: bool, // Handler
pub sni_consistency: SniConsistency, // Handler
/// Connection handling timeout
/// timeout to handle a connection, total time of receive request, serve, and send response. this might limits the max length of response.
pub connection_handling_timeout: Option<Duration>,
Expand Down Expand Up @@ -100,7 +100,7 @@ impl Default for ProxyConfig {
max_concurrent_streams: MAX_CONCURRENT_STREAMS,
keepalive: true,

sni_consistency: true,
sni_consistency: SniConsistency::Full,
connection_handling_timeout: None,

#[cfg(feature = "cache")]
Expand Down Expand Up @@ -169,6 +169,24 @@ pub struct UpstreamUri {
pub struct TlsConfig {
pub mutual_tls: bool,
pub https_redirection: bool,
pub cert: Option<String>,
#[cfg(feature = "acme")]
pub acme: bool,
}

#[derive(PartialEq, Eq, Clone)]
pub enum SniConsistency {
Full,
SameCertificate,
Ignore,
}

impl Display for SniConsistency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
SniConsistency::Full => "Full",
SniConsistency::SameCertificate => "Same-certificate",
SniConsistency::Ignore => "Ignore",
})
}
}
9 changes: 6 additions & 3 deletions rpxy-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use std::sync::Arc;
use tokio_util::sync::CancellationToken;

/* ------------------------------------------------ */
pub use crate::globals::{AppConfig, AppConfigList, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri};
pub use crate::globals::{AppConfig, AppConfigList, ProxyConfig, ReverseProxyConfig, SniConsistency, TlsConfig, UpstreamUri};
pub mod reexports {
pub use hyper::Uri;
}
Expand Down Expand Up @@ -91,8 +91,11 @@ pub async fn entrypoint(
if proxy_config.http3 {
info!("Experimental HTTP/3.0 is enabled. Note it is still very unstable.");
}
if !proxy_config.sni_consistency {
info!("Ignore consistency between TLS SNI and Host header (or Request line). Note it violates RFC.");
if proxy_config.sni_consistency != SniConsistency::Full {
info!(
"{} consistency between TLS SNI and Host header (or Request line). Note it violates RFC.",
proxy_config.sni_consistency
);
}
#[cfg(feature = "cache")]
if proxy_config.cache_enabled {
Expand Down
30 changes: 24 additions & 6 deletions rpxy-lib/src/message_handler/handler_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ use crate::{
backend::{BackendAppManager, LoadBalanceContext},
error::*,
forwarder::{ForwardRequest, Forwarder},
globals::Globals,
globals::{Globals, SniConsistency},
hyper_ext::body::{RequestBody, ResponseBody},
log::*,
name_exp::ServerName,
};
use derive_builder::Builder;
use http::{Request, Response, StatusCode};
use hyper_util::{client::legacy::connect::Connect, rt::TokioIo};
use std::{net::SocketAddr, sync::Arc};
use std::{cell::LazyCell, net::SocketAddr, sync::Arc};
use tokio::io::copy_bidirectional;

#[allow(dead_code)]
Expand Down Expand Up @@ -96,15 +96,33 @@ where
.map(|v| ServerName::from(v.as_slice()))
.map_err(|_e| HttpError::InvalidHostInRequestHeader)?;

let backend_app = LazyCell::new(|| self.app_manager.apps.get(&server_name));

// check consistency of between TLS SNI and HOST/Request URI Line.
#[allow(clippy::collapsible_if)]
if tls_enabled && self.globals.proxy_config.sni_consistency {
if server_name != tls_server_name.unwrap_or_default() {
return Err(HttpError::SniHostInconsistency);
if tls_enabled && self.globals.proxy_config.sni_consistency != SniConsistency::Ignore {
if Some(&server_name) != tls_server_name.as_ref() {
let is_consistent = tls_server_name.is_some()
&& match self.globals.proxy_config.sni_consistency {
SniConsistency::Full => false,
SniConsistency::Ignore => unreachable!(),
SniConsistency::SameCertificate => self
.app_manager
.apps
.get(&tls_server_name.unwrap())
.into_iter()
.filter_map(|tls_backend_app| tls_backend_app.cert.as_ref())
.filter_map(|tls_cert| (*backend_app).map(|backend_app| Some(tls_cert) == backend_app.cert.as_ref()))
.next()
.unwrap_or_default(),
};
if !is_consistent {
return Err(HttpError::SniHostInconsistency);
}
}
}
// Find backend application for given server_name, and drop if incoming request is invalid as request.
let backend_app = match self.app_manager.apps.get(&server_name) {
let backend_app = match *backend_app {
Some(backend_app) => backend_app,
None => {
let Some(default_server_name) = &self.app_manager.default_server_name else {
Expand Down