001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.protocol.http.servlet; 018 019import java.util.LinkedList; 020import java.util.regex.Pattern; 021 022import jakarta.servlet.FilterConfig; 023import jakarta.servlet.ServletRequest; 024import jakarta.servlet.http.HttpServletRequest; 025 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * Request wrapper factory to integrate "X-Forwarded-For" and "X-Forwarded-Proto" HTTP headers. 031 * <p> 032 * Most of the design of this Servlet Filter is a port of <a 033 * href="http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, this servlet 034 * filter replaces the apparent client remote IP address and hostname for the request with the IP 035 * address list presented by a proxy or a load balancer via a request headers (e.g. 036 * "X-Forwarded-For"). 037 * <p> 038 * Another feature of this servlet filter is to replace the apparent scheme (http/https) and server 039 * port with the scheme presented by a proxy or a load balancer via a request header (e.g. 040 * "X-Forwarded-Proto"). 041 * <p> 042 * This wrapper proceeds as follows: 043 * <p> 044 * If the incoming <code>request.getRemoteAddr()</code> matches the servlet filter's list of 045 * internal proxies : 046 * <ul> 047 * <li>Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer 048 * or proxy in the given request's Http header named <code>$remoteIPHeader</code> (default value 049 * <code>x-forwarded-for</code>). Values are processed in right-to-left order.</li> 050 * <li>For each ip/host of the list: 051 * <ul> 052 * <li>if it matches the internal proxies list, the ip/host is swallowed</li> 053 * <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li> 054 * <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li> 055 * </ul> 056 * </li> 057 * <li>If the request http header named <code>$protocolHeader</code> (e.g. 058 * <code>x-forwarded-for</code>) equals to the value of <code>protocolHeaderHttpsValue</code> 059 * configuration parameter (default <code>https</code>) then <code>request.isSecure = true</code>, 060 * <code>request.scheme = https</code> and <code>request.serverPort = 443</code>. Note that 443 can 061 * be overwritten with the <code>$httpsServerPort</code> configuration parameter.</li> 062 * </ul> 063 * </p> 064 * <p> 065 * <strong>Configuration parameters:</strong> 066 * <table border="1"> 067 * <caption>Configuration parameters</caption> 068 * <tr> 069 * <th>XForwardedFilter property</th> 070 * <th>Description</th> 071 * <th>Equivalent mod_remoteip directive</th> 072 * <th>Format</th> 073 * <th>Default Value</th> 074 * </tr> 075 * <tr> 076 * <td>remoteIPHeader</td> 077 * <td>Name of the Http Header read by this servlet filter that holds the list of traversed IP 078 * addresses starting from the requesting client</td> 079 * <td>RemoteIPHeader</td> 080 * <td>Compliant http header name</td> 081 * <td>x-forwarded-for</td> 082 * </tr> 083 * <tr> 084 * <td>allowedInternalProxies</td> 085 * <td>List of internal proxies ip adress. If they appear in the <code>remoteIpHeader</code> value, 086 * they will be trusted and will not appear in the <code>proxiesHeader</code> value</td> 087 * <td>RemoteIPInternalProxy</td> 088 * <td>Comma delimited list of regular expressions (in the syntax supported by the 089 * {@link java.util.regex.Pattern} library)</td> 090 * <td>10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 091 * 172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3}, 092 * 127\.\d{1,3}\.\d{1,3}\.\d{1,3} <br/> 093 * By default, 10/8, 192.168/16, 172.16/12, 169.254/16 and 127/8 are allowed</td> 094 * </tr> 095 * </tr> 096 * <tr> 097 * <td>proxiesHeader</td> 098 * <td>Name of the http header created by this servlet filter to hold the list of proxies that have 099 * been processed in the incoming <code>remoteIPHeader</code></td> 100 * <td>RemoteIPProxiesHeader</td> 101 * <td>Compliant http header name</td> 102 * <td>x-forwarded-by</td> 103 * </tr> 104 * <tr> 105 * <td>trustedProxies</td> 106 * <td>List of trusted proxies ip adress. If they appear in the <code>remoteIpHeader</code> value, 107 * they will be trusted and will appear in the <code>proxiesHeader</code> value</td> 108 * <td>RemoteIPTrustedProxy</td> 109 * <td>Comma delimited list of regular expressions (in the syntax supported by the 110 * {@link java.util.regex.Pattern} library)</td> 111 * <td> </td> 112 * </tr> 113 * <tr> 114 * <td>protocolHeader</td> 115 * <td>Name of the http header read by this servlet filter that holds the flag that this request</td> 116 * <td>N/A</td> 117 * <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code> 118 * or <code>Front-End-Https</code></td> 119 * <td><code>null</code></td> 120 * </tr> 121 * <tr> 122 * <td>protocolHeaderHttpsValue</td> 123 * <td>Value of the <code>protocolHeader</code> to indicate that it is an Https request</td> 124 * <td>N/A</td> 125 * <td>String like <code>https</code> or <code>ON</code></td> 126 * <td><code>https</code></td> 127 * </tr> 128 * <tr> 129 * <tr> 130 * <td>httpServerPort</td> 131 * <td>Value returned by {@link ServletRequest#getServerPort()} when the <code>protocolHeader</code> 132 * indicates <code>http</code> protocol</td> 133 * <td>N/A</td> 134 * <td>integer</td> 135 * <td>80</td> 136 * </tr> 137 * <tr> 138 * <td>httpsServerPort</td> 139 * <td>Value returned by {@link ServletRequest#getServerPort()} when the <code>protocolHeader</code> 140 * indicates <code>https</code> protocol</td> 141 * <td>N/A</td> 142 * <td>integer</td> 143 * <td>443</td> 144 * </tr> 145 * </table> 146 * </p> 147 * <p> 148 * <p> 149 * <strong>Regular expression vs. IP address blocks:</strong> <code>mod_remoteip</code> allows to 150 * use address blocks (e.g. <code>192.168/16</code>) to configure <code>RemoteIPInternalProxy</code> 151 * and <code>RemoteIPTrustedProxy</code> ; as the JVM doesnt have a library similar to <a href= 152 * "http://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#gb74d21b8898b7c40bf7fd07ad3eb993d" 153 * >apr_ipsubnet_test</a>. 154 * </p> 155 * <hr/> 156 * <p> 157 * <strong>Sample with internal proxies</strong> 158 * </p> 159 * <p> 160 * XForwardedFilter configuration: 161 * </p> 162 * <code><pre> 163 * <filter> 164 * <filter-name>XForwardedFilter</filter-name> 165 * <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class> 166 * <init-param> 167 * <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value> 168 * </init-param> 169 * <init-param> 170 * <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value> 171 * </init-param> 172 * <init-param> 173 * <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value> 174 * </init-param> 175 * <init-param> 176 * <param-name>protocolHeader</param-name><param-value>x-forwarded-proto</param-value> 177 * </init-param> 178 * </filter> 179 * 180 * <filter-mapping> 181 * <filter-name>XForwardedFilter</filter-name> 182 * <url-pattern>/*</url-pattern> 183 * <dispatcher>REQUEST</dispatcher> 184 * </filter-mapping></pre></code> 185 * <p> 186 * Request values: 187 * <table border="1"> 188 * <caption>Request values</caption> 189 * <tr> 190 * <th>property</th> 191 * <th>Value Before XForwardedFilter</th> 192 * <th>Value After XForwardedFilter</th> 193 * </tr> 194 * <tr> 195 * <td>request.remoteAddr</td> 196 * <td>192.168.0.10</td> 197 * <td>140.211.11.130</td> 198 * </tr> 199 * <tr> 200 * <td>request.header['x-forwarded-for']</td> 201 * <td>140.211.11.130, 192.168.0.10</td> 202 * <td>null</td> 203 * </tr> 204 * <tr> 205 * <td>request.header['x-forwarded-by']</td> 206 * <td>null</td> 207 * <td>null</td> 208 * </tr> 209 * <tr> 210 * <td>request.header['x-forwarded-proto']</td> 211 * <td>https</td> 212 * <td>https</td> 213 * </tr> 214 * <tr> 215 * <td>request.scheme</td> 216 * <td>http</td> 217 * <td>https</td> 218 * </tr> 219 * <tr> 220 * <td>request.secure</td> 221 * <td>false</td> 222 * <td>true</td> 223 * </tr> 224 * <tr> 225 * <td>request.serverPort</td> 226 * <td>80</td> 227 * <td>443</td> 228 * </tr> 229 * </table> 230 * Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed 231 * by the request. <code>x-forwarded-by</code> is null because all the proxies are trusted or 232 * internal. 233 * </p> 234 * <hr/> 235 * <p> 236 * <strong>Sample with trusted proxies</strong> 237 * </p> 238 * <p> 239 * XForwardedFilter configuration: 240 * </p> 241 * <code><pre> 242 * <filter> 243 * <filter-name>XForwardedFilter</filter-name> 244 * <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class> 245 * <init-param> 246 * <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value> 247 * </init-param> 248 * <init-param> 249 * <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value> 250 * </init-param> 251 * <init-param> 252 * <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value> 253 * </init-param> 254 * <init-param> 255 * <param-name>trustedProxies</param-name><param-value>proxy1, proxy2</param-value> 256 * </init-param> 257 * </filter> 258 * 259 * <filter-mapping> 260 * <filter-name>XForwardedFilter</filter-name> 261 * <url-pattern>/*</url-pattern> 262 * <dispatcher>REQUEST</dispatcher> 263 * </filter-mapping></pre></code> 264 * <p> 265 * Request values: 266 * <table border="1"> 267 * <caption>Request values</caption> 268 * <tr> 269 * <th>property</th> 270 * <th>Value Before XForwardedFilter</th> 271 * <th>Value After XForwardedFilter</th> 272 * </tr> 273 * <tr> 274 * <td>request.remoteAddr</td> 275 * <td>192.168.0.10</td> 276 * <td>140.211.11.130</td> 277 * </tr> 278 * <tr> 279 * <td>request.header['x-forwarded-for']</td> 280 * <td>140.211.11.130, proxy1, proxy2</td> 281 * <td>null</td> 282 * </tr> 283 * <tr> 284 * <td>request.header['x-forwarded-by']</td> 285 * <td>null</td> 286 * <td>proxy1, proxy2</td> 287 * </tr> 288 * </table> 289 * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in 290 * <code>x-forwarded-for</code> header, they both are migrated in <code>x-forwarded-by</code> 291 * header. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal. 292 * </p> 293 * <hr/> 294 * <p> 295 * <strong>Sample with internal and trusted proxies</strong> 296 * </p> 297 * <p> 298 * XForwardedFilter configuration: 299 * </p> 300 * <code><pre> 301 * <filter> 302 * <filter-name>XForwardedFilter</filter-name> 303 * <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class> 304 * <init-param> 305 * <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value> 306 * </init-param> 307 * <init-param> 308 * <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value> 309 * </init-param> 310 * <init-param> 311 * <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value> 312 * </init-param> 313 * <init-param> 314 * <param-name>trustedProxies</param-name><param-value>proxy1, proxy2</param-value> 315 * </init-param> 316 * </filter> 317 * 318 * <filter-mapping> 319 * <filter-name>XForwardedFilter</filter-name> 320 * <url-pattern>/*</url-pattern> 321 * <dispatcher>REQUEST</dispatcher> 322 * </filter-mapping></pre></code> 323 * <p> 324 * Request values: 325 * <table border="1"> 326 * <caption>Request values</caption> 327 * <tr> 328 * <th>property</th> 329 * <th>Value Before XForwardedFilter</th> 330 * <th>Value After XForwardedFilter</th> 331 * </tr> 332 * <tr> 333 * <td>request.remoteAddr</td> 334 * <td>192.168.0.10</td> 335 * <td>140.211.11.130</td> 336 * </tr> 337 * <tr> 338 * <td>request.header['x-forwarded-for']</td> 339 * <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td> 340 * <td>null</td> 341 * </tr> 342 * <tr> 343 * <td>request.header['x-forwarded-by']</td> 344 * <td>null</td> 345 * <td>proxy1, proxy2</td> 346 * </tr> 347 * </table> 348 * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in 349 * <code>x-forwarded-for</code> header, they both are migrated in <code>x-forwarded-by</code> 350 * header. As <code>192.168.0.10</code> is an internal proxy, it does not appear in 351 * <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies are 352 * trusted or internal. 353 * </p> 354 * <hr/> 355 * <p> 356 * <strong>Sample with an untrusted proxy</strong> 357 * </p> 358 * <p> 359 * XForwardedFilter configuration: 360 * </p> 361 * <code><pre> 362 * <filter> 363 * <filter-name>XForwardedFilter</filter-name> 364 * <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class> 365 * <init-param> 366 * <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value> 367 * </init-param> 368 * <init-param> 369 * <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value> 370 * </init-param> 371 * <init-param> 372 * <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value> 373 * </init-param> 374 * <init-param> 375 * <param-name>trustedProxies</param-name><param-value>proxy1, proxy2</param-value> 376 * </init-param> 377 * </filter> 378 * 379 * <filter-mapping> 380 * <filter-name>XForwardedFilter</filter-name> 381 * <url-pattern>/*</url-pattern> 382 * <dispatcher>REQUEST</dispatcher> 383 * </filter-mapping></pre></code> 384 * <p> 385 * Request values: 386 * <table border="1"> 387 * <caption>Request values</caption> 388 * <tr> 389 * <th>property</th> 390 * <th>Value Before XForwardedFilter</th> 391 * <th>Value After XForwardedFilter</th> 392 * </tr> 393 * <tr> 394 * <td>request.remoteAddr</td> 395 * <td>192.168.0.10</td> 396 * <td>untrusted-proxy</td> 397 * </tr> 398 * <tr> 399 * <td>request.header['x-forwarded-for']</td> 400 * <td>140.211.11.130, untrusted-proxy, proxy1</td> 401 * <td>140.211.11.130</td> 402 * </tr> 403 * <tr> 404 * <td>request.header['x-forwarded-by']</td> 405 * <td>null</td> 406 * <td>proxy1</td> 407 * </tr> 408 * </table> 409 * Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>. 410 * <code>x-forwarded-by</code> holds <code>140.211.11.130</code> because 411 * <code>untrusted-proxy</code> is not trusted and thus, we can not trust that 412 * <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is 413 * <code>untrusted-proxy</code> that is an IP verified by <code>proxy1</code>. 414 * </p> 415 * <hr/> 416 * 417 * @author <a href="mailto:cyrille@cyrilleleclerc.com">Cyrille Le Clerc</a> 418 * @author Juergen Donnerstag 419 */ 420public class XForwardedRequestWrapperFactory extends AbstractRequestWrapperFactory 421{ 422 /** Logger */ 423 private static final Logger log = LoggerFactory.getLogger(XForwardedRequestWrapperFactory.class); 424 425 protected static final String HTTP_SERVER_PORT_PARAMETER = "httpServerPort"; 426 427 protected static final String HTTPS_SERVER_PORT_PARAMETER = "httpsServerPort"; 428 429 protected static final String INTERNAL_PROXIES_PARAMETER = "allowedInternalProxies"; 430 431 protected static final String PROTOCOL_HEADER_PARAMETER = "protocolHeader"; 432 433 protected static final String PROTOCOL_HEADER_SSL_VALUE_PARAMETER = "protocolHeaderSslValue"; 434 435 protected static final String PROXIES_HEADER_PARAMETER = "proxiesHeader"; 436 437 protected static final String REMOTE_IP_HEADER_PARAMETER = "remoteIPHeader"; 438 439 protected static final String TRUSTED_PROXIES_PARAMETER = "trustedProxies"; 440 441 /** 442 * Filter Config 443 */ 444 public static class Config 445 { 446 // Enable / disable xforwarded functionality 447 private boolean enabled = true; 448 449 /** @see #setHttpServerPort(int) */ 450 private int httpServerPort = 80; 451 452 /** @see #setHttpsServerPort(int) */ 453 private int httpsServerPort = 443; 454 455 /** @see #setProtocolHeader(String) */ 456 private String protocolHeader = null; 457 458 /** @see #setProtocolHeaderSslValue(String) */ 459 private String protocolHeaderSslValue = "https"; 460 461 /** @see #setProxiesHeader(String) */ 462 private String proxiesHeader = "X-Forwarded-By"; 463 464 /** @see #setRemoteIPHeader(String) */ 465 private String remoteIPHeader = "X-Forwarded-For"; 466 467 /** @see #setTrustedProxies(String) */ 468 private Pattern[] trustedProxies = new Pattern[0]; 469 470 /** @see #setTrustedProxies(String) */ 471 private Pattern[] allowedInternalProxies = new Pattern[] { 472 Pattern.compile("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"), 473 Pattern.compile("192\\.168\\.\\d{1,3}\\.\\d{1,3}"), 474 Pattern.compile("172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}"), 475 Pattern.compile("169\\.254\\.\\d{1,3}\\.\\d{1,3}"), 476 Pattern.compile("127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}") }; 477 478 /** 479 * Comma delimited list of internal proxies. Expressed with regular expressions. 480 * <p> 481 * Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 482 * 172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3}, 483 * 127\.\d{1,3}\.\d{1,3}\.\d{1,3} 484 * 485 * @param allowedInternalProxies 486 */ 487 public void setAllowedInternalProxies(final String allowedInternalProxies) 488 { 489 this.allowedInternalProxies = commaDelimitedListToPatternArray(allowedInternalProxies); 490 } 491 492 /** 493 * Server Port value if the {@link #protocolHeader} does not indicate HTTPS 494 * <p> 495 * Default value : 80 496 * 497 * @param httpServerPort 498 */ 499 public void setHttpServerPort(final int httpServerPort) 500 { 501 this.httpServerPort = httpServerPort; 502 } 503 504 /** 505 * Server Port value if the {@link #protocolHeader} indicates HTTPS 506 * <p> 507 * Default value : 443 508 * 509 * @param httpsServerPort 510 */ 511 public void setHttpsServerPort(final int httpsServerPort) 512 { 513 this.httpsServerPort = httpsServerPort; 514 } 515 516 /** 517 * Header that holds the incoming protocol, usally named <code>X-Forwarded-Proto</code>. If 518 * <code>null</code>, request.scheme and request.secure will not be modified. 519 * <p> 520 * Default value : <code>null</code> 521 * 522 * @param protocolHeader 523 */ 524 public void setProtocolHeader(final String protocolHeader) 525 { 526 this.protocolHeader = protocolHeader; 527 } 528 529 /** 530 * Case insensitive value of the protocol header to indicate that the incoming http request 531 * uses SSL. 532 * <p> 533 * Default value : <code>HTTPS</code> 534 * 535 * @param protocolHeaderSslValue 536 */ 537 public void setProtocolHeaderSslValue(final String protocolHeaderSslValue) 538 { 539 this.protocolHeaderSslValue = protocolHeaderSslValue; 540 } 541 542 /** 543 * The proxiesHeader directive specifies a header into which mod_remoteip will collect a 544 * list of all of the intermediate client IP addresses trusted to resolve the actual remote 545 * IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header, 546 * while any intermediate RemoteIPInternalProxy addresses are discarded. 547 * <p> 548 * Name of the http header that holds the list of trusted proxies that has been traversed by 549 * the http request. 550 * <p> 551 * The value of this header can be comma delimited. 552 * <p> 553 * Default value : <code>X-Forwarded-By</code> 554 * 555 * @param proxiesHeader 556 */ 557 public void setProxiesHeader(final String proxiesHeader) 558 { 559 this.proxiesHeader = proxiesHeader; 560 } 561 562 /** 563 * Name of the http header from which the remote ip is extracted. 564 * <p> 565 * The value of this header can be comma delimited. 566 * <p> 567 * Default value : <code>X-Forwarded-For</code> 568 * 569 * @param remoteIPHeader 570 */ 571 public void setRemoteIPHeader(final String remoteIPHeader) 572 { 573 this.remoteIPHeader = remoteIPHeader; 574 } 575 576 /** 577 * Comma delimited list of proxies that are trusted when they appear in the 578 * {@link #remoteIPHeader} header. Can be expressed as a regular expression. 579 * <p> 580 * Default value : empty list, no external proxy is trusted. 581 * 582 * @param trustedProxies 583 */ 584 public void setTrustedProxies(final String trustedProxies) 585 { 586 this.trustedProxies = commaDelimitedListToPatternArray(trustedProxies); 587 } 588 589 /** 590 * Enable / disable XForwarded related processing 591 * 592 * @param enable 593 */ 594 public void setEnabled(boolean enable) 595 { 596 enabled = enable; 597 } 598 599 /** 600 * @return True, if filter is active 601 */ 602 public boolean isEnabled() 603 { 604 return enabled; 605 } 606 } 607 608 // Filter Config 609 private Config config = new Config(); 610 611 /** 612 * Construct. 613 */ 614 public XForwardedRequestWrapperFactory() 615 { 616 } 617 618 /** 619 * @return XForwarded filter specific config 620 */ 621 public final Config getConfig() 622 { 623 return config; 624 } 625 626 /** 627 * The Wicket application might want to provide its own config 628 * 629 * @param config 630 */ 631 public final void setConfig(final Config config) 632 { 633 this.config = config; 634 } 635 636 /** 637 * {@inheritDoc} 638 */ 639 @Override 640 public boolean needsWrapper(final HttpServletRequest request) 641 { 642 boolean rtn = matchesOne(request.getRemoteAddr(), config.allowedInternalProxies); 643 if (rtn == false) 644 { 645 if (log.isDebugEnabled()) 646 { 647 log.debug("Skip XForwardedFilter for request " + request.getRequestURI() + 648 " with remote address " + request.getRemoteAddr()); 649 } 650 } 651 return rtn; 652 } 653 654 /** 655 * 656 * @param request 657 * @return Either the original request or the wrapper 658 */ 659 @Override 660 public HttpServletRequest newRequestWrapper(final HttpServletRequest request) 661 { 662 String remoteIp = null; 663 664 // In java 6, proxiesHeaderValue should be declared as a java.util.Deque 665 LinkedList<String> proxiesHeaderValue = new LinkedList<String>(); 666 667 String[] remoteIPHeaderValue = commaDelimitedListToStringArray(request.getHeader(config.remoteIPHeader)); 668 669 // loop on remoteIPHeaderValue to find the first trusted remote ip and to build the 670 // proxies chain 671 int idx; 672 for (idx = remoteIPHeaderValue.length - 1; idx >= 0; idx--) 673 { 674 String currentRemoteIp = remoteIPHeaderValue[idx]; 675 remoteIp = currentRemoteIp; 676 if (matchesOne(currentRemoteIp, config.allowedInternalProxies)) 677 { 678 // do nothing, allowedInternalProxies IPs are not appended to the 679 } 680 else if (matchesOne(currentRemoteIp, config.trustedProxies)) 681 { 682 proxiesHeaderValue.addFirst(currentRemoteIp); 683 } 684 else 685 { 686 idx--; // decrement idx because break statement doesn't do it 687 break; 688 } 689 } 690 691 // continue to loop on remoteIPHeaderValue to build the new value of the remoteIPHeader 692 LinkedList<String> newRemoteIpHeaderValue = new LinkedList<String>(); 693 for (; idx >= 0; idx--) 694 { 695 String currentRemoteIp = remoteIPHeaderValue[idx]; 696 newRemoteIpHeaderValue.addFirst(currentRemoteIp); 697 } 698 699 XForwardedRequestWrapper xRequest = new XForwardedRequestWrapper(request); 700 if (remoteIp != null) 701 { 702 xRequest.setRemoteAddr(remoteIp); 703 xRequest.setRemoteHost(remoteIp); 704 705 if (proxiesHeaderValue.size() == 0) 706 { 707 xRequest.removeHeader(config.proxiesHeader); 708 } 709 else 710 { 711 String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue); 712 xRequest.setHeader(config.proxiesHeader, commaDelimitedListOfProxies); 713 } 714 if (newRemoteIpHeaderValue.size() == 0) 715 { 716 xRequest.removeHeader(config.remoteIPHeader); 717 } 718 else 719 { 720 String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue); 721 xRequest.setHeader(config.remoteIPHeader, commaDelimitedRemoteIpHeaderValue); 722 } 723 } 724 725 if (config.protocolHeader != null) 726 { 727 String protocolHeaderValue = request.getHeader(config.protocolHeader); 728 if (protocolHeaderValue == null) 729 { 730 // don't modify the secure,scheme and serverPort attributes of the request 731 } 732 else if (config.protocolHeaderSslValue.equalsIgnoreCase(protocolHeaderValue)) 733 { 734 xRequest.setSecure(true); 735 xRequest.setScheme("https"); 736 xRequest.setServerPort(config.httpsServerPort); 737 } 738 else 739 { 740 xRequest.setSecure(false); 741 xRequest.setScheme("http"); 742 xRequest.setServerPort(config.httpServerPort); 743 } 744 } 745 746 if (log.isDebugEnabled()) 747 { 748 log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" + 749 request.getRemoteAddr() + "', originalRemoteHost='" + request.getRemoteHost() + 750 "', originalSecure='" + request.isSecure() + "', originalScheme='" + 751 request.getScheme() + "', original[" + config.remoteIPHeader + "]='" + 752 request.getHeader(config.remoteIPHeader) + ", original[" + config.protocolHeader + 753 "]='" + 754 (config.protocolHeader == null ? null : request.getHeader(config.protocolHeader)) + 755 "' will be seen as newRemoteAddr='" + xRequest.getRemoteAddr() + 756 "', newRemoteHost='" + xRequest.getRemoteHost() + "', newScheme='" + 757 xRequest.getScheme() + "', newSecure='" + xRequest.isSecure() + "', new[" + 758 config.remoteIPHeader + "]='" + xRequest.getHeader(config.remoteIPHeader) + 759 ", new[" + config.proxiesHeader + "]='" + xRequest.getHeader(config.proxiesHeader) + 760 "'"); 761 } 762 return xRequest; 763 } 764 765 /** 766 * 767 * @param filterConfig 768 */ 769 public void init(final FilterConfig filterConfig) 770 { 771 if (filterConfig.getInitParameter(INTERNAL_PROXIES_PARAMETER) != null) 772 { 773 config.setAllowedInternalProxies(filterConfig.getInitParameter(INTERNAL_PROXIES_PARAMETER)); 774 } 775 776 if (filterConfig.getInitParameter(PROTOCOL_HEADER_PARAMETER) != null) 777 { 778 config.setProtocolHeader(filterConfig.getInitParameter(PROTOCOL_HEADER_PARAMETER)); 779 } 780 781 if (filterConfig.getInitParameter(PROTOCOL_HEADER_SSL_VALUE_PARAMETER) != null) 782 { 783 config.setProtocolHeaderSslValue(filterConfig.getInitParameter(PROTOCOL_HEADER_SSL_VALUE_PARAMETER)); 784 } 785 786 if (filterConfig.getInitParameter(PROXIES_HEADER_PARAMETER) != null) 787 { 788 config.setProxiesHeader(filterConfig.getInitParameter(PROXIES_HEADER_PARAMETER)); 789 } 790 791 if (filterConfig.getInitParameter(REMOTE_IP_HEADER_PARAMETER) != null) 792 { 793 config.setRemoteIPHeader(filterConfig.getInitParameter(REMOTE_IP_HEADER_PARAMETER)); 794 } 795 796 if (filterConfig.getInitParameter(TRUSTED_PROXIES_PARAMETER) != null) 797 { 798 config.setTrustedProxies(filterConfig.getInitParameter(TRUSTED_PROXIES_PARAMETER)); 799 } 800 801 if (filterConfig.getInitParameter(HTTP_SERVER_PORT_PARAMETER) != null) 802 { 803 try 804 { 805 config.setHttpServerPort(Integer.parseInt(filterConfig.getInitParameter(HTTP_SERVER_PORT_PARAMETER))); 806 } 807 catch (NumberFormatException e) 808 { 809 throw new NumberFormatException("Illegal " + HTTP_SERVER_PORT_PARAMETER + " : " + 810 e.getMessage()); 811 } 812 } 813 814 if (filterConfig.getInitParameter(HTTPS_SERVER_PORT_PARAMETER) != null) 815 { 816 try 817 { 818 config.setHttpsServerPort(Integer.parseInt(filterConfig.getInitParameter(HTTPS_SERVER_PORT_PARAMETER))); 819 } 820 catch (NumberFormatException e) 821 { 822 throw new NumberFormatException("Illegal " + HTTPS_SERVER_PORT_PARAMETER + " : " + 823 e.getMessage()); 824 } 825 } 826 } 827}