Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Scheme extractor #2507

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

Add Scheme extractor #2507

wants to merge 9 commits into from

Conversation

bengsparks
Copy link

Motivation

Closes #2504.

Solution

Detect the scheme in the following order:

  1. From proto within the Forwarded header
  2. Reading from X-Forwarded-Proto
  3. Extracting from the URI

@bengsparks
Copy link
Author

bengsparks commented Jan 11, 2024

The test for extracting the scheme from the URI is missing as I haven't groked how to pass a URI with a scheme to the test server.
I would be grateful for a pointer in the right direction; and in the same commit I'll also fix the formatting.

@bengsparks
Copy link
Author

bengsparks commented Jan 22, 2024

@davidpdrsn Is this ready for merge, or do I need to add more tests?

@Wiezzel
Copy link

Wiezzel commented Feb 21, 2024

May I suggest additionally supporting X-Forwarded-Scheme header? (It's mentioned in a comment, but only X-Forwarded-Proto is supported).

@Wiezzel
Copy link

Wiezzel commented Feb 21, 2024

It also seems to me that the extraction from parts is not really working. I've added a debug print statement there and the parts.uri seems to contain only URI path.

@bengsparks
Copy link
Author

bengsparks commented Feb 27, 2024

Thanks for your comments @Wiezzel.
I will follow-up sometime during the next weekend.

@bengsparks
Copy link
Author

@Wiezzel Thanks for taking the time to review the PR.

May I suggest additionally supporting X-Forwarded-Scheme header? (It's mentioned in a comment, but only X-Forwarded-Proto is supported).

Turns out this was a typo; X-Forwarded-Scheme does not exist, or at least I cannot find any reference to it. I have updated the PR to reflect this.

It also seems to me that the extraction from parts is not really working. I've added a debug print statement there and the parts.uri seems to contain only URI path.

I have added a manual test that calls from_request directly upon a manually constructed request, and the extraction seems to work. It would be surprising if it did not, as this is the only part of the implementation that depends solely on existing methods on http's Parts type.

@Wiezzel
Copy link

Wiezzel commented Mar 4, 2024

@bengsparks Please check out this minimal example:

Cargo.toml

[package]
name = "scheme-extractor"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7.3"
tokio = { version = "1.36.0", features = ["full"] }

[patch.crates-io]
axum = { git = "https://github.com/bengsparks/axum.git" }

main.rs

async fn test(scheme: axum::extract::Scheme) -> String {
    scheme.0
}

#[tokio::main]
async fn main() {
    let app = axum::Router::new()
        .route("/", axum::routing::get(test));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Then run:

$ cargo run &
$ curl -i  http://localhost:8000
HTTP/1.1 400 Bad Request
content-type: text/plain; charset=utf-8
content-length: 26
date: Mon, 04 Mar 2024 12:52:01 GMT

No scheme found in request

@jplatte
Copy link
Member

jplatte commented Mar 5, 2024

I think we've made a point to not handle any conventional, or even standard headers for "original" request data from proxies in ConnectInfo, because it can be spoofed at least in some cases. I think the same reasoning applies here.

@bengsparks
Copy link
Author

bengsparks commented Mar 13, 2024

@jplatte Could you concretise what this means for the PR? Does this mean X-Forwarded-Proto should be ignored? In this case, the Host extractor would also have to be updated, as it reads from X-Forwarded-Host.

@Wiezzel
Copy link

Wiezzel commented Mar 13, 2024

I could suggest a fallback to HTTP in case the scheme cannot be extract neither from headers, nor from path. This sounds like a reasonable default to me, but it could be optional in case some application needs stricter check.

@jplatte
Copy link
Member

jplatte commented Mar 13, 2024

@jplatte Could you concretise what this means for the PR? Does this mean X-Forwarded-Proto should be ignored? In this case, the Host extractor would also have to be updated, as it reads from X-Forwarded-Host.

Yes, that is what I meant. I didn't know we even had a Host extractor 😅
I'll try to find some time to figure out the background on how that extractor was added, and also to find those discussions I remember about ConnectInfo.

@bengsparks
Copy link
Author

@jplatte I will put this PR on pause then until the matter is resolved; Having a Host extractor was half the reason I considered writing this PR to go along with it. There is also the question of what to do if the scheme cannot be found in the URI, despite being provided, which is the case in @Wiezzel's example.

@sclu1034
Copy link

sclu1034 commented Mar 15, 2024

In my tests, the Request never contained a scheme value. Regardless of whether I used the Request or OriginalUri extractors.

I think we've made a point to not handle any conventional, or even standard headers for "original" request data from proxies in ConnectInfo, because it can be spoofed at least in some cases. I think the same reasoning applies here.

If you wanted to walk that path to the end, you'd have to remove the Host extractor entirely, and prevent stuff like req.uri().host(), as that's from the Host header. The protocol would at least be possible, as the server should know whether it currently speaks with or without TLS.

But as a user, the extractors came across to me as an implementation of common practices. Which is why I would expect them to check those extra headers. Because if they didn't, they'd boil down to a one-line like req.uri().host(), and I don't need an extractor for that.

I would not expect them to be subject to security-based mitigations.
Instead, the way I'd expect a security mitigation like this would be either some kind of middleware that I can deliberately add to have "spoofable" headers be stripped, or the reverse, where they are stripped/ignored by default unless some kind of .reverse_proxy(true) is set on a Router or for axum as a whole.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Scheme extractor
4 participants