Index: java/org/apache/catalina/connector/RemoteIpValve.java
===================================================================
--- java/org/apache/catalina/connector/RemoteIpValve.java (revision 0)
+++ java/org/apache/catalina/connector/RemoteIpValve.java (revision 0)
@@ -0,0 +1,528 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.connector;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.util.StringManager;
+import org.apache.catalina.valves.Constants;
+import org.apache.catalina.valves.RequestFilterValve;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ *
+ * Tomcat port of mod_remoteip, this valve replaces the apparent
+ * client remote IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request
+ * headers.
+ *
+ *
+ * This valve proceeds as follows:
+ *
+ * - Check if the incoming
request.getRemoteAddr()
matches the valve's list of internal proxies.
+ * - If so, loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer or proxy in the given request's
+ * Http header named
remoteIPHeader
(default value x-forwarded-for
). Values are processed in Right-to-Left order.
+ * - For each ip/host of the list:
+ *
+ * - if it matches the internal proxies list, the ip/host is swallowed
+ * - if it matches the trusted proxies list, the ip/host is added to the created proxies header
+ * - otherwise, the ip/host is declared to be the remote ip and looping is stopped.
+ *
+ *
+ *
+ *
+ *
+ * Configuration parameters:
+ *
+ *
+ * RemoteIpValve property |
+ * Equivalent mod_remoteip directive |
+ * Format |
+ * Default Value |
+ *
+ *
+ * remoteIPHeader |
+ * RemoteIPHeader |
+ * Compliant http header string |
+ * x-forwarded-for |
+ *
+ *
+ * internalProxies |
+ * RemoteIPInternalProxy |
+ * Comma delimited list of regular expressions (in the syntax supported by the {@link java.util.regex.Pattern} library) |
+ * 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3}
+ * By default, 10/8, 192.168/16, 169.254/16 and 127/8 are allowed ; 172.16/12 has not been enabled by default because it is complex to
+ * describe with regular expressions |
+ *
+ *
+ *
+ * proxiesHeader |
+ * RemoteIPProxiesHeader |
+ * Compliant http header String |
+ * x-forwarded-by |
+ *
+ *
+ * trustedProxies |
+ * RemoteIPTrustedProxy |
+ * Comma delimited list of regular expressions (in the syntax supported by the {@link java.util.regex.Pattern} library) |
+ * |
+ *
+ *
+ *
+ *
+ *
+ * This Valve may be attached to any Container, depending on the granularity of the filtering you wish to perform.
+ *
+ *
+ * Regular expression vs. IP address blocks: mod_remoteip
allows to use address blocks (e.g.
+ * 192.168/16
) to configure RemoteIPInternalProxy
and RemoteIPTrustedProxy
; as Tomcat doesn't have a
+ * library similar to apr_ipsubnet_test,
+ * RemoteIpValve
uses regular expression to configure internalProxies
and trustedProxies
in the same
+ * fashion as {@link RequestFilterValve} does.
+ *
+ *
+ * Package org.apache.catalina.connector vs. org.apache.catalina.valves: This valve is
+ * temporarily located in org.apache.catalina.connector
package instead of org.apache.catalina.valves
because it
+ * uses protected
visibility of {@link Request#remoteAddr} and {@link Request#remoteHost}. This valve could move to
+ * org.apache.catalina.valves
if {@link Request#setRemoteAddr(String)} and {@link Request#setRemoteHost(String)} were modified
+ * to no longer be no-op but actually set the underlying property.
+ *
+ *
+ *
+ * Sample with trusted proxies
+ *
+ *
+ * RemoteIpValve configuration:
+ *
+ *
+ * <Valve
+ * className="org.apache.catalina.connector.RemoteIpValve"
+ * allowedInternalProxies="192\.168\.0\.10, 192\.168\.0\.11"
+ * remoteIPHeader="x-forwarded-for"
+ * remoteIPProxiesHeader="x-forwarded-by"
+ * trustedProxies="proxy1, proxy2"
+ * />
+ *
+ * Request values:
+ *
+ *
+ * property |
+ * Value Before RemoteIpValve |
+ * Value After RemoteIpValve |
+ *
+ *
+ * request.remoteAddr |
+ * 192.168.0.10 |
+ * 140.211.11.130 |
+ *
+ *
+ * request.header['x-forwarded-for'] |
+ * 140.211.11.130, proxy1, proxy2 |
+ * null |
+ *
+ *
+ * request.header['x-forwarded-by'] |
+ * null |
+ * proxy1, proxy2 |
+ *
+ *
+ * Note : proxy1
and proxy2
are both trusted proxies that come in x-forwarded-for
header, they both
+ * are migrated in x-forwarded-by
header. x-forwarded-by
is null because all the proxies are trusted or internal.
+ *
+ *
+ *
+ * Sample with internal and trusted proxies
+ *
+ *
+ * RemoteIpValve configuration:
+ *
+ *
+ * <Valve
+ * className="org.apache.catalina.connector.RemoteIpValve"
+ * allowedInternalProxies="192\.168\.0\.10, 192\.168\.0\.11"
+ * remoteIPHeader="x-forwarded-for"
+ * remoteIPProxiesHeader="x-forwarded-by"
+ * trustedProxies="proxy1, proxy2"
+ * />
+ *
+ * Request values:
+ *
+ *
+ * property |
+ * Value Before RemoteIpValve |
+ * Value After RemoteIpValve |
+ *
+ *
+ * request.remoteAddr |
+ * 192.168.0.10 |
+ * 140.211.11.130 |
+ *
+ *
+ * request.header['x-forwarded-for'] |
+ * 140.211.11.130, proxy1, proxy2, 192.168.0.10 |
+ * null |
+ *
+ *
+ * request.header['x-forwarded-by'] |
+ * null |
+ * proxy1, proxy2 |
+ *
+ *
+ * Note : proxy1
and proxy2
are both trusted proxies that come in x-forwarded-for
header, they both
+ * are migrated in x-forwarded-by
header. As 192.168.0.10
is an internal proxy, it does not appear in
+ * x-forwarded-by
. x-forwarded-by
is null because all the proxies are trusted or internal.
+ *
+ *
+ *
+ * Sample with internal proxies
+ *
+ *
+ * RemoteIpValve configuration:
+ *
+ *
+ * <Valve
+ * className="org.apache.catalina.connector.RemoteIpValve"
+ * allowedInternalProxies="192\.168\.0\.10, 192\.168\.0\.11"
+ * remoteIPHeader="x-forwarded-for"
+ * remoteIPProxiesHeader="x-forwarded-by"
+ * />
+ *
+ *
+ * Request values:
+ *
+ *
+ * property |
+ * Value Before RemoteIpValve |
+ * Value After RemoteIpValve |
+ *
+ *
+ * request.remoteAddr |
+ * 192.168.0.10 |
+ * 140.211.11.130 |
+ *
+ *
+ * request.header['x-forwarded-for'] |
+ * 140.211.11.130, 192.168.0.10 |
+ * null |
+ *
+ *
+ * request.header['x-forwarded-by'] |
+ * null |
+ * null |
+ *
+ *
+ * Note : x-forwarded-by
header is null because only internal proxies as been traversed by the request.
+ * x-forwarded-by
is null because all the proxies are trusted or internal.
+ *
+ *
+ *
+ * Sample with an untrusted proxy
+ *
+ *
+ * RemoteIpValve configuration:
+ *
+ *
+ * <Valve
+ * className="org.apache.catalina.connector.RemoteIpValve"
+ * allowedInternalProxies="192\.168\.0\.10, 192\.168\.0\.11"
+ * remoteIPHeader="x-forwarded-for"
+ * remoteIPProxiesHeader="x-forwarded-by"
+ * trustedProxies="proxy1, proxy2"
+ * />
+ *
+ * Request values:
+ *
+ *
+ * property |
+ * Value Before RemoteIpValve |
+ * Value After RemoteIpValve |
+ *
+ *
+ * request.remoteAddr |
+ * 192.168.0.10 |
+ * untrusted-proxy |
+ *
+ *
+ * request.header['x-forwarded-for'] |
+ * 140.211.11.130, untrusted-proxy, proxy1 |
+ * 140.211.11.130 |
+ *
+ *
+ * request.header['x-forwarded-by'] |
+ * null |
+ * proxy1 |
+ *
+ *
+ * Note : x-forwarded-by
holds the trusted proxy proxy1
. x-forwarded-by
holds
+ * 140.211.11.130
because untrusted-proxy
is not trusted and thus, we can not trust that
+ * untrusted-proxy
is the actual remote ip. request.remoteAddr
is untrusted-proxy
that is an IP
+ * verified by proxy1
.
+ *
+ *
+ *
+ * TODO : add "remoteIpValve.syntax" NLSString.
+ *
+ */
+public class RemoteIpValve extends ValveBase {
+
+ /**
+ * Logger
+ */
+ private static Log log = LogFactory.getLog(RemoteIpValve.class);
+
+ /**
+ * {@link Pattern} for a comma delimited string that support whitespace characters
+ */
+ private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*");
+
+ /**
+ * The descriptive information related to this implementation.
+ */
+ private static final String info = "org.apache.catalina.connector.RemoteIpValve/1.0";
+
+ /**
+ * The StringManager for this package.
+ */
+ protected static StringManager sm = StringManager.getManager(Constants.Package);
+
+ /**
+ * Convert a given comma delimited list of regular expressions into an array of compiled {@link Pattern}
+ */
+ protected static Pattern[] commaDelimitedListToPatternArray(String commaDelimitedPatterns) {
+ String[] patterns = commaDelimitedListToStringArray(commaDelimitedPatterns);
+ List patternsList = new ArrayList();
+ for (String pattern : patterns) {
+ try {
+ patternsList.add(Pattern.compile(pattern));
+ } catch (PatternSyntaxException e) {
+ throw new IllegalArgumentException(sm.getString("remoteIpValve.syntax", pattern), e);
+ }
+ }
+ return patternsList.toArray(new Pattern[0]);
+ }
+
+ /**
+ * Convert a given comma delimited list of regular expressions into an array of String
+ */
+ protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) {
+ return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern
+ .split(commaDelimitedStrings);
+ }
+
+ /**
+ * Convert an array of strings in a comma delimited string
+ */
+ protected static String listToCommaDelimitedString(List stringList) {
+ if (stringList == null) {
+ return "";
+ }
+ StringBuilder result = new StringBuilder();
+ for (Iterator it = stringList.iterator(); it.hasNext();) {
+ String element = it.next();
+ if (element != null) {
+ result.append(element);
+ if (it.hasNext()) {
+ result.append(", ");
+ }
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Return true
if the given str
matches at least one of the given patterns
.
+ */
+ protected static boolean matchesOne(String str, Pattern... patterns) {
+ for (Pattern pattern : patterns) {
+ if (pattern.matcher(str).matches()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @see #setInternalProxies(String)
+ */
+ private Pattern[] internalProxies = new Pattern[] {
+ Pattern.compile("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"), Pattern.compile("192\\.168\\.\\d{1,3}\\.\\d{1,3}"),
+ Pattern.compile("169\\.254\\.\\d{1,3}\\.\\d{1,3}"), Pattern.compile("127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")
+ };
+
+ /**
+ * @see #setRemoteIPHeader(String)
+ */
+ private String remoteIPHeader = "X-Forwarded-For";
+
+ /**
+ * @see #setProxiesHeader(String)
+ */
+ private String proxiesHeader = "X-Forwarded-By";
+
+ /**
+ * @see RemoteIpValve#setTrustedProxies(String)
+ */
+ private Pattern[] trustedProxies = new Pattern[0];
+
+ /**
+ * Return descriptive information about this Valve implementation.
+ */
+ public String getInfo() {
+ return info;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void invoke(Request request, Response response) throws IOException, ServletException {
+ final String originalRemoteAddr = request.getRemoteAddr();
+ final String originalRemoteHost = request.getRemoteHost();
+
+ if (matchesOne(originalRemoteAddr, internalProxies)) {
+ String remoteIp = null;
+ // In java 6, proxiesHeaderValue should be declared as a java.util.Deque
+ LinkedList proxiesHeaderValue = new LinkedList();
+
+ String[] remoteIPHeaderValue = commaDelimitedListToStringArray(request.getHeader(remoteIPHeader));
+ int idx;
+ // loop on remoteIPHeaderValue to find the first trusted remote ip and to build the proxies chain
+ for (idx = remoteIPHeaderValue.length - 1; idx >= 0; idx--) {
+ String currentRemoteIp = remoteIPHeaderValue[idx];
+ remoteIp = currentRemoteIp;
+ if (matchesOne(currentRemoteIp, internalProxies)) {
+ // do nothing, internalProxies IPs are not appended to the
+ } else if (matchesOne(currentRemoteIp, trustedProxies)) {
+ proxiesHeaderValue.addFirst(currentRemoteIp);
+ } else {
+ idx--; // decrement idx because break statement doesn't do it
+ break;
+ }
+ }
+ // continue to loop on remoteIPHeaderValue to build the new value of the remoteIPHeader
+ LinkedList newRemoteIpHeaderValue = new LinkedList();
+ for (; idx >= 0; idx--) {
+ String currentRemoteIp = remoteIPHeaderValue[idx];
+ newRemoteIpHeaderValue.addFirst(currentRemoteIp);
+ }
+ if (remoteIp != null) {
+ if (log.isInfoEnabled()) {
+ log.debug("Overwrite remoteAddr '" + request.remoteAddr + "' and remoteHost '" + request.remoteHost + "' by remoteIp '"
+ + remoteIp + "' for incoming '" + remoteIPHeader + "' : '" + request.getHeader(remoteIPHeader) + "'");
+ }
+
+ // use field access instead of setters because request.setRemoteAddr(str) and request.setRemoteHost() are no-op in Tomcat 6.0
+ request.remoteAddr = remoteIp;
+ request.remoteHost = remoteIp;
+
+ // use request.coyoteRequest.mimeHeaders.setValue(str).setString(str) because request.addHeader(str, str) is no-op in Tomcat 6.0
+ if (proxiesHeaderValue.size() == 0) {
+ request.getCoyoteRequest().getMimeHeaders().removeHeader(proxiesHeader);
+ } else {
+ String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue);
+ request.getCoyoteRequest().getMimeHeaders().setValue(proxiesHeader).setString(commaDelimitedListOfProxies);
+ }
+ if (newRemoteIpHeaderValue.size() == 0) {
+ request.getCoyoteRequest().getMimeHeaders().removeHeader(remoteIPHeader);
+ } else {
+ String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue);
+ request.getCoyoteRequest().getMimeHeaders().setValue(remoteIPHeader).setString(commaDelimitedRemoteIpHeaderValue);
+ }
+ }
+ }
+ try {
+ getNext().invoke(request, response);
+ } finally {
+ // use field access instead of setters because setters are no-op in Tomcat 6.0
+ request.remoteAddr = originalRemoteAddr;
+ request.remoteHost = originalRemoteHost;
+ }
+ }
+
+ /**
+ *
+ * Comma delimited list of internal proxies. Can be expressed with regular expressions.
+ *
+ *
+ * Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3}
+ *
+ */
+ public void setInternalProxies(String commaAllowedInternalProxies) {
+ this.internalProxies = commaDelimitedListToPatternArray(commaAllowedInternalProxies);
+ }
+
+ /**
+ *
+ * Name of the http header from which the remote ip is extracted.
+ *
+ *
+ * The value of this header can be comma delimited.
+ *
+ *
+ * Default value : X-Forwarded-For
+ *
+ *
+ * @param remoteIPHeader
+ */
+ public void setRemoteIPHeader(String remoteIPHeader) {
+ this.remoteIPHeader = remoteIPHeader;
+ }
+
+ /**
+ *
+ * The proxiesHeader directive specifies a header into which mod_remoteip will collect a list of all of the intermediate client IP
+ * addresses trusted to resolve the actual remote IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header,
+ * while any intermediate RemoteIPInternalProxy addresses are discarded.
+ *
+ *
+ * Name of the http header that holds the list of trusted proxies that has been traversed by the http request.
+ *
+ *
+ * The value of this header can be comma delimited.
+ *
+ *
+ * Default value : X-Forwarded-By
+ *
+ */
+ public void setProxiesHeader(String proxiesHeader) {
+ this.proxiesHeader = proxiesHeader;
+ }
+
+ /**
+ *
+ * Comma delimited list of proxies that are trusted when they appear in the {@link #remoteIPHeader} header. Can be expressed as a
+ * regular expression.
+ *
+ *
+ * Default value : empty list, no external proxy is trusted.
+ *
+ */
+ public void setTrustedProxies(String commaDelimitedTrustedProxies) {
+ this.trustedProxies = commaDelimitedListToPatternArray(commaDelimitedTrustedProxies);
+ }
+}
Index: test/org/apache/catalina/connector/RemoteIpValveTest.java
===================================================================
--- test/org/apache/catalina/connector/RemoteIpValveTest.java (revision 0)
+++ test/org/apache/catalina/connector/RemoteIpValveTest.java (revision 0)
@@ -0,0 +1,379 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.connector;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.valves.ValveBase;
+import org.junit.Test;
+
+/**
+ * {@link RemoteIpValve} Tests
+ */
+public class RemoteIpValveTest {
+
+ static class RemoteAddrAndHostTrackerValve extends ValveBase {
+ private String remoteAddr;
+ private String remoteHost;
+
+ public String getRemoteAddr() {
+ return remoteAddr;
+ }
+
+ public String getRemoteHost() {
+ return remoteHost;
+ }
+
+ @Override
+ public void invoke(Request request, Response response) throws IOException, ServletException {
+ this.remoteHost = request.getRemoteHost();
+ this.remoteAddr = request.getRemoteAddr();
+ }
+ }
+
+ @Test
+ public void testCommaDelimitedListToStringArray() {
+ List elements = Arrays.asList("element1", "element2", "element3");
+ String actual = RemoteIpValve.listToCommaDelimitedString(elements);
+ assertEquals("element1, element2, element3", actual);
+ }
+
+ @Test
+ public void testCommaDelimitedListToStringArrayEmptyList() {
+ List elements = new ArrayList();
+ String actual = RemoteIpValve.listToCommaDelimitedString(elements);
+ assertEquals("", actual);
+ }
+
+ @Test
+ public void testCommaDelimitedListToStringArrayNullList() {
+ String actual = RemoteIpValve.listToCommaDelimitedString(null);
+ assertEquals("", actual);
+ }
+
+ @Test
+ public void testInvokeAllowedRemoteAddrWithNullRemoteIpHeader() throws Exception {
+ // PREPARE
+ RemoteIpValve remoteIpValve = new RemoteIpValve();
+ remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
+ remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
+ remoteIpValve.setRemoteIPHeader("x-forwarded-for");
+ remoteIpValve.setProxiesHeader("x-forwarded-by");
+ RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
+ remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+ Request request = new Request();
+ request.setCoyoteRequest(new org.apache.coyote.Request());
+ request.remoteAddr = "192.168.0.10";
+ request.remoteHost = "remote-host-original-value";
+
+ // TEST
+ remoteIpValve.invoke(request, null);
+
+ // VERIFY
+ String actualXForwardedFor = request.getHeader("x-forwarded-for");
+ assertNull("x-forwarded-for must be null", actualXForwardedFor);
+
+ String actualXForwardedBy = request.getHeader("x-forwarded-by");
+ assertNull("x-forwarded-by must be null", actualXForwardedBy);
+
+ String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
+ assertEquals("remoteAddr", "192.168.0.10", actualRemoteAddr);
+
+ String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
+ assertEquals("remoteHost", "remote-host-original-value", actualRemoteHost);
+
+ String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+ assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
+
+ String actualPostInvokeRemoteHost = request.getRemoteHost();
+ assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
+
+ }
+
+ @Test
+ public void testInvokeAllProxiesAreTrusted() throws Exception {
+
+ // PREPARE
+ RemoteIpValve remoteIpValve = new RemoteIpValve();
+ remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
+ remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
+ remoteIpValve.setRemoteIPHeader("x-forwarded-for");
+ remoteIpValve.setProxiesHeader("x-forwarded-by");
+ RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
+ remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+ Request request = new Request();
+ request.setCoyoteRequest(new org.apache.coyote.Request());
+ request.remoteAddr = "192.168.0.10";
+ request.remoteHost = "remote-host-original-value";
+ request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, proxy1, proxy2");
+
+ // TEST
+ remoteIpValve.invoke(request, null);
+
+ // VERIFY
+ String actualXForwardedFor = request.getHeader("x-forwarded-for");
+ assertNull("all proxies are trusted, x-forwarded-for must be null", actualXForwardedFor);
+
+ String actualXForwardedBy = request.getHeader("x-forwarded-by");
+ assertEquals("all proxies are trusted, they must appear in x-forwarded-by", "proxy1, proxy2", actualXForwardedBy);
+
+ String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
+ assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
+
+ String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
+ assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
+
+ String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+ assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
+
+ String actualPostInvokeRemoteHost = request.getRemoteHost();
+ assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
+ }
+
+ @Test
+ public void testInvokeAllProxiesAreTrustedOrInternal() throws Exception {
+
+ // PREPARE
+ RemoteIpValve remoteIpValve = new RemoteIpValve();
+ remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
+ remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
+ remoteIpValve.setRemoteIPHeader("x-forwarded-for");
+ remoteIpValve.setProxiesHeader("x-forwarded-by");
+ RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
+ remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+ Request request = new Request();
+ request.setCoyoteRequest(new org.apache.coyote.Request());
+ request.remoteAddr = "192.168.0.10";
+ request.remoteHost = "remote-host-original-value";
+ request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for")
+ .setString("140.211.11.130, proxy1, proxy2, 192.168.0.10, 192.168.0.11");
+
+ // TEST
+ remoteIpValve.invoke(request, null);
+
+ // VERIFY
+ String actualXForwardedFor = request.getHeader("x-forwarded-for");
+ assertNull("all proxies are trusted, x-forwarded-for must be null", actualXForwardedFor);
+
+ String actualXForwardedBy = request.getHeader("x-forwarded-by");
+ assertEquals("all proxies are trusted, they must appear in x-forwarded-by", "proxy1, proxy2", actualXForwardedBy);
+
+ String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
+ assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
+
+ String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
+ assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
+
+ String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+ assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
+
+ String actualPostInvokeRemoteHost = request.getRemoteHost();
+ assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
+ }
+
+ @Test
+ public void testInvokeAllProxiesAreInternal() throws Exception {
+
+ // PREPARE
+ RemoteIpValve remoteIpValve = new RemoteIpValve();
+ remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
+ remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
+ remoteIpValve.setRemoteIPHeader("x-forwarded-for");
+ remoteIpValve.setProxiesHeader("x-forwarded-by");
+ RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
+ remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+ Request request = new Request();
+ request.setCoyoteRequest(new org.apache.coyote.Request());
+ request.remoteAddr = "192.168.0.10";
+ request.remoteHost = "remote-host-original-value";
+ request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, 192.168.0.10, 192.168.0.11");
+
+ // TEST
+ remoteIpValve.invoke(request, null);
+
+ // VERIFY
+ String actualXForwardedFor = request.getHeader("x-forwarded-for");
+ assertNull("all proxies are internal, x-forwarded-for must be null", actualXForwardedFor);
+
+ String actualXForwardedBy = request.getHeader("x-forwarded-by");
+ assertNull("all proxies are internal, x-forwarded-by must be null", actualXForwardedBy);
+
+ String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
+ assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
+
+ String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
+ assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
+
+ String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+ assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
+
+ String actualPostInvokeRemoteHost = request.getRemoteHost();
+ assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
+ }
+
+ @Test
+ public void testInvokeAllProxiesAreTrustedAndRemoteAddrMatchRegexp() throws Exception {
+
+ // PREPARE
+ RemoteIpValve remoteIpValve = new RemoteIpValve();
+ remoteIpValve.setInternalProxies("127\\.0\\.0\\.1, 192\\.168\\..*, another-internal-proxy");
+ remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
+ remoteIpValve.setRemoteIPHeader("x-forwarded-for");
+ remoteIpValve.setProxiesHeader("x-forwarded-by");
+ RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
+ remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+ Request request = new Request();
+ request.setCoyoteRequest(new org.apache.coyote.Request());
+ request.remoteAddr = "192.168.0.10";
+ request.remoteHost = "remote-host-original-value";
+ request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, proxy1, proxy2");
+
+ // TEST
+ remoteIpValve.invoke(request, null);
+
+ // VERIFY
+ String actualXForwardedFor = request.getHeader("x-forwarded-for");
+ assertNull("all proxies are trusted, x-forwarded-for must be null", actualXForwardedFor);
+
+ String actualXForwardedBy = request.getHeader("x-forwarded-by");
+ assertEquals("all proxies are trusted, they must appear in x-forwarded-by", "proxy1, proxy2", actualXForwardedBy);
+
+ String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
+ assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
+
+ String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
+ assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
+
+ String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+ assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
+
+ String actualPostInvokeRemoteHost = request.getRemoteHost();
+ assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
+ }
+
+ @Test
+ public void testInvokeNotAllowedRemoteAddr() throws Exception {
+ // PREPARE
+ RemoteIpValve remoteIpValve = new RemoteIpValve();
+ remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
+ remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
+ remoteIpValve.setRemoteIPHeader("x-forwarded-for");
+ remoteIpValve.setProxiesHeader("x-forwarded-by");
+ RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
+ remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+ Request request = new Request();
+ request.setCoyoteRequest(new org.apache.coyote.Request());
+ request.remoteAddr = "not-allowed-internal-proxy";
+ request.remoteHost = "not-allowed-internal-proxy-host";
+ request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, proxy1, proxy2");
+
+ // TEST
+ remoteIpValve.invoke(request, null);
+
+ // VERIFY
+ String actualXForwardedFor = request.getHeader("x-forwarded-for");
+ assertEquals("x-forwarded-for must be unchanged", "140.211.11.130, proxy1, proxy2", actualXForwardedFor);
+
+ String actualXForwardedBy = request.getHeader("x-forwarded-by");
+ assertNull("x-forwarded-by must be null", actualXForwardedBy);
+
+ String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
+ assertEquals("remoteAddr", "not-allowed-internal-proxy", actualRemoteAddr);
+
+ String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
+ assertEquals("remoteHost", "not-allowed-internal-proxy-host", actualRemoteHost);
+
+ String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+ assertEquals("postInvoke remoteAddr", "not-allowed-internal-proxy", actualPostInvokeRemoteAddr);
+
+ String actualPostInvokeRemoteHost = request.getRemoteHost();
+ assertEquals("postInvoke remoteAddr", "not-allowed-internal-proxy-host", actualPostInvokeRemoteHost);
+ }
+
+ @Test
+ public void testInvokeUntrustedProxyInTheChain() throws Exception {
+ // PREPARE
+ RemoteIpValve remoteIpValve = new RemoteIpValve();
+ remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
+ remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
+ remoteIpValve.setRemoteIPHeader("x-forwarded-for");
+ remoteIpValve.setProxiesHeader("x-forwarded-by");
+ RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
+ remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+ Request request = new Request();
+ request.setCoyoteRequest(new org.apache.coyote.Request());
+ request.remoteAddr = "192.168.0.10";
+ request.remoteHost = "remote-host-original-value";
+ request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for")
+ .setString("140.211.11.130, proxy1, untrusted-proxy, proxy2");
+
+ // TEST
+ remoteIpValve.invoke(request, null);
+
+ // VERIFY
+ String actualXForwardedFor = request.getHeader("x-forwarded-for");
+ assertEquals("ip/host before untrusted-proxy must appear in x-forwarded-for", "140.211.11.130, proxy1", actualXForwardedFor);
+
+ String actualXForwardedBy = request.getHeader("x-forwarded-by");
+ assertEquals("ip/host after untrusted-proxy must appear in x-forwarded-by", "proxy2", actualXForwardedBy);
+
+ String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
+ assertEquals("remoteAddr", "untrusted-proxy", actualRemoteAddr);
+
+ String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
+ assertEquals("remoteHost", "untrusted-proxy", actualRemoteHost);
+
+ String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+ assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
+
+ String actualPostInvokeRemoteHost = request.getRemoteHost();
+ assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
+ }
+
+ @Test
+ public void testListToCommaDelimitedString() {
+ String[] actual = RemoteIpValve.commaDelimitedListToStringArray("element1, element2, element3");
+ String[] expected = new String[] {
+ "element1", "element2", "element3"
+ };
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void testListToCommaDelimitedStringMixedSpaceChars() {
+ String[] actual = RemoteIpValve.commaDelimitedListToStringArray("element1 , element2,\t element3");
+ String[] expected = new String[] {
+ "element1", "element2", "element3"
+ };
+ assertArrayEquals(expected, actual);
+ }
+}