Bug 57665 - support x-forwarded-host
Summary: support x-forwarded-host
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 8
Classification: Unclassified
Component: Util (show other bugs)
Version: 8.0.x-trunk
Hardware: Macintosh Mac OS X 10.4
: P2 enhancement with 29 votes (vote)
Target Milestone: ----
Assignee: Tomcat Developers Mailing List
URL:
Keywords: PatchAvailable
: 57711 (view as bug list)
Depends on:
Blocks:
 
Reported: 2015-03-05 09:21 UTC by Maciej Kowalski
Modified: 2019-07-30 20:54 UTC (History)
6 users (show)



Attachments
patch that adds optional X-Forwarded-Host support (7.36 KB, patch)
2016-06-24 12:06 UTC, Stefan Fussenegger
Details | Diff
patch that adds optional X-Forwarded-Host support to RemoteIpValve and RemoteIpFilter (23.16 KB, patch)
2017-03-14 14:31 UTC, Stefan Fussenegger
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Maciej Kowalski 2015-03-05 09:21:25 UTC
tomcat-embed-core-8.0.20-sources.jar!/org/apache/catalina/valves/RemoteIpValve.java

tl;dr
Please add support for X-Forwarded-Host

when running tomcat-embedder behing a proxy
request goes to https://example.com/application/path

from there it's proxies to the application
with 
GET /application/path HTTP...
Host: internaladdress.xx
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
...

Later when a redirect/url is build
X-Forwarded-Host should be used
Comment 1 Mark Thomas 2015-03-15 23:43:01 UTC
*** Bug 57711 has been marked as a duplicate of this bug. ***
Comment 2 Robert 2016-05-26 18:18:56 UTC
The best workaround so far (which is really just a hack) is to extend Http11NioProtocol as shown below:

/**
 * Custom Tomcat Protocol based off of Http11NioProtocol that looks for an
 * X-Forwarded-Host header and sets the serverName in the request to that 
 * value.
 * 
 * This couldn't be done in a Valve because a Valve is processed too late to 
 * handle a context name redirect. For example if the url
 * `https://example.org/book` is requested.  Really early in the request 
 * Tomcat will redirect this url to `https://example.org/book/`.
 * This protocol will provide the X-Forwarded-Host header value even for that
 * type of redirect.
 * 
 * To use simply set this class as the value of the
 * {@code Connector->protocol} attribute in server.xml
 */
