v0.34.0_10
This commit is contained in:
1 parent
8c4cbc583f
commit
dd2502dd11
13 files changed
+389
-113
No files matched your search
+108
-45
@@ -28,6 +28,8 @@ pub struct Context {
|
||||
pub original_url: String,
|
||||
values: HeaderMap,
|
||||
}
|
||||
include!("forwarding/peer_formatting.rs");
|
||||
|
||||
impl Settings {
|
||||
pub fn compile(&self) -> Result<Compiled> {
|
||||
ensure!(
|
||||
@@ -231,24 +233,58 @@ impl Compiled {
|
||||
uri: &Uri,
|
||||
include_original_url: bool,
|
||||
) -> Result<Context> {
|
||||
let peer_ip = canonical(peer.ip());
|
||||
self.context_with_peer(
|
||||
incoming, peer, local, tls, authority, uri, include_original_url,
|
||||
None, false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Only the host/path validation and formatting data from the same request
|
||||
/// and accepted connection are reused. Dynamic trust and XFF are evaluated
|
||||
/// on every request, including after an in-place configuration reload.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn context_with_peer(
|
||||
&self,
|
||||
incoming: &HeaderMap,
|
||||
peer: SocketAddr,
|
||||
local: SocketAddr,
|
||||
tls: bool,
|
||||
authority: &HeaderValue,
|
||||
uri: &Uri,
|
||||
include_original_url: bool,
|
||||
cached: Option<&PeerFormatting>,
|
||||
validated_host: bool,
|
||||
) -> Result<Context> {
|
||||
let cached = cached.filter(|cached| cached.peer == peer && cached.local == local);
|
||||
let peer_ip = cached.map_or_else(|| canonical(peer.ip()), |cached| cached.peer_ip);
|
||||
let trusted_peer = self.trusted(peer_ip);
|
||||
if !trusted_peer {
|
||||
let scheme = if tls { "https" } else { "http" };
|
||||
let authority_text = authority.to_str()?;
|
||||
crate::config::request_host(authority_text)?;
|
||||
if !validated_host { crate::config::request_host(authority_text)?; }
|
||||
let target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
|
||||
let original_url = if include_original_url {
|
||||
format!("{scheme}://{authority_text}{target}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let peer_text = peer_ip.to_string();
|
||||
let peer_header = HeaderValue::from_str(&peer_text)?;
|
||||
let peer_node = if peer_ip.is_ipv4() { None } else { Some(node(peer_ip)) };
|
||||
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 peer_header = if let Some(cached) = cached {
|
||||
cached.peer_header.clone()
|
||||
} else {
|
||||
HeaderValue::from_str(peer_text.as_deref().unwrap_or(""))?
|
||||
};
|
||||
let by_text = cached.is_none().then(|| node(canonical(local.ip())));
|
||||
let peer_node = cached.map_or_else(
|
||||
|| if peer_ip.is_ipv4() { peer_text.as_deref().unwrap_or("") }
|
||||
else { ipv6_node.as_deref().unwrap_or("") },
|
||||
|cached| cached.peer_node.as_str(),
|
||||
);
|
||||
let by_node = cached.map_or_else(|| by_text.as_deref().unwrap_or(""),
|
||||
|cached| cached.by_node.as_str());
|
||||
let forwarded = format!(
|
||||
"for={};by={};proto={scheme};host=\"{authority_text}\"",
|
||||
peer_node.as_deref().unwrap_or(&peer_text), node(canonical(local.ip()))
|
||||
"for={peer_node};by={by_node};proto={scheme};host=\"{authority_text}\""
|
||||
);
|
||||
let mut values = HeaderMap::with_capacity(7);
|
||||
values.insert("forwarded", forwarded.parse()?);
|
||||
@@ -256,7 +292,11 @@ impl Compiled {
|
||||
values.insert("x-real-ip", peer_header);
|
||||
values.insert("x-forwarded-host", authority.clone());
|
||||
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
|
||||
values.insert("x-forwarded-port", local.port().to_string().parse()?);
|
||||
values.insert("x-forwarded-port", if let Some(cached) = cached {
|
||||
cached.port_header.clone()
|
||||
} else {
|
||||
local.port().to_string().parse()?
|
||||
});
|
||||
values.insert("x-forwarded-uri", target.parse()?);
|
||||
return Ok(Context {
|
||||
client_ip: peer_ip,
|
||||
@@ -287,29 +327,39 @@ impl Compiled {
|
||||
}
|
||||
let authority_text = authority.to_str()?;
|
||||
// Validate before inserting authority into Forwarded's quoted host field.
|
||||
crate::config::request_host(authority_text)?;
|
||||
if !validated_host { crate::config::request_host(authority_text)?; }
|
||||
let original_target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
|
||||
let original_url = if include_original_url {
|
||||
format!("{scheme}://{authority_text}{original_target}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let mut forwarded: Vec<_> = ips.iter().map(|ip| format!("for={}", node(*ip))).collect();
|
||||
let last = forwarded.last_mut().expect("actual peer is always present");
|
||||
last.push_str(&format!(
|
||||
";by={};proto={scheme};host=\"{authority_text}\"",
|
||||
node(canonical(local.ip()))
|
||||
));
|
||||
let mut values = HeaderMap::new();
|
||||
values.insert("forwarded", forwarded.join(", ").parse()?);
|
||||
values.insert(
|
||||
"x-forwarded-for",
|
||||
ips.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
.parse()?,
|
||||
);
|
||||
use std::fmt::Write as _;
|
||||
let mut forwarded = String::with_capacity(ips.len() * 52 + 80 + authority_text.len());
|
||||
let mut xff = String::with_capacity(ips.len() * 44);
|
||||
for (index, ip) in ips.iter().enumerate() {
|
||||
if index > 0 { forwarded.push_str(", "); xff.push_str(", "); }
|
||||
forwarded.push_str("for=");
|
||||
if index + 1 == ips.len() {
|
||||
if let Some(cached) = cached { forwarded.push_str(&cached.peer_node); }
|
||||
else { forwarded.push_str(&node(*ip)); }
|
||||
} else {
|
||||
forwarded.push_str(&node(*ip));
|
||||
}
|
||||
if index + 1 == ips.len() {
|
||||
if let Some(cached) = cached { xff.push_str(cached.peer_header.to_str()?); }
|
||||
else { write!(&mut xff, "{ip}")?; }
|
||||
} else {
|
||||
write!(&mut xff, "{ip}")?;
|
||||
}
|
||||
}
|
||||
forwarded.push_str(";by=");
|
||||
if let Some(cached) = cached { forwarded.push_str(&cached.by_node); }
|
||||
else { forwarded.push_str(&node(canonical(local.ip()))); }
|
||||
write!(&mut forwarded, ";proto={scheme};host=\"{authority_text}\"")?;
|
||||
let mut values = HeaderMap::with_capacity(7);
|
||||
values.insert("forwarded", forwarded.parse()?);
|
||||
values.insert("x-forwarded-for", xff.parse()?);
|
||||
values.insert("x-real-ip", client_ip.to_string().parse()?);
|
||||
values.insert("x-forwarded-host", authority.clone());
|
||||
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
|
||||
@@ -321,7 +371,11 @@ impl Compiled {
|
||||
} else {
|
||||
local.port()
|
||||
};
|
||||
values.insert("x-forwarded-port", port.to_string().parse()?);
|
||||
values.insert("x-forwarded-port", if cached.is_some() && !(trusted_peer && self.trust_proto) {
|
||||
cached.expect("tested above").port_header.clone()
|
||||
} else {
|
||||
port.to_string().parse()?
|
||||
});
|
||||
values.insert("x-forwarded-uri", original_target.parse()?);
|
||||
Ok(Context {
|
||||
client_ip,
|
||||
@@ -371,21 +425,35 @@ impl Context {
|
||||
Ok(())
|
||||
}
|
||||
pub fn apply(&self, headers: &mut HeaderMap) {
|
||||
let remove: Vec<_> = headers
|
||||
.keys()
|
||||
.filter(|name| {
|
||||
name.as_str().starts_with("x-forwarded-")
|
||||
|| matches!(
|
||||
name.as_str(),
|
||||
"forwarded" | "x-real-ip" | "x-original-url" | "x-original-method"
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
for name in remove {
|
||||
headers.remove(name);
|
||||
self.apply_clean(headers, true);
|
||||
}
|
||||
fn apply_clean(&self, headers: &mut HeaderMap, include_client_ip: bool) {
|
||||
// HeaderMap::insert replaces all values for a known canonical name.
|
||||
// Remove unknown/spoofed forwarding names while retaining the valid ones.
|
||||
// The normal request has no extra spoofed header names. Allocate
|
||||
// a removal list only for requests which actually need sanitization.
|
||||
let mut remove = None;
|
||||
for name in headers.keys() {
|
||||
let text = name.as_str();
|
||||
if matches!(text, "x-original-url" | "x-original-method")
|
||||
|| (text.starts_with("x-forwarded-")
|
||||
&& !self.values.contains_key(text))
|
||||
{
|
||||
remove.get_or_insert_with(Vec::new).push(name.clone());
|
||||
}
|
||||
}
|
||||
if let Some(remove) = remove {
|
||||
for name in remove { headers.remove(name); }
|
||||
}
|
||||
if !include_client_ip {
|
||||
for name in ["forwarded", "x-forwarded-for", "x-real-ip"] {
|
||||
headers.remove(name);
|
||||
}
|
||||
}
|
||||
for (name, value) in &self.values {
|
||||
if !include_client_ip && matches!(name.as_str(), "forwarded" | "x-forwarded-for" | "x-real-ip") {
|
||||
continue;
|
||||
}
|
||||
headers.insert(name.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
@@ -395,12 +463,7 @@ impl Context {
|
||||
/// the visitor address so frameworks such as ProxyFix see the RustProxy socket peer.
|
||||
/// Authentication verifiers use `apply` directly and still receive the real client IP.
|
||||
pub fn apply_upstream(&self, headers: &mut HeaderMap, forward_client_ip: bool) {
|
||||
self.apply(headers);
|
||||
if !forward_client_ip {
|
||||
headers.remove("forwarded");
|
||||
headers.remove("x-forwarded-for");
|
||||
headers.remove("x-real-ip");
|
||||
}
|
||||
self.apply_clean(headers, forward_client_ip);
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user