/* * 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.valves; import java.io.IOException; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; import javax.servlet.ServletException; import org.apache.catalina.AccessLog; import org.apache.catalina.Globals; import org.apache.catalina.connector.Request; 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; /** *
* 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 or trusted proxies:
*
$remoteIpHeader (default value x-forwarded-for). Values are processed in right-to-left order.$protocolHeader (e.g. x-forwarded-proto) consists only of forwards that match
* 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.| 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 | *Regular expression that matches the IP addresses of internal proxies.
* If they appear in the remoteIpHeader value, they will be
* trusted and will not appear
* in the proxiesHeader value |
* RemoteIPInternalProxy | *Regular expression (in the syntax supported by * {@link java.util.regex.Pattern java.util.regex}) | *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}|
* 172\.1[6-9]{1}\.\d{1,3}\.\d{1,3}|172\.2[0-9]{1}\.\d{1,3}\.\d{1,3}|
* 172\.3[0-1]{1}\.\d{1,3}\.\d{1,3}|
* 0:0:0:0:0:0:0:1|::1
* * By default, 10/8, 192.168/16, 169.254/16, 127/8, 172.16/12, and ::1 are allowed. |
*
| proxiesHeader | *Name of the http header created by this valve to hold the list of proxies that have been processed in the incoming
* remoteIpHeader |
* proxiesHeader | *Compliant http header name | *x-forwarded-by | *
| trustedProxies | *Regular expression that matches the IP addresses of trusted proxies.
* If they appear in the remoteIpHeader value, they will be
* trusted and will appear in the proxiesHeader value |
* RemoteIPTrustedProxy | *Regular expression (in the syntax supported by * {@link java.util.regex.Pattern java.util.regex}) | ** |
| 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 |
*
| httpServerPort | *Value returned by {@link javax.servlet.ServletRequest#getServerPort()} when the protocolHeader indicates http protocol |
* N/A | *integer | *80 | *
| httpsServerPort | *Value returned by {@link javax.servlet.ServletRequest#getServerPort()} when the protocolHeader indicates https protocol |
* N/A | *integer | *443 | *
* 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.
*
* Sample with internal proxies *
** RemoteIpValve configuration: *
*
* <Valve
* className="org.apache.catalina.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* protocolHeader="x-forwarded-proto"
* />
* | 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 | *
* 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 trusted proxies *
** RemoteIpValve configuration: *
*
* <Valve
* className="org.apache.catalina.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* trustedProxies="proxy1|proxy2"
* />
* | 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.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* trustedProxies="proxy1|proxy2"
* />
* | 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 an untrusted proxy *
** RemoteIpValve configuration: *
*
* <Valve
* className="org.apache.catalina.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* trustedProxies="proxy1|proxy2"
* />
* | 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 cannot trust that
* untrusted-proxy is the actual remote ip. request.remoteAddr is untrusted-proxy that is an IP
* verified by proxy1.
*
null)
*/
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
* @param stringList The string list to convert
* @return The concatenated string
*/
protected static String listToCommaDelimitedString(Listtrue.
*/
public RemoteIpValve() {
// Async requests are supported with this valve
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;
}
/**
* 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 boolean isChangeLocalName() {
return changeLocalName;
}
public void setChangeLocalName(boolean changeLocalName) {
this.changeLocalName = changeLocalName;
}
public int getHttpServerPort() {
return httpServerPort;
}
public int getHttpsServerPort() {
return httpsServerPort;
}
/**
* Obtain the name of the HTTP header used to override the value returned
* by {@link Request#getServerPort()} and (optionally depending on {link
* {@link #isChangeLocalPort()} {@link Request#getLocalPort()}.
*
* @return The HTTP header name
*/
public String getPortHeader() {
return portHeader;
}
/**
* Set the name of the HTTP header used to override the value returned
* by {@link Request#getServerPort()} and (optionally depending on {link
* {@link #isChangeLocalPort()} {@link Request#getLocalPort()}.
*
* @param portHeader The HTTP header name
*/
public void setPortHeader(String portHeader) {
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
*/
public String getInternalProxies() {
if (internalProxies == null) {
return null;
}
return internalProxies.toString();
}
/**
* @see #setProtocolHeader(String)
* @return the protocol header (e.g. "X-Forwarded-Proto")
*/
public String getProtocolHeader() {
return protocolHeader;
}
/**
* @see RemoteIpValve#setProtocolHeaderHttpsValue(String)
* @return the value of the protocol header for incoming https request (e.g. "https")
*/
public String getProtocolHeaderHttpsValue() {
return protocolHeaderHttpsValue;
}
/**
* @see #setProxiesHeader(String)
* @return the proxies header name (e.g. "X-Forwarded-By")
*/
public String getProxiesHeader() {
return proxiesHeader;
}
/**
* @see #setRemoteIpHeader(String)
* @return the remote IP header name (e.g. "X-Forwarded-For")
*/
public String getRemoteIpHeader() {
return remoteIpHeader;
}
/**
* @see #setRequestAttributesEnabled(boolean)
* @return true if the attributes will be logged, otherwise
* false
*/
public boolean getRequestAttributesEnabled() {
return requestAttributesEnabled;
}
/**
* @see #setTrustedProxies(String)
* @return Regular expression that defines the trusted proxies
*/
public String getTrustedProxies() {
if (trustedProxies == null) {
return null;
}
return trustedProxies.toString();
}
/**
* {@inheritDoc}
*/
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
final String originalRemoteAddr = request.getRemoteAddr();
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 &&
internalProxies.matcher(originalRemoteAddr).matches();
if (isInternal || (trustedProxies != null &&
trustedProxies.matcher(originalRemoteAddr).matches())) {
String remoteIp = null;
// In java 6, proxiesHeaderValue should be declared as a java.util.Deque
LinkedList
* Server Port value if the {@link #protocolHeader} is not null and does not indicate HTTP
*
* Default value : 80 *
* @param httpServerPort The server port */ public void setHttpServerPort(int httpServerPort) { this.httpServerPort = httpServerPort; } /** ** Server Port value if the {@link #protocolHeader} indicates HTTPS *
** Default value : 443 *
* @param httpsServerPort The server port */ public void setHttpsServerPort(int httpsServerPort) { this.httpsServerPort = httpsServerPort; } /** ** Regular expression that defines the internal proxies. *
** Default value : 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}|0:0:0:0:0:0:0:1 *
* @param internalProxies The proxy regular expression */ public void setInternalProxies(String internalProxies) { if (internalProxies == null || internalProxies.length() == 0) { this.internalProxies = null; } else { this.internalProxies = Pattern.compile(internalProxies); } } /** *
* Header that holds the incoming protocol, usually 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
*
true.
*
* The attributes set are:
* true causes the attributes
* to be set, false disables
* the setting of the attributes.
*/
public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
this.requestAttributesEnabled = requestAttributesEnabled;
}
/**
* * Regular expression defining proxies that are trusted when they appear in * the {@link #remoteIpHeader} header. *
** Default value : empty list, no external proxy is trusted. *
* @param trustedProxies The regular expression */ public void setTrustedProxies(String trustedProxies) { if (trustedProxies == null || trustedProxies.length() == 0) { this.trustedProxies = null; } else { this.trustedProxies = Pattern.compile(trustedProxies); } } }