All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.valves.RemoteIpValve Maven / Gradle / Ivy

/*
 * 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.connector.Request;
import org.apache.catalina.connector.Response;
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 : *

    *
  • 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.
    • *
    *
  • *
  • If the request http header named $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 propertyDescriptionEquivalent mod_remoteip directiveFormatDefault Value
remoteIpHeaderName of the Http Header read by this valve that holds the list of traversed IP addresses starting from the requesting clientRemoteIPHeaderCompliant http header namex-forwarded-for
internalProxiesRegular 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 valueRemoteIPInternalProxyRegular 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}
* 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
proxiesHeaderName of the http header created by this valve to hold the list of proxies that have been processed in the incoming * remoteIpHeaderRemoteIPProxiesHeaderCompliant http header namex-forwarded-by
trustedProxiesRegular 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 valueRemoteIPTrustedProxyRegular expression (in the syntax supported by * {@link java.util.regex.Pattern java.util.regex}) 
protocolHeaderName of the http header read by this valve that holds the flag that this request N/ACompliant http header name like X-Forwarded-Proto, X-Forwarded-Ssl or Front-End-Httpsnull
protocolHeaderHttpsValueValue of the protocolHeader to indicate that it is an Https requestN/AString like https or ONhttps
httpServerPortValue returned by {@link javax.servlet.ServletRequest#getServerPort()} when the protocolHeader indicates http protocolN/Ainteger80
httpsServerPortValue returned by {@link javax.servlet.ServletRequest#getServerPort()} when the protocolHeader indicates https protocolN/Ainteger443
*

*

*

* 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"
 *   remoteIpProxiesHeader="x-forwarded-by"
 *   protocolHeader="x-forwarded-proto"
 *   />
*

* Request values: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
propertyValue Before RemoteIpValveValue After RemoteIpValve
request.remoteAddr192.168.0.10140.211.11.130
request.header['x-forwarded-for']140.211.11.130, 192.168.0.10null
request.header['x-forwarded-by']nullnull
request.header['x-forwarded-proto']httpshttps
request.schemehttphttps
request.securefalsetrue
request.serverPort80443
* 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"
 *   remoteIpProxiesHeader="x-forwarded-by"
 *   trustedProxies="proxy1|proxy2"
 *   />
*

* Request values: *

* * * * * * * * * * * * * * * * * * * * *
propertyValue Before RemoteIpValveValue After RemoteIpValve
request.remoteAddr192.168.0.10140.211.11.130
request.header['x-forwarded-for']140.211.11.130, proxy1, proxy2null
request.header['x-forwarded-by']nullproxy1, 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"
 *   remoteIpProxiesHeader="x-forwarded-by"
 *   trustedProxies="proxy1|proxy2"
 *   />
*

* Request values: *

* * * * * * * * * * * * * * * * * * * * *
propertyValue Before RemoteIpValveValue After RemoteIpValve
request.remoteAddr192.168.0.10140.211.11.130
request.header['x-forwarded-for']140.211.11.130, proxy1, proxy2, 192.168.0.10null
request.header['x-forwarded-by']nullproxy1, 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"
 *   remoteIpProxiesHeader="x-forwarded-by"
 *   trustedProxies="proxy1|proxy2"
 *   />
*

* Request values: *

* * * * * * * * * * * * * * * * * * * * *
propertyValue Before RemoteIpValveValue After RemoteIpValve
request.remoteAddr192.168.0.10untrusted-proxy
request.header['x-forwarded-for']140.211.11.130, untrusted-proxy, proxy1140.211.11.130
request.header['x-forwarded-by']nullproxy1
* 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. *

*/ 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.valves.RemoteIpValve/1.0"; /** * Logger */ private static final Log log = LogFactory.getLog(RemoteIpValve.class); /** * Convert a given comma delimited String into an array of String * * @return array of String (non 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 */ protected static String listToCommaDelimitedString(List stringList) { if (stringList == null) { return ""; } StringBuilder result = new StringBuilder(); for (Iterator it = stringList.iterator(); it.hasNext();) { Object element = it.next(); if (element != null) { result.append(element); if (it.hasNext()) { result.append(", "); } } } return result.toString(); } /** * @see #setHttpServerPort(int) */ private int httpServerPort = 80; /** * @see #setHttpsServerPort(int) */ private int httpsServerPort = 443; private boolean changeLocalPort = false; /** * @see #setInternalProxies(String) */ private Pattern internalProxies = Pattern.compile( "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}"); /** * @see #setProtocolHeader(String) */ private String protocolHeader = null; /** * @see #setProtocolHeaderHttpsValue(String) */ private String protocolHeaderHttpsValue = "https"; private String portHeader = null; /** * @see #setProxiesHeader(String) */ private String proxiesHeader = "X-Forwarded-By"; /** * @see #setRemoteIpHeader(String) */ private String remoteIpHeader = "X-Forwarded-For"; /** * @see #setRequestAttributesEnabled(boolean) */ private boolean requestAttributesEnabled = true; /** * @see RemoteIpValve#setTrustedProxies(String) */ private Pattern trustedProxies = null; /** * Default constructor that ensures {@link ValveBase#ValveBase(boolean)} is * called with true. */ public RemoteIpValve() { // Async requests are supported with this valve super(true); } public int getHttpsServerPort() { return httpsServerPort; } public int getHttpServerPort() { return httpServerPort; } public boolean isChangeLocalPort() { return changeLocalPort; } public void setChangeLocalPort(boolean changeLocalPort) { this.changeLocalPort = changeLocalPort; } /** * 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; } /** * Return descriptive information about this Valve implementation. */ @Override public String getInfo() { return info; } /** * @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 int originalServerPort = request.getServerPort(); if (internalProxies !=null && internalProxies.matcher(originalRemoteAddr).matches()) { String remoteIp = null; // In java 6, proxiesHeaderValue should be declared as a java.util.Deque LinkedList proxiesHeaderValue = new LinkedList(); StringBuilder concatRemoteIpHeaderValue = new StringBuilder(); for (Enumeration e = request.getHeaders(remoteIpHeader); e.hasMoreElements();) { if (concatRemoteIpHeaderValue.length() > 0) { concatRemoteIpHeaderValue.append(", "); } concatRemoteIpHeaderValue.append(e.nextElement()); } String[] remoteIpHeaderValue = commaDelimitedListToStringArray(concatRemoteIpHeaderValue.toString()); 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 (internalProxies.matcher(currentRemoteIp).matches()) { // do nothing, internalProxies IPs are not appended to the } else if (trustedProxies != null && trustedProxies.matcher(currentRemoteIp).matches()) { 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) { request.setRemoteAddr(remoteIp); request.setRemoteHost(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); } } if (protocolHeader != null) { String protocolHeaderValue = request.getHeader(protocolHeader); if (protocolHeaderValue == null) { // don't modify the secure,scheme and serverPort attributes // of the request } else if (protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) { request.setSecure(true); // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0 request.getCoyoteRequest().scheme().setString("https"); setPorts(request, httpsServerPort); } else { request.setSecure(false); // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0 request.getCoyoteRequest().scheme().setString("http"); setPorts(request, httpServerPort); } } 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() + "'"); } } else { if (log.isDebugEnabled()) { log.debug("Skip RemoteIpValve for request " + request.getRequestURI() + " with originalRemoteAddr '" + request.getRemoteAddr() + "'"); } } if (requestAttributesEnabled) { request.setAttribute(AccessLog.REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr()); request.setAttribute(AccessLog.REMOTE_HOST_ATTRIBUTE, request.getRemoteHost()); request.setAttribute(AccessLog.PROTOCOL_ATTRIBUTE, request.getProtocol()); request.setAttribute(AccessLog.SERVER_PORT_ATTRIBUTE, Integer.valueOf(request.getServerPort())); } try { getNext().invoke(request, response); } finally { request.setRemoteAddr(originalRemoteAddr); request.setRemoteHost(originalRemoteHost); request.setSecure(originalSecure); // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0 request.getCoyoteRequest().scheme().setString(originalScheme); request.setServerPort(originalServerPort); } } private void setPorts(Request request, int defaultPort) { int port = defaultPort; if (portHeader != null) { String portHeaderValue = request.getHeader(portHeader); if (portHeaderValue != null) { try { port = Integer.parseInt(portHeaderValue); } catch (NumberFormatException nfe) { if (log.isDebugEnabled()) { log.debug(sm.getString( "remoteIpValve.invalidPortHeader", portHeaderValue, portHeader), nfe); } } } } request.setServerPort(port); if (changeLocalPort) { request.setLocalPort(port); } } /** *

