Issue 342: tests that guarantee we know if an http engine supports untrusted certs

This commit is contained in:
Adrian Cole 2010-09-10 21:32:46 -07:00
parent dc3e326493
commit ec1160f2a7
11 changed files with 308 additions and 128 deletions

View File

@ -19,32 +19,13 @@
package org.jclouds.http.config;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.TransformingHttpCommandExecutorService;
import org.jclouds.http.TransformingHttpCommandExecutorServiceImpl;
import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
import org.jclouds.logging.Logger;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/**
* Configures {@link JavaUrlHttpCommandExecutorService}.
@ -58,80 +39,10 @@ public class JavaUrlHttpCommandExecutorServiceModule extends AbstractModule {
@Override
protected void configure() {
bindClient();
}
protected void bindClient() {
install(new SSLModule());
bind(HttpCommandExecutorService.class).to(JavaUrlHttpCommandExecutorService.class).in(Scopes.SINGLETON);
bind(HostnameVerifier.class).to(LogToMapHostnameVerifier.class);
bind(TransformingHttpCommandExecutorService.class).to(TransformingHttpCommandExecutorServiceImpl.class).in(
Scopes.SINGLETON);
bind(new TypeLiteral<Supplier<SSLContext>>() {
}).annotatedWith(Names.named("untrusted")).to(new TypeLiteral<UntrustedSSLContextSupplier>() {
});
}
/**
*
* Used to get more information about HTTPS hostname wrong errors.
*
* @author Adrian Cole
*/
@Singleton
static class LogToMapHostnameVerifier implements HostnameVerifier {
@Resource
private Logger logger = Logger.NULL;
private final Map<String, String> sslMap = Maps.newHashMap();;
public boolean verify(String hostname, SSLSession session) {
logger.warn("hostname was %s while session was %s", hostname, session.getPeerHost());
sslMap.put(hostname, session.getPeerHost());
return true;
}
}
@Singleton
public static class UntrustedSSLContextSupplier implements Supplier<SSLContext> {
private final TrustAllCerts trustAllCerts;
@Inject
UntrustedSSLContextSupplier(TrustAllCerts trustAllCerts) {
this.trustAllCerts = trustAllCerts;
}
@Override
public SSLContext get() {
try {
SSLContext sc;
sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { trustAllCerts }, new SecureRandom());
return sc;
} catch (Exception e) {
Throwables.propagate(e);
return null;
}
}
}
/**
*
* Used to trust all certs
*
* @author Adrian Cole
*/
@Singleton
static class TrustAllCerts implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
return;
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
return;
}
}
}

View File

@ -0,0 +1,122 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.jclouds.http.config;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.jclouds.logging.Logger;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/**
*
*
* @author Adrian Cole
*/
public class SSLModule extends AbstractModule {
@Override
protected void configure() {
bind(HostnameVerifier.class).annotatedWith(Names.named("untrusted")).to(LogToMapHostnameVerifier.class);
bind(new TypeLiteral<Supplier<SSLContext>>() {
}).annotatedWith(Names.named("untrusted")).to(new TypeLiteral<UntrustedSSLContextSupplier>() {
});
}
/**
*
* Used to get more information about HTTPS hostname wrong errors.
*
* @author Adrian Cole
*/
@Singleton
static class LogToMapHostnameVerifier implements HostnameVerifier {
@Resource
private Logger logger = Logger.NULL;
private final Map<String, String> sslMap = Maps.newHashMap();;
public boolean verify(String hostname, SSLSession session) {
logger.warn("hostname was %s while session was %s", hostname, session.getPeerHost());
sslMap.put(hostname, session.getPeerHost());
return true;
}
}
@Singleton
public static class UntrustedSSLContextSupplier implements Supplier<SSLContext> {
private final TrustAllCerts trustAllCerts;
@Inject
UntrustedSSLContextSupplier(TrustAllCerts trustAllCerts) {
this.trustAllCerts = trustAllCerts;
}
@Override
public SSLContext get() {
try {
SSLContext sc;
sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { trustAllCerts }, new SecureRandom());
return sc;
} catch (Exception e) {
Throwables.propagate(e);
return null;
}
}
}
/**
*
* Used to trust all certs
*
* @author Adrian Cole
*/
@Singleton
static class TrustAllCerts implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
return;
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
return;
}
}
}

