Mercurial > hg > thermostat-ng > web-gateway
changeset 203:de32338d6dcd
[TLS] Make the connector (optionally) encrypted.
Reviewed-by: jkang
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-July/024126.html
author | Severin Gehwolf <sgehwolf@redhat.com> |
---|---|
date | Mon, 17 Jul 2017 11:45:37 +0200 |
parents | c0140877e78b |
children | 6e9a0f606b38 |
files | common/core/src/main/java/com/redhat/thermostat/gateway/common/core/config/GlobalConfiguration.java distribution/src/etc/global-config.properties server/src/main/java/com/redhat/thermostat/gateway/server/CoreServerBuilder.java server/src/main/java/com/redhat/thermostat/gateway/server/Start.java server/src/test/java/com/redhat/thermostat/gateway/server/CoreServerBuilderTest.java server/src/test/resources/test_gw_home/test_me.jks tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/IntegrationTest.java |
diffstat | 7 files changed, 208 insertions(+), 13 deletions(-) [+] |
line wrap: on
line diff
--- a/common/core/src/main/java/com/redhat/thermostat/gateway/common/core/config/GlobalConfiguration.java Mon Jul 17 11:37:33 2017 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/gateway/common/core/config/GlobalConfiguration.java Mon Jul 17 11:45:37 2017 +0200 @@ -80,7 +80,17 @@ * context path /web-client, for built web-client assets * should get created. */ - WITH_WEB_CLIENT + WITH_WEB_CLIENT, + /** + * Determines whether or not the connector at the given host + * and port will be TLS enabled or not. + */ + WITH_TLS, + /** + * The path to the JKS keystore file with the keymaterial, + * alias 'thermostat', when WITH_TLS=true. + */ + KEYSTORE_FILE, } }
--- a/distribution/src/etc/global-config.properties Mon Jul 17 11:37:33 2017 +0200 +++ b/distribution/src/etc/global-config.properties Mon Jul 17 11:45:37 2017 +0200 @@ -16,6 +16,22 @@ # should get created. # WITH_WEB_CLIENT=true +# +# Specifies whether or not the configured connector at +# IP:PORT will be TLS enabled or not. This will use +# the keymaterial with alias "thermostat" in keystore +# thermostat.jks +# +WITH_TLS=true +# +# Keystore file with the key material. Alias 'thermostat' +# must be present. Relative paths resolve relative to +# THERMOSTAT_GATEWAY_HOME. Specify the keystore password +# via Java system property 'org.eclipse.jetty.ssl.password' +# and the keymanager password via Java system property +# 'org.eclipse.jetty.ssl.keypassword' +# +KEYSTORE_FILE=thermostat.jks MONGO_URL=mongodb://127.0.0.1:27518 MONGO_DB=thermostat
--- a/server/src/main/java/com/redhat/thermostat/gateway/server/CoreServerBuilder.java Mon Jul 17 11:37:33 2017 +0200 +++ b/server/src/main/java/com/redhat/thermostat/gateway/server/CoreServerBuilder.java Mon Jul 17 11:45:37 2017 +0200 @@ -36,18 +36,25 @@ package com.redhat.thermostat.gateway.server; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Map; import javax.servlet.ServletException; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import com.redhat.thermostat.gateway.common.core.config.Configuration; @@ -60,11 +67,13 @@ public class CoreServerBuilder { + private static final String THERMOSTAT_ALIAS = "thermostat"; private final SwaggerUiHandler swaggerHandler; private final StaticAssetsHandler staticAssetsHandler; private final Server server = new Server(); private CoreServiceBuilder coreServiceBuilder; private Configuration serverConfig; + private String gatewayHome; public CoreServerBuilder() { this(new SwaggerUiHandler(), new StaticAssetsHandler()); @@ -76,6 +85,8 @@ this.swaggerHandler = swaggerHandler; } + + public CoreServerBuilder setServiceBuilder(CoreServiceBuilder builder) { this.coreServiceBuilder = builder; return this; @@ -86,6 +97,11 @@ return this; } + public CoreServerBuilder setGatewayHome(String gatewayHome) { + this.gatewayHome = gatewayHome; + return this; + } + public Server build() { setupHandler(); setupConnector(); @@ -134,19 +150,68 @@ } private void setupConnector() { + Map<String, Object> serverConfigMap = serverConfig.asMap(); + String listenAddress = (String)serverConfigMap.get(GlobalConfiguration.ConfigurationKey.IP.toString()); + int listenPort = Integer.parseInt((String)serverConfigMap.get(GlobalConfiguration.ConfigurationKey.PORT.toString())); + Connector connector; + if (isEnabled(serverConfigMap, ConfigurationKey.WITH_TLS)) { + connector = getHttpsConnector(listenAddress, listenPort, serverConfigMap); + } else { + connector = getHttpConnector(listenAddress, listenPort); + } + server.setConnectors(new Connector[]{connector} ); + } + + private Connector getHttpsConnector(String listenAddress, int listenPort, Map<String, Object> serverConfigMap) { + String keystoreCandidate = (String)serverConfigMap.get((GlobalConfiguration.ConfigurationKey.KEYSTORE_FILE.name())); + Path keystoreFile = Paths.get(keystoreCandidate); + if (!keystoreFile.isAbsolute()) { + // resolve relative to GW home + keystoreFile = Paths.get(gatewayHome, keystoreCandidate); + } + ServerConnector connector = getPreconfiguredHttpsConnector(keystoreFile.toFile()); + return configureHostPort(connector, listenAddress, listenPort); + } + + private Connector getHttpConnector(String listenAddress, int listenPort) { + ServerConnector connector = getPreconfiguredHttpConnector(); + return configureHostPort(connector, listenAddress, listenPort); + } + + private ServerConnector configureHostPort(ServerConnector connector, String listenAddress, int listenPort) { + connector.setPort(listenPort); + connector.setHost(listenAddress); + return connector; + } + + private ServerConnector getPreconfiguredHttpConnector() { ServerConnector httpConnector = new ServerConnector(server); HttpConfiguration httpConfig = new HttpConfiguration(); httpConnector.addConnectionFactory(new HttpConnectionFactory(httpConfig)); + return httpConnector; + } - Map<String, Object> serverConfigMap = serverConfig.asMap(); - String listenAddress = (String)serverConfigMap.get(GlobalConfiguration.ConfigurationKey.IP.toString()); - int listenPort = Integer.parseInt((String)serverConfigMap.get(GlobalConfiguration.ConfigurationKey.PORT.toString())); + private ServerConnector getPreconfiguredHttpsConnector(File keystoreFile) { + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath()); + // May be overridden by setting the property -Dorg.eclipse.jetty.ssl.password=<password> + sslContextFactory.setKeyStorePassword("OBF:1sot1v961saj1v9i1v941sar1v9g1sox"); + // May be overridden by setting the property -Dorg.eclipse.jetty.ssl.keypassword=<password> + sslContextFactory.setKeyManagerPassword("OBF:1sot1v961saj1v9i1v941sar1v9g1sox"); + sslContextFactory.setCertAlias(THERMOSTAT_ALIAS); - httpConnector.setHost(listenAddress); - httpConnector.setPort(listenPort); + HttpConfiguration httpsConfig = new HttpConfiguration(); + httpsConfig.setSecureScheme("https"); + httpsConfig.setSecurePort(8443); + SecureRequestCustomizer src = new SecureRequestCustomizer(); + httpsConfig.addCustomizer(src); - server.setConnectors(new Connector[]{httpConnector}); + ServerConnector https = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, + HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(httpsConfig)); + return https; } }
--- a/server/src/main/java/com/redhat/thermostat/gateway/server/Start.java Mon Jul 17 11:37:33 2017 +0200 +++ b/server/src/main/java/com/redhat/thermostat/gateway/server/Start.java Mon Jul 17 11:45:37 2017 +0200 @@ -37,6 +37,7 @@ package com.redhat.thermostat.gateway.server; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import com.redhat.thermostat.gateway.common.core.config.Configuration; import com.redhat.thermostat.gateway.common.core.config.ConfigurationFactory; @@ -44,7 +45,6 @@ import com.redhat.thermostat.gateway.server.services.CoreServiceBuilder; import com.redhat.thermostat.gateway.server.services.CoreServiceBuilderFactory; import com.redhat.thermostat.gateway.server.services.CoreServiceBuilderFactory.CoreServiceType; -import org.eclipse.jetty.util.component.AbstractLifeCycle; public class Start implements Runnable { @@ -68,6 +68,7 @@ CoreServerBuilder serverBuilder = new CoreServerBuilder(); setServerConfig(serverBuilder, factory); setServiceBuilder(serverBuilder, factory); + serverBuilder.setGatewayHome(gatewayHome); server = serverBuilder.build(); if (listener != null) {
--- a/server/src/test/java/com/redhat/thermostat/gateway/server/CoreServerBuilderTest.java Mon Jul 17 11:37:33 2017 +0200 +++ b/server/src/test/java/com/redhat/thermostat/gateway/server/CoreServerBuilderTest.java Mon Jul 17 11:45:37 2017 +0200 @@ -37,17 +37,24 @@ package com.redhat.thermostat.gateway.server; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.servlet.ServletContextHandler; import org.junit.Test; @@ -78,6 +85,7 @@ configMap.put(GlobalConfiguration.ConfigurationKey.PORT.name(), port); configMap.put(GlobalConfiguration.ConfigurationKey.WITH_SWAGGER_UI.name(), Boolean.FALSE.toString()); configMap.put(GlobalConfiguration.ConfigurationKey.WITH_WEB_CLIENT.name(), Boolean.FALSE.toString()); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_TLS.name(), Boolean.FALSE.toString()); Configuration configuration = getMockConfiguration(configMap); CoreServerBuilder builder = new CoreServerBuilder(); @@ -110,6 +118,7 @@ configMap.put(GlobalConfiguration.ConfigurationKey.PORT.name(), port); configMap.put(GlobalConfiguration.ConfigurationKey.WITH_SWAGGER_UI.name(), Boolean.TRUE.toString()); configMap.put(GlobalConfiguration.ConfigurationKey.WITH_WEB_CLIENT.name(), Boolean.TRUE.toString()); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_TLS.name(), Boolean.FALSE.toString()); Configuration configuration = getMockConfiguration(configMap); SwaggerUiHandler swaggerHandler = new SwaggerUiHandler(); @@ -127,6 +136,58 @@ assertEquals(3, handler.getHandlers().length); } + @Test + public void testHttpConnector() { + CoreServiceBuilder serviceBuilder = getMockServiceBuilder(); + + Map<String, Object> configMap = new HashMap<>(); + String ip = "127.0.0.1"; + String port = "8080"; + configMap.put(GlobalConfiguration.ConfigurationKey.IP.name(), ip); + configMap.put(GlobalConfiguration.ConfigurationKey.PORT.name(), port); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_SWAGGER_UI.name(), Boolean.FALSE.toString()); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_WEB_CLIENT.name(), Boolean.FALSE.toString()); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_TLS.name(), Boolean.FALSE.toString()); + Configuration configuration = getMockConfiguration(configMap); + CoreServerBuilder builder = new CoreServerBuilder(); + builder.setServerConfiguration(configuration); + builder.setServiceBuilder(serviceBuilder); + + Server server = builder.build(); + Connector[] connectors = server.getConnectors(); + assertEquals(1, connectors.length); + assertTrue(connectors[0] instanceof ServerConnector); + ServerConnector serverConnector = (ServerConnector)connectors[0]; + assertTrue(serverConnector.getDefaultConnectionFactory() instanceof HttpConnectionFactory); + } + + @Test + public void testHttpsConnectorDefaultKeystoreLocation() { + CoreServiceBuilder serviceBuilder = getMockServiceBuilder(); + + Map<String, Object> configMap = new HashMap<>(); + String ip = "127.0.0.1"; + String port = "8080"; + configMap.put(GlobalConfiguration.ConfigurationKey.IP.name(), ip); + configMap.put(GlobalConfiguration.ConfigurationKey.PORT.name(), port); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_SWAGGER_UI.name(), Boolean.FALSE.toString()); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_WEB_CLIENT.name(), Boolean.FALSE.toString()); + configMap.put(GlobalConfiguration.ConfigurationKey.WITH_TLS.name(), Boolean.TRUE.toString()); + configMap.put(GlobalConfiguration.ConfigurationKey.KEYSTORE_FILE.name(), "test_me.jks"); + Configuration configuration = getMockConfiguration(configMap); + CoreServerBuilder builder = new CoreServerBuilder(); + builder.setServerConfiguration(configuration); + builder.setServiceBuilder(serviceBuilder); + builder.setGatewayHome(getTestRoot("/test_gw_home")); + + Server server = builder.build(); + Connector[] connectors = server.getConnectors(); + assertEquals(1, connectors.length); + assertTrue(connectors[0] instanceof ServerConnector); + ServerConnector serverConnector = (ServerConnector)connectors[0]; + assertTrue(serverConnector.getDefaultConnectionFactory() instanceof SslConnectionFactory); + } + private Configuration getMockConfiguration(Map<String, Object> configMap) { Configuration configuration = mock(Configuration.class); when(configuration.asMap()).thenReturn(configMap); @@ -144,4 +205,19 @@ when(serviceBuilder.build()).thenReturn(serviceList); return serviceBuilder; } + + private String getTestRoot(String path) { + URL rootUrl = CoreServerBuilderTest.class.getResource(path); + return decodeFilePath(rootUrl); + } + + private String decodeFilePath(URL url) { + try { + // Spaces are encoded as %20 in URLs - handle cases like that. + // requires Java 1.7 + return Paths.get(url.toURI()).toFile().toString(); + } catch (URISyntaxException e) { + throw new AssertionError("Syntax error in URI" + e.getMessage()); + } + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/src/test/resources/test_gw_home/test_me.jks Mon Jul 17 11:45:37 2017 +0200 @@ -0,0 +1,1 @@ +Not really a Java keystore file, but sufficient for the test. \ No newline at end of file
--- a/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/IntegrationTest.java Mon Jul 17 11:37:33 2017 +0200 +++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/IntegrationTest.java Mon Jul 17 11:45:37 2017 +0200 @@ -41,18 +41,25 @@ import java.nio.file.Paths; import java.util.concurrent.CountDownLatch; -import com.redhat.thermostat.gateway.common.core.servlet.GlobalConstants; -import com.redhat.thermostat.gateway.server.Start; - import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.AfterClass; import org.junit.BeforeClass; +import com.redhat.thermostat.gateway.common.core.config.Configuration; +import com.redhat.thermostat.gateway.common.core.config.ConfigurationFactory; +import com.redhat.thermostat.gateway.common.core.config.GlobalConfiguration; +import com.redhat.thermostat.gateway.common.core.servlet.GlobalConstants; +import com.redhat.thermostat.gateway.server.Start; +import com.redhat.thermostat.gateway.tests.utils.MongodTestUtil; + public class IntegrationTest { + private static final ConfigurationFactory factory; + protected static HttpClient client; - protected static String baseUrl = "http://127.0.0.1:30000"; + protected static String baseUrl; protected static final Path distributionImage; @@ -62,6 +69,14 @@ if (distributionImage == null) { throw new RuntimeException("Environment variable THERMOSTAT_GATEWAY_HOME not defined!"); } + factory = new ConfigurationFactory(distDir); + String scheme; + if (isTLSEnabled()) { + scheme = "https"; + } else { + scheme = "http"; + } + baseUrl = scheme + "://127.0.0.1:30000"; } protected String resourceUrl; @@ -72,12 +87,23 @@ @BeforeClass public static void beforeClassIntegrationTest() throws Exception { - client = new HttpClient(); + if (isTLSEnabled()) { + SslContextFactory sslFactory = new SslContextFactory(); + sslFactory.setTrustAll(true); + client = new HttpClient(sslFactory); + } else { + client = new HttpClient(); + } client.start(); startServer(); } + private static boolean isTLSEnabled() { + Configuration config = factory.createGlobalConfiguration(); + return Boolean.parseBoolean((String)config.asMap().get(GlobalConfiguration.ConfigurationKey.WITH_TLS.name())); + } + private static Thread serverThread = null; private static Start serverObject = null;