v0.34.0_11

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-18 09:39:49 +02:00
1 parent dd2502dd11
commit 943d51b528
4 files changed
+142 -33

No files matched your search

+28 -1
View File
@@ -1,4 +1,4 @@
/// Immutable formatting shared by all HTTP/1 requests on one accepted socket.
/// Socket formatting shared by all HTTP/1 requests on one accepted socket.
/// No routing or trust decision is cached: those follow the current snapshot.
#[derive(Debug)]
pub(crate) struct PeerFormatting {
@@ -9,6 +9,15 @@ pub(crate) struct PeerFormatting {
peer_node: String,
by_node: String,
port_header: HeaderValue,
// One-entry, per-accepted-socket cache. No policy or client identity is cached.
untrusted_headers: std::sync::Mutex<Option<UntrustedHeaders>>,
}
#[derive(Debug)]
struct UntrustedHeaders {
authority: HeaderValue,
tls: bool,
// The URI is intentionally excluded: it can change on every request.
base: HeaderMap,
}
impl PeerFormatting {
pub(crate) fn new(peer: SocketAddr, local: SocketAddr) -> Self {
@@ -23,7 +32,25 @@ impl PeerFormatting {
by_node: node(canonical(local.ip())),
port_header: HeaderValue::from_str(&local.port().to_string())
.expect("TCP port is a valid header"),
untrusted_headers: std::sync::Mutex::new(None),
}
}
fn peer_header(&self) -> &HeaderValue { &self.peer_header }
fn cached_untrusted(&self, authority: &HeaderValue, tls: bool) -> Option<HeaderMap> {
self.untrusted_headers.lock().unwrap_or_else(|e| e.into_inner())
.as_ref()
.filter(|entry| entry.tls == tls
&& entry.authority.as_bytes() == authority.as_bytes())
.map(|entry| entry.base.clone())
}
fn remember_untrusted(&self, authority: &HeaderValue, tls: bool, base: &HeaderMap) {
*self.untrusted_headers.lock().unwrap_or_else(|e| e.into_inner()) =
Some(UntrustedHeaders {
authority: authority.clone(),
tls,
base: base.clone(),
});
}
}
+68
View File
@@ -63,6 +63,74 @@ mod tests {
}
}
#[test]
fn untrusted_socket_header_cache_respects_host_uri_scheme_and_trust() {
for (peer, local) in [
("192.0.2.51:32000", "127.0.0.1:8080"),
("[2001:db8::8]:32100", "[2001:db8::1]:8443"),
] {
let peer: SocketAddr = peer.parse().unwrap();
let local: SocketAddr = local.parse().unwrap();
let formatter = PeerFormatting::new(peer, local);
let compiled = Settings::default().compile().unwrap();
let mut incoming = HeaderMap::new();
incoming.append("x-forwarded-for", "203.0.113.11".parse().unwrap());
incoming.append("forwarded", "for=203.0.113.12".parse().unwrap());
for (host, tls, path) in [
("app.example:8080", false, "/small.html"),
("app.example:8080", false, "/small.html"),
("app.example:8080", false, "/other?x=1"),
("other.example:8080", false, "/small.html"),
("other.example:8080", true, "/small.html"),
("app.example:8080", true, "/last?x=1&y=2"),
] {
let host = HeaderValue::from_str(host).unwrap();
let uri: Uri = path.parse().unwrap();
for original_url in [false, true] {
let direct = compiled.context_with_original_url(
&incoming, peer, local, tls, &host, &uri, original_url,
).unwrap();
let cached = compiled.context_with_peer(
&incoming, peer, local, tls, &host, &uri, original_url,
Some(&formatter), false,
).unwrap();
assert_eq!(cached.client_ip, direct.client_ip);
assert_eq!(cached.scheme, direct.scheme);
assert_eq!(cached.original_url, direct.original_url);
assert_eq!(cached.values, direct.values);
let mut direct_headers = incoming.clone();
let mut cached_headers = incoming.clone();
direct.apply_upstream(&mut direct_headers, true);
cached.apply_upstream(&mut cached_headers, true);
assert_eq!(direct_headers, cached_headers);
}
}
// When the peer becomes trusted after a config swap, the single-entry
// untrusted cache must never override the incoming proxy chain.
if peer.is_ipv4() {
let trusted = Settings {
trusted_proxies: vec!["192.0.2.0/24".to_owned()],
trust_forwarded_proto: true,
}.compile().unwrap();
let mut forwarded = HeaderMap::new();
forwarded.insert("x-forwarded-for", "198.51.100.9".parse().unwrap());
forwarded.insert("x-forwarded-proto", "https".parse().unwrap());
let host = HeaderValue::from_static("app.example");
let uri: Uri = "/trusted".parse().unwrap();
let direct = trusted.context_with_original_url(
&forwarded, peer, local, false, &host, &uri, false,
).unwrap();
let cached = trusted.context_with_peer(
&forwarded, peer, local, false, &host, &uri, false,
Some(&formatter), false,
).unwrap();
assert_eq!(cached.client_ip, "198.51.100.9".parse::<IpAddr>().unwrap());
assert_eq!(cached.scheme, "https");
assert_eq!(cached.values, direct.values);
}
}
}
#[test]
fn canonical_forwarding_headers_replace_duplicate_untrusted_values() {
let compiled = Settings::default().compile().unwrap();