/* * 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 (e.g. "X-Forwarded-For"). *
** Another feature of this valve is to replace the apparent scheme (http/https) and server port with the scheme presented by a proxy or a * load balancer via a request header (e.g. "X-Forwarded-Proto"). *
** This valve proceeds as follows: *
*
* If the incoming request.getRemoteAddr()
matches the valve's list of internal proxies :
*
$remoteIPHeader
(default value x-forwarded-for
). Values are processed in right-to-left order.$protocolHeader
(e.g. x-forwarded-for
) equals to the value of
* protocolHeaderHttpsValue
configuration parameter (default https
) then request.isSecure = true
,
* request.scheme = https
and request.serverPort = 443
. Note that 443 can be overwritten with the
* $httpsServerPort
configuration parameter.* Configuration parameters: *
RemoteIpValve property | *Description | *Equivalent mod_remoteip directive | *Format | *Default Value | *
---|---|---|---|---|
remoteIPHeader | *Name of the Http Header read by this valve that holds the list of traversed IP addresses starting from the requesting client | *RemoteIPHeader | *Compliant http header name | *x-forwarded-for | *
internalProxies | *List of internal proxies ip adress. If they appear in the remoteIpHeader value, they will be trusted and will not appear
* in the proxiesHeader value |
* 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 | *Name of the http header created by this valve to hold the list of proxies that have been processed in the incoming
* remoteIPHeader |
* RemoteIPProxiesHeader | *Compliant http header name | *x-forwarded-by | *
trustedProxies | *List of trusted proxies ip adress. If they appear in the remoteIpHeader value, they will be trusted and will appear
* in the proxiesHeader value |
* RemoteIPTrustedProxy | *Comma delimited list of regular expressions (in the syntax supported by the {@link java.util.regex.Pattern} library) | ** |
protocolHeader | *Name of the http header read by this valve that holds the flag that this request | *N/A | *Compliant http header name like X-Forwarded-Proto , X-Forwarded-Ssl or Front-End-Https |
* null |
*
protocolHeaderHttpsValue | *Value of the protocolHeader to indicate that it is an Https request |
* N/A | *String like https or ON |
* https |
*
*
* 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 internal proxies *
** RemoteIpValve configuration: *
*
* <Valve
* className="org.apache.catalina.connector.RemoteIpValve"
* internalProxies="192\.168\.0\.10, 192\.168\.0\.11"
* remoteIPHeader="x-forwarded-for"
* remoteIPProxiesHeader="x-forwarded-by"
* protocolHeader="x-forwarded-proto"
* />
* * 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 | *
request.header['x-forwarded-proto'] | *https | *https | *
request.scheme | *http | *https | *
request.secure | *false | *true | *
request.serverPort | *80 | *443 | *
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 trusted proxies *
** RemoteIpValve configuration: *
*
* <Valve
* className="org.apache.catalina.connector.RemoteIpValve"
* internalProxies="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 | *
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"
* internalProxies="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 | *
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 an untrusted proxy *
** RemoteIpValve configuration: *
*
* <Valve
* className="org.apache.catalina.connector.RemoteIpValve"
* internalProxies="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 | *
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, declare valve in mbeans-descriptors.xml *
*/ public class RemoteIpValve extends ValveBase { /** * {@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"; /** * Logger */ private static Log log = LogFactory.getLog(RemoteIpValve.class); /** * 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} * * @return array of patterns (notnull
)
*/
protected static Pattern[] commaDelimitedListToPatternArray(String commaDelimitedPatterns) {
String[] patterns = commaDelimitedListToStringArray(commaDelimitedPatterns);
Listnull
)
*/
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(Listtrue
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 #setHttpsServerPort(int)
*/
private int httpsServerPort = 443;
/**
* @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 #setProtocolHeader(String)
*/
private String protocolHeader = null;
/**
* @see #setProtocolHeaderHttpsValue(String)
*/
private String protocolHeaderHttpsValue = "https";
/**
* @see #setProxiesHeader(String)
*/
private String proxiesHeader = "X-Forwarded-By";
/**
* @see #setRemoteIPHeader(String)
*/
private String remoteIPHeader = "X-Forwarded-For";
/**
* @see RemoteIpValve#setTrustedProxies(String)
*/
private Pattern[] trustedProxies = new Pattern[0];
public int getHttpsServerPort() {
return httpsServerPort;
}
/**
* Return descriptive information about this Valve implementation.
*/
public String getInfo() {
return info;
}
/**
* @see #setInternalProxies(String)
* @return comma delimited list of internal proxies
*/
public String getInternalProxies() {
List* Server Port value if the {@link #protocolHeader} indicates HTTPS *
** Default value : 443 *
*/ public void setHttpsServerPort(int httpsServerPort) { this.httpsServerPort = httpsServerPort; } /** ** 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 commaDelimitedInternalProxies) { this.internalProxies = commaDelimitedListToPatternArray(commaDelimitedInternalProxies); } /** *
* Header that holds the incoming protocol, usally named X-Forwarded-Proto
. If null
, request.scheme and
* request.secure will not be modified.
*
* Default value : null
*
* Case insensitive value of the protocol header to indicate that the incoming http request uses SSL. *
*
* Default value : https
*
* 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
*
* 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
*
* 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); } }