Issue #185 Implement RFC 7239

Improved test harness
Added more configurability
Fixed SSL session and certificate bugs
This commit is contained in:
Greg Wilkins 2016-08-12 11:39:36 +10:00
parent 5c9a637d4f
commit 5fefd5d8bd
4 changed files with 176 additions and 26 deletions

View File

@ -4,14 +4,17 @@
<Call name="addCustomizer"> <Call name="addCustomizer">
<Arg> <Arg>
<New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"> <New class="org.eclipse.jetty.server.ForwardedRequestCustomizer">
<Set name="forwardedOnly"><Property name="jetty.httpConfig.forwardedOnly" default="false"/></Set>
<Set name="proxyAsAuthority"><Property name="jetty.httpConfig.forwardedProxyAsAuthority" default="false"/></Set>
<Set name="forwardedHeader"><Property name="jetty.httpConfig.forwardedHeader" default="Forwarded"/></Set>
<Set name="forwardedHostHeader"><Property name="jetty.httpConfig.forwardedHostHeader" default="X-Forwarded-Host"/></Set> <Set name="forwardedHostHeader"><Property name="jetty.httpConfig.forwardedHostHeader" default="X-Forwarded-Host"/></Set>
<Set name="forwardedServerHeader"><Property name="jetty.httpConfig.forwardedServerHeader" default="X-Forwarded-Server"/></Set> <Set name="forwardedServerHeader"><Property name="jetty.httpConfig.forwardedServerHeader" default="X-Forwarded-Server"/></Set>
<Set name="forwardedProtoHeader"><Property name="jetty.httpConfig.forwardedProtoHeader" default="X-Forwarded-Proto"/></Set> <Set name="forwardedProtoHeader"><Property name="jetty.httpConfig.forwardedProtoHeader" default="X-Forwarded-Proto"/></Set>
<Set name="forwardedForHeader"><Property name="jetty.httpConfig.forwardedForHeader" default="X-Forwarded-For"/></Set> <Set name="forwardedForHeader"><Property name="jetty.httpConfig.forwardedForHeader" default="X-Forwarded-For"/></Set>
<Set name="forwardedSslSessionIdHeader"><Property name="jetty.httpConfig.forwardedSslSessionIdHeader" /></Set> <Set name="forwardedForHeader"><Property name="jetty.httpConfig.forwardedHttpsHeader" default="X-Proxied-Https"/></Set>
<Set name="forwardedCipherSuiteHeader"><Property name="jetty.httpConfig.forwardedCipherSuiteHeader" /></Set> <Set name="forwardedSslSessionIdHeader"><Property name="jetty.httpConfig.forwardedSslSessionIdHeader" default="Proxy-ssl-id" /></Set>
<Set name="forwardedCipherSuiteHeader"><Property name="jetty.httpConfig.forwardedCipherSuiteHeader" default="Proxy-auth-cert"/></Set>
</New> </New>
</Arg> </Arg>
</Call> </Call>
</Configure> </Configure>

View File

@ -11,10 +11,14 @@ etc/jetty-http-forwarded.xml
[ini-template] [ini-template]
### ForwardedRequestCustomizer Configuration ### ForwardedRequestCustomizer Configuration
# jetty.httpConfig.forwardedOnly=false
# jetty.httpConfig.forwardedProxyAsAuthority=false
# jetty.httpConfig.forwardedHeader=Forwarded
# jetty.httpConfig.forwardedHostHeader=X-Forwarded-Host # jetty.httpConfig.forwardedHostHeader=X-Forwarded-Host
# jetty.httpConfig.forwardedServerHeader=X-Forwarded-Server # jetty.httpConfig.forwardedServerHeader=X-Forwarded-Server
# jetty.httpConfig.forwardedProtoHeader=X-Forwarded-Proto # jetty.httpConfig.forwardedProtoHeader=X-Forwarded-Proto
# jetty.httpConfig.forwardedForHeader=X-Forwarded-For # jetty.httpConfig.forwardedForHeader=X-Forwarded-For
# jetty.httpConfig.forwardedSslSessionIdHeader= # jetty.httpConfig.forwardedHttpsHeader=X-Proxied-Https
# jetty.httpConfig.forwardedCipherSuiteHeader= # jetty.httpConfig.forwardedSslSessionIdHeader=Proxy-ssl-id
# jetty.httpConfig.forwardedCipherSuiteHeader=Proxy-auth-cert

