Mercurial > hg > release > thermostat-0.6
changeset 878:e5cda64075ed
Add TLS support to command channel.
This patch adds TLS support for the command channel. SSL for cmd-channel
communication is only used if things have been configured that way in
$THERMOSTAT_HOME/etc/ssl.properties. The reason as to why this has been done is
because if this is always on, users would need to set up a proper server (i.e.
agent) side keystore which is used for serving SSL enabled communication. An
alternative would be to ship with a default keystore. However, this defeats the
purpose of securing the channel in the first place. The world would have access
to this keystore. That's why we've opted for default == false. Note that these
defaults can be changed by packagers if they so desire.
If COMMAND_CHANNEL_USE_SSL=true then appropriate server and client SSL contexts
are created and passed to netty's SSLHandler. After a connection to the agent
was successfully established an SSL handshake is initiated (if so configured).
Tests have been added for most (all?) things I've added for SSL-enablement of
the command channel.
Reviewed-by: vanaltj
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-December/004770.html
PR1239
line wrap: on
line diff
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContext.java Tue Dec 18 16:39:38 2012 -0500 +++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContext.java Tue Dec 18 12:32:36 2012 +0100 @@ -37,6 +37,10 @@ package com.redhat.thermostat.agent.command.internal; import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.SSLEngine; import org.jboss.netty.bootstrap.Bootstrap; import org.jboss.netty.bootstrap.ServerBootstrap; @@ -46,13 +50,21 @@ import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.ssl.SslHandler; import org.osgi.framework.BundleContext; import com.redhat.thermostat.agent.command.ReceiverRegistry; import com.redhat.thermostat.common.command.ConfigurationCommandContext; +import com.redhat.thermostat.common.config.InvalidConfigurationException; +import com.redhat.thermostat.common.ssl.SSLContextFactory; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; +import com.redhat.thermostat.common.ssl.SslInitException; +import com.redhat.thermostat.common.utils.LoggingUtils; class ConfigurationServerContext implements ConfigurationCommandContext { + private static final Logger logger = LoggingUtils.getLogger(ConfigurationServerContext.class); + private final ServerBootstrap bootstrap; private final ChannelGroup channels; private final BundleContext context; @@ -95,6 +107,18 @@ @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); + if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) { + SSLEngine engine = null; + try { + engine = SSLContextFactory.getServerContext() + .createSSLEngine(); + engine.setUseClientMode(false); + } catch (SslInitException | InvalidConfigurationException e) { + logger.log(Level.SEVERE, + "Failed to initiate command channel endpoint", e); + } + pipeline.addLast("ssl", new SslHandler(engine)); + } pipeline.addLast("decoder", new RequestDecoder()); pipeline.addLast("encoder", new ResponseEncoder()); pipeline.addLast("handler", new ServerHandler(new ReceiverRegistry(context)));
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerImpl.java Tue Dec 18 16:39:38 2012 -0500 +++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerImpl.java Tue Dec 18 12:32:36 2012 +0100 @@ -37,14 +37,18 @@ package com.redhat.thermostat.agent.command.internal; import java.net.InetSocketAddress; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jboss.netty.bootstrap.ServerBootstrap; import com.redhat.thermostat.agent.command.ConfigurationServer; +import com.redhat.thermostat.common.utils.LoggingUtils; class ConfigurationServerImpl implements ConfigurationServer { private final ConfigurationServerContext ctx; + private static final Logger logger = LoggingUtils.getLogger(ConfigurationServerImpl.class); ConfigurationServerImpl(ConfigurationServerContext ctx) { this.ctx = ctx; @@ -55,13 +59,16 @@ ServerBootstrap bootstrap = (ServerBootstrap) ctx.getBootstrap(); String [] host = address.split(":"); + InetSocketAddress addr = new InetSocketAddress(host[0], Integer.parseInt(host[1])); + logger.log(Level.FINE, "Starting command channel server on " + addr.toString()); // Bind and start to accept incoming connections. - bootstrap.bind(new InetSocketAddress(host[0], Integer.parseInt(host[1]))); + bootstrap.bind(addr); } @Override public void stopListening() { + logger.log(Level.FINE, "Stopping command channel server"); ctx.getChannelGroup().close().awaitUninterruptibly(); ctx.getChannelGroup().clear(); ctx.getBootstrap().releaseExternalResources();
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java Tue Dec 18 16:39:38 2012 -0500 +++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java Tue Dec 18 12:32:36 2012 +0100 @@ -41,12 +41,15 @@ import org.apache.commons.codec.binary.Base64; import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.handler.ssl.SslHandler; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; @@ -56,21 +59,45 @@ import com.redhat.thermostat.common.command.Request; import com.redhat.thermostat.common.command.Response; import com.redhat.thermostat.common.command.Response.ResponseType; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.storage.core.AuthToken; import com.redhat.thermostat.storage.core.SecureStorage; import com.redhat.thermostat.storage.core.Storage; -class ServerHandler extends SimpleChannelHandler { +class ServerHandler extends SimpleChannelUpstreamHandler { + private static final Logger logger = LoggingUtils.getLogger(ServerHandler.class); private ReceiverRegistry receivers; public ServerHandler(ReceiverRegistry receivers) { this.receivers = receivers; } - private static final Logger logger = LoggingUtils.getLogger(ServerHandler.class); + @Override + public void handleUpstream( + ChannelHandlerContext ctx, ChannelEvent e) throws Exception { + if (e instanceof ChannelStateEvent) { + logger.log(Level.FINEST, e.toString()); + } + super.handleUpstream(ctx, e); + } + + @Override + public void channelConnected( + ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) { + // Get the SslHandler in the current pipeline. + // We added it in ConfigurationServerContext$ServerPipelineFactory. + final SslHandler sslHandler = ctx.getPipeline().get( + SslHandler.class); + // Get notified when SSL handshake is done. + ChannelFuture handshakeFuture = sslHandler.handshake(); + handshakeFuture.addListener(new SSLHandshakeDoneListener()); + } + } + @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { Request request = (Request) e.getMessage(); @@ -123,5 +150,20 @@ logger.log(Level.WARNING, "Unexpected exception from downstream.", e.getCause()); e.getChannel().close(); } + + /* + * Only registered if SSL is enabled + */ + static final class SSLHandshakeDoneListener implements ChannelFutureListener { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + logger.log(Level.FINE, "Finished SSL handshake."); + } else { + logger.log(Level.WARNING, "SSL handshake failed!"); + future.getChannel().close(); + } + } + } }
--- a/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContextTest.java Tue Dec 18 16:39:38 2012 -0500 +++ b/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ConfigurationServerContextTest.java Tue Dec 18 12:32:36 2012 +0100 @@ -40,21 +40,32 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import org.jboss.netty.bootstrap.Bootstrap; import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.handler.ssl.SslHandler; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.osgi.framework.BundleContext; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; -import com.redhat.thermostat.agent.command.internal.ConfigurationServerContext; -import com.redhat.thermostat.agent.command.internal.RequestDecoder; -import com.redhat.thermostat.agent.command.internal.ResponseEncoder; -import com.redhat.thermostat.agent.command.internal.ServerHandler; +import com.redhat.thermostat.common.ssl.SSLContextFactory; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; +@RunWith(PowerMockRunner.class) +@PrepareForTest({ SSLKeystoreConfiguration.class, SSLContextFactory.class, + SSLEngine.class, SSLContext.class }) public class ConfigurationServerContextTest { ConfigurationServerContext ctx; @@ -83,7 +94,7 @@ try { p = pf.getPipeline(); } catch (Exception e) { - // Do nothing + e.printStackTrace(); } assertNotNull(p); @@ -99,6 +110,37 @@ assertNotNull(handler); assertTrue(handler instanceof ServerHandler); } + + @Test + public void testBootstrapSSL() throws Exception { + PowerMockito.mockStatic(SSLKeystoreConfiguration.class); + when(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()).thenReturn(true); + PowerMockito.mockStatic(SSLContextFactory.class); + // SSL classes need to be mocked with PowerMockito + SSLContext context = PowerMockito.mock(SSLContext.class); + when(SSLContextFactory.getServerContext()).thenReturn(context); + SSLEngine engine = PowerMockito.mock(SSLEngine.class); + when(context.createSSLEngine()).thenReturn(engine); + + Bootstrap bootstrap = ctx.getBootstrap(); + assertNotNull(bootstrap); + + ChannelPipelineFactory pf = bootstrap.getPipelineFactory(); + assertNotNull(pf); + + ChannelPipeline p = null; + try { + p = pf.getPipeline(); + } catch (Exception e) { + e.printStackTrace(); + } + assertNotNull(p); + + ChannelHandler encoder = p.get("ssl"); + assertNotNull(encoder); + assertTrue(encoder instanceof SslHandler); + Mockito.verify(engine).setUseClientMode(false); + } @Test public void testChannelGroup() {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ServerHandlerTest.java Tue Dec 18 12:32:36 2012 +0100 @@ -0,0 +1,80 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.agent.command.internal; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.handler.ssl.SslHandler; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.redhat.thermostat.agent.command.internal.ServerHandler.SSLHandshakeDoneListener; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ SSLKeystoreConfiguration.class}) +public class ServerHandlerTest { + + @Test + public void channelConnectedAddsSSLListener() throws Exception { + ServerHandler handler = new ServerHandler(null); + + // enable ssl + PowerMockito.mockStatic(SSLKeystoreConfiguration.class); + when(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()).thenReturn(true); + + ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); + ChannelPipeline pipeline = mock(ChannelPipeline.class); + when(ctx.getPipeline()).thenReturn(pipeline); + SslHandler sslHandler = mock(SslHandler.class); + when(pipeline.get(SslHandler.class)).thenReturn(sslHandler); + ChannelFuture future = mock(ChannelFuture.class); + when(sslHandler.handshake()).thenReturn(future); + + handler.channelConnected(ctx, null); + verify(future).addListener(any(SSLHandshakeDoneListener.class)); + } +}
--- a/client/command/pom.xml Tue Dec 18 16:39:38 2012 -0500 +++ b/client/command/pom.xml Tue Dec 18 12:32:36 2012 +0100 @@ -62,6 +62,16 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.jboss.netty</groupId> <artifactId>netty</artifactId> </dependency>
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContext.java Tue Dec 18 16:39:38 2012 -0500 +++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContext.java Tue Dec 18 12:32:36 2012 +0100 @@ -38,14 +38,19 @@ import java.util.concurrent.Executors; +import javax.net.ssl.SSLEngine; + import org.jboss.netty.bootstrap.Bootstrap; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.handler.ssl.SslHandler; import com.redhat.thermostat.common.command.ConfigurationCommandContext; +import com.redhat.thermostat.common.ssl.SSLContextFactory; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; public class ConfigurationRequestContext implements ConfigurationCommandContext { @@ -84,6 +89,12 @@ @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); + if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) { + SSLEngine engine = SSLContextFactory.getClientContext() + .createSSLEngine(); + engine.setUseClientMode(true); + pipeline.addLast("ssl", new SslHandler(engine)); + } pipeline.addLast("decoder", new ResponseDecoder()); pipeline.addLast("encoder", new RequestEncoder()); return pipeline;
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/RequestQueueImpl.java Tue Dec 18 16:39:38 2012 -0500 +++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/RequestQueueImpl.java Tue Dec 18 12:32:36 2012 +0100 @@ -38,11 +38,16 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; import org.apache.commons.codec.binary.Base64; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.handler.ssl.SslHandler; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; @@ -52,6 +57,8 @@ import com.redhat.thermostat.common.command.RequestResponseListener; import com.redhat.thermostat.common.command.Response; import com.redhat.thermostat.common.command.Response.ResponseType; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; +import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.storage.core.AuthToken; import com.redhat.thermostat.storage.core.SecureStorage; import com.redhat.thermostat.storage.core.Storage; @@ -59,6 +66,7 @@ class RequestQueueImpl implements RequestQueue { + private static final Logger logger = LoggingUtils.getLogger(RequestQueueImpl.class); private final BlockingQueue<Request> queue; private final ConfigurationRequestContext ctx; private volatile boolean processing; @@ -136,7 +144,11 @@ f.awaitUninterruptibly(); if (f.isSuccess()) { Channel c = f.getChannel(); - c.getPipeline().addLast("responseHandler", new ResponseHandler(request)); + ChannelPipeline pipeline = c.getPipeline(); + if (SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()) { + doSSLHandShake(pipeline, request); + } + pipeline.addLast("responseHandler", new ResponseHandler(request)); c.write(request); } else { Response response = new Response(ResponseType.ERROR); @@ -146,11 +158,44 @@ } } - + private void fireComplete(Request request, Response response) { // TODO add more information once Response supports parameters. for (RequestResponseListener listener : request.getListeners()) { listener.fireComplete(request, response); } } + + private void doSSLHandShake(ChannelPipeline pipeline, Request request) { + // Get the SslHandler from the pipeline + // which was added in ConfigurationRequestContext$ClientPipelineFactory + SslHandler sslHandler = pipeline.get(SslHandler.class); + + logger.log(Level.FINE, "Starting SSL handshake"); + // Begin handshake. + ChannelFuture future = sslHandler.handshake(); + + // Register a future listener, since it gives us a way to + // report an error on client side. + future.addListener(new SSLErrorReporter(request)); + } + + private class SSLErrorReporter implements ChannelFutureListener { + + private final Request request; + + private SSLErrorReporter(Request request) { + this.request = request; + } + + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + logger.log(Level.WARNING, + "SSL handshake failed check agent logs for details!", + future.getCause()); + fireComplete(request, new Response(ResponseType.ERROR)); + } + } + } }
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ResponseHandler.java Tue Dec 18 16:39:38 2012 -0500 +++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ResponseHandler.java Tue Dec 18 12:32:36 2012 +0100 @@ -36,6 +36,7 @@ package com.redhat.thermostat.client.command.internal; +import java.util.logging.Level; import java.util.logging.Logger; import org.jboss.netty.channel.ChannelHandlerContext; @@ -72,6 +73,7 @@ @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { // TODO when response has support for parameters, provide the exception as well. + logger.log(Level.WARNING, "exception caught: ", e.getCause()); Response response = new Response(ResponseType.ERROR); notifyListeners(response); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/command/src/test/java/com/redhat/thermostat/client/command/internal/ConfigurationRequestContextTest.java Tue Dec 18 12:32:36 2012 +0100 @@ -0,0 +1,143 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.command.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.jboss.netty.bootstrap.Bootstrap; +import org.jboss.netty.channel.ChannelHandler; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.handler.ssl.SslHandler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.redhat.thermostat.common.ssl.SSLContextFactory; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ SSLKeystoreConfiguration.class, SSLContextFactory.class, + SSLContext.class, SSLEngine.class }) +public class ConfigurationRequestContextTest { + + ConfigurationRequestContext ctx; + + @Before + public void setUp() { + ctx = new ConfigurationRequestContext(); + } + + @After + public void tearDown() { + ctx = null; + } + + @Test + public void testSSLHandlersAdded() throws Exception { + PowerMockito.mockStatic(SSLKeystoreConfiguration.class); + when(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()).thenReturn( + true); + PowerMockito.mockStatic(SSLContextFactory.class); + // SSL classes need to be mocked with PowerMockito + SSLContext context = PowerMockito.mock(SSLContext.class); + when(SSLContextFactory.getClientContext()).thenReturn(context); + SSLEngine engine = PowerMockito.mock(SSLEngine.class); + when(context.createSSLEngine()).thenReturn(engine); + + Bootstrap bootstrap = ctx.getBootstrap(); + assertNotNull(bootstrap); + + ChannelPipelineFactory pf = bootstrap.getPipelineFactory(); + assertNotNull(pf); + + ChannelPipeline p = null; + try { + p = pf.getPipeline(); + } catch (Exception e) { + e.printStackTrace(); + } + assertNotNull(p); + + ChannelHandler sslHandler = p.get("ssl"); + assertNotNull(sslHandler); + assertTrue(sslHandler instanceof SslHandler); + Mockito.verify(engine).setUseClientMode(true); + } + + @Test + public void testBootstrapAndHandlers() { + Bootstrap bootstrap = ctx.getBootstrap(); + assertNotNull(bootstrap); + + assertTrue((Boolean) bootstrap.getOption("tcpNoDelay")); + assertTrue((Boolean) bootstrap.getOption("keepAlive")); + assertTrue((Boolean) bootstrap.getOption("reuseAddress")); + assertEquals(100, bootstrap.getOption("connectTimeoutMillis")); + assertTrue((Boolean) bootstrap.getOption("readWriteFair")); + + ChannelPipelineFactory pf = bootstrap.getPipelineFactory(); + assertNotNull(pf); + + ChannelPipeline p = null; + try { + p = pf.getPipeline(); + } catch (Exception e) { + e.printStackTrace(); + } + assertNotNull(p); + + ChannelHandler encoder = p.get("encoder"); + assertNotNull(encoder); + assertTrue(encoder instanceof RequestEncoder); + + ChannelHandler decoder = p.get("decoder"); + assertNotNull(decoder); + assertTrue(decoder instanceof ResponseDecoder); + } +}
--- a/common/core/src/main/java/com/redhat/thermostat/common/internal/CustomX509TrustManager.java Tue Dec 18 16:39:38 2012 -0500 +++ b/common/core/src/main/java/com/redhat/thermostat/common/internal/CustomX509TrustManager.java Tue Dec 18 12:32:36 2012 +0100 @@ -53,6 +53,7 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; import com.redhat.thermostat.common.utils.LoggingUtils; /**
--- a/common/core/src/main/java/com/redhat/thermostat/common/internal/SSLKeystoreConfiguration.java Tue Dec 18 16:39:38 2012 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -/* - * Copyright 2012 Red Hat, Inc. - * - * This file is part of Thermostat. - * - * Thermostat is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * Thermostat is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Thermostat; see the file COPYING. If not see - * <http://www.gnu.org/licenses/>. - * - * Linking this code with other modules is making a combined work - * based on this code. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this code give - * you permission to link this code with independent modules to - * produce an executable, regardless of the license terms of these - * independent modules, and to copy and distribute the resulting - * executable under terms of your choice, provided that you also - * meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module - * which is not derived from or based on this code. If you modify - * this code, you may extend this exception to your version of the - * library, but you are not obligated to do so. If you do not wish - * to do so, delete this exception statement from your version. - */ - -package com.redhat.thermostat.common.internal; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Properties; - -import com.redhat.thermostat.common.config.ConfigUtils; -import com.redhat.thermostat.common.config.InvalidConfigurationException; - -public class SSLKeystoreConfiguration { - - private static Properties clientProps = null; - private static final String KEYSTORE_FILE_KEY = "KEYSTORE_FILE"; - private static final String KEYSTORE_FILE_PWD_KEY = "KEYSTORE_PASSWORD"; - - /** - * - * @return The keystore file as specified in - * $THERMOSTAT_HOME/ssl.properties if any. null otherwise. - */ - public static File getKeystoreFile() { - try { - loadClientProperties(); - } catch (InvalidConfigurationException e) { - // Thermostat home not set? Should have failed earlier. Do something - // reasonable. - return null; - } - String path = clientProps.getProperty(KEYSTORE_FILE_KEY); - if (path != null) { - File file = new File(path); - return file; - } - return null; - } - - /** - * - * @return The keystore file as specified in - * $THERMOSTAT_HOME/ssl.properties if any. The empty string - * otherwise. - * @throws InvalidConfigurationException - */ - public static String getKeyStorePassword() { - try { - loadClientProperties(); - } catch (InvalidConfigurationException e) { - // Thermostat home not set? Do something reasonable - return ""; - } - String pwd = clientProps.getProperty(KEYSTORE_FILE_PWD_KEY); - if (pwd == null) { - return ""; - } else { - return pwd; - } - } - - // testing hook - static void initClientProperties(File clientPropertiesFile) { - clientProps = new Properties(); - try { - clientProps.load(new FileInputStream(clientPropertiesFile)); - } catch (IOException | IllegalArgumentException e) { - // Could not load ssl properties file. This is fine as it's - // an optional config. - } - } - - private static void loadClientProperties() - throws InvalidConfigurationException { - if (clientProps == null) { - File thermostatEtcDir = new File(ConfigUtils.getThermostatHome(), - "etc"); - File clientPropertiesFile = new File(thermostatEtcDir, - "ssl.properties"); - initClientProperties(clientPropertiesFile); - } - } -}
--- a/common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLContextFactory.java Tue Dec 18 16:39:38 2012 -0500 +++ b/common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLContextFactory.java Tue Dec 18 12:32:36 2012 +0100 @@ -36,23 +36,50 @@ package com.redhat.thermostat.common.ssl; +import java.io.File; +import java.security.GeneralSecurityException; import java.security.KeyManagementException; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; +import com.redhat.thermostat.common.config.InvalidConfigurationException; +import com.redhat.thermostat.common.internal.KeyStoreProvider; import com.redhat.thermostat.common.internal.TrustManagerFactory; +import com.redhat.thermostat.common.utils.LoggingUtils; public class SSLContextFactory { + private static final Logger logger = LoggingUtils.getLogger(SSLContextFactory.class); private static final String PROTOCOL = "TLS"; + private static final String ALGORITHM = "SunX509"; + private static SSLContext serverContext; private static SSLContext clientContext; + + /** + * + * @return An initialized SSLContext + * @throws SslInitException + * @throws InvalidConfigurationException + */ + public static SSLContext getServerContext() throws SslInitException, + InvalidConfigurationException { + if (serverContext != null) { + return serverContext; + } + initServerContext(); + return serverContext; + } /** * - * @return An initialized SSLContext with Thermostat's own X509TrustManager + * @return An initialized SSLContext with Thermostat's only X509TrustManager * registered. * @throws SslInitException if SSL initialization failed. */ @@ -76,4 +103,46 @@ } clientContext = clientCtxt; } + + private static void initServerContext() throws SslInitException, + InvalidConfigurationException { + SSLContext serverCtxt = null; + File trustStoreFile = SSLKeystoreConfiguration.getKeystoreFile(); + String keyStorePassword = SSLKeystoreConfiguration + .getKeyStorePassword(); + KeyStore ks = KeyStoreProvider.getKeyStore(trustStoreFile, + keyStorePassword); + if (ks == null) { + // This is bad news. We need a proper key store for retrieving the + // server certificate. + logReason(trustStoreFile); + throw new SslInitException( + "Failed to initialize server side SSL context"); + } + try { + // Set up key manager factory to use our key store + KeyManagerFactory kmf = KeyManagerFactory.getInstance(ALGORITHM); + String keystorePassword = SSLKeystoreConfiguration.getKeyStorePassword(); + kmf.init(ks, keystorePassword.toCharArray()); + + // Initialize the SSLContext to work with our key managers. + serverCtxt = SSLContext.getInstance(PROTOCOL); + serverCtxt.init(kmf.getKeyManagers(), null, new SecureRandom()); + } catch (GeneralSecurityException e) { + throw new SslInitException(e); + } + serverContext = serverCtxt; + } + + private static void logReason(File trustStoreFile) { + String detail = "Reason: no keystore file specified!"; + if (trustStoreFile != null) { + if (!trustStoreFile.exists()) { + detail = "Reason: keystore file '" + trustStoreFile.toString() + "' does not exist!"; + } else { + detail = "Reason: illegal keystore password!"; + } + } + logger.log(Level.SEVERE, "Failed to load keystore. " + detail); + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/core/src/main/java/com/redhat/thermostat/common/ssl/SSLKeystoreConfiguration.java Tue Dec 18 12:32:36 2012 +0100 @@ -0,0 +1,138 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.common.ssl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +import com.redhat.thermostat.common.config.ConfigUtils; +import com.redhat.thermostat.common.config.InvalidConfigurationException; + +public class SSLKeystoreConfiguration { + + private static Properties clientProps = null; + private static final String KEYSTORE_FILE_KEY = "KEYSTORE_FILE"; + private static final String KEYSTORE_FILE_PWD_KEY = "KEYSTORE_PASSWORD"; + private static final String CMD_CHANNEL_SSL_KEY = "COMMAND_CHANNEL_USE_SSL"; + + /** + * + * @return The keystore file as specified in $THERMOSTAT_HOME/etc/ssl.properties + * if any. null otherwise. + */ + public static File getKeystoreFile() { + try { + loadClientProperties(); + } catch (InvalidConfigurationException e) { + // Thermostat home not set? Should have failed earlier. Do something + // reasonable. + return null; + } + String path = clientProps.getProperty(KEYSTORE_FILE_KEY); + if (path != null) { + File file = new File(path); + return file; + } + return null; + } + + /** + * + * @return The keystore file as specified in $THERMOSTAT_HOME/etc/ssl.properties + * if any. The empty string otherwise. + */ + public static String getKeyStorePassword() { + try { + loadClientProperties(); + } catch (InvalidConfigurationException e) { + // Thermostat home not set? Do something reasonable + return ""; + } + String pwd = clientProps.getProperty(KEYSTORE_FILE_PWD_KEY); + if (pwd == null) { + return ""; + } else { + return pwd; + } + } + + /** + * + * @return true if and only if SSL should be enabled for command channel + * communication between agent and client. I.e. if + * $THERMOSTAT_HOME/etc/ssl.properties exists and proper config has + * been added. false otherwise. + */ + public static boolean shouldSSLEnableCmdChannel() { + boolean result = false; + try { + loadClientProperties(); + } catch (InvalidConfigurationException e) { + // Thermostat home not set? Do something reasonable + return result; + } + String token = clientProps.getProperty(CMD_CHANNEL_SSL_KEY); + if (token != null) { + result = Boolean.parseBoolean(token); + } + return result; + } + + // testing hook + static void initClientProperties(File clientPropertiesFile) { + clientProps = new Properties(); + try { + clientProps.load(new FileInputStream(clientPropertiesFile)); + } catch (IOException | IllegalArgumentException e) { + // Could not load ssl properties file. This is fine as it's + // an optional config. + } + } + + private static void loadClientProperties() + throws InvalidConfigurationException { + if (clientProps == null) { + File thermostatEtcDir = new File(ConfigUtils.getThermostatHome(), + "etc"); + File clientPropertiesFile = new File(thermostatEtcDir, + "ssl.properties"); + initClientProperties(clientPropertiesFile); + } + } +}
--- a/common/core/src/test/java/com/redhat/thermostat/common/internal/SSLKeystoreConfigurationTest.java Tue Dec 18 16:39:38 2012 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/* - * Copyright 2012 Red Hat, Inc. - * - * This file is part of Thermostat. - * - * Thermostat is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * Thermostat is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Thermostat; see the file COPYING. If not see - * <http://www.gnu.org/licenses/>. - * - * Linking this code with other modules is making a combined work - * based on this code. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this code give - * you permission to link this code with independent modules to - * produce an executable, regardless of the license terms of these - * independent modules, and to copy and distribute the resulting - * executable under terms of your choice, provided that you also - * meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module - * which is not derived from or based on this code. If you modify - * this code, you may extend this exception to your version of the - * library, but you are not obligated to do so. If you do not wish - * to do so, delete this exception statement from your version. - */ - -package com.redhat.thermostat.common.internal; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.io.File; - -import org.junit.Test; - -import com.redhat.thermostat.common.internal.SSLKeystoreConfiguration; - -public class SSLKeystoreConfigurationTest { - - @Test - public void canGetKeystoreFileFromProps() throws Exception { - File clientProps = new File(this.getClass().getResource("/client.properties").getFile()); - SSLKeystoreConfiguration.initClientProperties(clientProps); - String keystorePath = "/path/to/thermostat.keystore"; - String keystorePwd = "some password"; - assertEquals(keystorePath, SSLKeystoreConfiguration.getKeystoreFile().getAbsolutePath()); - assertEquals(keystorePwd, SSLKeystoreConfiguration.getKeyStorePassword()); - } - - @Test - public void notExistingPropertiesFileReturnsNull() throws Exception { - File clientProps = new File("i/am/not/there/file.txt"); - SSLKeystoreConfiguration.initClientProperties(clientProps); - assertTrue(SSLKeystoreConfiguration.getKeystoreFile() == null); - assertEquals("", SSLKeystoreConfiguration.getKeyStorePassword()); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/core/src/test/java/com/redhat/thermostat/common/ssl/SSLContextFactoryTest.java Tue Dec 18 12:32:36 2012 +0100 @@ -0,0 +1,139 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.common.ssl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.security.KeyStore; +import java.security.SecureRandom; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ SSLKeystoreConfiguration.class, SSLContext.class, + KeyManagerFactory.class }) +public class SSLContextFactoryTest { + + /* + * cmdChanServer.keystore is a keystore converted from openssl. It contains + * key material which was signed by ca.crt. More information as to how to + * create such a file here (first create server.crt => convert it to java + * keystore format): + * http://icedtea.classpath.org/wiki/Thermostat/DevDeployWarInTomcatNotes + * + * Unfortunately, powermock messes up the KeyManagerFactory. We can only + * verify that proper methods are called. + */ + @Test + public void verifySetsUpServerContextWithProperKeyMaterial() + throws Exception { + File keystoreFile = new File(this.getClass() + .getResource("/cmdChanServer.keystore").getFile()); + + PowerMockito.mockStatic(SSLKeystoreConfiguration.class); + when(SSLKeystoreConfiguration.getKeystoreFile()).thenReturn( + keystoreFile); + when(SSLKeystoreConfiguration.getKeyStorePassword()).thenReturn( + "testpassword"); + + PowerMockito.mockStatic(SSLContext.class); + SSLContext context = PowerMockito.mock(SSLContext.class); + when(SSLContext.getInstance("TLS")).thenReturn(context); + KeyManagerFactory factory = PowerMockito.mock(KeyManagerFactory.class); + PowerMockito.mockStatic(KeyManagerFactory.class); + when(KeyManagerFactory.getInstance("SunX509")).thenReturn(factory); + + ArgumentCaptor<KeyStore> keystoreArgCaptor = ArgumentCaptor + .forClass(KeyStore.class); + ArgumentCaptor<char[]> pwdCaptor = ArgumentCaptor + .forClass(char[].class); + SSLContextFactory.getServerContext(); + verify(context).init(any(KeyManager[].class), + any(TrustManager[].class), any(SecureRandom.class)); + verify(factory).init(keystoreArgCaptor.capture(), pwdCaptor.capture()); + verify(factory).getKeyManagers(); + + KeyStore keystore = keystoreArgCaptor.getValue(); + String password = new String(pwdCaptor.getValue()); + assertNotNull(keystore); + assertNotNull(password); + assertEquals("testpassword", password); + } + + @Test + public void verifySetsUpClientContextWithProperTrustManager() + throws Exception { + File keystoreFile = new File(this.getClass() + .getResource("/cmdChanServer.keystore").getFile()); + + PowerMockito.mockStatic(SSLKeystoreConfiguration.class); + when(SSLKeystoreConfiguration.getKeystoreFile()).thenReturn( + keystoreFile); + when(SSLKeystoreConfiguration.getKeyStorePassword()).thenReturn( + "testpassword"); + + PowerMockito.mockStatic(SSLContext.class); + SSLContext context = PowerMockito.mock(SSLContext.class); + when(SSLContext.getInstance("TLS")).thenReturn(context); + + ArgumentCaptor<TrustManager[]> tmsCaptor = ArgumentCaptor + .forClass(TrustManager[].class); + SSLContextFactory.getClientContext(); + verify(context).init(any(KeyManager[].class), tmsCaptor.capture(), + any(SecureRandom.class)); + TrustManager[] tms = tmsCaptor.getValue(); + assertEquals(1, tms.length); + assertEquals(tms[0].getClass().getName(), + "com.redhat.thermostat.common.internal.CustomX509TrustManager"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/core/src/test/java/com/redhat/thermostat/common/ssl/SSLKeystoreConfigurationTest.java Tue Dec 18 12:32:36 2012 +0100 @@ -0,0 +1,78 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.common.ssl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.Test; + +import com.redhat.thermostat.common.ssl.SSLKeystoreConfiguration; + +public class SSLKeystoreConfigurationTest { + + @Test + public void canGetKeystoreFileFromProps() throws Exception { + File clientProps = new File(this.getClass().getResource("/client.properties").getFile()); + SSLKeystoreConfiguration.initClientProperties(clientProps); + String keystorePath = "/path/to/thermostat.keystore"; + String keystorePwd = "some password"; + assertEquals(keystorePath, SSLKeystoreConfiguration.getKeystoreFile().getAbsolutePath()); + assertEquals(keystorePwd, SSLKeystoreConfiguration.getKeyStorePassword()); + } + + @Test + public void notExistingPropertiesFileReturnsNull() throws Exception { + File clientProps = new File("i/am/not/there/file.txt"); + SSLKeystoreConfiguration.initClientProperties(clientProps); + assertTrue(SSLKeystoreConfiguration.getKeystoreFile() == null); + assertEquals("", SSLKeystoreConfiguration.getKeyStorePassword()); + } + + @Test + public void canGetSSLEnabledConfig() { + File clientProps = new File(this.getClass().getResource("/client.properties").getFile()); + SSLKeystoreConfiguration.initClientProperties(clientProps); + assertTrue(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()); + clientProps = new File(this.getClass().getResource("/ssl.properties").getFile()); + SSLKeystoreConfiguration.initClientProperties(clientProps); + assertFalse(SSLKeystoreConfiguration.shouldSSLEnableCmdChannel()); + } +}
--- a/common/core/src/test/resources/client.properties Tue Dec 18 16:39:38 2012 -0500 +++ b/common/core/src/test/resources/client.properties Tue Dec 18 12:32:36 2012 +0100 @@ -1,2 +1,4 @@ +# Random comment KEYSTORE_FILE=/path/to/thermostat.keystore -KEYSTORE_PASSWORD=some password \ No newline at end of file +KEYSTORE_PASSWORD=some password +COMMAND_CHANNEL_USE_SSL=true \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/core/src/test/resources/ssl.properties Tue Dec 18 12:32:36 2012 +0100 @@ -0,0 +1,1 @@ +COMMAND_CHANNEL_USE_SSL=somethingNotABoolean \ No newline at end of file
--- a/distribution/config/ssl.properties Tue Dec 18 16:39:38 2012 -0500 +++ b/distribution/config/ssl.properties Tue Dec 18 12:32:36 2012 +0100 @@ -9,4 +9,10 @@ # The password for the keystore file. If none is provided the empty password # is assumed. Only used if KEYSTORE_FILE was specified. -#KEYSTORE_PASSWORD=nopassword \ No newline at end of file +#KEYSTORE_PASSWORD=nopassword + +# Uncomment the following line if you would like to enable SSL for command +# channel communication. Note that if this is set to true, both of the above +# configs are required on the agent host, since it will use the key material +# in the keystore file for SSL handshakes. +#COMMAND_CHANNEL_USE_SSL=true \ No newline at end of file