View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server;
20  
21  import java.net.InetSocketAddress;
22  
23  import javax.servlet.ServletRequest;
24  
25  import org.eclipse.jetty.http.HostPortHttpField;
26  import org.eclipse.jetty.http.HttpField;
27  import org.eclipse.jetty.http.HttpFields;
28  import org.eclipse.jetty.http.HttpHeader;
29  import org.eclipse.jetty.http.HttpScheme;
30  import org.eclipse.jetty.http.QuotedCSV;
31  import org.eclipse.jetty.server.HttpConfiguration.Customizer;
32  import org.eclipse.jetty.util.StringUtil;
33  
34  
35  /* ------------------------------------------------------------ */
36  /** Customize Requests for Proxy Forwarding.
37   * <p>
38   * This customizer looks at at HTTP request for headers that indicate
39   * it has been forwarded by one or more proxies.  Specifically handled are
40   * <ul>
41   * <li>{@code Forwarded}, as defined by <a href="https://tools.ietf.org/html/rfc7239">rfc7239</a>
42   * <li>{@code X-Forwarded-Host}</li>
43   * <li>{@code X-Forwarded-Server}</li>
44   * <li>{@code X-Forwarded-For}</li>
45   * <li>{@code X-Forwarded-Proto}</li>
46   * <li>{@code X-Proxied-Https}</li>
47   * </ul>
48   * <p>If these headers are present, then the {@link Request} object is updated
49   * so that the proxy is not seen as the other end point of the connection on which
50   * the request came</p>
51   * <p>Headers can also be defined so that forwarded SSL Session IDs and Cipher
52   * suites may be customised</p> 
53   * @see <a href="http://en.wikipedia.org/wiki/X-Forwarded-For">Wikipedia: X-Forwarded-For</a>
54   */
55  public class ForwardedRequestCustomizer implements Customizer
56  {
57      private HostPortHttpField _forcedHost;
58      private String _forwardedHeader = HttpHeader.FORWARDED.toString();
59      private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
60      private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
61      private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
62      private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
63      private String _forwardedHttpsHeader = "X-Proxied-Https";
64      private String _forwardedCipherSuiteHeader = "Proxy-auth-cert";
65      private String _forwardedSslSessionIdHeader = "Proxy-ssl-id";
66      private boolean _proxyAsAuthority=false;
67      private boolean _sslIsSecure=true;
68      
69      /**
70       * @return true if the proxy address obtained via
71       * {@code X-Forwarded-Server} or RFC7239 "by" is used as
72       * the request authority. Default false
73       */
74      public boolean getProxyAsAuthority()
75      {
76          return _proxyAsAuthority;
77      }
78  
79      /**
80       * @param proxyAsAuthority if true, use the proxy address obtained via
81       * {@code X-Forwarded-Server} or RFC7239 "by" as the request authority.
82       */
83      public void setProxyAsAuthority(boolean proxyAsAuthority)
84      {
85          _proxyAsAuthority = proxyAsAuthority;
86      }
87  
88      /**
89       * Configure to only support the RFC7239 Forwarded header and to
90       * not support any {@code X-Forwarded-} headers.   This convenience method
91       * clears all the non RFC headers if passed true and sets them to
92       * the default values (if not already set) if passed false.
93       */
94      public void setForwardedOnly(boolean rfc7239only)
95      {
96          if (rfc7239only)
97          {
98              if (_forwardedHeader==null)
99                  _forwardedHeader=HttpHeader.FORWARDED.toString();
100             _forwardedHostHeader=null;
101             _forwardedHostHeader=null;
102             _forwardedServerHeader=null;
103             _forwardedForHeader=null;
104             _forwardedProtoHeader=null;
105             _forwardedHttpsHeader=null;
106         }
107         else
108         {
109             if (_forwardedHostHeader==null)
110                 _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
111             if (_forwardedServerHeader==null)
112                 _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
113             if (_forwardedForHeader==null)
114                 _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
115             if (_forwardedProtoHeader==null)
116                 _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
117             if (_forwardedHttpsHeader==null)
118                 _forwardedHttpsHeader = "X-Proxied-Https";
119         }
120     }
121     
122     public String getForcedHost()
123     {
124         return _forcedHost.getValue();
125     }
126     
127     /**
128      * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
129      *
130      * @param hostAndPort
131      *            The value of the host header to force.
132      */
133     public void setForcedHost(String hostAndPort)
134     {
135         _forcedHost = new HostPortHttpField(hostAndPort);
136     }
137 
138     /**
139      * @return The header name for RFC forwarded (default Forwarded)
140      */
141     public String getForwardedHeader()
142     {
143         return _forwardedHeader;
144     }
145 
146     /**
147      * @param forwardedHeader 
148      *            The header name for RFC forwarded (default Forwarded)
149      */
150     public void setForwardedHeader(String forwardedHeader)
151     {
152         _forwardedHeader = forwardedHeader;
153     }
154 
155     public String getForwardedHostHeader()
156     {
157         return _forwardedHostHeader;
158     }
159 
160     /**
161      * @param forwardedHostHeader
162      *            The header name for forwarded hosts (default {@code X-Forwarded-Host})
163      */
164     public void setForwardedHostHeader(String forwardedHostHeader)
165     {
166         _forwardedHostHeader = forwardedHostHeader;
167     }
168 
169     /**
170      * @return the header name for forwarded server.
171      */
172     public String getForwardedServerHeader()
173     {
174         return _forwardedServerHeader;
175     }
176 
177     /**
178      * @param forwardedServerHeader
179      *            The header name for forwarded server (default {@code X-Forwarded-Server})
180      */
181     public void setForwardedServerHeader(String forwardedServerHeader)
182     {
183         _forwardedServerHeader = forwardedServerHeader;
184     }
185 
186     /**
187      * @return the forwarded for header
188      */
189     public String getForwardedForHeader()
190     {
191         return _forwardedForHeader;
192     }
193 
194     /**
195      * @param forwardedRemoteAddressHeader
196      *            The header name for forwarded for (default {@code X-Forwarded-For})
197      */
198     public void setForwardedForHeader(String forwardedRemoteAddressHeader)
199     {
200         _forwardedForHeader = forwardedRemoteAddressHeader;
201     }
202 
203     /**
204      * Get the forwardedProtoHeader.
205      *
206      * @return the forwardedProtoHeader (default {@code X-Forwarded-Proto})
207      */
208     public String getForwardedProtoHeader()
209     {
210         return _forwardedProtoHeader;
211     }
212 
213     /**
214      * Set the forwardedProtoHeader.
215      *
216      * @param forwardedProtoHeader
217      *            the forwardedProtoHeader to set (default {@code X-Forwarded-Proto})
218      */
219     public void setForwardedProtoHeader(String forwardedProtoHeader)
220     {
221         _forwardedProtoHeader = forwardedProtoHeader;
222     }
223 
224     /**
225      * @return The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert})
226      */
227     public String getForwardedCipherSuiteHeader()
228     {
229         return _forwardedCipherSuiteHeader;
230     }
231 
232     /**
233      * @param forwardedCipherSuite
234      *            The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert})
235      */
236     public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
237     {
238         _forwardedCipherSuiteHeader = forwardedCipherSuite;
239     }
240 
241     /**
242      * @return The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id})
243      */
244     public String getForwardedSslSessionIdHeader()
245     {
246         return _forwardedSslSessionIdHeader;
247     }
248 
249     /**
250      * @param forwardedSslSessionId
251      *            The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id})
252      */
253     public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
254     {
255         _forwardedSslSessionIdHeader = forwardedSslSessionId;
256     }
257 
258     /**
259      * @return The header name holding a forwarded Https status indicator (on|off true|false) (default {@code X-Proxied-Https})
260      */
261     public String getForwardedHttpsHeader()
262     {
263         return _forwardedHttpsHeader;
264     }
265 
266     /**
267      * @param forwardedHttpsHeader the header name holding a forwarded Https status indicator(default {@code X-Proxied-Https})
268      */
269     public void setForwardedHttpsHeader(String forwardedHttpsHeader)
270     {
271         _forwardedHttpsHeader = forwardedHttpsHeader;
272     }
273     
274     /**
275      * @return true if the presence of a SSL session or certificate header is sufficient
276      * to indicate a secure request (default is true)
277      */
278     public boolean isSslIsSecure()
279     {
280         return _sslIsSecure;
281     }
282 
283     /**
284      * @param sslIsSecure true if the presence of a SSL session or certificate header is sufficient
285      * to indicate a secure request (default is true)
286      */
287     public void setSslIsSecure(boolean sslIsSecure)
288     {
289         _sslIsSecure = sslIsSecure;
290     }
291 
292     @Override
293     public void customize(Connector connector, HttpConfiguration config, Request request)
294     {
295         HttpFields httpFields = request.getHttpFields();
296 
297         RFC7239 rfc7239 = null;
298         String forwardedHost = null;
299         String forwardedServer = null;
300         String forwardedFor = null;
301         String forwardedProto = null;
302         String forwardedHttps = null;
303         
304         // Do a single pass through the header fields as it is a more efficient single iteration.
305         for (HttpField field : httpFields)
306         {
307             String name = field.getName();
308             
309             if (getForwardedCipherSuiteHeader()!=null && getForwardedCipherSuiteHeader().equalsIgnoreCase(name))
310             {
311                 request.setAttribute("javax.servlet.request.cipher_suite",field.getValue());
312                 if (isSslIsSecure())
313                 {
314                     request.setSecure(true);
315                     request.setScheme(config.getSecureScheme());
316                 }
317             }
318             
319             if (getForwardedSslSessionIdHeader()!=null && getForwardedSslSessionIdHeader().equalsIgnoreCase(name))
320             {
321                 request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue());
322                 if (isSslIsSecure())
323                 {
324                     request.setSecure(true);
325                     request.setScheme(config.getSecureScheme());
326                 }
327             }
328             
329             if (forwardedHost==null && _forwardedHostHeader!=null && _forwardedHostHeader.equalsIgnoreCase(name))
330                 forwardedHost = getLeftMost(field.getValue());
331             
332             if (forwardedServer==null && _forwardedServerHeader!=null && _forwardedServerHeader.equalsIgnoreCase(name))
333                 forwardedServer = getLeftMost(field.getValue());
334             
335             if (forwardedFor==null && _forwardedForHeader!=null && _forwardedForHeader.equalsIgnoreCase(name))
336                 forwardedFor = getLeftMost(field.getValue());
337             
338             if (forwardedProto==null && _forwardedProtoHeader!=null && _forwardedProtoHeader.equalsIgnoreCase(name))
339                 forwardedProto = getLeftMost(field.getValue());
340             
341             if (forwardedHttps==null && _forwardedHttpsHeader!=null && _forwardedHttpsHeader.equalsIgnoreCase(name))
342                 forwardedHttps = getLeftMost(field.getValue());
343             
344             if (_forwardedHeader!=null && _forwardedHeader.equalsIgnoreCase(name))
345             {
346                 if (rfc7239==null)
347                     rfc7239= new RFC7239();
348                 rfc7239.addValue(field.getValue());
349             }
350         }
351         
352         // Handle host header if if not available any RFC7230.by or X-ForwardedServer header      
353         if (_forcedHost != null)
354         {
355             // Update host header
356             httpFields.put(_forcedHost);
357             request.setAuthority(_forcedHost.getHost(),_forcedHost.getPort());
358         }
359         else if (rfc7239!=null && rfc7239._host!=null)
360         {
361             HostPortHttpField auth = rfc7239._host;
362             httpFields.put(auth);
363             request.setAuthority(auth.getHost(),auth.getPort());
364         }
365         else if (forwardedHost != null)
366         {
367             HostPortHttpField auth = new HostPortHttpField(forwardedHost);
368             httpFields.put(auth);
369             request.setAuthority(auth.getHost(),auth.getPort());
370         }
371         else if (_proxyAsAuthority)
372         {
373             if (rfc7239!=null && rfc7239._by!=null)
374             {
375                 HostPortHttpField auth = rfc7239._by;
376                 httpFields.put(auth);
377                 request.setAuthority(auth.getHost(),auth.getPort());
378             }
379             else if (forwardedServer != null)
380             {
381                 request.setAuthority(forwardedServer,request.getServerPort());
382             }
383         }
384 
385         // handle remote end identifier
386         if (rfc7239!=null && rfc7239._for!=null)
387         {
388             request.setRemoteAddr(InetSocketAddress.createUnresolved(rfc7239._for.getHost(),rfc7239._for.getPort()));
389         }
390         else if (forwardedFor != null)
391         {
392             request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
393         }
394 
395         // handle protocol identifier
396         if (rfc7239!=null && rfc7239._proto!=null)
397         {
398             request.setScheme(rfc7239._proto);
399             if (rfc7239._proto.equals(config.getSecureScheme()))
400                 request.setSecure(true);
401         }
402         else if (forwardedProto != null)
403         {
404             request.setScheme(forwardedProto);
405             if (forwardedProto.equals(config.getSecureScheme()))
406                 request.setSecure(true);
407         }
408         else if (forwardedHttps !=null && ("on".equalsIgnoreCase(forwardedHttps)||"true".equalsIgnoreCase(forwardedHttps)))
409         {
410             request.setScheme(HttpScheme.HTTPS.asString());
411             if (HttpScheme.HTTPS.asString().equals(config.getSecureScheme()))
412                 request.setSecure(true);
413         }
414     }
415 
416     /* ------------------------------------------------------------ */
417     protected String getLeftMost(String headerValue)
418     {
419         if (headerValue == null)
420             return null;
421 
422         int commaIndex = headerValue.indexOf(',');
423 
424         if (commaIndex == -1)
425         {
426             // Single value
427             return headerValue;
428         }
429 
430         // The left-most value is the farthest downstream client
431         return headerValue.substring(0,commaIndex).trim();
432     }
433     
434     @Override
435     public String toString()
436     {
437         return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
438     }
439 
440     @Deprecated
441     public String getHostHeader()
442     {
443         return _forcedHost.getValue();
444     }
445     
446     /**
447      * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
448      *
449      * @param hostHeader
450      *            The value of the host header to force.
451      */
452     @Deprecated
453     public void setHostHeader(String hostHeader)
454     {
455         _forcedHost = new HostPortHttpField(hostHeader);
456     }
457 
458     private final class RFC7239 extends QuotedCSV
459     {
460         HostPortHttpField _by;
461         HostPortHttpField _for;
462         HostPortHttpField _host;
463         String _proto;
464         
465         private RFC7239()
466         {
467             super(false);
468         }
469 
470         @Override
471         protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
472         {
473             if (valueLength==0 && paramValue>paramName)
474             {
475                 String name=StringUtil.asciiToLowerCase(buffer.substring(paramName,paramValue-1));
476                 String value=buffer.substring(paramValue);
477                 switch(name)
478                 {
479                     case "by":
480                         if (_by==null && !value.startsWith("_") && !"unknown".equals(value))
481                             _by=new HostPortHttpField(value);
482                         break;
483                     case "for":
484                         if (_for==null && !value.startsWith("_") && !"unknown".equals(value))
485                             _for=new HostPortHttpField(value);
486                         break;
487                     case "host":
488                         if (_host==null)
489                             _host=new HostPortHttpField(value);
490                         break;
491                     case "proto":
492                         if (_proto==null)
493                             _proto=value;
494                         break;
495                 }
496             }
497         }
498     }
499 }