PoC of introducing SpoofableValue

PoC to check which solution to pick for https://github.com/tokio-rs/axum/issues/2998
This commit is contained in:
Yann Simon
2024-10-20 12:52:34 +02:00
parent ffeb4f9407
commit 591d9c9ca5
5 changed files with 32 additions and 14 deletions
+10 -7
View File
@@ -1,4 +1,7 @@
use super::rejection::{FailedToResolveHost, HostRejection};
use super::{
rejection::{FailedToResolveHost, HostRejection},
SpoofableValue,
};
use axum::extract::FromRequestParts;
use http::{
header::{HeaderMap, FORWARDED},
@@ -18,7 +21,7 @@ const X_FORWARDED_HOST_HEADER_KEY: &str = "X-Forwarded-Host";
/// Note that user agents can set `X-Forwarded-Host` and `Host` headers to arbitrary values so make
/// sure to validate them to avoid security issues.
#[derive(Debug, Clone)]
pub struct Host(pub String);
pub struct Host(pub SpoofableValue);
impl<S> FromRequestParts<S> for Host
where
@@ -28,7 +31,7 @@ where
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
if let Some(host) = parse_forwarded(&parts.headers) {
return Ok(Host(host.to_owned()));
return Ok(Host(SpoofableValue::new(host.to_owned())));
}
if let Some(host) = parts
@@ -36,7 +39,7 @@ where
.get(X_FORWARDED_HOST_HEADER_KEY)
.and_then(|host| host.to_str().ok())
{
return Ok(Host(host.to_owned()));
return Ok(Host(SpoofableValue::new(host.to_owned())));
}
if let Some(host) = parts
@@ -44,11 +47,11 @@ where
.get(http::header::HOST)
.and_then(|host| host.to_str().ok())
{
return Ok(Host(host.to_owned()));
return Ok(Host(SpoofableValue::new(host.to_owned())));
}
if let Some(host) = parts.uri.host() {
return Ok(Host(host.to_owned()));
return Ok(Host(SpoofableValue::new(host.to_owned())));
}
Err(HostRejection::FailedToResolveHost(FailedToResolveHost))
@@ -81,7 +84,7 @@ mod tests {
fn test_client() -> TestClient {
async fn host_as_body(Host(host): Host) -> String {
host
host.spoofable_value()
}
TestClient::new(Router::new().route("/", get(host_as_body)))
+13
View File
@@ -63,3 +63,16 @@ pub use crate::json_lines::JsonLines;
#[cfg(feature = "typed-header")]
#[doc(no_inline)]
pub use crate::typed_header::TypedHeader;
#[derive(Debug, Clone)]
pub struct SpoofableValue(String);
impl SpoofableValue {
pub fn new(value: String) -> Self {
Self(value)
}
pub fn spoofable_value(self) -> String {
self.0
}
}
+7 -5
View File
@@ -9,6 +9,8 @@ use http::{
header::{HeaderMap, FORWARDED},
request::Parts,
};
use super::SpoofableValue;
const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto";
/// Extractor that resolves the scheme / protocol of a request.
@@ -21,7 +23,7 @@ const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto";
/// Note that user agents can set the `X-Forwarded-Proto` header to arbitrary values so make
/// sure to validate them to avoid security issues.
#[derive(Debug, Clone)]
pub struct Scheme(pub String);
pub struct Scheme(pub SpoofableValue);
/// Rejection type used if the [`Scheme`] extractor is unable to
/// resolve a scheme.
@@ -43,7 +45,7 @@ where
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
// Within Forwarded header
if let Some(scheme) = parse_forwarded(&parts.headers) {
return Ok(Scheme(scheme.to_owned()));
return Ok(Scheme(SpoofableValue::new(scheme.to_owned())));
}
// X-Forwarded-Proto
@@ -52,12 +54,12 @@ where
.get(X_FORWARDED_PROTO_HEADER_KEY)
.and_then(|scheme| scheme.to_str().ok())
{
return Ok(Scheme(scheme.to_owned()));
return Ok(Scheme(SpoofableValue::new(scheme.to_owned())));
}
// From parts of an HTTP/2 request
if let Some(scheme) = parts.uri.scheme_str() {
return Ok(Scheme(scheme.to_owned()));
return Ok(Scheme(SpoofableValue::new(scheme.to_owned())));
}
Err(SchemeMissing)
@@ -89,7 +91,7 @@ mod tests {
fn test_client() -> TestClient {
async fn scheme_as_body(Scheme(scheme): Scheme) -> String {
scheme
scheme.spoofable_value()
}
TestClient::new(Router::new().route("/", get(scheme_as_body)))
+1 -1
View File
@@ -122,7 +122,7 @@ where
}
let redirect = move |Host(host): Host, uri: Uri| async move {
match make_https(host, uri, ports) {
match make_https(host.spoofable_value(), uri, ports) {
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
Err(error) => {
tracing::warn!(%error, "failed to convert URI to HTTPS");
+1 -1
View File
@@ -88,7 +88,7 @@ async fn redirect_http_to_https(ports: Ports) {
}
let redirect = move |Host(host): Host, uri: Uri| async move {
match make_https(host, uri, ports) {
match make_https(host.spoofable_value(), uri, ports) {
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
Err(error) => {
tracing::warn!(%error, "failed to convert URI to HTTPS");