* Server Port value if the {@link #protocolHeader} is not null and does not indicate HTTP *

*

* Default value : 80 *

*/ public void setHttpServerPort(int httpServerPort) { this.httpServerPort = httpServerPort; } /** *

* Server Port value if the {@link #protocolHeader} indicates HTTPS *

*

* Default value : 443 *

*/ 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} *

*/ 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, usally named X-Forwarded-Proto. If null, request.scheme and * request.secure will not be modified. *

*

* Default value : null *

*/ public void setProtocolHeader(String protocolHeader) { this.protocolHeader = protocolHeader; } /** *

* Case insensitive value of the protocol header to indicate that the incoming http request uses SSL. *

*

* Default value : https *

*/ public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) { this.protocolHeaderHttpsValue = protocolHeaderHttpsValue; } /** *

* 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; } /** *

* 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; } /** * Should this valve set request attributes for IP address, Hostname, * protocol and port used for the request? This are typically used in * conjunction with the {@link AccessLog} which will otherwise log the * original values. Default is true. * * The attributes set are: *
    *
  • org.apache.catalina.AccessLog.RemoteAddr
  • *
  • org.apache.catalina.AccessLog.RemoteHost
  • *
  • org.apache.catalina.AccessLog.Protocol
  • *
  • org.apache.catalina.AccessLog.ServerPort
  • *
* * @param requestAttributesEnabled 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. *

*/ public void setTrustedProxies(String trustedProxies) { if (trustedProxies == null || trustedProxies.length() == 0) { this.trustedProxies = null; } else { this.trustedProxies = Pattern.compile(trustedProxies); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy