Add better support for X-Forward- headers when constructing external url

This commit is contained in:
Skyler Mansfield 2023-11-14 12:24:32 +00:00 committed by Eugene
parent b0a9130a51
commit d9af7470a4
2 changed files with 31 additions and 23 deletions

View file

@ -141,7 +141,7 @@ pub struct HTTPConfig {
pub key: String, pub key: String,
#[serde(default)] #[serde(default)]
pub trust_x_forwarded_for: bool, pub trust_x_forwarded_headers: bool,
} }
impl Default for HTTPConfig { impl Default for HTTPConfig {
@ -151,7 +151,7 @@ impl Default for HTTPConfig {
listen: _default_http_listen(), listen: _default_http_listen(),
certificate: "".to_owned(), certificate: "".to_owned(),
key: "".to_owned(), key: "".to_owned(),
trust_x_forwarded_for: false, trust_x_forwarded_headers: false,
} }
} }
} }
@ -299,26 +299,34 @@ impl WarpgateConfig {
&self, &self,
for_request: Option<&poem::Request>, for_request: Option<&poem::Request>,
) -> Result<Url, WarpgateError> { ) -> Result<Url, WarpgateError> {
let url = if let Some(value) = for_request.and_then(|x| x.header(http::header::HOST)) { // if trust x-forwarded, get x-forwarded-host, then try Host, then fallback on external_host
let value = value.to_string(); // if trust x-forwarded, get x-forwarded-proto, then try request scheme, then fallback https
let mut url = Url::parse(&format!("https://{value}/"))?; // if trust x-forwarded, get x-forwarded-port, then try request port, then fallback http listen port
if let Some(value) = for_request.and_then(|x| x.header("x-forwarded-proto")) { let trust_forwarded_headers = self.store.http.trust_x_forwarded_headers;
let _ = url.set_scheme(value); let (scheme, host, port) = ("https".to_string(), self.store.external_host.clone(), self.store.http.listen.port());
}
url let (scheme, host, port) = match for_request {
} else { Some(req) => {
let ext_host = self.store.external_host.as_deref(); let scheme = req.uri().scheme().map(|x| x.to_string()).unwrap_or(scheme.clone());
let Some(ext_host) = ext_host else { let host = req.uri().host().map(|x| x.to_string()).or(host);
return Err(WarpgateError::ExternalHostNotSet); let host = req.header(http::header::HOST).map(|x| x.to_string()).or(host);
}; let port = req.uri().port_u16().unwrap_or(port);
let mut url = Url::parse(&format!("https://{ext_host}/"))?; match trust_forwarded_headers {
let ext_port = self.store.http.listen.port(); true => {
if ext_port != 443 { let scheme = for_request.and_then(|x| x.header("x-forwarded-proto")).map(|x| x.to_string()).unwrap_or(scheme);
let _ = url.set_port(Some(ext_port)); let host = for_request.and_then(|x| x.header("x-forwarded-host")).map(|x| x.to_string()).or(host);
} let port = for_request.and_then(|x| x.header("x-forwarded-port")).and_then(|x| x.parse::<u16>().ok()).unwrap_or(port);
url (scheme, host, port)
},
false =>(scheme, host, port)
}
},
None => (scheme, host, port),
}; };
Ok(url) let Some(host) = host else {
return Err(WarpgateError::ExternalHostNotSet);
};
Url::parse(&format!("{}://{}:{}/", scheme, host, port)).map_err(|e| WarpgateError::UrlParse(e))
} }
} }

View file

@ -17,9 +17,9 @@ pub async fn span_for_request(req: &Request) -> poem::Result<Span> {
.map(|x| x.ip().to_string()) .map(|x| x.ip().to_string())
.unwrap_or("<unknown>".into()); .unwrap_or("<unknown>".into());
let client_ip = match config.store.http.trust_x_forwarded_for { let client_ip = match config.store.http.trust_x_forwarded_headers {
true => req true => req
.header("X-Forwarded-For") .header("x-forwarded-for")
.map(|x| x.to_string()) .map(|x| x.to_string())
.unwrap_or(remote_ip), .unwrap_or(remote_ip),
false => remote_ip, false => remote_ip,