View File

@ -87,7 +87,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
public JavaUrlHttpCommandExecutorService(HttpUtils utils,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioWorkerExecutor,
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire, HostnameVerifier verifier,
DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider) throws SecurityException,
NoSuchFieldException {
super(utils, ioWorkerExecutor, retryHandler, ioRetryHandler, errorHandler, wire);

View File

@ -44,15 +44,18 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jclouds.Constants;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.io.InputSuppliers;
import org.jclouds.rest.RestContext;
import org.jclouds.rest.RestContextBuilder;
import org.jclouds.rest.RestContextFactory.ContextSpec;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.ssl.SslSocketConnector;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Optional;
@ -101,7 +104,7 @@ public abstract class BaseJettyTest {
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
} else if (target.indexOf("redirect") > 0) {
response.sendRedirect("http://localhost:" + (testPort + 1) + "/");
response.sendRedirect("https://localhost:" + (testPort + 1) + "/");
} else if (target.indexOf("101constitutions") > 0) {
response.setContentType("text/plain");
response.setHeader("Content-MD5", md5);
@ -164,6 +167,18 @@ public abstract class BaseJettyTest {
server.setHandler(server1Handler);
server.start();
setupAndStartSSLServer(testPort);
Properties properties = new Properties();
addConnectionProperties(properties);
context = newBuilder(testPort, properties, createConnectionModule()).buildContext();
client = context.getApi();
assert client != null;
assert client.newStringBuffer() != null;
}
protected void setupAndStartSSLServer(final int testPort) throws Exception {
Handler server2Handler = new AbstractHandler() {
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
throws IOException, ServletException {
@ -206,17 +221,17 @@ public abstract class BaseJettyTest {
}
};
server2 = new Server(testPort + 1);
server2 = new Server();
server2.setHandler(server2Handler);
SslSocketConnector ssl = new SslSocketConnector();
ssl.setPort(testPort + 1);
ssl.setMaxIdleTime(30000);
ssl.setKeystore("src/test/resources/test.jks");
ssl.setKeyPassword("jclouds");
ssl.setTruststore("src/test/resources/test.jks");
ssl.setTrustPassword("jclouds");
server2.setConnectors(new Connector[] { ssl });
server2.start();
Properties properties = new Properties();
addConnectionProperties(properties);
context = newBuilder(testPort, properties, createConnectionModule()).buildContext();
client = context.getApi();
assert client != null;
assert client.newStringBuffer() != null;
}
@SuppressWarnings("unchecked")
@ -234,18 +249,19 @@ public abstract class BaseJettyTest {
public static RestContextBuilder<IntegrationTestClient, IntegrationTestAsyncClient> newBuilder(int testPort,
Properties properties, Module... connectionModules) {
properties.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true");
properties.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, "true");
ContextSpec<IntegrationTestClient, IntegrationTestAsyncClient> contextSpec = contextSpec("test",
"http://localhost:" + testPort, "1", "identity", null, IntegrationTestClient.class,
IntegrationTestAsyncClient.class, ImmutableSet.<Module> copyOf(connectionModules));
return createContextBuilder(contextSpec, properties);
}
@AfterTest
public void tearDownJetty() throws Exception {
context.close();
server2.stop();
if (server2 != null)
server2.stop();
server.stop();
}

Binary file not shown.

View File

@ -25,6 +25,7 @@ import java.net.ProxySelector;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.net.ssl.SSLContext;
@ -53,8 +54,10 @@ import org.jclouds.http.TransformingHttpCommandExecutorService;
import org.jclouds.http.TransformingHttpCommandExecutorServiceImpl;
import org.jclouds.http.apachehc.ApacheHCHttpCommandExecutorService;
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
import org.jclouds.http.config.SSLModule;
import org.jclouds.lifecycle.Closer;
import com.google.common.base.Supplier;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Scopes;
@ -72,6 +75,7 @@ public class ApacheHCHttpCommandExecutorServiceModule extends AbstractModule {
@Override
protected void configure() {
install(new SSLModule());
bindClient();
}
@ -80,14 +84,12 @@ public class ApacheHCHttpCommandExecutorServiceModule extends AbstractModule {
HttpParams newBasicHttpParams(HttpUtils utils) {
BasicHttpParams params = new BasicHttpParams();
params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, true)
.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true).setParameter(
CoreProtocolPNames.ORIGIN_SERVER, "jclouds/1.0");
params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024).setBooleanParameter(
CoreConnectionPNames.STALE_CONNECTION_CHECK, true).setBooleanParameter(CoreConnectionPNames.TCP_NODELAY,
true).setParameter(CoreProtocolPNames.ORIGIN_SERVER, "jclouds/1.0");
if (utils.getConnectionTimeout() > 0) {
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, utils
.getConnectionTimeout());
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, utils.getConnectionTimeout());
}
if (utils.getSocketOpenTimeout() > 0) {
@ -96,7 +98,7 @@ public class ApacheHCHttpCommandExecutorServiceModule extends AbstractModule {
if (utils.getMaxConnections() > 0)
ConnManagerParams.setMaxTotalConnections(params, utils.getMaxConnections());
if (utils.getMaxConnectionsPerHost() > 0) {
ConnPerRoute connectionsPerRoute = new ConnPerRouteBean(utils.getMaxConnectionsPerHost());
ConnManagerParams.setMaxConnectionsPerRoute(params, connectionsPerRoute);
@ -114,17 +116,26 @@ public class ApacheHCHttpCommandExecutorServiceModule extends AbstractModule {
@Singleton
@Provides
ClientConnectionManager newClientConnectionManager(HttpParams params,
X509HostnameVerifier verifier, Closer closer) throws NoSuchAlgorithmException,
KeyManagementException {
SSLContext newSSLSocketFactory(HttpUtils utils, @Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider)
throws NoSuchAlgorithmException, KeyManagementException {
if (utils.trustAllCerts())
return untrustedSSLContextProvider.get();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
return context;
}
@Singleton
@Provides
ClientConnectionManager newClientConnectionManager(HttpParams params, X509HostnameVerifier verifier,
SSLContext context, Closer closer) throws NoSuchAlgorithmException, KeyManagementException {
SchemeRegistry schemeRegistry = new SchemeRegistry();
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
SSLSocketFactory sf = new SSLSocketFactory(context);
sf.setHostnameVerifier(verifier);
Scheme https = new Scheme("https", sf, 443);
@ -134,7 +145,7 @@ public class ApacheHCHttpCommandExecutorServiceModule extends AbstractModule {
sr.register(https);
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
schemeRegistry.register(new Scheme("https", sf, 443));
final ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
closer.addToClose(new Closeable() {
@Override
@ -147,23 +158,21 @@ public class ApacheHCHttpCommandExecutorServiceModule extends AbstractModule {
@Provides
@Singleton
HttpClient newDefaultHttpClient(HttpUtils utils, BasicHttpParams params,
ClientConnectionManager cm) {
HttpClient newDefaultHttpClient(HttpUtils utils, BasicHttpParams params, ClientConnectionManager cm) {
DefaultHttpClient client = new DefaultHttpClient(cm, params);
if (utils.useSystemProxies()) {
ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(client
.getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault());
ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(client.getConnectionManager()
.getSchemeRegistry(), ProxySelector.getDefault());
client.setRoutePlanner(routePlanner);
}
return client;
}
protected void bindClient() {
bind(HttpCommandExecutorService.class).to(ApacheHCHttpCommandExecutorService.class).in(
Scopes.SINGLETON);
bind(HttpCommandExecutorService.class).to(ApacheHCHttpCommandExecutorService.class).in(Scopes.SINGLETON);
bind(TransformingHttpCommandExecutorService.class).to(
TransformingHttpCommandExecutorServiceImpl.class).in(Scopes.SINGLETON);
bind(TransformingHttpCommandExecutorService.class).to(TransformingHttpCommandExecutorServiceImpl.class).in(
Scopes.SINGLETON);
}
}

Binary file not shown.

Binary file not shown.

View File

@ -24,6 +24,7 @@ import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Map;
@ -31,6 +32,9 @@ import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jclouds.concurrent.Futures;
import org.jclouds.concurrent.MoreExecutors;
import org.jclouds.concurrent.SingleThreaded;
@ -65,6 +69,14 @@ import com.google.inject.Module;
public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpCommandExecutorServiceIntegrationTest {
Logger logger = Logger.CONSOLE;
protected void setupAndStartSSLServer(final int testPort) throws Exception {
}
protected boolean redirectEveryTwentyRequests(HttpServletRequest request, HttpServletResponse response)
throws IOException {
return false;
}
@Test(enabled = false)
public void testPerformanceVsNothing() {
setupApiProxy();
@ -231,8 +243,9 @@ public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpC
super.testGetStringSynch(path);
}
// local env does not support snakeoil certs
@Override
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
@Test(enabled = true, expectedExceptions = UndeclaredThrowableException.class)
public void testGetStringRedirect() throws MalformedURLException, ExecutionException, InterruptedException,
TimeoutException {
setupApiProxy();
@ -278,7 +291,7 @@ public class AsyncGaeHttpCommandExecutorServiceIntegrationTest extends BaseHttpC
}
@Override
@Test(enabled = true, invocationCount = 5, timeOut = 3000)
@Test(enabled = true, expectedExceptions = UndeclaredThrowableException.class)
public void testPutRedirect() throws MalformedURLException, ExecutionException, InterruptedException,
TimeoutException {
setupApiProxy();

Binary file not shown.

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2009 Cloud Conscious, LLC.
<info@cloudconscious.com>
====================================================================
Licensed 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.
====================================================================
-->
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--
For more configuration infromation and examples see the Apache
Log4j website: http://logging.apache.org/log4j/
-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
debug="false">
<!-- A time/date based rolling appender -->
<appender name="WIREFILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="target/test-data/jclouds-wire.log" />
<param name="Append" value="true" />
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="Threshold" value="TRACE" />
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message\n -->
<param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
<!--
The full pattern: Date MS Priority [Category]
(Thread:NDC) Message\n <param name="ConversionPattern"
value="%d %-5r %-5p [%c] (%t:%x) %m%n"/>
-->
</layout>
</appender>
<!-- A time/date based rolling appender -->
<appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="target/test-data/jclouds.log" />
<param name="Append" value="true" />
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="Threshold" value="TRACE" />
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message\n -->
<param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
<!--
The full pattern: Date MS Priority [Category]
(Thread:NDC) Message\n <param name="ConversionPattern"
value="%d %-5r %-5p [%c] (%t:%x) %m%n"/>
-->
</layout>
</appender>
<appender name="ASYNC" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<appender name="ASYNCWIRE" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="WIREFILE" />
</appender>
<!-- ================ -->
<!-- Limit categories -->
<!-- ================ -->
<category name="org.jclouds">
<priority value="DEBUG" />
<appender-ref ref="ASYNC" />
</category>
<category name="jclouds.headers">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category>
<category name="jclouds.wire">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category>
<!-- ======================= -->
<!-- Setup the Root category -->
<!-- ======================= -->
<root>
<priority value="WARN" />
</root>
</log4j:configuration>