v0.34.0_4
This commit is contained in:
1 parent
f20c8da718
commit
5910e6e901
21 files changed
+377
-51
No files matched your search
@@ -172,9 +172,9 @@ fn response_context(
|
||||
let authority = effective_host(request, tls).ok()?;
|
||||
let host = request_host(authority.to_str().ok()?).ok()?;
|
||||
let frontend = snapshot.frontend(&host, local, tls).cloned();
|
||||
let route = crate::acl::routing_path(request.uri().path())
|
||||
let route = crate::acl::routing_path_view(request.uri().path())
|
||||
.ok()
|
||||
.and_then(|path| snapshot.select_on_arc(&host, &path, local, tls).cloned());
|
||||
.and_then(|path| snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned());
|
||||
Some((frontend, route))
|
||||
})();
|
||||
context.unwrap_or((None, None))
|
||||
|
||||
@@ -42,7 +42,7 @@ if let Some(ticket) = &cache_ticket {
|
||||
uri: uri.clone(),
|
||||
public_suffix: public_suffix.clone(),
|
||||
host: host.clone(),
|
||||
path: path.clone(),
|
||||
path: path.as_ref().to_owned(),
|
||||
scope: scope.to_owned(),
|
||||
timeout: frontend
|
||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||
@@ -79,7 +79,7 @@ if let Some(ticket) = &cache_ticket {
|
||||
uri: uri.clone(),
|
||||
public_suffix: public_suffix.clone(),
|
||||
host: host.clone(),
|
||||
path: path.clone(),
|
||||
path: path.as_ref().to_owned(),
|
||||
scope: scope.to_owned(),
|
||||
timeout: frontend
|
||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||
@@ -240,7 +240,7 @@ if handshake.is_none()
|
||||
uri: uri.clone(),
|
||||
public_suffix: public_suffix.clone(),
|
||||
host: host.clone(),
|
||||
path: path.clone(),
|
||||
path: path.as_ref().to_owned(),
|
||||
scope: scope.to_owned(),
|
||||
timeout: frontend
|
||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||
|
||||
@@ -170,7 +170,7 @@ if let (Some(ticket), Some(plan), Some(transformed)) =
|
||||
uri: active_uri.clone(),
|
||||
public_suffix: public_suffix.clone(),
|
||||
host: host.clone(),
|
||||
path: path.clone(),
|
||||
path: path.as_ref().to_owned(),
|
||||
scope: scope.to_owned(),
|
||||
timeout: frontend
|
||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
}
|
||||
let service_path = match route.rewritten_path(&path) {
|
||||
Ok(Some(value)) => value,
|
||||
Ok(None) => path.clone(),
|
||||
Ok(None) => path.as_ref().to_owned(),
|
||||
Err(_) => return error(StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid Web Server rewrite path\n"),
|
||||
};
|
||||
|
||||
@@ -101,7 +101,12 @@ async fn forward_request(
|
||||
.extensions()
|
||||
.get::<InternalFallbackRoute>()
|
||||
.cloned();
|
||||
let Ok(path) = crate::acl::routing_path(request.uri().path()) else {
|
||||
// Retain the original URI's shared backing bytes independently of `request`.
|
||||
// A normalized, already-safe path can then be borrowed for the entire
|
||||
// forwarding operation, even after the request URI/headers are rewritten.
|
||||
// Encoded/slash-folded paths still allocate via the strict decoder.
|
||||
let routing_uri = request.uri().clone();
|
||||
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
|
||||
return error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: ambiguous request path\n",
|
||||
@@ -216,9 +221,15 @@ async fn forward_request(
|
||||
cloudflare_country = crate::forwarding::cloudflare_country(request.headers(), peer.0);
|
||||
}
|
||||
}
|
||||
if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
|
||||
*exchange.client_ip.lock().unwrap_or_else(|e| e.into_inner()) =
|
||||
Some(context.client_ip.to_string());
|
||||
// `observe` already stored the actual transport peer. Most requests
|
||||
// don't use a trusted forwarding hop, so avoid formatting the same IP
|
||||
// and taking the per-request mutex a second time. IPv4-mapped peers
|
||||
// still take this path so their canonical form is correctly logged.
|
||||
if context.client_ip != peer.0.ip() {
|
||||
if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
|
||||
*exchange.client_ip.lock().unwrap_or_else(|e| e.into_inner()) =
|
||||
Some(context.client_ip.to_string());
|
||||
}
|
||||
}
|
||||
// GeoIP stays off the normal request path. Resolve lazily for any vhost or
|
||||
// location policy that actually needs a country code, or for log capture.
|
||||
|
||||
+21
-9
@@ -4,15 +4,27 @@ use http::{
|
||||
};
|
||||
/// Remove both standard hop-by-hop fields and fields named by Connection.
|
||||
pub fn strip_hop_by_hop(headers: &mut HeaderMap) {
|
||||
let nominated: Vec<HeaderName> = headers
|
||||
.get_all(CONNECTION)
|
||||
.iter()
|
||||
.filter_map(|v| v.to_str().ok())
|
||||
.flat_map(|v| v.split(','))
|
||||
.filter_map(|v| v.trim().parse().ok())
|
||||
.collect();
|
||||
for name in nominated {
|
||||
headers.remove(name);
|
||||
// The common HTTP/1.1 "Connection: keep-alive" case has no dynamic
|
||||
// nominated headers to collect. Keep the general multi-value/token
|
||||
// parser for all other requests (including maliciously repeated fields).
|
||||
let only_keep_alive = {
|
||||
let mut values = headers.get_all(CONNECTION).iter();
|
||||
values.next().is_some_and(|value| {
|
||||
value.as_bytes().eq_ignore_ascii_case(b"keep-alive")
|
||||
&& values.next().is_none()
|
||||
})
|
||||
};
|
||||
if !only_keep_alive {
|
||||
let nominated: Vec<HeaderName> = headers
|
||||
.get_all(CONNECTION)
|
||||
.iter()
|
||||
.filter_map(|v| v.to_str().ok())
|
||||
.flat_map(|v| v.split(','))
|
||||
.filter_map(|v| v.trim().parse().ok())
|
||||
.collect();
|
||||
for name in nominated {
|
||||
headers.remove(name);
|
||||
}
|
||||
}
|
||||
for name in [
|
||||
"connection",
|
||||
|
||||
@@ -52,4 +52,34 @@ mod tests {
|
||||
restore_admin_cookie(&mut h, &admin);
|
||||
assert_eq!(h.get("cookie").unwrap(), "a=1; b=2; rustproxy_session=secret");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_keep_alive_does_not_strip_unrelated_headers() {
|
||||
for value in ["keep-alive", "KEEP-ALIVE", "Keep-Alive"] {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("connection", value.parse().unwrap());
|
||||
headers.insert("keep-alive", "timeout=2".parse().unwrap());
|
||||
headers.insert("x-tenant", "alpha".parse().unwrap());
|
||||
headers.insert("authorization", "Bearer allowed".parse().unwrap());
|
||||
strip_hop_by_hop(&mut headers);
|
||||
assert!(!headers.contains_key("connection"));
|
||||
assert!(!headers.contains_key("keep-alive"));
|
||||
assert_eq!(headers["x-tenant"], "alpha");
|
||||
assert_eq!(headers["authorization"], "Bearer allowed");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn multiple_connection_fields_still_strip_all_nominated_headers() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.append("connection", "keep-alive".parse().unwrap());
|
||||
headers.append("connection", "x-hidden, upgrade".parse().unwrap());
|
||||
headers.insert("x-hidden", "sensitive".parse().unwrap());
|
||||
headers.insert("x-public", "allowed".parse().unwrap());
|
||||
headers.insert("upgrade", "websocket".parse().unwrap());
|
||||
strip_hop_by_hop(&mut headers);
|
||||
assert!(!headers.contains_key("x-hidden"));
|
||||
assert!(!headers.contains_key("connection"));
|
||||
assert!(!headers.contains_key("upgrade"));
|
||||
assert_eq!(headers["x-public"], "allowed");
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,14 @@ fn request_id_hash(sequence: u64) -> String {
|
||||
|
||||
fn request_id_hex(digest: &[u8]) -> String {
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut encoded = String::with_capacity(digest.len() * 2);
|
||||
for &byte in digest {
|
||||
encoded.push(HEX[(byte >> 4) as usize] as char);
|
||||
encoded.push(HEX[(byte & 0x0f) as usize] as char);
|
||||
// Encode two ASCII bytes per digest byte in one allocation; avoid 64
|
||||
// separate String::push bounds checks for the 32-byte SHA-256 digest.
|
||||
let mut encoded = vec![0u8; digest.len() * 2];
|
||||
for (i, &byte) in digest.iter().enumerate() {
|
||||
encoded[2 * i] = HEX[(byte >> 4) as usize];
|
||||
encoded[2 * i + 1] = HEX[(byte & 0x0f) as usize];
|
||||
}
|
||||
encoded
|
||||
String::from_utf8(encoded).expect("hex digits are always valid UTF-8")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user