Merge pull request #4178 from eclipse/jetty-9.4.x-4177-http_proxy_sslcontextfactory

Fixes #4177 - Configure HTTP proxy with SslContextFactory.
This commit is contained in:
Simone Bordet 2019-10-10 10:02:48 +02:00 committed by GitHub
commit 53ed8f346c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 124 additions and 11 deletions

View File

@ -1160,11 +1160,26 @@ public class HttpClient extends ContainerLifeCycle
return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme); return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme);
} }
/**
* Creates a new {@code SslClientConnectionFactory} wrapping the given connection factory.
*
* @param connectionFactory the connection factory to wrap
* @return a new SslClientConnectionFactory
* @deprecated use {@link #newSslClientConnectionFactory(SslContextFactory, ClientConnectionFactory)} instead
*/
@Deprecated
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{ {
return new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), connectionFactory); return new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), connectionFactory);
} }
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
{
if (sslContextFactory == null)
return newSslClientConnectionFactory(connectionFactory);
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory);
}
private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory> private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
{ {
private final Set<ContentDecoder.Factory> set = new HashSet<>(); private final Set<ContentDecoder.Factory> set = new HashSet<>();

View File

@ -48,6 +48,7 @@ import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection; import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.Sweeper; import org.eclipse.jetty.util.thread.Sweeper;
@ -86,12 +87,12 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
{ {
connectionFactory = proxy.newClientConnectionFactory(connectionFactory); connectionFactory = proxy.newClientConnectionFactory(connectionFactory);
if (proxy.isSecure()) if (proxy.isSecure())
connectionFactory = newSslClientConnectionFactory(connectionFactory); connectionFactory = newSslClientConnectionFactory(proxy.getSslContextFactory(), connectionFactory);
} }
else else
{ {
if (isSecure()) if (isSecure())
connectionFactory = newSslClientConnectionFactory(connectionFactory); connectionFactory = newSslClientConnectionFactory(null, connectionFactory);
} }
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
@ -132,9 +133,9 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
return new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination()); return new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
} }
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
{ {
return client.newSslClientConnectionFactory(connectionFactory); return client.newSslClientConnectionFactory(sslContextFactory, connectionFactory);
} }
public boolean isSecure() public boolean isSecure()

View File

@ -54,6 +54,11 @@ public class HttpProxy extends ProxyConfiguration.Proxy
super(address, secure); super(address, secure);
} }
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory)
{
super(address, sslContextFactory);
}
@Override @Override
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory) public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
{ {
@ -204,7 +209,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise); context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
HttpClient client = destination.getHttpClient(); HttpClient client = destination.getHttpClient();
ClientConnectionFactory sslConnectionFactory = client.newSslClientConnectionFactory(connectionFactory); ClientConnectionFactory sslConnectionFactory = client.newSslClientConnectionFactory(null, connectionFactory);
HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection(); HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost()); context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort()); context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());

View File