View File

@ -54,7 +54,6 @@ import org.eclipse.jetty.util.StringUtil;
*/ */
public class ForwardedRequestCustomizer implements Customizer public class ForwardedRequestCustomizer implements Customizer
{ {
private HostPortHttpField _forcedHost; private HostPortHttpField _forcedHost;
private String _forwardedHeader = HttpHeader.FORWARDED.toString(); private String _forwardedHeader = HttpHeader.FORWARDED.toString();
private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
@ -62,11 +61,11 @@ public class ForwardedRequestCustomizer implements Customizer
private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
private String _forwardedHttpsHeader = "X-Proxied-Https"; private String _forwardedHttpsHeader = "X-Proxied-Https";
private String _forwardedCipherSuiteHeader; private String _forwardedCipherSuiteHeader = "Proxy-auth-cert";
private String _forwardedSslSessionIdHeader; private String _forwardedSslSessionIdHeader = "Proxy-ssl-id";
private boolean _proxyAsAuthority=false; private boolean _proxyAsAuthority=false;
private boolean _sslIsSecure=true;
/** /**
* @return true if the proxy address obtained via * @return true if the proxy address obtained via
* X-Forwarded-Server or RFC7239 "by" is used as * X-Forwarded-Server or RFC7239 "by" is used as
@ -77,7 +76,6 @@ public class ForwardedRequestCustomizer implements Customizer
return _proxyAsAuthority; return _proxyAsAuthority;
} }
/** /**
* @param proxyAsAuthority if true, use the proxy address obtained via * @param proxyAsAuthority if true, use the proxy address obtained via
* X-Forwarded-Server or RFC7239 "by" as the request authority. * X-Forwarded-Server or RFC7239 "by" as the request authority.
@ -87,15 +85,19 @@ public class ForwardedRequestCustomizer implements Customizer
_proxyAsAuthority = proxyAsAuthority; _proxyAsAuthority = proxyAsAuthority;
} }
/** /**
* Configure to only support the RFC7239 Forwarded header and to * Configure to only support the RFC7239 Forwarded header and to
* not support any X-Forwarded- headers. * not support any X-Forwarded- headers. This convenience method
* clears all the non RFC headers if passed true and sets them to
* the default values (if not already set) if passed false.
*/ */
public void setForwardedOnly(boolean rfc7239only) public void setForwardedOnly(boolean rfc7239only)
{ {
if (rfc7239only) if (rfc7239only)
{ {
if (_forwardedHeader==null)
_forwardedHeader=HttpHeader.FORWARDED.toString();
_forwardedHostHeader=null;
_forwardedHostHeader=null; _forwardedHostHeader=null;
_forwardedServerHeader=null; _forwardedServerHeader=null;
_forwardedForHeader=null; _forwardedForHeader=null;
@ -104,15 +106,19 @@ public class ForwardedRequestCustomizer implements Customizer
} }
else else
{ {
_forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); if (_forwardedHostHeader==null)
_forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString(); _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
_forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); if (_forwardedServerHeader==null)
_forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
_forwardedHttpsHeader = "X-Proxied-Https"; if (_forwardedForHeader==null)
_forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
if (_forwardedProtoHeader==null)
_forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
if (_forwardedHttpsHeader==null)
_forwardedHttpsHeader = "X-Proxied-Https";
} }
} }
public String getForcedHost() public String getForcedHost()
{ {
return _forcedHost.getValue(); return _forcedHost.getValue();
@ -129,6 +135,23 @@ public class ForwardedRequestCustomizer implements Customizer
_forcedHost = new HostPortHttpField(hostAndPort); _forcedHost = new HostPortHttpField(hostAndPort);
} }
/**
* @return The header name for RFC forwarded (default Forwarded)
*/
public String getForwardedHeader()
{
return _forwardedHeader;
}
/**
* @param forwardedHeader
* The header name for RFC forwarded (default Forwarded)
*/
public void setForwardedHeader(String forwardedHeader)
{
_forwardedHeader = forwardedHeader;
}
public String getForwardedHostHeader() public String getForwardedHostHeader()
{ {
return _forwardedHostHeader; return _forwardedHostHeader;
@ -199,7 +222,7 @@ public class ForwardedRequestCustomizer implements Customizer
} }
/** /**
* @return The header name holding a forwarded cipher suite (default null) * @return The header name holding a forwarded cipher suite (default Proxy-auth-cert)
*/ */
public String getForwardedCipherSuiteHeader() public String getForwardedCipherSuiteHeader()
{ {
@ -208,7 +231,7 @@ public class ForwardedRequestCustomizer implements Customizer
/** /**
* @param forwardedCipherSuite * @param forwardedCipherSuite
* The header name holding a forwarded cipher suite (default null) * The header name holding a forwarded cipher suite (default Proxy-auth-cert)
*/ */
public void setForwardedCipherSuiteHeader(String forwardedCipherSuite) public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
{ {
@ -216,7 +239,7 @@ public class ForwardedRequestCustomizer implements Customizer
} }
/** /**
* @return The header name holding a forwarded SSL Session ID (default null) * @return The header name holding a forwarded SSL Session ID (default Proxy-ssl-id)
*/ */
public String getForwardedSslSessionIdHeader() public String getForwardedSslSessionIdHeader()
{ {
@ -225,7 +248,7 @@ public class ForwardedRequestCustomizer implements Customizer
/** /**
* @param forwardedSslSessionId * @param forwardedSslSessionId
* The header name holding a forwarded SSL Session ID (default null) * The header name holding a forwarded SSL Session ID (default Proxy-ssl-id)
*/ */
public void setForwardedSslSessionIdHeader(String forwardedSslSessionId) public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
{ {
@ -247,6 +270,24 @@ public class ForwardedRequestCustomizer implements Customizer
{ {
_forwardedHttpsHeader = forwardedHttpsHeader; _forwardedHttpsHeader = forwardedHttpsHeader;
} }
/**
* @return true if the presence of a SSL session or certificate header is sufficient
* to indicate a secure request (default is true)
*/
public boolean isSslIsSecure()
{
return _sslIsSecure;
}
/**
* @param sslIsSecure true if the presence of a SSL session or certificate header is sufficient
* to indicate a secure request (default is true)
*/
public void setSslIsSecure(boolean sslIsSecure)
{
_sslIsSecure = sslIsSecure;
}
@Override @Override
public void customize(Connector connector, HttpConfiguration config, Request request) public void customize(Connector connector, HttpConfiguration config, Request request)
@ -265,11 +306,25 @@ public class ForwardedRequestCustomizer implements Customizer
{ {
String name = field.getName(); String name = field.getName();
if (getForwardedCipherSuiteHeader()!=null && httpFields.get(getForwardedCipherSuiteHeader()).equalsIgnoreCase(name)) if (getForwardedCipherSuiteHeader()!=null && getForwardedCipherSuiteHeader().equalsIgnoreCase(name))
{
request.setAttribute("javax.servlet.request.cipher_suite",field.getValue()); request.setAttribute("javax.servlet.request.cipher_suite",field.getValue());
if (isSslIsSecure())
{
request.setSecure(true);
request.setScheme(config.getSecureScheme());
}
}
if (getForwardedSslSessionIdHeader()!=null && httpFields.get(getForwardedSslSessionIdHeader()).equalsIgnoreCase(name)) if (getForwardedSslSessionIdHeader()!=null && getForwardedSslSessionIdHeader().equalsIgnoreCase(name))
{
request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue()); request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue());
if (isSslIsSecure())
{
request.setSecure(true);
request.setScheme(config.getSecureScheme());
}
}
if (forwardedHost==null && _forwardedHostHeader!=null && _forwardedHostHeader.equalsIgnoreCase(name)) if (forwardedHost==null && _forwardedHostHeader!=null && _forwardedHostHeader.equalsIgnoreCase(name))
forwardedHost = getLeftMost(field.getValue()); forwardedHost = getLeftMost(field.getValue());
@ -444,5 +499,4 @@ public class ForwardedRequestCustomizer implements Customizer
} }
} }
} }
} }

