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

Bug: registerWebInterface option 'ignoreTrailingSlash' causes redirect to absolute URL, breaking reverse-proxies #2705

Open
vnayar opened this issue Jan 13, 2023 · 4 comments

Comments

@vnayar
Copy link

vnayar commented Jan 13, 2023

vibe.d/web/vibe/web/web.d

Lines 216 to 228 in fee3872

if (settings.ignoreTrailingSlash && !fullurl.endsWith("*") && fullurl != "/") {
auto m = fullurl.endsWith("/") ? fullurl[0 .. $-1] : fullurl ~ "/";
router.match(minfo.method, m, delegate void (HTTPServerRequest req, HTTPServerResponse res) @safe {
static if (minfo.method == HTTPMethod.GET) {
URL redurl = req.fullURL;
auto redpath = redurl.path;
redpath.endsWithSlash = !redpath.endsWithSlash;
redurl.path = redpath;
res.redirect(redurl);
} else {
() @trusted { handleRequest!(M, overload)(req, res, instance, settings); } ();
}
});

When an HTTP GET request arrives, e.g. "/configs" that is missing a trailing slash, the code above causes this to be a redirect to "/configs/". However, because the redirect URL is an absolute URL, this causes errors when the server is behind a reverse proxy.

For example:

HTTP GET [browser] https://company.com/configs ==> [proxy] ==> http://service1:8080/configs
         [browser] http://service1:8080/configs/ <== [proxy] <== Status 302 :: location -> http://service1:8080/configs/
HTTP GET [browser] http://service1:8080/configs/ ==> [nowhere]

Either, the redirect should use a relative URL, or the redirect step should be entirely skipped and the WebInterface function should simply be called directly.

@vnayar
Copy link
Author

vnayar commented Jan 13, 2023

This problem only seems to occur when a @path annotation is on both the class itself and the method, e.g.:

@path("/web/checkout")
class CheckoutController {
  // Use the /web path so that it does not strictly require JWT before being loaded.
  @path("/")  // The same thing happens with @path("")
  void getCheckout() {
    string priceLookupKey = "funnel-service-standard";
    render!("checkout.dt", priceLookupKey);
  }

@s-ludwig
Copy link
Member

If the reverse proxy setup sets the X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers, the HTTP server will use them to determine the canonical URL of the request. The redirect location should then be correct. But I think it should also be possible to change the implementation of redirect to attempt to construct a relative path instead of a full URL in cases where that is possible.

@vnayar
Copy link
Author

vnayar commented Jan 13, 2023

Interesting.
I happen to be using Envoy Proxy, and it seems to split the information into a few headers:

  • origin :: This contains the original scheme, host, and port, e.g. "http://localhost:8180"
  • referer :: This contains the URL that led to the request, e.g. "http://localhost:8180/web/checkout"
  • host :: This contains only the host and port, without the scheme, e.g. "localhost:8180"
  • x-forwarded-proto :: The scheme used in forwarding, e.g. "http".

According to the docs I can find (https://www.envoyproxy.io/docs/envoy/v1.24.1/configuration/http/http_conn_man/headers#x-forwarded-for), the x-forwarded-for header is used in the case of a forward proxy, when the proxy is the client of a request you are receiving that came from someone behind a proxy, but I don't think it's present in the reverse-proxy situation, where your proxy is the server rather the client. But perhaps I need to enable the use_remote_address option and see if that fixes it.

I wasn't aware that vibe could make use of these headers, let me dig further and make sure they are there if possible.

@vnayar
Copy link
Author

vnayar commented Jan 13, 2023

I enabled the use_remote_address and tried it with my local server. The x-forwarded-for header is now present, but it merely contains an IP address, which I believe is for the original client rather than the reverse proxy, e.g. 'x-forwarded-for', '192.168.2.33'. In this case, my reverse proxy is running on port 8180 and forwarding to port 8082, where the target server is running. The response returned by the server looks like this:

vnayar@krootox:admin$ curl -D - -s -o /dev/null http://localhost:8180/admin/checkout/ -H "Authorization: Bearer $ACCESS_TOKEN"
HTTP/1.1 302 Found
server: envoy
date: Fri, 13 Jan 2023 18:05:54 GMT
location: http://localhost:8082/admin/checkout

While debugging, the only information the server running on port 8082 receives, that even hints that the reverse proxy is on 8180 is via the host header (the origin header seems to vanish when I enable the use_remote_address setting.)

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

No branches or pull requests

2 participants