public static class XForwardedHostHandlingHttp11NioProtocol extends Http11NioProtocol
{
    @Override
    public void setAdapter(final Adapter adapter) {
        Adapter adapterFacade = (Adapter) Proxy.newProxyInstance(
                XForwardedHostHandlingHttp11NioProtocol.class.getClassLoader(),
                new Class[] {Adapter.class}, (proxy, method, args) ->
                {
                    if (method.getName().equals("service")) {
                        Request req = (Request)args[0];
                        String header = req.getHeader("X-Forwarded-Host");
                        if (header != null) {
                            req.serverName().setString(header);
                        }
                    }
                    return method.invoke(adapter, args);
                });
        super.setAdapter(adapterFacade);
    }
}
Comment 3 Stefan Fussenegger 2016-06-24 12:06:15 UTC
Created attachment 33985 [details]
patch that adds optional X-Forwarded-Host support
Comment 4 Stefan Fussenegger 2016-06-24 12:10:04 UTC
The patch adds support for a hostHeader that works analogue to the existing portHeader. It's disabled by default, keeping backward compatibility. Setting it to a value like X-Forwarded-Host will override the value returned by ServletRequest.getServerName()
Comment 5 Balasubramanian 2017-03-02 13:06:15 UTC
(In reply to Stefan Fussenegger from comment #4)
> The patch adds support for a hostHeader that works analogue to the existing
> portHeader. It's disabled by default, keeping backward compatibility.
> Setting it to a value like X-Forwarded-Host will override the value returned
> by ServletRequest.getServerName()

In which version of tomcat, this fix is available? I did not see this fix in tomcat 9 and 8.
Comment 6 Christopher Schultz 2017-03-07 15:49:26 UTC
(In reply to Balasubramanian from comment #5)
> In which version of tomcat, this fix is available? I did not see this fix in
> tomcat 9 and 8.

What led you to believe that this enhancement request and patch had been applied to /any/ version of Tomcat?
Comment 7 Christopher Schultz 2017-03-07 15:59:20 UTC
I'm curious why this enhancement has even been requested. My understanding of the HTTP spec is that the "Host" header should not be modified by an intervening reverse-proxy.

https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.23

If you request http://example.com/application/path, then the client RFC-MUST supply "Host: example.com" in the headers and any reverse-proxy RFC-MUST forward that "Host: example.com" header on to the origin server.

What reverse-proxy are you using which changes the "Host" header?
Comment 8 Mike Youngstrom 2017-03-07 16:25:58 UTC
We modify the Host header mostly to work around limitations in application platforms our apps are hosted on.

For example, Cloud Foundry only recently added support for path based routing and even still it only supports different paths for applications hosted in the same "space".  If I have 2 applications that with different paths but need the same host then with Cloud Foundry modifying the Host header via configuration in an upstream load balancer is often the only option available to many of us.
Comment 9 Stefan Fussenegger 2017-03-07 16:31:12 UTC
There are other scenarios than "an intervening reverse-proxy". One example would be a CDN that use the Host header of the backend and adds the original value as X-Forwarded-Host. (e.g. "Host: origin.example.com" and "X-Fowarded-Host: cdn.example.com"). Some applications running on "origin.example.com" will then use the Host header to create absolute links and redirects to "origin.example.com" which isn't wanted. The easiest workaround is to hide the real value of the "Host" header and use "X-Forwarded-Host" instead - which is what this patch is doing.


see http://stackoverflow.com/questions/19084340/real-life-usage-of-the-x-forwarded-host-header for more examples
Comment 10 Christopher Schultz 2017-03-07 16:48:13 UTC
Okay. I have no particular objection to this patch.. I just wanted to understand the use-cases a little more.
Comment 11 Mark Thomas 2017-03-10 14:24:05 UTC
The proposed patch adds this to the Valve. The Filter also needs to be updated as does the documentation.

Generally, I don't like adding features in Tomcat to work around 3rd party components that don't follow the relevant spec. In this case it looks like we might need to make an exception.
Comment 12 Christopher Schultz 2017-03-10 21:49:46 UTC
I think there are reasonable use-cases for X-Forwarded-Host. Stefan, if you wouldn't mind augmenting the patch to include similar changes to the Filter and the documentation as well, I'm happy to commit it.
Comment 13 Stefan Fussenegger 2017-03-14 14:31:30 UTC
Created attachment 34824 [details]
patch that adds optional X-Forwarded-Host support to RemoteIpValve and RemoteIpFilter

new patch that adds X-Forwarded-Host header support to RemoteIpFilter and RemoteIpValve

there currently is no documentation of X-Forwarded-Port to use as an example.
Comment 14 Richard Swart 2017-05-09 05:14:42 UTC
Note that proxies like Apache with mod_proxy will create a comma separate list of the X-Forwarded-Host if the incoming request already contains a X-Forwarded-Host header (similar to X-Forwarded-For). Rfc7239 is not completely clear whether this is allowed or not, but it still common practice. 

The patch does not seem to support this (at least no unit test covers this situation).
Comment 15 Stefan Fussenegger 2017-05-09 07:42:52 UTC
(In reply to Richard Swart from comment #14)
> Note that proxies like Apache with mod_proxy will create a comma separate
> list of the X-Forwarded-Host if the incoming request already contains a
> X-Forwarded-Host header (similar to X-Forwarded-For). Rfc7239 is not
> completely clear whether this is allowed or not, but it still common
> practice. 
> 
> The patch does not seem to support this (at least no unit test covers this
> situation).

This case is actually covered with this line:

String hostAndPort[] = hostHeaderValue.split(",")[0].trim().split(":", 2);
Comment 16 Robert 2017-10-03 21:14:05 UTC
What is the release target for this patch?
Comment 17 Christopher Schultz 2017-10-03 23:49:10 UTC
(In reply to Robert from comment #16)
> What is the release target for this patch?

There is none; it hasn't been merged.
Comment 18 Sergey Ponomarev 2018-07-09 20:02:15 UTC
Can we set more prio and finally merge the patch?

I tried to use this filter as a workaround
https://github.com/qaware/x-forwarded-filter
which looks quite good and working according to all specifications and even covers an RFC 7239 `Forwarded` header.
The author of the lib made a great investigation of all reverse proxy filters and made a comparison in README.
IMHO the filter can be  as kind of reference implementation.

I trying to create a base tomcat image for my company's legacy apps and put them behind reverse proxy Nginx.
I declared the filter in CATALINA_BASE/but the filter become last in filter chain and other filters of webapps are failed to determine correct server name. And I didn't find any solution how to set an order for filters declared in conf/web.xml

Now I copied the RemoteIpValve, applied the patch and changed a package, built, added it as dependency in tomcat/lib and finally used it in server.xml.
That costed me for a lot of time so I would be happy if Tomcat can do it itself.

Meanwhile the patch has some problems:
1. The hostHeader property should be specified

    private String hostHeader = null; 

I have no idea why you didn't assign a default value i.e.

    private String hostHeader = ";

Without this we need to always specify the header name while it de-facto always ``

2. Remote Host won't be populated it  is not specified.
This looks like a bug when remote host is populated in setHostAndPorts() method but the method is called only if protocolHeader property is specified. BTW the protocolHeader also can have a default `X-Forwarded-Proto`.


So to start working the RemoteIpValve should be specified like:

    <Valve className="org.apache.catalina.valves.RemoteIpValve" hostHeader="X-Forwarded-Host" protocolHeader="X-Forwarded-Proto"/>

To understand this defaults I also spent some time.
Also here is a possible tricky situation when port from X-Forwarded-Host can override port from `X-Forwarded-Port` which is doesn't supported by RemoteIpValve at all.
Comment 19 Michael Osipov 2019-02-21 13:20:13 UTC
Is there a reason why we don't have this in place already? One has to add "ProxyPreserveHost On" to make this work.
Comment 20 Christopher Schultz 2019-02-22 14:39:30 UTC
Nobody has merged this patch, yet. I don't see any objections, here. I didn't do it because I don't have a convenient setup to test it's efficacy.
Comment 21 Michael Osipov 2019-02-22 14:48:15 UTC
(In reply to Christopher Schultz from comment #20)
> Nobody has merged this patch, yet. I don't see any objections, here. I
> didn't do it because I don't have a convenient setup to test it's efficacy.

I think I do. I can share a config where this makes sense to merge.
Comment 22 Michael Osipov 2019-02-28 15:46:45 UTC
Here is possible use case:

> <Service name="Catalina">
> 	<Connector address="localhost" port="8081" connectionTimeout="20000"
> 		maxHttpHeaderSize="24576" redirectPort="8444" maxThreads="250" />
> 
> 	<Connector port="8444" connectionTimeout="20000"
> 		maxHttpHeaderSize="24576" maxThreads="250"
> 		SSLEnabled="true" scheme="https" secure="true"
> 		defaultSSLHostConfigName="blnn719x.ad001.siemens.net">
> 		<SSLHostConfig hostName="blnn719x.ad001.siemens.net" protocols="TLSv1.2"
> 			honorCipherOrder="true" ciphers="HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK:!DSS">
> 			<Certificate certificateFile="/etc/ssl/blnn719x.ad001.siemens.net/cert/public.pem"
> 				certificateKeyFile="/etc/ssl/blnn719x.ad001.siemens.net/key/private.pem"
> 				type="RSA" />
> 		</SSLHostConfig>
> 	</Connector>
> 
> 	<Engine name="Catalina" defaultHost="blnn719x.ad001.siemens.net">
> 		<Host name="blnn719x.ad001.siemens.net" appBase="webapps" unpackWARs="true" autoDeploy="true">
> 			<Valve className="org.apache.catalina.valves.RemoteIpValve" protocolHeader="X-Forwarded-Proto" />
> 		</Host>
> 	</Engine>
> </Service>
> 

Tomcat is serving the stuff itself via HTTPS while the Apache Webserver on the same host has another hostname. Traffic might go to localhost or blnn719x.ad001.siemens.net. I don't see a reason not to support it.
Comment 23 Alexander Veit 2019-04-08 13:02:46 UTC
Same problem here. Our reverse proxy is IIS with ARR (Application Request Routing). In our case we can not send the original Host header via a Host header to the backend (embedded Tomcat).

The strange thing is that Tomcat valves or filters provide support for other X-Forwarded-* headers but not for X-Forwarded-Host.
Comment 24 Mark Thomas 2019-07-30 20:54:49 UTC
Fixed in:
- master for 9.0.23 onwards
- 8.5.x for 8.5.44 onwards
- 7.0.x for 7.0.97 onwards