diff --git a/myriad-scheduler/src/main/java/org/apache/myriad/configuration/MyriadConfiguration.java b/myriad-scheduler/src/main/java/org/apache/myriad/configuration/MyriadConfiguration.java index 8a6e5f31..3d48c8e0 100644 --- a/myriad-scheduler/src/main/java/org/apache/myriad/configuration/MyriadConfiguration.java +++ b/myriad-scheduler/src/main/java/org/apache/myriad/configuration/MyriadConfiguration.java @@ -92,6 +92,7 @@ public class MyriadConfiguration { */ public static final Boolean DEFAULT_HA_ENABLED = false; + public static final Boolean DEFAULT_SSL_ENABLED = false; /** * By default framework failover timeout is 1 day. */ @@ -157,6 +158,9 @@ public class MyriadConfiguration { @JsonProperty private Boolean haEnabled; + @JsonProperty + private Boolean isSSLEnabled; + @JsonProperty private NodeManagerConfiguration nodemanager; @@ -253,6 +257,9 @@ public Boolean isHAEnabled() { return Optional.fromNullable(haEnabled).or(DEFAULT_HA_ENABLED); } + public Boolean isSSLEnabled() { + return Optional.fromNullable(isSSLEnabled).or(DEFAULT_SSL_ENABLED); + } public NodeManagerConfiguration getNodeManagerConfiguration() { return nodemanager; } diff --git a/myriad-scheduler/src/main/java/org/apache/myriad/webapp/HttpConnectorProvider.java b/myriad-scheduler/src/main/java/org/apache/myriad/webapp/HttpConnectorProvider.java index f397a23c..d7313291 100644 --- a/myriad-scheduler/src/main/java/org/apache/myriad/webapp/HttpConnectorProvider.java +++ b/myriad-scheduler/src/main/java/org/apache/myriad/webapp/HttpConnectorProvider.java @@ -20,15 +20,30 @@ import com.google.inject.Provider; import javax.inject.Inject; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.ssl.SslSocketConnectorSecure; +import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.myriad.configuration.MyriadConfiguration; +import org.mortbay.jetty.AbstractConnector; import org.mortbay.jetty.Connector; import org.mortbay.jetty.nio.SelectChannelConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import static org.apache.hadoop.yarn.webapp.util.WebAppUtils.WEB_APP_KEYSTORE_PASSWORD_KEY; +import static org.apache.hadoop.yarn.webapp.util.WebAppUtils.WEB_APP_KEY_PASSWORD_KEY; +import static org.apache.hadoop.yarn.webapp.util.WebAppUtils.WEB_APP_TRUSTSTORE_PASSWORD_KEY; /** * The factory for creating the http connector for the myriad scheduler */ public class HttpConnectorProvider implements Provider { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpConnectorProvider.class); + private MyriadConfiguration myriadConf; @Inject @@ -38,11 +53,58 @@ public HttpConnectorProvider(MyriadConfiguration myriadConf) { @Override public Connector get() { - SelectChannelConnector ret = new SelectChannelConnector(); + final AbstractConnector ret; + if (!myriadConf.isSSLEnabled()) { + final SelectChannelConnector listener = new SelectChannelConnector(); + ret = listener; + } else { + final Configuration sslConf = new Configuration(false); + boolean needsClientAuth = YarnConfiguration.YARN_SSL_CLIENT_HTTPS_NEED_AUTH_DEFAULT; + sslConf.addResource(YarnConfiguration.YARN_SSL_SERVER_RESOURCE_DEFAULT); + + final SslSocketConnectorSecure listener = new SslSocketConnectorSecure(); + listener.setHeaderBufferSize(1024 * 64); + listener.setNeedClientAuth(needsClientAuth); + listener.setKeyPassword(getPassword(sslConf, WEB_APP_KEY_PASSWORD_KEY)); + + String keyStore = sslConf.get("ssl.server.keystore.location"); + + if (keyStore != null) { + listener.setKeystore(keyStore); + listener.setKeystoreType(sslConf.get("ssl.server.keystore.type", "jks")); + listener.setPassword(getPassword(sslConf, WEB_APP_KEYSTORE_PASSWORD_KEY)); + } + + String trustStore = sslConf.get("ssl.server.truststore.location"); + + if (trustStore != null) { + listener.setTruststore(trustStore); + listener.setTruststoreType(sslConf.get("ssl.server.truststore.type", "jks")); + listener.setTrustPassword(getPassword(sslConf, WEB_APP_TRUSTSTORE_PASSWORD_KEY)); + } + ret = listener; + } + ret.setName("Myriad"); ret.setHost("0.0.0.0"); ret.setPort(myriadConf.getRestApiPort()); - return ret; } + + static String getPassword(Configuration conf, String alias) { + String password = null; + try { + char[] passchars = conf.getPassword(alias); + if (passchars != null) { + password = new String(passchars); + } + } catch (IOException ioe) { + password = null; + } + if (password == null) { + LOGGER.warn("No password found in config as property: {}. If you don't want prompted please set ...", alias); + } + return password; + } + } diff --git a/myriad-scheduler/src/main/resources/myriad-config-default.yml b/myriad-scheduler/src/main/resources/myriad-config-default.yml index 066f7341..97bc3afd 100644 --- a/myriad-scheduler/src/main/resources/myriad-config-default.yml +++ b/myriad-scheduler/src/main/resources/myriad-config-default.yml @@ -18,6 +18,7 @@ ## mesosMaster: 10.0.2.15:5050 checkpoint: false +isSSLEnabled: false frameworkFailoverTimeout: 43200000 frameworkName: MyriadAlpha frameworkRole: "*" diff --git a/myriad-scheduler/src/test/java/org/apache/myriad/webapp/HttpConnectorProviderTest.java b/myriad-scheduler/src/test/java/org/apache/myriad/webapp/HttpConnectorProviderTest.java index d5665cce..f441be75 100644 --- a/myriad-scheduler/src/test/java/org/apache/myriad/webapp/HttpConnectorProviderTest.java +++ b/myriad-scheduler/src/test/java/org/apache/myriad/webapp/HttpConnectorProviderTest.java @@ -17,12 +17,21 @@ */ package org.apache.myriad.webapp; +import static junit.framework.TestCase.assertNotNull; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.apache.hadoop.security.ssl.SslSocketConnectorSecure; import org.apache.myriad.BaseConfigurableTest; +import org.apache.myriad.configuration.MyriadConfiguration; import org.junit.Test; import org.mortbay.jetty.Connector; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; + /** * Unit tests for HttpConnectionProvider */ @@ -36,4 +45,57 @@ public void testConnector() throws Exception { assertEquals("0.0.0.0", connector.getHost()); assertEquals("Myriad", connector.getName()); } + + @Test + public void testConnectorSSLOn() throws Exception { + Field[] fields = MyriadConfiguration.class.getDeclaredFields(); + for (Field field : fields) { + if ("isSSLEnabled".equalsIgnoreCase(field.getName())) { + field.setAccessible(true); + field.set(cfg, true); + break; + } + } + assertTrue(cfg.isSSLEnabled()); + + try (InputStream keystore = MyriadWebServerTest.class.getResourceAsStream("/ssl_keystore")) { + try (InputStream truststore = MyriadWebServerTest.class.getResourceAsStream("/ssl_truststore")) { + if (keystore != null && truststore != null) { + try (OutputStream keyStoreOS = new FileOutputStream(MyriadWebServerTest.tmpKeystore)) { + byte[] bytes = new byte[1024]; + int length = 0; + while ((length = keystore.read(bytes)) != -1) { + keyStoreOS.write(bytes, 0, length); + } + } + try (OutputStream trustStoreOS = new FileOutputStream(MyriadWebServerTest.tmpTruststore)) { + byte[] bytes = new byte[1024]; + int length = 0; + while ((length = truststore.read(bytes)) != -1) { + trustStoreOS.write(bytes, 0, length); + } + } + } + } + } + System.setProperty("tmptest.dir", MyriadWebServerTest.tmpDir); + try { + HttpConnectorProvider provider = new HttpConnectorProvider(cfg); + Connector connector = provider.get(); + assertTrue(connector instanceof SslSocketConnectorSecure); + SslSocketConnectorSecure secureConnector = (SslSocketConnectorSecure) connector; + assertNotNull(secureConnector.getKeystore()); + assertEquals(secureConnector.getKeystore(), MyriadWebServerTest.tmpDir + "/ssl_keystore"); + + assertNotNull(secureConnector.getTruststore()); + assertEquals(secureConnector.getTruststore(), MyriadWebServerTest.tmpDir + "/ssl_truststore"); + + assertEquals(8192, connector.getPort()); + assertEquals("0.0.0.0", connector.getHost()); + assertEquals("Myriad", connector.getName()); + } finally { + assertTrue(MyriadWebServerTest.tmpKeystore.delete()); + assertTrue(MyriadWebServerTest.tmpTruststore.delete()); + } + } } \ No newline at end of file diff --git a/myriad-scheduler/src/test/java/org/apache/myriad/webapp/MyriadWebServerTest.java b/myriad-scheduler/src/test/java/org/apache/myriad/webapp/MyriadWebServerTest.java index c86a5084..35e340fd 100644 --- a/myriad-scheduler/src/test/java/org/apache/myriad/webapp/MyriadWebServerTest.java +++ b/myriad-scheduler/src/test/java/org/apache/myriad/webapp/MyriadWebServerTest.java @@ -18,26 +18,84 @@ package org.apache.myriad.webapp; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.apache.myriad.BaseConfigurableTest; import org.apache.myriad.TestObjectFactory; +import org.apache.myriad.configuration.MyriadConfiguration; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; + /** * Unit test cases for MyriadWebServer class */ public class MyriadWebServerTest extends BaseConfigurableTest { MyriadWebServer webServer; + static String tmpDir = System.getProperty("java.io.tmpdir"); + static File tmpKeystore = new File(tmpDir, "ssl_keystore"); + static File tmpTruststore = new File(tmpDir, "ssl_truststore"); + @Before public void setUp() throws Exception { super.setUp(); - webServer = TestObjectFactory.getMyriadWebServer(cfg); + + try (InputStream keystore = MyriadWebServerTest.class.getResourceAsStream("/ssl_keystore")) { + try (InputStream truststore = MyriadWebServerTest.class.getResourceAsStream("/ssl_truststore")) { + if (keystore != null && truststore != null) { + try (OutputStream keyStoreOS = new FileOutputStream(tmpKeystore)) { + byte[] bytes = new byte[1024]; + int length = 0; + while ((length = keystore.read(bytes)) != -1) { + keyStoreOS.write(bytes, 0, length); + } + } + try (OutputStream trustStoreOS = new FileOutputStream(tmpTruststore)) { + byte[] bytes = new byte[1024]; + int length = 0; + while ((length = truststore.read(bytes)) != -1) { + trustStoreOS.write(bytes, 0, length); + } + } + } + } + } + System.setProperty("tmptest.dir", tmpDir); } + @After + public void tearDown() throws Exception { + assertTrue(tmpKeystore.delete()); + assertTrue(tmpTruststore.delete()); + } @Test public void testStartStopMyriadWebServer() throws Exception { + webServerStartStop(); + } + + @Test + public void testsStartStopMyriadWebServerWithSSL() throws Exception { + Field[] fields = MyriadConfiguration.class.getDeclaredFields(); + for (Field field : fields) { + if ("isSSLEnabled".equalsIgnoreCase(field.getName())) { + field.setAccessible(true); + field.set(cfg, true); + break; + } + } + assertTrue(cfg.isSSLEnabled()); + webServerStartStop(); + } + + private void webServerStartStop() throws Exception { + webServer = TestObjectFactory.getMyriadWebServer(cfg); webServer.start(); assertEquals(MyriadWebServer.Status.STARTED, webServer.getStatus()); webServer.stop(); diff --git a/myriad-scheduler/src/test/resources/myriad-config-test-default.yml b/myriad-scheduler/src/test/resources/myriad-config-test-default.yml index 18cd830e..c2478176 100644 --- a/myriad-scheduler/src/test/resources/myriad-config-test-default.yml +++ b/myriad-scheduler/src/test/resources/myriad-config-test-default.yml @@ -20,6 +20,7 @@ servedBinaryPath: /tmp/myriadBinary mesosMaster: 10.0.2.15:5050 haEnabled: false checkpoint: false +isSSLEnabled: false frameworkFailoverTimeout: 44200000 frameworkName: MyriadTest frameworkRole: "*" diff --git a/myriad-scheduler/src/test/resources/ssl-server.xml b/myriad-scheduler/src/test/resources/ssl-server.xml new file mode 100644 index 00000000..fa3f46ae --- /dev/null +++ b/myriad-scheduler/src/test/resources/ssl-server.xml @@ -0,0 +1,75 @@ + + + + + + + ssl.server.truststore.location + ${tmptest.dir}/ssl_truststore + Truststore to be used by webservers. Must be specified. + + + + + ssl.server.truststore.password + myriad + Optional. Default value is "". + + + + + ssl.server.truststore.type + jks + Optional. The keystore file format, default value is "jks". + + + + + ssl.server.truststore.reload.interval + 10000 + Truststore reload check interval, in milliseconds. + Default value is 10000 (10 seconds). + + + + + ssl.server.keystore.location + ${tmptest.dir}/ssl_keystore + Keystore to be used by webservers. Must be specified. + + + + + ssl.server.keystore.password + myriad + Must be specified. + + + + + ssl.server.keystore.keypassword + myriad + Must be specified. + + + + + ssl.server.keystore.type + jks + Optional. The keystore file format, default value is "jks". + + + + \ No newline at end of file diff --git a/myriad-scheduler/src/test/resources/ssl_keystore b/myriad-scheduler/src/test/resources/ssl_keystore new file mode 100644 index 00000000..7539b1fc Binary files /dev/null and b/myriad-scheduler/src/test/resources/ssl_keystore differ diff --git a/myriad-scheduler/src/test/resources/ssl_truststore b/myriad-scheduler/src/test/resources/ssl_truststore new file mode 100644 index 00000000..54af3ad7 Binary files /dev/null and b/myriad-scheduler/src/test/resources/ssl_truststore differ