View File

@ -19,11 +19,15 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -34,6 +38,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -43,6 +48,11 @@ public class ForwardedRequestCustomizerTest
private LocalConnector _connector; private LocalConnector _connector;
private RequestHandler _handler; private RequestHandler _handler;
final Deque<String> _results = new ArrayDeque<>(); final Deque<String> _results = new ArrayDeque<>();
final AtomicBoolean _wasSecure = new AtomicBoolean(false);
final AtomicReference<String> _sslSession = new AtomicReference<>();
final AtomicReference<String> _sslCertificate = new AtomicReference<>();
ForwardedRequestCustomizer _customizer;
@Before @Before
public void init() throws Exception public void init() throws Exception
@ -53,7 +63,7 @@ public class ForwardedRequestCustomizerTest
http.getHttpConfiguration().setRequestHeaderSize(512); http.getHttpConfiguration().setRequestHeaderSize(512);
http.getHttpConfiguration().setResponseHeaderSize(512); http.getHttpConfiguration().setResponseHeaderSize(512);
http.getHttpConfiguration().setOutputBufferSize(2048); http.getHttpConfiguration().setOutputBufferSize(2048);
http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer()); http.getHttpConfiguration().addCustomizer(_customizer=new ForwardedRequestCustomizer());
_connector = new LocalConnector(_server,http); _connector = new LocalConnector(_server,http);
_server.addConnector(_connector); _server.addConnector(_connector);
_handler = new RequestHandler(); _handler = new RequestHandler();
@ -64,6 +74,9 @@ public class ForwardedRequestCustomizerTest
@Override @Override
public boolean check(HttpServletRequest request,HttpServletResponse response) public boolean check(HttpServletRequest request,HttpServletResponse response)
{ {
_wasSecure.set(request.isSecure());
_sslSession.set(String.valueOf(request.getAttribute("javax.servlet.request.ssl_session_id")));
_sslCertificate.set(String.valueOf(request.getAttribute("javax.servlet.request.cipher_suite")));
_results.add(request.getScheme()); _results.add(request.getScheme());
_results.add(request.getServerName()); _results.add(request.getServerName());
_results.add(Integer.toString(request.getServerPort())); _results.add(Integer.toString(request.getServerPort()));
@ -203,7 +216,83 @@ public class ForwardedRequestCustomizerTest
assertEquals("443",_results.poll()); assertEquals("443",_results.poll());
assertEquals("0.0.0.0",_results.poll()); assertEquals("0.0.0.0",_results.poll());
assertEquals("0",_results.poll()); assertEquals("0",_results.poll());
assertTrue(_wasSecure.get());
} }
@Test
public void testSslSession() throws Exception
{
_customizer.setSslIsSecure(false);
String response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Proxy-Ssl-Id: Wibble\n"+
"\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("80",_results.poll());
assertEquals("0.0.0.0",_results.poll());
assertEquals("0",_results.poll());
assertFalse(_wasSecure.get());
assertEquals("Wibble",_sslSession.get());
_customizer.setSslIsSecure(true);
response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Proxy-Ssl-Id: 0123456789abcdef\n"+
"\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("https",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("443",_results.poll());
assertEquals("0.0.0.0",_results.poll());
assertEquals("0",_results.poll());
assertTrue(_wasSecure.get());
assertEquals("0123456789abcdef",_sslSession.get());
}
@Test
public void testSslCertificate() throws Exception
{
_customizer.setSslIsSecure(false);
String response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Proxy-auth-cert: Wibble\n"+
"\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("80",_results.poll());
assertEquals("0.0.0.0",_results.poll());
assertEquals("0",_results.poll());
assertFalse(_wasSecure.get());
assertEquals("Wibble",_sslCertificate.get());
_customizer.setSslIsSecure(true);
response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Proxy-auth-cert: 0123456789abcdef\n"+
"\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("https",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("443",_results.poll());
assertEquals("0.0.0.0",_results.poll());
assertEquals("0",_results.poll());
assertTrue(_wasSecure.get());
assertEquals("0123456789abcdef",_sslCertificate.get());
}
interface RequestTester interface RequestTester
{ {