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
*** Bug 57711 has been marked as a duplicate of this bug. ***
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); } }
Created attachment 33985 [details] patch that adds optional X-Forwarded-Host support
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 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.
(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?
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?
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.
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
Okay. I have no particular objection to this patch.. I just wanted to understand the use-cases a little more.
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.
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.
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.
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).
(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);
What is the release target for this patch?
(In reply to Robert from comment #16) > What is the release target for this patch? There is none; it hasn't been merged.
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.
Is there a reason why we don't have this in place already? One has to add "ProxyPreserveHost On" to make this work.
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.
(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.
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.
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.
Fixed in: - master for 9.0.23 onwards - 8.5.x for 8.5.44 onwards - 7.0.x for 7.0.97 onwards