| /* |
| * 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.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Deque; |
| import java.util.Enumeration; |
| import java.util.List; |
| |
| import jakarta.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.catalina.util.NetMaskSet; |
| import org.apache.catalina.util.RequestUtil; |
| import org.apache.juli.logging.Log; |
| import org.apache.juli.logging.LogFactory; |
| import org.apache.tomcat.util.buf.StringUtils; |
| import org.apache.tomcat.util.http.MimeHeaders; |
| import org.apache.tomcat.util.http.parser.Host; |
| |
| /** |
| * <p> |
| * Tomcat port of <a href="https://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, 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"). |
| * </p> |
| * <p> |
| * 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"). |
| * </p> |
| * <p> |
| * This valve proceeds as follows: |
| * </p> |
| * <p> |
| * If the incoming <code>request.getRemoteAddr()</code> matches the valve's list of internal or trusted proxies: |
| * </p> |
| * <ul> |
| * <li>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 <code>$remoteIpHeader</code> (default value <code>x-forwarded-for</code>). Values are |
| * processed in right-to-left order.</li> |
| * <li>For each ip/host of the list: |
| * <ul> |
| * <li>if it matches the internal proxies list, the ip/host is swallowed</li> |
| * <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li> |
| * <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li> |
| * </ul> |
| * </li> |
| * <li>If the request http header named <code>$protocolHeader</code> (default value <code>X-Forwarded-Proto</code>) |
| * consists only of forwards that match <code>protocolHeaderHttpsValue</code> configuration parameter (default |
| * <code>https</code>) then <code>request.isSecure = true</code>, <code>request.scheme = https</code> and |
| * <code>request.serverPort = 443</code>. Note that 443 can be overwritten with the <code>$httpsServerPort</code> |
| * configuration parameter.</li> |
| * <li>Mark the request with the attribute {@link Globals#REQUEST_FORWARDED_ATTRIBUTE} and value {@code Boolean.TRUE} to |
| * indicate that this request has been forwarded by one or more proxies.</li> |
| * </ul> |
| * <table border="1"> |
| * <caption>Configuration parameters</caption> |
| * <tr> |
| * <th>RemoteIpValve property</th> |
| * <th>Description</th> |
| * <th>Equivalent mod_remoteip directive</th> |
| * <th>Format</th> |
| * <th>Default Value</th> |
| * </tr> |
| * <tr> |
| * <td>remoteIpHeader</td> |
| * <td>Name of the Http Header read by this valve that holds the list of traversed IP addresses starting from the |
| * requesting client</td> |
| * <td>RemoteIPHeader</td> |
| * <td>Compliant http header name</td> |
| * <td>x-forwarded-for</td> |
| * </tr> |
| * <tr> |
| * <td>internalProxies</td> |
| * <td>A comma separated list of CIDR blocks that matches the IP addresses of the internal proxies. If they appear in |
| * the <code>remoteIpHeader</code> value, they will be trusted and will not appear in the <code>proxiesHeader</code> |
| * value</td> |
| * <td>RemoteIPInternalProxy</td> |
| * <td>Comma separated list of CIDR blocks</td> |
| * <td>10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,100.64.0.0/10,127.0.0.0/8,::1/128,fe80::/10,fc00::/7</td> |
| * </tr> |
| * <tr> |
| * <td>proxiesHeader</td> |
| * <td>Name of the http header created by this valve to hold the list of proxies that have been processed in the |
| * incoming <code>remoteIpHeader</code></td> |
| * <td>proxiesHeader</td> |
| * <td>Compliant http header name</td> |
| * <td>x-forwarded-by</td> |
| * </tr> |
| * <tr> |
| * <td>trustedProxies</td> |
| * <td>A comma separated list of CIDR blocks that matches the IP addresses of the internal proxies. If they appear in |
| * the <code>remoteIpHeader</code> value, they will be trusted and will appear in the <code>proxiesHeader</code> value |
| * </td> |
| * <td>RemoteIPTrustedProxy</td> |
| * <td>Comma separated list of CIDR blocks</td> |
| * <td> </td> |
| * </tr> |
| * <tr> |
| * <td>protocolHeader</td> |
| * <td>Name of the http header read by this valve that holds the flag that this request</td> |
| * <td>N/A</td> |
| * <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code> or |
| * <code>Front-End-Https</code></td> |
| * <td><code>X-Forwarded-Proto</code></td> |
| * </tr> |
| * <tr> |
| * <td>protocolHeaderHttpsValue</td> |
| * <td>Value of the <code>protocolHeader</code> to indicate that it is a Https request</td> |
| * <td>N/A</td> |
| * <td>String like <code>https</code> or <code>ON</code></td> |
| * <td><code>https</code></td> |
| * </tr> |
| * <tr> |
| * <td>httpServerPort</td> |
| * <td>Value returned by {@link jakarta.servlet.ServletRequest#getServerPort()} when the <code>protocolHeader</code> |
| * indicates <code>http</code> protocol</td> |
| * <td>N/A</td> |
| * <td>integer</td> |
| * <td>80</td> |
| * </tr> |
| * <tr> |
| * <td>httpsServerPort</td> |
| * <td>Value returned by {@link jakarta.servlet.ServletRequest#getServerPort()} when the <code>protocolHeader</code> |
| * indicates <code>https</code> protocol</td> |
| * <td>N/A</td> |
| * <td>integer</td> |
| * <td>443</td> |
| * </tr> |
| * </table> |
| * <p> |
| * This Valve may be attached to any Container, depending on the granularity of the filtering you wish to perform. |
| * </p> |
| * <hr> |
| * <p> |
| * <strong>Sample with internal proxies</strong> |
| * </p> |
| * <p> |
| * RemoteIpValve configuration: |
| * </p> |
| * <code> |
| * <Valve |
| * className="org.apache.catalina.valves.RemoteIpValve" |
| * internalProxies="192.168.0.10/31" |
| * remoteIpHeader="x-forwarded-for" |
| * proxiesHeader="x-forwarded-by" |
| * protocolHeader="x-forwarded-proto" |
| * /></code> |
| * <table border="1"> |
| * <caption>Request Values</caption> |
| * <tr> |
| * <th>property</th> |
| * <th>Value Before RemoteIpValve</th> |
| * <th>Value After RemoteIpValve</th> |
| * </tr> |
| * <tr> |
| * <td>request.remoteAddr</td> |
| * <td>192.168.0.10</td> |
| * <td>140.211.11.130</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-for']</td> |
| * <td>140.211.11.130, 192.168.0.10</td> |
| * <td>null</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-by']</td> |
| * <td>null</td> |
| * <td>null</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-proto']</td> |
| * <td>https</td> |
| * <td>https</td> |
| * </tr> |
| * <tr> |
| * <td>request.scheme</td> |
| * <td>http</td> |
| * <td>https</td> |
| * </tr> |
| * <tr> |
| * <td>request.secure</td> |
| * <td>false</td> |
| * <td>true</td> |
| * </tr> |
| * <tr> |
| * <td>request.serverPort</td> |
| * <td>80</td> |
| * <td>443</td> |
| * </tr> |
| * </table> |
| * <p> |
| * Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed by the request. |
| * <code>x-forwarded-by</code> is null because all the proxies are trusted or internal. |
| * </p> |
| * <hr> |
| * <p> |
| * <strong>Sample with trusted proxies</strong> |
| * </p> |
| * <p> |
| * RemoteIpValve configuration: |
| * </p> |
| * <code> |
| * <Valve |
| * className="org.apache.catalina.valves.RemoteIpValve" |
| * internalProxies="192.168.0.10/31" |
| * remoteIpHeader="x-forwarded-for" |
| * proxiesHeader="x-forwarded-by" |
| * trustedProxies="proxy1|proxy2" |
| * /></code> |
| * <table border="1"> |
| * <caption>Request Values</caption> |
| * <tr> |
| * <th>property</th> |
| * <th>Value Before RemoteIpValve</th> |
| * <th>Value After RemoteIpValve</th> |
| * </tr> |
| * <tr> |
| * <td>request.remoteAddr</td> |
| * <td>192.168.0.10</td> |
| * <td>140.211.11.130</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-for']</td> |
| * <td>140.211.11.130, proxy1, proxy2</td> |
| * <td>null</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-by']</td> |
| * <td>null</td> |
| * <td>proxy1, proxy2</td> |
| * </tr> |
| * </table> |
| * <p> |
| * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> |
| * header, they both are migrated in <code>x-forwarded-by</code> header. <code>x-forwarded-by</code> is null because all |
| * the proxies are trusted or internal. |
| * </p> |
| * <hr> |
| * <p> |
| * <strong>Sample with internal and trusted proxies</strong> |
| * </p> |
| * <p> |
| * RemoteIpValve configuration: |
| * </p> |
| * <code> |
| * <Valve |
| * className="org.apache.catalina.valves.RemoteIpValve" |
| * internalProxies="192.168.0.10/31" |
| * remoteIpHeader="x-forwarded-for" |
| * proxiesHeader="x-forwarded-by" |
| * trustedProxies="proxy1|proxy2" |
| * /></code> |
| * <table border="1"> |
| * <caption>Request Values</caption> |
| * <tr> |
| * <th>property</th> |
| * <th>Value Before RemoteIpValve</th> |
| * <th>Value After RemoteIpValve</th> |
| * </tr> |
| * <tr> |
| * <td>request.remoteAddr</td> |
| * <td>192.168.0.10</td> |
| * <td>140.211.11.130</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-for']</td> |
| * <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td> |
| * <td>null</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-by']</td> |
| * <td>null</td> |
| * <td>proxy1, proxy2</td> |
| * </tr> |
| * </table> |
| * <p> |
| * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> |
| * header, they both are migrated in <code>x-forwarded-by</code> header. As <code>192.168.0.10</code> is an internal |
| * proxy, it does not appear in <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies |
| * are trusted or internal. |
| * </p> |
| * <hr> |
| * <p> |
| * <strong>Sample with an untrusted proxy</strong> |
| * </p> |
| * <p> |
| * RemoteIpValve configuration: |
| * </p> |
| * <code> |
| * <Valve |
| * className="org.apache.catalina.valves.RemoteIpValve" |
| * internalProxies="192.168.0.10/31" |
| * remoteIpHeader="x-forwarded-for" |
| * proxiesHeader="x-forwarded-by" |
| * trustedProxies="proxy1|proxy2" |
| * /></code> |
| * <table border="1"> |
| * <caption>Request Values</caption> |
| * <tr> |
| * <th>property</th> |
| * <th>Value Before RemoteIpValve</th> |
| * <th>Value After RemoteIpValve</th> |
| * </tr> |
| * <tr> |
| * <td>request.remoteAddr</td> |
| * <td>192.168.0.10</td> |
| * <td>untrusted-proxy</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-for']</td> |
| * <td>140.211.11.130, untrusted-proxy, proxy1</td> |
| * <td>140.211.11.130</td> |
| * </tr> |
| * <tr> |
| * <td>request.header['x-forwarded-by']</td> |
| * <td>null</td> |
| * <td>proxy1</td> |
| * </tr> |
| * </table> |
| * <p> |
| * Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>. <code>x-forwarded-by</code> holds |
| * <code>140.211.11.130</code> because <code>untrusted-proxy</code> is not trusted and thus, we cannot trust that |
| * <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is <code>untrusted-proxy</code> |
| * that is an IP verified by <code>proxy1</code>. |
| * </p> |
| */ |
| public class RemoteIpValve extends ValveBase { |
| |
| private static final Log log = LogFactory.getLog(RemoteIpValve.class); |
| |
| private String hostHeader = null; |
| |
| private boolean changeLocalName = false; |
| |
| private int httpServerPort = 80; |
| |
| private int httpsServerPort = 443; |
| |
| private String portHeader = null; |
| |
| private boolean changeLocalPort = false; |
| |
| private NetMaskSet internalProxies = |
| NetMaskSet.parse("10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,100.64.0.0/10,127.0.0.0/8," + |
| "::1/128,fe80::/10,fc00::/7"); |
| |
| private String protocolHeader = "X-Forwarded-Proto"; |
| |
| private String protocolHeaderHttpsValue = "https"; |
| |
| private String proxiesHeader = "X-Forwarded-By"; |
| |
| private String remoteIpHeader = "X-Forwarded-For"; |
| |
| private boolean requestAttributesEnabled = true; |
| |
| private NetMaskSet trustedProxies = null; |
| |
| |
| /** |
| * Default constructor that ensures {@link ValveBase#ValveBase(boolean)} is called with <code>true</code>. |
| */ |
| 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; |
| } |
| |
| /** |
| * Returns whether the local name should be changed. |
| * |
| * @return {@code true} if the local name should be changed |
| */ |
| public boolean isChangeLocalName() { |
| return changeLocalName; |
| } |
| |
| /** |
| * Set whether the local name should be changed. |
| * |
| * @param changeLocalName {@code true} to change the local name |
| */ |
| public void setChangeLocalName(boolean changeLocalName) { |
| this.changeLocalName = changeLocalName; |
| } |
| |
| /** |
| * Returns the configured HTTP server port. |
| * |
| * @return the HTTP server port |
| */ |
| public int getHttpServerPort() { |
| return httpServerPort; |
| } |
| |
| /** |
| * Returns the configured HTTPS server port. |
| * |
| * @return the HTTPS server port |
| */ |
| 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; |
| } |
| |
| /** |
| * Returns whether the local port should be changed. |
| * |
| * @return {@code true} if the local port should be changed |
| */ |
| public boolean isChangeLocalPort() { |
| return changeLocalPort; |
| } |
| |
| /** |
| * Set whether the local port should be changed. |
| * |
| * @param changeLocalPort {@code true} to change the local port |
| */ |
| public void setChangeLocalPort(boolean changeLocalPort) { |
| this.changeLocalPort = changeLocalPort; |
| } |
| |
| /** |
| * Obtain the currently configured internal proxies. |
| * |
| * @return The currently configured internal proxies. |
| */ |
| public String getInternalProxies() { |
| if (internalProxies != null) { |
| return internalProxies.toString(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Return the configured protocol header name. |
| * |
| * @see #setProtocolHeader(String) |
| * |
| * @return the protocol header name (e.g. "X-Forwarded-Proto") |
| */ |
| public String getProtocolHeader() { |
| return protocolHeader; |
| } |
| |
| /** |
| * Return the configured protocol header HTTPS value. |
| * |
| * @see #setProtocolHeaderHttpsValue(String) |
| * |
| * @return the protocol header value for HTTPS requests (e.g. "https") |
| */ |
| public String getProtocolHeaderHttpsValue() { |
| return protocolHeaderHttpsValue; |
| } |
| |
| /** |
| * Return the configured proxies header name. |
| * |
| * @see #setProxiesHeader(String) |
| * |
| * @return the proxies header name (e.g. "X-Forwarded-By") |
| */ |
| public String getProxiesHeader() { |
| return proxiesHeader; |
| } |
| |
| /** |
| * Return the configured remote IP header name. |
| * |
| * @see #setRemoteIpHeader(String) |
| * |
| * @return the remote IP header name (e.g. "X-Forwarded-For") |
| */ |
| public String getRemoteIpHeader() { |
| return remoteIpHeader; |
| } |
| |
| /** |
| * Return whether request attributes are enabled. |
| * |
| * @see #setRequestAttributesEnabled(boolean) |
| * |
| * @return {@code true} if request attributes are enabled |
| */ |
| public boolean getRequestAttributesEnabled() { |
| return requestAttributesEnabled; |
| } |
| |
| /** |
| * Obtain the currently configured trusted proxies. |
| * |
| * @return The currently configured trusted proxies. |
| */ |
| public String getTrustedProxies() { |
| if (trustedProxies != null) { |
| return trustedProxies.toString(); |
| } else { |
| return null; |
| } |
| } |
| |
| @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 = isChangeLocalName() ? request.getLocalName() : null; |
| final int originalServerPort = request.getServerPort(); |
| final int originalLocalPort = request.getLocalPort(); |
| final List<String> originalProxiesHeaderValues = new ArrayList<>(); |
| for (Enumeration<String> e = request.getHeaders(proxiesHeader); e.hasMoreElements();) { |
| originalProxiesHeaderValues.add(e.nextElement()); |
| } |
| final List<String> originalRemoteIpHeaderValues = new ArrayList<>(); |
| for (Enumeration<String> e = request.getHeaders(remoteIpHeader); e.hasMoreElements();) { |
| originalRemoteIpHeaderValues.add(e.nextElement()); |
| } |
| |
| boolean isInternal = isInternalProxy(originalRemoteAddr); |
| |
| if (isInternal || isTrustedProxy(originalRemoteAddr)) { |
| String remoteIp = null; |
| Deque<String> proxiesHeaderValue = new ArrayDeque<>(); |
| StringBuilder concatRemoteIpHeaderValue = new StringBuilder(); |
| |
| for (Enumeration<String> e = request.getHeaders(remoteIpHeader); e.hasMoreElements();) { |
| if (!concatRemoteIpHeaderValue.isEmpty()) { |
| concatRemoteIpHeaderValue.append(", "); |
| } |
| |
| concatRemoteIpHeaderValue.append(e.nextElement()); |
| } |
| |
| String[] remoteIpHeaderValue = StringUtils.splitCommaSeparated(concatRemoteIpHeaderValue.toString()); |
| int idx; |
| if (!isInternal) { |
| proxiesHeaderValue.addFirst(originalRemoteAddr); |
| } |
| // 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 (isInternalProxy(currentRemoteIp)) { |
| // do nothing, internalProxies IPs are not appended to the |
| } else if (isTrustedProxy(currentRemoteIp)) { |
| 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 |
| Deque<String> newRemoteIpHeaderValue = new ArrayDeque<>(); |
| for (; idx >= 0; idx--) { |
| String currentRemoteIp = remoteIpHeaderValue[idx]; |
| newRemoteIpHeaderValue.addFirst(currentRemoteIp); |
| } |
| if (remoteIp != null) { |
| |
| request.setRemoteAddr(remoteIp); |
| if (request.getConnector().getEnableLookups()) { |
| // This isn't a lazy lookup but that would be a little more |
| // invasive - mainly in Request.getRemoteHost() - and if |
| // enableLookups is true it seems reasonable that the |
| // hotsname will be required so look it up here. |
| try { |
| InetAddress inetAddress = InetAddress.getByName(remoteIp); |
| // We know we need a DNS look up so use getCanonicalHostName() |
| request.setRemoteHost(inetAddress.getCanonicalHostName()); |
| } catch (UnknownHostException e) { |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("remoteIpValve.invalidRemoteAddress", remoteIp), e); |
| } |
| request.setRemoteHost(remoteIp); |
| } |
| } else { |
| request.setRemoteHost(remoteIp); |
| } |
| |
| if (proxiesHeaderValue.isEmpty()) { |
| request.getCoyoteRequest().getMimeHeaders().removeHeader(proxiesHeader); |
| } else { |
| String commaDelimitedListOfProxies = StringUtils.join(proxiesHeaderValue); |
| request.getCoyoteRequest().getMimeHeaders().setValue(proxiesHeader) |
| .setString(commaDelimitedListOfProxies); |
| } |
| if (newRemoteIpHeaderValue.isEmpty()) { |
| request.getCoyoteRequest().getMimeHeaders().removeHeader(remoteIpHeader); |
| } else { |
| String commaDelimitedRemoteIpHeaderValue = StringUtils.join(newRemoteIpHeaderValue); |
| request.getCoyoteRequest().getMimeHeaders().setValue(remoteIpHeader) |
| .setString(commaDelimitedRemoteIpHeaderValue); |
| } |
| } |
| |
| if (protocolHeader != null && !protocolHeader.isEmpty()) { |
| String protocolHeaderValue = RequestUtil.getMergedHeaderValue(request, protocolHeader); |
| if (protocolHeaderValue == null) { |
| // Don't modify the secure, scheme and serverPort attributes |
| // of the request |
| } else if (isForwardedProtoHeaderValueSecure(protocolHeaderValue)) { |
| request.setSecure(true); |
| request.getCoyoteRequest().scheme().setString("https"); |
| setPorts(request, httpsServerPort); |
| } else { |
| request.setSecure(false); |
| request.getCoyoteRequest().scheme().setString("http"); |
| setPorts(request, httpServerPort); |
| } |
| } |
| |
| 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), iae); |
| } |
| } |
| } |
| |
| request.setAttribute(Globals.REQUEST_FORWARDED_ATTRIBUTE, Boolean.TRUE); |
| |
| if (log.isTraceEnabled()) { |
| log.trace("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.isTraceEnabled()) { |
| log.trace("Skip RemoteIpValve for request " + request.getRequestURI() + " with originalRemoteAddr '" + |
| request.getRemoteAddr() + "'"); |
| } |
| } |
| if (requestAttributesEnabled) { |
| request.setAttribute(AccessLog.REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr()); |
| request.setAttribute(Globals.REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr()); |
| request.setAttribute(AccessLog.REMOTE_HOST_ATTRIBUTE, request.getRemoteHost()); |
| request.setAttribute(AccessLog.PROTOCOL_ATTRIBUTE, request.getProtocol()); |
| request.setAttribute(AccessLog.SERVER_NAME_ATTRIBUTE, request.getServerName()); |
| request.setAttribute(AccessLog.SERVER_PORT_ATTRIBUTE, Integer.valueOf(request.getServerPort())); |
| } |
| try { |
| getNext().invoke(request, response); |
| } finally { |
| if (!request.isAsync()) { |
| request.setRemoteAddr(originalRemoteAddr); |
| request.setRemoteHost(originalRemoteHost); |
| request.setSecure(originalSecure); |
| request.getCoyoteRequest().scheme().setString(originalScheme); |
| request.getCoyoteRequest().serverName().setString(originalServerName); |
| if (isChangeLocalName()) { |
| request.getCoyoteRequest().localName().setString(originalLocalName); |
| } |
| request.setServerPort(originalServerPort); |
| request.setLocalPort(originalLocalPort); |
| |
| MimeHeaders headers = request.getCoyoteRequest().getMimeHeaders(); |
| headers.removeHeader(proxiesHeader); |
| for (String v : originalProxiesHeaderValues) { |
| headers.addValue(proxiesHeader).setString(v); |
| } |
| |
| headers.removeHeader(remoteIpHeader); |
| for (String v : originalRemoteIpHeaderValues) { |
| headers.addValue(remoteIpHeader).setString(v); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks if the given IP address is from an internal proxy. |
| * |
| * @param remoteIp The IP address to check |
| * |
| * @return {@code true} if the IP address is from an internal proxy, otherwise {@code false} |
| */ |
| private boolean isInternalProxy(String remoteIp) { |
| return checkIsCidr(internalProxies, remoteIp); |
| } |
| |
| /** |
| * Checks if the given IP address is from a trusted proxy. |
| * |
| * @param remoteIp The IP address to check |
| * |
| * @return {@code true} if the IP address is from a trusted proxy, otherwise {@code false} |
| */ |
| private boolean isTrustedProxy(String remoteIp) { |
| return checkIsCidr(trustedProxies, remoteIp); |
| } |
| |
| private boolean checkIsCidr(NetMaskSet netMaskSet, String remoteIp) { |
| if (netMaskSet == null) { |
| return false; |
| } |
| try { |
| return netMaskSet.contains(remoteIp); |
| } catch (UnknownHostException uhe) { |
| log.debug(sm.getString("remoteIpFilter.invalidRemoteAddress", remoteIp), uhe); |
| } |
| return false; |
| } |
| |
| /* |
| * Considers the value to be secure if it exclusively holds forwards for {@link #protocolHeaderHttpsValue}. |
| */ |
| private boolean isForwardedProtoHeaderValueSecure(String protocolHeaderValue) { |
| if (!protocolHeaderValue.contains(",")) { |
| return protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue); |
| } |
| String[] forwardedProtocols = StringUtils.splitCommaSeparated(protocolHeaderValue); |
| if (forwardedProtocols.length == 0) { |
| return false; |
| } |
| for (String forwardedProtocol : forwardedProtocols) { |
| if (!protocolHeaderHttpsValue.equalsIgnoreCase(forwardedProtocol)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| 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); |
| } |
| } |
| |
| /** |
| * <p> |
| * Server Port value if the {@link #protocolHeader} is not <code>null</code> and does not indicate HTTP |
| * </p> |
| * <p> |
| * Default value : 80 |
| * </p> |
| * |
| * @param httpServerPort The server port |
| */ |
| public void setHttpServerPort(int httpServerPort) { |
| this.httpServerPort = httpServerPort; |
| } |
| |
| /** |
| * <p> |
| * Server Port value if the {@link #protocolHeader} indicates HTTPS |
| * </p> |
| * <p> |
| * Default value : 443 |
| * </p> |
| * |
| * @param httpsServerPort The server port |
| */ |
| public void setHttpsServerPort(int httpsServerPort) { |
| this.httpsServerPort = httpsServerPort; |
| } |
| |
| /** |
| * Set the internal proxies as a comma separated list of CIDR blocks. |
| * |
| * @param internalProxies The new internal proxies |
| */ |
| public void setInternalProxies(String internalProxies) { |
| if (internalProxies == null || internalProxies.isEmpty()) { |
| this.internalProxies = null; |
| } else { |
| this.internalProxies = NetMaskSet.parse(internalProxies); |
| } |
| } |
| |
| /** |
| * <p> |
| * Header that holds the incoming protocol, usually named <code>X-Forwarded-Proto</code>. If <code>null</code>, |
| * request.scheme and request.secure will not be modified. |
| * </p> |
| * <p> |
| * Default value : <code>X-Forwarded-Proto</code> |
| * </p> |
| * |
| * @param protocolHeader The header name |
| */ |
| public void setProtocolHeader(String protocolHeader) { |
| this.protocolHeader = protocolHeader; |
| } |
| |
| /** |
| * <p> |
| * Case-insensitive value of the protocol header to indicate that the incoming http request uses SSL. |
| * </p> |
| * <p> |
| * Default value : <code>https</code> |
| * </p> |
| * |
| * @param protocolHeaderHttpsValue The header name |
| */ |
| public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) { |
| this.protocolHeaderHttpsValue = protocolHeaderHttpsValue; |
| } |
| |
| /** |
| * <p> |
| * 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. |
| * </p> |
| * <p> |
| * Name of the http header that holds the list of trusted proxies that has been traversed by the http request. |
| * </p> |
| * <p> |
| * The value of this header can be comma-delimited. |
| * </p> |
| * <p> |
| * Default value : <code>X-Forwarded-By</code> |
| * </p> |
| * |
| * @param proxiesHeader The header name |
| */ |
| public void setProxiesHeader(String proxiesHeader) { |
| this.proxiesHeader = proxiesHeader; |
| } |
| |
| /** |
| * <p> |
| * Name of the http header from which the remote ip is extracted. |
| * </p> |
| * <p> |
| * The value of this header can be comma-delimited. |
| * </p> |
| * <p> |
| * Default value : <code>X-Forwarded-For</code> |
| * </p> |
| * |
| * @param remoteIpHeader The header name |
| */ |
| 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? These |
| * are typically used in conjunction with the {@link AccessLog} which will otherwise log the original values. |
| * Default is <code>true</code>. The attributes set are: |
| * <ul> |
| * <li>org.apache.catalina.AccessLog.RemoteAddr</li> |
| * <li>org.apache.catalina.AccessLog.RemoteHost</li> |
| * <li>org.apache.catalina.AccessLog.Protocol</li> |
| * <li>org.apache.catalina.AccessLog.ServerPort</li> |
| * <li>org.apache.tomcat.remoteAddr</li> |
| * </ul> |
| * |
| * @param requestAttributesEnabled <code>true</code> causes the attributes to be set, <code>false</code> disables |
| * the setting of the attributes. |
| */ |
| public void setRequestAttributesEnabled(boolean requestAttributesEnabled) { |
| this.requestAttributesEnabled = requestAttributesEnabled; |
| } |
| |
| /** |
| * Set the trusted proxies as a comma separated list of CIDR blocks. |
| * |
| * @param trustedProxies The new trusted proxies |
| */ |
| public void setTrustedProxies(String trustedProxies) { |
| if (trustedProxies == null || trustedProxies.isEmpty()) { |
| this.trustedProxies = null; |
| } else { |
| this.trustedProxies = NetMaskSet.parse(trustedProxies); |
| } |
| } |
| } |