Skip to content

ReactorServerHttpRequest does not reflect forwarded host and port when forwarding-header-strategy=native or cloud platform detected #28601

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

Closed
mabako opened this issue Jun 9, 2022 · 3 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Milestone

Comments

@mabako
Copy link

mabako commented Jun 9, 2022

Affects: Spring Webflux 5.3.20


Running our app in our kubernetes cluster (or simulating it via spring.main.cloud-platform=kubernetes) or using server.forward-headers-strategy=native produces the wrong scheme when calling ServerWebExchange.getRequest().getURI() when forward headers are present.

This seems to be because ReactorServerHttpRequest uses request.scheme() (which netty fills from X-Forwarded-Proto if present), and combines it with the Host header, where there's no clear correlation between those two headers.

Effectively, the following cURL request:

curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: my-gateway" -H "X-Forwarded-Port: 8181" http://localhost:8080/print-uri

... will return ServerWebExchange.getRequest().getURI() = https://localhost:8080/print-uri, even though the scheme used for the request was HTTP.

Minimal Controller Example: https://github.com/mabako/spring-native-forwarding-headers-webflux/blob/master/src/main/java/com/example/demo/PrintUriController.java

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 9, 2022
@rstoyanchev rstoyanchev added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jan 24, 2023
@rstoyanchev rstoyanchev self-assigned this Feb 8, 2023
@rstoyanchev rstoyanchev changed the title WebFlux: ServerWebExchange.getRequest().getURI() returns wrong scheme if forwarding-header-strategy is set or cloud platform is detected ServerWebExchange returns URI with the wrong scheme if forwarding-header-strategy=native or cloud platform is detected Feb 8, 2023
@rstoyanchev
Copy link
Contributor

Thanks for the sample.

Indeed, we take the scheme from the Reactor Netty HttpServerRequest, which reflects forwarded headers, but the "Host" header, which we use for the host and port, does not:

private static URI resolveBaseUrl(HttpServerRequest request) throws URISyntaxException {
String scheme = getScheme(request);
String header = request.requestHeaders().get(HttpHeaderNames.HOST);
if (header != null) {
final int portIndex;
if (header.startsWith("[")) {
portIndex = header.indexOf(':', header.indexOf(']'));
}
else {
portIndex = header.indexOf(':');
}
if (portIndex != -1) {
try {
return new URI(scheme, null, header.substring(0, portIndex),
Integer.parseInt(header, portIndex + 1, header.length(), 10), null, null, null);
}
catch (NumberFormatException ex) {
throw new URISyntaxException(header, "Unable to parse port", portIndex);
}
}
else {
return new URI(scheme, header, null, null);
}
}
else {
InetSocketAddress localAddress = request.hostAddress();
Assert.state(localAddress != null, "No host address available");
return new URI(scheme, null, localAddress.getHostString(),
localAddress.getPort(), null, null, null);
}
}
private static String getScheme(HttpServerRequest request) {
return request.scheme();
}

It looks like up until 5cac619 before we were using the remoteAddress incorrectly (itt's the client address). At that point we switched to using to the "Host" header, falling back on the localAddress which does reflect forwarded headers, but only if the "Host" header is not present.

@violetagg what is your recommendation for how we should be getting the host and address? Is there a reason to look at the "Host" header, or should we always use request.hostAddress()?

/cc @bclozel

@rstoyanchev rstoyanchev changed the title ServerWebExchange returns URI with the wrong scheme if forwarding-header-strategy=native or cloud platform is detected ReactorServerHttpRequest does not reflect forwarded host and port when forwarding-header-strategy=native or cloud platform detected Feb 14, 2023
@rstoyanchev rstoyanchev added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Feb 14, 2023
@rstoyanchev rstoyanchev added this to the 6.0.5 milestone Feb 14, 2023
@violetagg
Copy link
Member

violetagg commented Feb 14, 2023

@rstoyanchev I think that X-Forwarded-Proto is the scheme between the client and the proxy/load balancer, isn't it?

The X-Forwarded-Proto (XFP) header is a de-facto standard header for identifying the protocol (HTTP or HTTPS) that a client used to connect to your proxy or load balancer. Your server access logs contain the protocol used between the server and the load balancer, but not the protocol used between the client and the load balancer. To determine the protocol used between the client and the load balancer, the X-Forwarded-Proto request header can be used.

X-Forwarded-Host should be the Host header

The X-Forwarded-Host (XFH) header is a de-facto standard header for identifying the original host requested by the client in the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP request header.

Recently we exposed all information:

See https://projectreactor.io/docs/netty/release/api/reactor/netty/http/server/ConnectionInformation.html
It is available from HttpServerRequest and HttpServerResponse

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Feb 14, 2023

@violetagg, for the scheme, we use request#scheme() and it reflects X-Forwarded-Proto. In the above example, it is "https", which is the original between client and proxy. This part works as expected, no problem that I see there.

The Host header is "localhost:8080", and not the "mygateway:8181" from the forwarded headers. Is this expected? If not you can run the sample and have a closer look. If it is, then I'm wondering if we should just stop parsing the "Host" header ourselves, and use the API you suggested, ConnectionInformation instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Projects
None yet
Development

No branches or pull requests

4 participants