This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/master by this push: new 67c3af9 Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=57665 67c3af9 is described below commit 67c3af97230135af8f6f7566c77a79a69722a713 Author: Mark Thomas <ma...@apache.org> AuthorDate: Tue Jul 30 21:42:37 2019 +0100 Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=57665 Implement X-Forwarded-Host support for RemoteIpFilter and RemoteIpValve --- java/org/apache/catalina/AccessLog.java | 15 ++- .../catalina/filters/LocalStrings.properties | 2 + .../apache/catalina/filters/RemoteIpFilter.java | 113 ++++++++++++++++++-- .../catalina/valves/AbstractAccessLogValve.java | 19 +++- .../apache/catalina/valves/LocalStrings.properties | 2 + java/org/apache/catalina/valves/RemoteIpValve.java | 95 ++++++++++++++--- .../catalina/filters/TestRemoteIpFilter.java | 80 ++++++++++++++ .../apache/catalina/valves/TestRemoteIpValve.java | 118 +++++++++++++++++++++ webapps/docs/changelog.xml | 5 + webapps/docs/config/filter.xml | 13 +++ webapps/docs/config/valve.xml | 13 +++ 11 files changed, 446 insertions(+), 29 deletions(-) diff --git a/java/org/apache/catalina/AccessLog.java b/java/org/apache/catalina/AccessLog.java index 017a27b..7e6f28d 100644 --- a/java/org/apache/catalina/AccessLog.java +++ b/java/org/apache/catalina/AccessLog.java @@ -38,28 +38,35 @@ public interface AccessLog { * the AccessLog. */ public static final String REMOTE_ADDR_ATTRIBUTE = - "org.apache.catalina.AccessLog.RemoteAddr"; + "org.apache.catalina.AccessLog.RemoteAddr"; /** * Name of request attribute used to override remote host name recorded by * the AccessLog. */ public static final String REMOTE_HOST_ATTRIBUTE = - "org.apache.catalina.AccessLog.RemoteHost"; + "org.apache.catalina.AccessLog.RemoteHost"; /** * Name of request attribute used to override the protocol recorded by the * AccessLog. */ public static final String PROTOCOL_ATTRIBUTE = - "org.apache.catalina.AccessLog.Protocol"; + "org.apache.catalina.AccessLog.Protocol"; + + /** + * Name of request attribute used to override the server name recorded by + * the AccessLog. + */ + public static final String SERVER_NAME_ATTRIBUTE = + "org.apache.catalina.AccessLog.ServerName"; /** * Name of request attribute used to override the server port recorded by * the AccessLog. */ public static final String SERVER_PORT_ATTRIBUTE = - "org.apache.catalina.AccessLog.ServerPort"; + "org.apache.catalina.AccessLog.ServerPort"; /** diff --git a/java/org/apache/catalina/filters/LocalStrings.properties b/java/org/apache/catalina/filters/LocalStrings.properties index 41a4bef..3f2b136 100644 --- a/java/org/apache/catalina/filters/LocalStrings.properties +++ b/java/org/apache/catalina/filters/LocalStrings.properties @@ -55,6 +55,8 @@ httpHeaderSecurityFilter.committed=Unable to add HTTP headers since response is remoteCidrFilter.invalid=Invalid configuration provided for [{0}]. See previous messages for details. remoteCidrFilter.noRemoteIp=Client does not have an IP address. Request denied. +remoteIpFilter.invalidHostHeader=Invalid value [{0}] found for Host in HTTP header [{1}] +remoteIpFilter.invalidHostWithPort=Host value [{0}] in HTTP header [{1}] included a port number which will be ignored remoteIpFilter.invalidNumber=Illegal number for parameter [{0}]: [{1}] requestFilter.deny=Denied request for [{0}] based on property [{1}] diff --git a/java/org/apache/catalina/filters/RemoteIpFilter.java b/java/org/apache/catalina/filters/RemoteIpFilter.java index 1afe033..4664a85 100644 --- a/java/org/apache/catalina/filters/RemoteIpFilter.java +++ b/java/org/apache/catalina/filters/RemoteIpFilter.java @@ -45,6 +45,7 @@ import org.apache.catalina.util.RequestUtil; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.http.FastHttpDateFormat; +import org.apache.tomcat.util.http.parser.Host; import org.apache.tomcat.util.res.StringManager; /** @@ -448,6 +449,8 @@ public class RemoteIpFilter extends GenericFilter { protected final Map<String, List<String>> headers; + protected String localName; + protected int localPort; protected String remoteAddr; @@ -458,15 +461,19 @@ public class RemoteIpFilter extends GenericFilter { protected boolean secure; + protected String serverName; + protected int serverPort; public XForwardedRequest(HttpServletRequest request) { super(request); + this.localName = request.getLocalName(); this.localPort = request.getLocalPort(); this.remoteAddr = request.getRemoteAddr(); this.remoteHost = request.getRemoteHost(); this.scheme = request.getScheme(); this.secure = request.isSecure(); + this.serverName = request.getServerName(); this.serverPort = request.getServerPort(); headers = new HashMap<>(); @@ -531,6 +538,11 @@ public class RemoteIpFilter extends GenericFilter { } @Override + public String getLocalName() { + return localName; + } + + @Override public int getLocalPort() { return localPort; } @@ -551,6 +563,11 @@ public class RemoteIpFilter extends GenericFilter { } @Override + public String getServerName() { + return serverName; + } + + @Override public int getServerPort() { return serverPort; } @@ -578,6 +595,10 @@ public class RemoteIpFilter extends GenericFilter { } + public void setLocalName(String localName) { + this.localName = localName; + } + public void setLocalPort(int localPort) { this.localPort = localPort; } @@ -598,6 +619,10 @@ public class RemoteIpFilter extends GenericFilter { this.secure = secure; } + public void setServerName(String serverName) { + this.serverName = serverName; + } + public void setServerPort(int serverPort) { this.serverPort = serverPort; } @@ -642,8 +667,12 @@ public class RemoteIpFilter extends GenericFilter { protected static final String PROTOCOL_HEADER_HTTPS_VALUE_PARAMETER = "protocolHeaderHttpsValue"; + protected static final String HOST_HEADER_PARAMETER = "hostHeader"; + protected static final String PORT_HEADER_PARAMETER = "portHeader"; + protected static final String CHANGE_LOCAL_NAME_PARAMETER = "changeLocalName"; + protected static final String CHANGE_LOCAL_PORT_PARAMETER = "changeLocalPort"; protected static final String PROXIES_HEADER_PARAMETER = "proxiesHeader"; @@ -716,6 +745,10 @@ public class RemoteIpFilter extends GenericFilter { private String protocolHeaderHttpsValue = "https"; + private String hostHeader = null; + + private boolean changeLocalName = false; + private String portHeader = null; private boolean changeLocalPort = false; @@ -822,17 +855,37 @@ public class RemoteIpFilter extends GenericFilter { } } + if (hostHeader != null) { + String hostHeaderValue = request.getHeader(hostHeader); + if (hostHeaderValue != null) { + try { + int portIndex = Host.parse(hostHeaderValue); + if (portIndex > -1) { + log.debug(sm.getString("remoteIpFilter.invalidHostWithPort", hostHeaderValue, hostHeader)); + hostHeaderValue = hostHeaderValue.substring(0, portIndex); + } + + xRequest.setServerName(hostHeaderValue); + if (isChangeLocalName()) { + xRequest.setLocalName(hostHeaderValue); + } + + } catch (IllegalArgumentException iae) { + log.debug(sm.getString("remoteIpFilter.invalidHostHeader", hostHeaderValue, hostHeader)); + } + } + } request.setAttribute(Globals.REQUEST_FORWARDED_ATTRIBUTE, Boolean.TRUE); if (log.isDebugEnabled()) { - log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" + request.getRemoteAddr() - + "', originalRemoteHost='" + request.getRemoteHost() + "', originalSecure='" + request.isSecure() - + "', originalScheme='" + request.getScheme() + "', original[" + remoteIpHeader + "]='" - + concatRemoteIpHeaderValue + "', original[" + protocolHeader + "]='" - + (protocolHeader == null ? null : request.getHeader(protocolHeader)) + "' will be seen as newRemoteAddr='" - + xRequest.getRemoteAddr() + "', newRemoteHost='" + xRequest.getRemoteHost() + "', newScheme='" - + xRequest.getScheme() + "', newSecure='" + xRequest.isSecure() + "', new[" + remoteIpHeader + "]='" - + xRequest.getHeader(remoteIpHeader) + "', new[" + proxiesHeader + "]='" + xRequest.getHeader(proxiesHeader) + "'"); + log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr [" + request.getRemoteAddr() + + "], originalRemoteHost=[" + request.getRemoteHost() + "], originalSecure=[" + request.isSecure() + + "], originalScheme=[" + request.getScheme() + "], originalServerName=[" + request.getServerName() + + "], originalServerPort=[" + request.getServerPort() + + "] will be seen as newRemoteAddr=[" + xRequest.getRemoteAddr() + + "], newRemoteHost=[" + xRequest.getRemoteHost() + "], newSecure=[" + xRequest.isSecure() + + "], newScheme=[" + xRequest.getScheme() + "], newServerName=[" + xRequest.getServerName() + + "], newServerPort=[" + xRequest.getServerPort() + "]"); } if (requestAttributesEnabled) { request.setAttribute(AccessLog.REMOTE_ADDR_ATTRIBUTE, @@ -843,6 +896,8 @@ public class RemoteIpFilter extends GenericFilter { xRequest.getRemoteHost()); request.setAttribute(AccessLog.PROTOCOL_ATTRIBUTE, xRequest.getProtocol()); + request.setAttribute(AccessLog.SERVER_NAME_ATTRIBUTE, + xRequest.getServerName()); request.setAttribute(AccessLog.SERVER_PORT_ATTRIBUTE, Integer.valueOf(xRequest.getServerPort())); } @@ -909,6 +964,10 @@ public class RemoteIpFilter extends GenericFilter { } } + public boolean isChangeLocalName() { + return changeLocalName; + } + public boolean isChangeLocalPort() { return changeLocalPort; } @@ -968,10 +1027,18 @@ public class RemoteIpFilter extends GenericFilter { setProtocolHeaderHttpsValue(getInitParameter(PROTOCOL_HEADER_HTTPS_VALUE_PARAMETER)); } + if (getInitParameter(HOST_HEADER_PARAMETER) != null) { + setHostHeader(getInitParameter(HOST_HEADER_PARAMETER)); + } + if (getInitParameter(PORT_HEADER_PARAMETER) != null) { setPortHeader(getInitParameter(PORT_HEADER_PARAMETER)); } + if (getInitParameter(CHANGE_LOCAL_NAME_PARAMETER) != null) { + setChangeLocalName(Boolean.parseBoolean(getInitParameter(CHANGE_LOCAL_NAME_PARAMETER))); + } + if (getInitParameter(CHANGE_LOCAL_PORT_PARAMETER) != null) { setChangeLocalPort(Boolean.parseBoolean(getInitParameter(CHANGE_LOCAL_PORT_PARAMETER))); } @@ -1008,6 +1075,22 @@ public class RemoteIpFilter extends GenericFilter { /** * <p> * If <code>true</code>, the return values for both {@link + * ServletRequest#getLocalName()} and {@link ServletRequest#getServerName()} + * will be modified by this Filter rather than just + * {@link ServletRequest#getServerName()}. + * </p> + * <p> + * Default value : <code>false</code> + * </p> + * @param changeLocalName The new flag value + */ + public void setChangeLocalName(boolean changeLocalName) { + this.changeLocalName = changeLocalName; + } + + /** + * <p> + * If <code>true</code>, the return values for both {@link * ServletRequest#getLocalPort()} and {@link ServletRequest#getServerPort()} * will be modified by this Filter rather than just * {@link ServletRequest#getServerPort()}. @@ -1067,6 +1150,20 @@ public class RemoteIpFilter extends GenericFilter { /** * <p> + * Header that holds the incoming host, usually named + * <code>X-Forwarded-HOst</code>. + * </p> + * <p> + * Default value : <code>null</code> + * </p> + * @param hostHeader The header name + */ + public void setHostHeader(String hostHeader) { + this.hostHeader = hostHeader; + } + + /** + * <p> * Header that holds the incoming port, usually named * <code>X-Forwarded-Port</code>. If <code>null</code>, * {@link #httpServerPort} or {@link #httpsServerPort} will be used. diff --git a/java/org/apache/catalina/valves/AbstractAccessLogValve.java b/java/org/apache/catalina/valves/AbstractAccessLogValve.java index 62d52e7..9f700e3 100644 --- a/java/org/apache/catalina/valves/AbstractAccessLogValve.java +++ b/java/org/apache/catalina/valves/AbstractAccessLogValve.java @@ -1394,11 +1394,24 @@ public abstract class AbstractAccessLogValve extends ValveBase implements Access @Override public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time) { + String value = null; + if (requestAttributesEnabled) { + Object serverName = request.getAttribute(SERVER_NAME_ATTRIBUTE); + if (serverName != null) { + value = serverName.toString(); + } + } + if (value == null || value.length() == 0) { + value = request.getServerName(); + } + if (value == null || value.length() == 0) { + value = "-"; + } + if (ipv6Canonical) { - buf.append(IPv6Utils.canonize(request.getServerName())); - } else { - buf.append(request.getServerName()); + value = IPv6Utils.canonize(value); } + buf.append(value); } } diff --git a/java/org/apache/catalina/valves/LocalStrings.properties b/java/org/apache/catalina/valves/LocalStrings.properties index 74821cd..7cf2d11 100644 --- a/java/org/apache/catalina/valves/LocalStrings.properties +++ b/java/org/apache/catalina/valves/LocalStrings.properties @@ -132,6 +132,8 @@ jdbcAccessLogValve.exception=Exception performing insert access entry remoteCidrValve.invalid=Invalid configuration provided for [{0}]. See previous messages for details. remoteCidrValve.noRemoteIp=Client does not have an IP address. Request denied. +remoteIpValve.invalidHostHeader=Invalid value [{0}] found for Host in HTTP header [{1}] +remoteIpValve.invalidHostWithPort=Host value [{0}] in HTTP header [{1}] included a port number which will be ignored remoteIpValve.invalidPortHeader=Invalid value [{0}] found for port in HTTP header [{1}] requestFilterValve.configInvalid=One or more invalid configuration settings were provided for the Remote[Addr|Host]Valve which prevented the Valve and its parent containers from starting diff --git a/java/org/apache/catalina/valves/RemoteIpValve.java b/java/org/apache/catalina/valves/RemoteIpValve.java index cd08cc7..89aa4b4 100644 --- a/java/org/apache/catalina/valves/RemoteIpValve.java +++ b/java/org/apache/catalina/valves/RemoteIpValve.java @@ -32,6 +32,7 @@ import org.apache.catalina.connector.Response; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.http.MimeHeaders; +import org.apache.tomcat.util.http.parser.Host; /** * <p> @@ -394,6 +395,10 @@ public class RemoteIpValve extends ValveBase { return result.toString(); } + private String hostHeader = null; + + private boolean changeLocalName = false; + /** * @see #setHttpServerPort(int) */ @@ -404,6 +409,8 @@ public class RemoteIpValve extends ValveBase { */ private int httpsServerPort = 443; + private String portHeader = null; + private boolean changeLocalPort = false; /** @@ -429,8 +436,6 @@ public class RemoteIpValve extends ValveBase { */ private String protocolHeaderHttpsValue = "https"; - private String portHeader = null; - /** * @see #setProxiesHeader(String) */ @@ -461,21 +466,42 @@ public class RemoteIpValve extends ValveBase { super(true); } + /** + * Obtain the name of the HTTP header used to override the value returned + * by {@link Request#getServerName()} and (optionally depending on {link + * {@link #isChangeLocalName()} {@link Request#getLocalName()}. + * + * @return The HTTP header name + */ + public String getHostHeader() { + return hostHeader; + } - public int getHttpsServerPort() { - return httpsServerPort; + /** + * Set the name of the HTTP header used to override the value returned + * by {@link Request#getServerName()} and (optionally depending on {link + * {@link #isChangeLocalName()} {@link Request#getLocalName()}. + * + * @param hostHeader The HTTP header name + */ + public void setHostHeader(String hostHeader) { + this.hostHeader = hostHeader; } - public int getHttpServerPort() { - return httpServerPort; + public boolean isChangeLocalName() { + return changeLocalName; } - public boolean isChangeLocalPort() { - return changeLocalPort; + public void setChangeLocalName(boolean changeLocalName) { + this.changeLocalName = changeLocalName; } - public void setChangeLocalPort(boolean changeLocalPort) { - this.changeLocalPort = changeLocalPort; + public int getHttpServerPort() { + return httpServerPort; + } + + public int getHttpsServerPort() { + return httpsServerPort; } /** @@ -500,6 +526,14 @@ public class RemoteIpValve extends ValveBase { this.portHeader = portHeader; } + public boolean isChangeLocalPort() { + return changeLocalPort; + } + + public void setChangeLocalPort(boolean changeLocalPort) { + this.changeLocalPort = changeLocalPort; + } + /** * @see #setInternalProxies(String) * @return Regular expression that defines the internal proxies @@ -572,7 +606,10 @@ public class RemoteIpValve extends ValveBase { final String originalRemoteHost = request.getRemoteHost(); final String originalScheme = request.getScheme(); final boolean originalSecure = request.isSecure(); + final String originalServerName = request.getServerName(); + final String originalLocalName = request.getLocalName(); final int originalServerPort = request.getServerPort(); + final int originalLocalPort = request.getLocalPort(); final String originalProxiesHeader = request.getHeader(proxiesHeader); final String originalRemoteIpHeader = request.getHeader(remoteIpHeader); boolean isInternal = internalProxies != null && @@ -653,13 +690,38 @@ public class RemoteIpValve extends ValveBase { } } + if (hostHeader != null) { + String hostHeaderValue = request.getHeader(hostHeader); + if (hostHeaderValue != null) { + try { + int portIndex = Host.parse(hostHeaderValue); + if (portIndex > -1) { + log.debug(sm.getString("remoteIpValve.invalidHostWithPort", hostHeaderValue, hostHeader)); + hostHeaderValue = hostHeaderValue.substring(0, portIndex); + } + + request.getCoyoteRequest().serverName().setString(hostHeaderValue); + if (isChangeLocalName()) { + request.getCoyoteRequest().localName().setString(hostHeaderValue); + } + + } catch (IllegalArgumentException iae) { + log.debug(sm.getString("remoteIpValve.invalidHostHeader", hostHeaderValue, hostHeader)); + } + } + } + request.setAttribute(Globals.REQUEST_FORWARDED_ATTRIBUTE, Boolean.TRUE); if (log.isDebugEnabled()) { - log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" + originalRemoteAddr - + "', originalRemoteHost='" + originalRemoteHost + "', originalSecure='" + originalSecure + "', originalScheme='" - + originalScheme + "' will be seen as newRemoteAddr='" + request.getRemoteAddr() + "', newRemoteHost='" - + request.getRemoteHost() + "', newScheme='" + request.getScheme() + "', newSecure='" + request.isSecure() + "'"); + log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr [" + originalRemoteAddr + + "], originalRemoteHost=[" + originalRemoteHost + "], originalSecure=[" + originalSecure + + "], originalScheme=[" + originalScheme + "], originalServerName=[" + originalServerName + + "], originalServerPort=[" + originalServerPort + + "] will be seen as newRemoteAddr=[" + request.getRemoteAddr() + + "], newRemoteHost=[" + request.getRemoteHost() + "], newSecure=[" + request.isSecure() + + "], newScheme=[" + request.getScheme() + "], newServerName=[" + request.getServerName() + + "], newServerPort=[" + request.getServerPort() + "]"); } } else { if (log.isDebugEnabled()) { @@ -676,6 +738,8 @@ public class RemoteIpValve extends ValveBase { request.getRemoteHost()); request.setAttribute(AccessLog.PROTOCOL_ATTRIBUTE, request.getProtocol()); + request.setAttribute(AccessLog.SERVER_NAME_ATTRIBUTE, + Integer.valueOf(request.getServerName())); request.setAttribute(AccessLog.SERVER_PORT_ATTRIBUTE, Integer.valueOf(request.getServerPort())); } @@ -686,7 +750,10 @@ public class RemoteIpValve extends ValveBase { request.setRemoteHost(originalRemoteHost); request.setSecure(originalSecure); request.getCoyoteRequest().scheme().setString(originalScheme); + request.getCoyoteRequest().serverName().setString(originalServerName); + request.getCoyoteRequest().localName().setString(originalLocalName); request.setServerPort(originalServerPort); + request.setLocalPort(originalLocalPort); MimeHeaders headers = request.getCoyoteRequest().getMimeHeaders(); if (originalProxiesHeader == null || originalProxiesHeader.length() == 0) { diff --git a/test/org/apache/catalina/filters/TestRemoteIpFilter.java b/test/org/apache/catalina/filters/TestRemoteIpFilter.java index 956bbf1..88e8f6b 100644 --- a/test/org/apache/catalina/filters/TestRemoteIpFilter.java +++ b/test/org/apache/catalina/filters/TestRemoteIpFilter.java @@ -97,6 +97,7 @@ public class TestRemoteIpFilter extends TomcatBaseTest { writer.println("request.remoteHost=" + request.getRemoteHost()); writer.println("request.secure=" + request.isSecure()); writer.println("request.scheme=" + request.getScheme()); + writer.println("request.serverName=" + request.getServerName()); writer.println("request.serverPort=" + request.getServerPort()); writer.println(); @@ -549,6 +550,85 @@ public class TestRemoteIpFilter extends TomcatBaseTest { } @Test + public void testInvokeXforwardedHost() throws Exception { + // PREPARE + FilterDef filterDef = new FilterDef(); + filterDef.addInitParameter("hostHeader", "x-forwarded-host"); + filterDef.addInitParameter("portHeader", "x-forwarded-port"); + filterDef.addInitParameter("protocolHeader", "x-forwarded-proto"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + // protocol + request.setSecure(false); + request.setServerPort(8080); + request.setScheme("http"); + // host and port + request.getCoyoteRequest().serverName().setString("10.0.0.1"); + request.setHeader("x-forwarded-host", "example.com"); + request.setHeader("x-forwarded-port", "8443"); + request.setHeader("x-forwarded-proto", "https"); + + // TEST + HttpServletRequest actualRequest = testRemoteIpFilter(filterDef, request).getRequest(); + + // VERIFY + // protocol + String actualServerName = actualRequest.getServerName(); + Assert.assertEquals("postInvoke serverName", "example.com", actualServerName); + + String actualScheme = actualRequest.getScheme(); + Assert.assertEquals("postInvoke scheme", "https", actualScheme); + + int actualServerPort = actualRequest.getServerPort(); + Assert.assertEquals("postInvoke serverPort", 8443, actualServerPort); + + boolean actualSecure = actualRequest.isSecure(); + Assert.assertTrue("postInvoke secure", actualSecure); + } + + @Test + public void testInvokeXforwardedHostAndPort() throws Exception { + // PREPARE + FilterDef filterDef = new FilterDef(); + filterDef.addInitParameter("hostHeader", "x-forwarded-host"); + filterDef.addInitParameter("portHeader", "x-forwarded-port"); + filterDef.addInitParameter("protocolHeader", "x-forwarded-proto"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + // protocol + request.setSecure(false); + request.setServerPort(8080); + request.setScheme("http"); + // host and port + request.getCoyoteRequest().serverName().setString("10.0.0.1"); + request.setHeader("x-forwarded-host", "example.com:8443"); + request.setHeader("x-forwarded-proto", "https"); + + // TEST + HttpServletRequest actualRequest = testRemoteIpFilter(filterDef, request).getRequest(); + + // VERIFY + // protocol + String actualServerName = actualRequest.getServerName(); + Assert.assertEquals("postInvoke serverName", "example.com", actualServerName); + + String actualScheme = actualRequest.getScheme(); + Assert.assertEquals("postInvoke scheme", "https", actualScheme); + + int actualServerPort = actualRequest.getServerPort(); + Assert.assertEquals("postInvoke serverPort", 443, actualServerPort); + + boolean actualSecure = actualRequest.isSecure(); + Assert.assertTrue("postInvoke secure", actualSecure); + } + + @Test public void testListToCommaDelimitedString() { String[] actual = RemoteIpFilter.commaDelimitedListToStringArray("element1, element2, element3"); String[] expected = new String[] { "element1", "element2", "element3" }; diff --git a/test/org/apache/catalina/valves/TestRemoteIpValve.java b/test/org/apache/catalina/valves/TestRemoteIpValve.java index 8ab03b4..6b3d511 100644 --- a/test/org/apache/catalina/valves/TestRemoteIpValve.java +++ b/test/org/apache/catalina/valves/TestRemoteIpValve.java @@ -41,6 +41,7 @@ public class TestRemoteIpValve { private String remoteHost; private String scheme; private boolean secure; + private String serverName; private int serverPort; private String forwardedFor; private String forwardedBy; @@ -57,6 +58,10 @@ public class TestRemoteIpValve { return scheme; } + public String getServerName() { + return serverName; + } + public int getServerPort() { return serverPort; } @@ -79,6 +84,7 @@ public class TestRemoteIpValve { this.remoteAddr = request.getRemoteAddr(); this.scheme = request.getScheme(); this.secure = request.isSecure(); + this.serverName = request.getServerName(); this.serverPort = request.getServerPort(); this.forwardedFor = request.getHeader("x-forwarded-for"); this.forwardedBy = request.getHeader("x-forwarded-by"); @@ -851,6 +857,118 @@ public class TestRemoteIpValve { } @Test + public void testInvokeXforwardedHost() throws Exception { + + // PREPARE + RemoteIpValve remoteIpValve = new RemoteIpValve(); + remoteIpValve.setHostHeader("x-forwarded-host"); + remoteIpValve.setPortHeader("x-forwarded-port"); + remoteIpValve.setProtocolHeader("x-forwarded-proto"); + RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve(); + remoteIpValve.setNext(remoteAddrAndHostTrackerValve); + + Request request = new MockRequest(); + request.setCoyoteRequest(new org.apache.coyote.Request()); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + // protocol + request.setSecure(false); + request.setServerPort(8080); + request.getCoyoteRequest().scheme().setString("http"); + // host and port + request.getCoyoteRequest().serverName().setString("10.0.0.1"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-host").setString("example.com:8443"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-port").setString("8443"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-proto").setString("https"); + + // TEST + remoteIpValve.invoke(request, null); + + // VERIFY + // protocol + String actualServerName = remoteAddrAndHostTrackerValve.getServerName(); + Assert.assertEquals("tracked serverName", "example.com", actualServerName); + + String actualScheme = remoteAddrAndHostTrackerValve.getScheme(); + Assert.assertEquals("tracked scheme", "https", actualScheme); + + int actualServerPort = remoteAddrAndHostTrackerValve.getServerPort(); + Assert.assertEquals("tracked serverPort", 8443, actualServerPort); + + boolean actualSecure = remoteAddrAndHostTrackerValve.isSecure(); + Assert.assertTrue("tracked secure", actualSecure); + + String actualPostInvokeServerName = request.getServerName(); + Assert.assertEquals("postInvoke serverName", "10.0.0.1", actualPostInvokeServerName); + + boolean actualPostInvokeSecure = request.isSecure(); + Assert.assertFalse("postInvoke secure", actualPostInvokeSecure); + + int actualPostInvokeServerPort = request.getServerPort(); + Assert.assertEquals("postInvoke serverPort", 8080, actualPostInvokeServerPort); + + String actualPostInvokeScheme = request.getScheme(); + Assert.assertEquals("postInvoke scheme", "http", actualPostInvokeScheme); + } + + @Test + public void testInvokeXforwardedHostAndPort() throws Exception { + + // PREPARE + RemoteIpValve remoteIpValve = new RemoteIpValve(); + remoteIpValve.setHostHeader("x-forwarded-host"); + remoteIpValve.setPortHeader("x-forwarded-port"); + remoteIpValve.setProtocolHeader("x-forwarded-proto"); + RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve(); + remoteIpValve.setNext(remoteAddrAndHostTrackerValve); + + Request request = new MockRequest(); + request.setCoyoteRequest(new org.apache.coyote.Request()); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + // protocol + request.setSecure(false); + request.setServerPort(8080); + request.getCoyoteRequest().scheme().setString("http"); + // host and port + request.getCoyoteRequest().serverName().setString("10.0.0.1"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-host").setString("example.com"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-port").setString("8443"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-proto").setString("https"); + + // TEST + remoteIpValve.invoke(request, null); + + // VERIFY + // protocol + String actualServerName = remoteAddrAndHostTrackerValve.getServerName(); + Assert.assertEquals("tracked serverName", "example.com", actualServerName); + + String actualScheme = remoteAddrAndHostTrackerValve.getScheme(); + Assert.assertEquals("tracked scheme", "https", actualScheme); + + int actualServerPort = remoteAddrAndHostTrackerValve.getServerPort(); + Assert.assertEquals("tracked serverPort", 8443, actualServerPort); + + boolean actualSecure = remoteAddrAndHostTrackerValve.isSecure(); + Assert.assertTrue("tracked secure", actualSecure); + + String actualPostInvokeServerName = request.getServerName(); + Assert.assertEquals("postInvoke serverName", "10.0.0.1", actualPostInvokeServerName); + + boolean actualPostInvokeSecure = request.isSecure(); + Assert.assertFalse("postInvoke secure", actualPostInvokeSecure); + + int actualPostInvokeServerPort = request.getServerPort(); + Assert.assertEquals("postInvoke serverPort", 8080, actualPostInvokeServerPort); + + String actualPostInvokeScheme = request.getScheme(); + Assert.assertEquals("postInvoke scheme", "http", actualPostInvokeScheme); + } + + @Test public void testInvokeNotAllowedRemoteAddr() throws Exception { // PREPARE RemoteIpValve remoteIpValve = new RemoteIpValve(); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index b70fc1c..62cc09a 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -47,6 +47,11 @@ <section name="Tomcat 9.0.23 (markt)" rtext="in development"> <subsection name="Catalina"> <changelog> + <add> + <bug>57665</bug>: Add support for the <code>X-Forwarded-Host</code> + header to the <code>RemoteIpFilter</code> and <code>RemotepValve</code>. + (markt) + </add> <fix> <bug>63550</bug>: Only try the <code>alternateURL</code> in the <code>JNDIRealm</code> if one has been specified. (markt) diff --git a/webapps/docs/config/filter.xml b/webapps/docs/config/filter.xml index 9e5b5fb..72cbaf4 100644 --- a/webapps/docs/config/filter.xml +++ b/webapps/docs/config/filter.xml @@ -1597,6 +1597,12 @@ FINE: Request "/docs/config/manager.html" with response status "200" default of <code>X-Forwarded-Proto</code> is used.</p> </attribute> + <attribute name="hostHeader" required="false"> + <p>Name of the HTTP Header read by this valve that holds the host + used by the client to connect to the proxy. If not specified, the + default of <code>null</code> is used.</p> + </attribute> + <attribute name="portHeader" required="false"> <p>Name of the HTTP Header read by this valve that holds the port used by the client to connect to the proxy. If not specified, the @@ -1623,6 +1629,13 @@ FINE: Request "/docs/config/manager.html" with response status "200" specified, the default of <code>443</code> is used.</p> </attribute> + <attribute name="changeLocalName" required="false"> + <p>If <code>true</code>, the value returned by + <code>ServletRequest.getLocalName()</code> and + <code>ServletRequest.getServerName()</code> is modified by the this + filter. If not specified, the default of <code>false</code> is used.</p> + </attribute> + <attribute name="changeLocalPort" required="false"> <p>If <code>true</code>, the value returned by <code>ServletRequest.getLocalPort()</code> and diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml index 3b889a3..5abc2c3 100644 --- a/webapps/docs/config/valve.xml +++ b/webapps/docs/config/valve.xml @@ -1003,6 +1003,12 @@ default of <code>X-Forwarded-Proto</code> is used.</p> </attribute> + <attribute name="hostHeader" required="false"> + <p>Name of the HTTP Header read by this valve that holds the host + used by the client to connect to the proxy. If not specified, the + default of <code>null</code> is used.</p> + </attribute> + <attribute name="portHeader" required="false"> <p>Name of the HTTP Header read by this valve that holds the port used by the client to connect to the proxy. If not specified, the @@ -1029,6 +1035,13 @@ specified, the default of <code>443</code> is used.</p> </attribute> + <attribute name="changeLocalHost" required="false"> + <p>If <code>true</code>, the value returned by + <code>ServletRequest.getLocalHost()</code> and + <code>ServletRequest.getServerHost()</code> is modified by the this + valve. If not specified, the default of <code>false</code> is used.</p> + </attribute> + <attribute name="changeLocalPort" required="false"> <p>If <code>true</code>, the value returned by <code>ServletRequest.getLocalPort()</code> and --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org