Repository: incubator-brooklyn Updated Branches: refs/heads/master e4f44c88a -> de67eb0cd
Use white listed protocols and ciphers for console https Jetty upgrade needed because of support for white-listing. Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/1de9eea5 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/1de9eea5 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/1de9eea5 Branch: refs/heads/master Commit: 1de9eea5f29f055bd06b10e4760e00af31e17005 Parents: d0cbcf3 Author: Svetoslav Neykov <[email protected]> Authored: Tue May 5 15:27:33 2015 +0300 Committer: Svetoslav Neykov <[email protected]> Committed: Thu May 7 20:34:00 2015 +0300 ---------------------------------------------------------------------- pom.xml | 2 +- .../brooklyn/launcher/BrooklynWebServer.java | 97 +++++++++++++------- .../launcher/BrooklynWebServerTest.java | 22 +++++ .../java/brooklyn/rest/BrooklynWebConfig.java | 58 ++++++++++++ 4 files changed, 144 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1de9eea5/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 962bc30..c063d79 100644 --- a/pom.xml +++ b/pom.xml @@ -144,7 +144,7 @@ <sshj.version>0.8.1</sshj.version> <felix.framework.version>4.4.0</felix.framework.version> <reflections.version>0.9.9-RC1</reflections.version> - <jetty.version>8.1.4.v20120524</jetty.version> + <jetty.version>8.1.17.v20150415</jetty.version> <airline.version>0.6</airline.version> <mockwebserver.version>20121111</mockwebserver.version> <httpclient.version>4.2.5</httpclient.version> http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1de9eea5/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java ---------------------------------------------------------------------- diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java index 8112b13..ad8bd7a 100644 --- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java +++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java @@ -31,6 +31,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.EnumSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import javax.servlet.DispatcherType; @@ -86,6 +87,7 @@ import brooklyn.util.text.Strings; import brooklyn.util.web.ContextHandlerCollectionHotSwappable; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Splitter; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; @@ -181,6 +183,12 @@ public class BrooklynWebServer { @SetFromFlag private String trustStorePassword; + + @SetFromFlag + private String transportProtocols; + + @SetFromFlag + private String transportCiphers; private File webappTempDir; @@ -235,9 +243,7 @@ public class BrooklynWebServer { } public boolean getHttpsEnabled() { - if (httpsEnabled!=null) return httpsEnabled; - httpsEnabled = managementContext.getConfig().getConfig(BrooklynWebConfig.HTTPS_REQUIRED); - return httpsEnabled; + return getConfig(httpsEnabled, BrooklynWebConfig.HTTPS_REQUIRED); } public PortRange getRequestedPort() { @@ -352,10 +358,7 @@ public class BrooklynWebServer { if (server != null) throw new IllegalStateException(""+this+" already running"); if (actualPort == -1){ - PortRange portRange = requestedPort; - if (portRange==null) { - portRange = managementContext.getConfig().getConfig(BrooklynWebConfig.WEB_CONSOLE_PORT); - } + PortRange portRange = getConfig(requestedPort, BrooklynWebConfig.WEB_CONSOLE_PORT); if (portRange==null) { portRange = getHttpsEnabled() ? httpsPort : httpPort; } @@ -430,57 +433,83 @@ public class BrooklynWebServer { SslContextFactory sslContextFactory = new SslContextFactory(); // allow webconsole keystore & related properties to be set in brooklyn.properties - if (Strings.isNonBlank(keystorePath)) { - if (keystoreUrl==null) { - log.warn("Deprecated 'keystorePath' used; callers should use 'keystoreUrl'"); - keystoreUrl = keystorePath; - } else if (!keystoreUrl.equals(keystorePath)) { - log.warn("Deprecated 'keystorePath' supplied with different value than 'keystoreUrl', preferring the latter: "+ - keystorePath+" / "+keystoreUrl); - } - } - if (keystoreUrl==null) keystoreUrl = managementContext.getConfig().getConfig(BrooklynWebConfig.KEYSTORE_URL); - if (keystorePassword==null) keystorePassword = managementContext.getConfig().getConfig(BrooklynWebConfig.KEYSTORE_PASSWORD); - if (keystoreCertAlias==null) keystoreCertAlias = managementContext.getConfig().getConfig(BrooklynWebConfig.KEYSTORE_CERTIFICATE_ALIAS); + String ksUrl = getKeystoreUrl(); + String ksPassword = getConfig(keystorePassword, BrooklynWebConfig.KEYSTORE_PASSWORD); + String ksCertAlias = getConfig(keystoreCertAlias, BrooklynWebConfig.KEYSTORE_CERTIFICATE_ALIAS); + String trProtos = getConfig(transportProtocols, BrooklynWebConfig.TRANSPORT_PROTOCOLS); + String trCiphers = getConfig(transportCiphers, BrooklynWebConfig.TRANSPORT_CIPHERS); - if (keystoreUrl!=null) { - sslContextFactory.setKeyStorePath(getLocalKeyStorePath(keystoreUrl)); - if (Strings.isEmpty(keystorePassword)) + if (ksUrl!=null) { + sslContextFactory.setKeyStorePath(getLocalKeyStorePath(ksUrl)); + if (Strings.isEmpty(ksPassword)) throw new IllegalArgumentException("Keystore password is required and non-empty if keystore is specified."); - sslContextFactory.setKeyStorePassword(keystorePassword); - if (Strings.isNonEmpty(keystoreCertAlias)) - sslContextFactory.setCertAlias(keystoreCertAlias); + sslContextFactory.setKeyStorePassword(ksPassword); + if (Strings.isNonEmpty(ksCertAlias)) + sslContextFactory.setCertAlias(ksCertAlias); } else { log.info("No keystore specified but https enabled; creating a default keystore"); - if (Strings.isEmpty(keystoreCertAlias)) - keystoreCertAlias = "web-console"; + if (Strings.isEmpty(ksCertAlias)) + ksCertAlias = "web-console"; // if password is blank the process will block and read from stdin ! - if (Strings.isEmpty(keystorePassword)) { - keystorePassword = Identifiers.makeRandomId(8); - log.debug("created random password "+keystorePassword+" for ad hoc internal keystore"); + if (Strings.isEmpty(ksPassword)) { + ksPassword = Identifiers.makeRandomId(8); + log.debug("created random password "+ksPassword+" for ad hoc internal keystore"); } KeyStore ks = SecureKeys.newKeyStore(); KeyPair key = SecureKeys.newKeyPair(); X509Certificate cert = new FluentKeySigner("brooklyn").newCertificateFor("web-console", key); - ks.setKeyEntry(keystoreCertAlias, key.getPrivate(), keystorePassword.toCharArray(), + ks.setKeyEntry(ksCertAlias, key.getPrivate(), ksPassword.toCharArray(), new Certificate[] { cert }); sslContextFactory.setKeyStore(ks); - sslContextFactory.setKeyStorePassword(keystorePassword); - sslContextFactory.setCertAlias(keystoreCertAlias); + sslContextFactory.setKeyStorePassword(ksPassword); + sslContextFactory.setCertAlias(ksCertAlias); } if (!Strings.isEmpty(truststorePath)) { sslContextFactory.setTrustStore(checkFileExists(truststorePath, "truststore")); sslContextFactory.setTrustStorePassword(trustStorePassword); } - sslContextFactory.addExcludeProtocols("SSLv3"); + if (Strings.isNonBlank(trProtos)) { + sslContextFactory.setIncludeProtocols(parseArray(trProtos)); + } + if (Strings.isNonBlank(trCiphers)) { + sslContextFactory.setIncludeCipherSuites(parseArray(trCiphers)); + } return sslContextFactory; } + private String[] parseArray(String list) { + List<String> arr = Splitter.on(",").omitEmptyStrings().trimResults().splitToList(list); + return arr.toArray(new String[arr.size()]); + } + + private String getKeystoreUrl() { + if (keystoreUrl != null) { + if (Strings.isNonBlank(keystorePath) && !keystoreUrl.equals(keystorePath)) { + log.warn("Deprecated 'keystorePath' supplied with different value than 'keystoreUrl', preferring the latter: "+ + keystorePath+" / "+keystoreUrl); + } + return keystoreUrl; + } else if (Strings.isNonBlank(keystorePath)) { + log.warn("Deprecated 'keystorePath' used; callers should use 'keystoreUrl'"); + return keystorePath; + } else { + return managementContext.getConfig().getConfig(BrooklynWebConfig.KEYSTORE_URL); + } + } + + private <T> T getConfig(T override, ConfigKey<T> key) { + if (override!=null) { + return override; + } else { + return managementContext.getConfig().getConfig(key); + } + } + private String getLocalKeyStorePath(String keystoreUrl) { ResourceUtils res = ResourceUtils.create(this); res.checkUrlExists(keystoreUrl, BrooklynWebConfig.KEYSTORE_URL.getName()); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1de9eea5/usage/launcher/src/test/java/brooklyn/launcher/BrooklynWebServerTest.java ---------------------------------------------------------------------- diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynWebServerTest.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynWebServerTest.java index 3b7e8f6..d20ee1a 100644 --- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynWebServerTest.java +++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynWebServerTest.java @@ -19,6 +19,8 @@ package brooklyn.launcher; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; import java.io.File; import java.io.FileInputStream; @@ -27,6 +29,8 @@ import java.security.SecureRandom; import java.util.List; import java.util.Map; +import javax.net.ssl.SSLPeerUnverifiedException; + import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; @@ -43,6 +47,7 @@ import brooklyn.management.internal.LocalManagementContext; import brooklyn.rest.BrooklynWebConfig; import brooklyn.test.entity.LocalManagementContextForTests; import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; import brooklyn.util.http.HttpTool; import brooklyn.util.http.HttpToolResponse; @@ -131,6 +136,23 @@ public class BrooklynWebServerTest { brooklynProperties.put(BrooklynWebConfig.HTTPS_REQUIRED, true); brooklynProperties.put(BrooklynWebConfig.KEYSTORE_URL, getFile("server.ks")); brooklynProperties.put(BrooklynWebConfig.KEYSTORE_PASSWORD, "password"); + verifyHttpsFromConfig(brooklynProperties); + } + + @Test + public void verifyHttpsCiphers() throws Exception { + brooklynProperties.put(BrooklynWebConfig.HTTPS_REQUIRED, true); + brooklynProperties.put(BrooklynWebConfig.TRANSPORT_PROTOCOLS, "XXX"); + brooklynProperties.put(BrooklynWebConfig.TRANSPORT_CIPHERS, "XXX"); + try { + verifyHttpsFromConfig(brooklynProperties); + fail("Expected to fail due to unsupported ciphers during connection negotiation"); + } catch (Exception e) { + assertTrue(Exceptions.getFirstThrowableOfType(e, SSLPeerUnverifiedException.class) != null, "Expected to fail due to inability to negotiate"); + } + } + + private void verifyHttpsFromConfig(BrooklynProperties brooklynProperties) throws Exception { webServer = new BrooklynWebServer(MutableMap.of(), newManagementContext(brooklynProperties)); webServer.start(); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1de9eea5/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java index 289837a..4443b00 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java @@ -87,6 +87,64 @@ public class BrooklynWebConfig { BASE_NAME+".security.keystore.certificate.alias", "Alias in "+KEYSTORE_URL+" for the certificate to use; defaults to the first if not supplied"); + public final static ConfigKey<String> TRANSPORT_PROTOCOLS = ConfigKeys.newStringConfigKey( + BASE_NAME+".security.transport.protocols", + "SSL/TLS protocol versions to use for web console connections", + "TLSv1, TLSv1.1, TLSv1.2"); + + // https://wiki.mozilla.org/Security/Server_Side_TLS (v3.4) + // http://stackoverflow.com/questions/19846020/how-to-map-a-openssls-cipher-list-to-java-jsse + // list created on 05.05.2015, Intermediate config from first link + public final static ConfigKey<String> TRANSPORT_CIPHERS = ConfigKeys.newStringConfigKey( + BASE_NAME+".security.transport.ciphers", + "SSL/TLS cipher suites to use for web console connections", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," + + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," + + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256," + + "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384," + + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," + + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," + + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," + + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," + + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA," + + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256," + + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA," + + "TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384," + + "TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256," + + "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA," + + "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," + + "TLS_SRP_SHA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256," + + "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," + + "TLS_SRP_SHA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA," + + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," + + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," + + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA," + + "TLS_RSA_WITH_3DES_EDE_CBC_SHA," + + // Same as above but with SSL_ prefix, IBM Java compatibility (cipher is independent of protocol) + // https://www-01.ibm.com/support/knowledgecenter/SSYKE2_7.0.0/com.ibm.java.security.component.70.doc/security-component/jsse2Docs/ciphersuites.html + "SSL_ECDHE_RSA_WITH_AES_128_GCM_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," + + "SSL_ECDHE_RSA_WITH_AES_256_GCM_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," + + "SSL_DHE_RSA_WITH_AES_128_GCM_SHA256,SSL_DHE_DSS_WITH_AES_128_GCM_SHA256," + + "SSL_DHE_DSS_WITH_AES_256_GCM_SHA384,SSL_DHE_RSA_WITH_AES_256_GCM_SHA384," + + "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," + + "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," + + "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," + + "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," + + "SSL_DHE_RSA_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_128_CBC_SHA," + + "SSL_DHE_DSS_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_256_CBC_SHA256," + + "SSL_DHE_DSS_WITH_AES_256_CBC_SHA,SSL_DHE_RSA_WITH_AES_256_CBC_SHA," + + "SSL_RSA_WITH_AES_128_GCM_SHA256,SSL_RSA_WITH_AES_256_GCM_SHA384," + + "SSL_RSA_WITH_AES_128_CBC_SHA256,SSL_RSA_WITH_AES_256_CBC_SHA256," + + "SSL_RSA_WITH_AES_128_CBC_SHA,SSL_RSA_WITH_AES_256_CBC_SHA," + + "SSL_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," + + "SSL_SRP_SHA_WITH_AES_256_CBC_SHA,SSL_DHE_DSS_WITH_AES_256_CBC_SHA256," + + "SSL_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," + + "SSL_SRP_SHA_WITH_AES_128_CBC_SHA,SSL_DHE_DSS_WITH_AES_128_CBC_SHA," + + "SSL_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," + + "SSL_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," + + "SSL_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,SSL_RSA_WITH_CAMELLIA_128_CBC_SHA," + + "SSL_RSA_WITH_3DES_EDE_CBC_SHA"); + public final static boolean hasNoSecurityOptions(ConfigMap config) { return config.submap(ConfigPredicates.startingWith(BASE_NAME_SECURITY)).isEmpty(); }
