0.34 #2
No files matched your search
+4
-4
@@ -326,9 +326,9 @@ b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
|
|||||||
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
|
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
|
||||||
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
|
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
|
||||||
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
|
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
|
||||||
526ae9c7f777b0442ced235237ded659e6e9457aaccc68a1fd5af80b58d3c567 src/forwarding.rs
|
61cad5d547c4aa56017d7a7ab80f356670fdd4f5b232dc04a50dbb5969645dac src/forwarding.rs
|
||||||
4be1a43a911e61ffccad01f0ca71913e5e1ee682d83591d02d478baeb900b54e src/forwarding/peer_formatting.rs
|
9c712ed59cfca15b79c3339a031c8903c83468c9d3b5e29f7e59d9a222b97561 src/forwarding/peer_formatting.rs
|
||||||
f3c1de0f428b008ed830ca410bfa5b4ca489a0861275b8541dbfa6217d16cb95 src/forwarding/tests.rs
|
0259def26930308047a248548ff911f64638b43bdfe7f765ae8c82a87a6aab21 src/forwarding/tests.rs
|
||||||
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
|
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
|
||||||
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
|
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
|
||||||
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
|
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
|
||||||
@@ -392,7 +392,7 @@ bb8c9117e05b0cb4bbfe029ef4d1a031fc755cdce255b15dad072afd1c845737 src/proxy/forw
|
|||||||
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
|
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
|
||||||
cc7fe83f42acb19d79abe83107427cdc6b0ed14083702f4fe66af22f7cdfc4cc src/proxy/headers.rs
|
cc7fe83f42acb19d79abe83107427cdc6b0ed14083702f4fe66af22f7cdfc4cc src/proxy/headers.rs
|
||||||
0f9314d1380febe436def36b5f381731e97cbac90625637575f38f3d72eaaace src/proxy/headers/tests.rs
|
0f9314d1380febe436def36b5f381731e97cbac90625637575f38f3d72eaaace src/proxy/headers/tests.rs
|
||||||
30a01e70328e8e5d544d86e3df540c0078a17de33376d389b5ad06e351477634 src/proxy/identity.rs
|
9ed6577b381285d2b7b874f60fd38d8192a0a5f3dbabdb5b737091158c43ac31 src/proxy/identity.rs
|
||||||
050c66311f71cac75c1d4c8547351a1d6f1111ed0db128691366fdd73647e90a src/proxy/mod.rs
|
050c66311f71cac75c1d4c8547351a1d6f1111ed0db128691366fdd73647e90a src/proxy/mod.rs
|
||||||
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
|
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
|
||||||
2280841494c3e14ccc5e3f02b026fe3c4328a3dab04ab449b9286bb69e39c028 src/proxy/request_id.rs
|
2280841494c3e14ccc5e3f02b026fe3c4328a3dab04ab449b9286bb69e39c028 src/proxy/request_id.rs
|
||||||
|
|||||||
+15
-1
@@ -268,6 +268,15 @@ impl Compiled {
|
|||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
// Repeated HTTP/1.1 requests generally share Host and TLS state.
|
||||||
|
// Cache only the untrusted-peer metadata for this accepted socket;
|
||||||
|
// X-Forwarded-Uri, original URL and all trust decisions remain per-request.
|
||||||
|
let target_header: HeaderValue = target.parse()?;
|
||||||
|
let mut values = if let Some(values) =
|
||||||
|
cached.and_then(|peer| peer.cached_untrusted(authority, tls))
|
||||||
|
{
|
||||||
|
values
|
||||||
|
} else {
|
||||||
let peer_text = cached.is_none().then(|| peer_ip.to_string());
|
let peer_text = cached.is_none().then(|| peer_ip.to_string());
|
||||||
let ipv6_node = (cached.is_none() && peer_ip.is_ipv6()).then(|| node(peer_ip));
|
let ipv6_node = (cached.is_none() && peer_ip.is_ipv6()).then(|| node(peer_ip));
|
||||||
let peer_header = if let Some(cached) = cached {
|
let peer_header = if let Some(cached) = cached {
|
||||||
@@ -297,7 +306,12 @@ impl Compiled {
|
|||||||
} else {
|
} else {
|
||||||
local.port().to_string().parse()?
|
local.port().to_string().parse()?
|
||||||
});
|
});
|
||||||
values.insert("x-forwarded-uri", target.parse()?);
|
if let Some(peer) = cached {
|
||||||
|
peer.remember_untrusted(authority, tls, &values);
|
||||||
|
}
|
||||||
|
values
|
||||||
|
};
|
||||||
|
values.insert("x-forwarded-uri", target_header);
|
||||||
return Ok(Context {
|
return Ok(Context {
|
||||||
client_ip: peer_ip,
|
client_ip: peer_ip,
|
||||||
scheme,
|
scheme,
|
||||||
|
|||||||
@@ -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.
|
/// No routing or trust decision is cached: those follow the current snapshot.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct PeerFormatting {
|
pub(crate) struct PeerFormatting {
|
||||||
@@ -9,6 +9,15 @@ pub(crate) struct PeerFormatting {
|
|||||||
peer_node: String,
|
peer_node: String,
|
||||||
by_node: String,
|
by_node: String,
|
||||||
port_header: HeaderValue,
|
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 {
|
impl PeerFormatting {
|
||||||
pub(crate) fn new(peer: SocketAddr, local: SocketAddr) -> Self {
|
pub(crate) fn new(peer: SocketAddr, local: SocketAddr) -> Self {
|
||||||
@@ -23,7 +32,25 @@ impl PeerFormatting {
|
|||||||
by_node: node(canonical(local.ip())),
|
by_node: node(canonical(local.ip())),
|
||||||
port_header: HeaderValue::from_str(&local.port().to_string())
|
port_header: HeaderValue::from_str(&local.port().to_string())
|
||||||
.expect("TCP port is a valid header"),
|
.expect("TCP port is a valid header"),
|
||||||
|
untrusted_headers: std::sync::Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn peer_header(&self) -> &HeaderValue { &self.peer_header }
|
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(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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]
|
#[test]
|
||||||
fn canonical_forwarding_headers_replace_duplicate_untrusted_values() {
|
fn canonical_forwarding_headers_replace_duplicate_untrusted_values() {
|
||||||
let compiled = Settings::default().compile().unwrap();
|
let compiled = Settings::default().compile().unwrap();
|
||||||
|
|||||||
Reference in new issue
Block a user