@ -22,10 +22,12 @@ import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.ssl.SslContextFactory;
/** /**
* The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}. * The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
@ -64,11 +66,23 @@ public class ProxyConfiguration
private final Set<String> excluded = new HashSet<>(); private final Set<String> excluded = new HashSet<>();
private final Origin.Address address; private final Origin.Address address;
private final boolean secure; private final boolean secure;
private final SslContextFactory.Client sslContextFactory;
protected Proxy(Origin.Address address, boolean secure) protected Proxy(Origin.Address address, boolean secure)
{
this(address, secure, null);
}
public Proxy(Origin.Address address, SslContextFactory.Client sslContextFactory)
{
this(address, true, Objects.requireNonNull(sslContextFactory));
}
private Proxy(Origin.Address address, boolean secure, SslContextFactory.Client sslContextFactory)
{ {
this.address = address; this.address = address;
this.secure = secure; this.secure = secure;
this.sslContextFactory = sslContextFactory;
} }
/** /**
@ -87,6 +101,14 @@ public class ProxyConfiguration
return secure; return secure;
} }
/**
* @return the optional SslContextFactory to use when connecting to proxies
*/
public SslContextFactory.Client getSslContextFactory()
{
return sslContextFactory;
}
/** /**
* @return the list of origins that must be proxied * @return the list of origins that must be proxied
* @see #matches(Origin) * @see #matches(Origin)

View File

@ -64,7 +64,7 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
} }
@Override @Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
{ {
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
Executor executor = destination.getHttpClient().getExecutor(); Executor executor = destination.getHttpClient().getExecutor();
@ -198,7 +198,7 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
HttpClient client = destination.getHttpClient(); HttpClient client = destination.getHttpClient();
ClientConnectionFactory connectionFactory = this.connectionFactory; ClientConnectionFactory connectionFactory = this.connectionFactory;
if (destination.isSecure()) if (destination.isSecure())
connectionFactory = client.newSslClientConnectionFactory(connectionFactory); connectionFactory = client.newSslClientConnectionFactory(null, connectionFactory);
org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context); org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context);
getEndPoint().upgrade(newConnection); getEndPoint().upgrade(newConnection);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())

View File

@ -474,9 +474,9 @@ public class HttpClientTLSTest
client = new HttpClient(createClientSslContextFactory()) client = new HttpClient(createClientSslContextFactory())
{ {
@Override @Override
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
{ {
SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(connectionFactory); SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(sslContextFactory, connectionFactory);
ssl.setAllowMissingCloseMessage(false); ssl.setAllowMissingCloseMessage(false);
return ssl; return ssl;
} }

View File

@ -689,6 +689,74 @@ public class ForwardProxyTLSServerTest
} }
} }
@Test
public void testBothProxyAndServerNeedClientAuthWithDifferentKeyStores() throws Exception
{
SslContextFactory.Server serverTLS = newServerSslContextFactory();
serverTLS.setEndpointIdentificationAlgorithm(null);
serverTLS.setNeedClientAuth(true);
startTLSServer(serverTLS, new ServerHandler());
int serverPort = serverConnector.getLocalPort();
SslContextFactory.Server proxyServerTLS = newProxySslContextFactory();
proxyServerTLS.setEndpointIdentificationAlgorithm(null);
proxyServerTLS.setNeedClientAuth(true);
startProxy(proxyServerTLS);
int proxyPort = proxyConnector.getLocalPort();
SslContextFactory.Client clientTLS = new SslContextFactory.Client()
{
@Override
public SSLEngine newSSLEngine(String host, int port)
{
if (port != serverPort)
throw new IllegalStateException();
return super.newSSLEngine(host, port);
}
};
clientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_server_keystore.p12").getAbsolutePath());
clientTLS.setKeyStorePassword("storepwd");
clientTLS.setEndpointIdentificationAlgorithm(null);
HttpClient httpClient = new HttpClient(clientTLS);
SslContextFactory.Client proxyClientTLS = new SslContextFactory.Client()
{
@Override
public SSLEngine newSSLEngine(String host, int port)
{
if (port != proxyPort)
throw new IllegalStateException();
return super.newSSLEngine(host, port);
}
};
proxyClientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_proxy_keystore.p12").getAbsolutePath());
proxyClientTLS.setKeyStorePassword("storepwd");
proxyClientTLS.setEndpointIdentificationAlgorithm(null);
proxyClientTLS.start();
HttpProxy httpProxy = new HttpProxy(new Origin.Address("localhost", proxyConnector.getLocalPort()), proxyClientTLS);
httpClient.getProxyConfiguration().getProxies().add(httpProxy);
httpClient.start();
try
{
String body = "BODY";
ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.method(HttpMethod.GET)
.path("/echo?body=" + URLEncoder.encode(body, "UTF-8"))
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
String content = response.getContentAsString();
assertEquals(body, content);
}
finally
{
httpClient.stop();
}
}
@Test @Test
@Tag("external") @Tag("external")
@Disabled @Disabled

View File

@ -254,9 +254,11 @@ public class HttpClientTimeoutTest extends AbstractTest<TransportScenario>
scenario.client = new HttpClient(scenario.provideClientTransport(), sslContextFactory) scenario.client = new HttpClient(scenario.provideClientTransport(), sslContextFactory)
{ {
@Override @Override
public ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) public ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
{ {
return new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), connectionFactory) if (sslContextFactory == null)
sslContextFactory = getSslContextFactory();
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory)
{ {
@Override @Override
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine) protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)