From 84eb26b24a6854bb78a24832f6224337e6dc0046 Mon Sep 17 00:00:00 2001 From: Yuliya Feldman Date: Tue, 11 Oct 2016 14:35:02 -0700 Subject: [PATCH 1/2] MYRIAD-244 Add https/ssl support for Myriad REST APIs and UI --- .../configuration/MyriadConfiguration.java | 7 ++ .../myriad/webapp/HttpConnectorProvider.java | 63 +++++++++++++++++- .../webapp/HttpConnectorProviderTest.java | 62 +++++++++++++++++ .../myriad/webapp/MyriadWebServerTest.java | 60 ++++++++++++++++- .../resources/myriad-config-test-default.yml | 1 + .../src/test/resources/ssl-server.xml | 63 ++++++++++++++++++ .../src/test/resources/ssl_keystore | Bin 0 -> 2124 bytes .../src/test/resources/ssl_truststore | Bin 0 -> 835 bytes 8 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 myriad-scheduler/src/test/resources/ssl-server.xml create mode 100644 myriad-scheduler/src/test/resources/ssl_keystore create mode 100644 myriad-scheduler/src/test/resources/ssl_truststore 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..f4149685 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,55 @@ 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; + } + return password; + } + } 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..950809d7 --- /dev/null +++ b/myriad-scheduler/src/test/resources/ssl-server.xml @@ -0,0 +1,63 @@ + + + + + + + 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 0000000000000000000000000000000000000000..7539b1fc3c2a1606b7e097bcd4be027a21f9feaa GIT binary patch literal 2124 zcma)+c{J1uAI9f51|e&qA=pJ32m}Hj3HV!*J`V!W zPOfmYuM5`MDabDX?u2o2@o=3lj#v2ZWmK zq3kfz9~vEsB^6`!BR}*v~S5!b{mp+lgydulYUaQ z7sKoX*N&O9W-5N|Xq)i5wRfi~G&A||eQ=A}MQh1^($i+M?RMa?LUM$ai4Q({CASLF zFIaSTHY)p;DbSbxc1yU60V>{wv#IewZ;P|_GB*-t+z`%A-Q>7uNRU7idmg`o&WAbZ z_?Pu{4V0r3=+eSZ^rbIp-z8_9ueFiabPGvtd+<$mRh_+DNw|+1P2;?0G42=cyqE3@( z6tBKZHBS0w0l*B678RNJ4rFZE(uy}p)G_{vpHjl*M;y)3Cf zK#4hnYd@=ExscAL9{8<``P;){?p)iaWOS7;Phks9OTy=~h|}UJNJ_?NDrC!wp{YPN z!t-g_4%b=!aJenCrsvRUF*0GlnOWhnR&2eS>ymHg^K63} zx7vuF0UE5YVP;XGKto(Oy4m;o#*9d=c40J}+JWR;vIza+wh+=(?J9j~@j!%2x{zUg zoF_)%)Vb(_F60PwZKN#C6M=GGhv$#W*u4cm3Er%ngSn)KB<2{OwAG+WSPiV|?L@z5 zR=^tUm}uulP1tw)$cqbROC2C1=MQ;d(|rQeaEyY(c8|gR#uUfueOXDqyOV3o-Si)YjLM&0icyG|!Y_HtT-L$ySA=1(BXA+pG{% zz8*mJv|l6E2*GWw*&`KVq=9)1NWyhTuj`S}-Fv8lbqn!S#rZ2$ZWT)=ve%q%eds7} zEq?3vJ&|5r=CZN?LyDXtZNy8-h2uG&@rBNM)b)FWH@G#ZkbB&DmoY@Rh3TM3<0OHg zO%|Q#dvzHr<6ndFvU#}K;+#L}KvW&}Uh6wxiOWS%$qu|9USYugoNl>dC!4%Skg5NcGt zPVH zlU2pmziG*N6Rw50aKuk00ta43-~e(S2N(c@A@W?QbOb*ak4l0?!cTqx;O7J(6xCq} zIW7)c2*-*4M_GilIKR5KIzmH71EHyl(77$nukl~2^*{Ts|KI?*e?~Y0GzSh41RbU1 z1>*n!l$IykdRlo@eA~ck1;oWb{`~sUOlyumh*fJ^T6#>d{bPK7$!HV5dp}2HR-T&U z*b5Ema!T8N6^h&7vt!w{4X+motG=nNnF~4JWzp?XJ-_`PEY`#)+P>>H`!WW~klwEp zlw>zMvSOr@-5Q^#O7|@6IhIx0jaeGQUOq?saM9q;N3J>dlv*a#G zp?z7Y6_cOd`BU$(Dov=%obL^8;pRn$f zCU$M#UlBjTJV%rb&^+9eQI_}+I8&QZj^=rp^y>2P2D5M7a^Ky=rokO2aiaQbThoKp zF80ODBQvg*y#vrF_;~lo`TmvT-exjIO4T7>d`y*8=Bl&=F+rzqM8Xd3 R@Y>%cZ0yyMBNG#gGF$IU170>xtu~Lg@4SqRysQicYKAHXN^H!b zEX>0A3^$My=QT7lG%zwZGBCCpY>{kkz`|FPQg_nu#v55R$dDtU5`Lp zrq8n_7bJ^Uv$vakz4l^J&63)h{crq#{jup^^Tazb#>X{i$JsWWH~H&TS}o2TcfR(b zW6ErwYx=Uap;;>zS%+j6Ze9QDY49nvm&a>VCQa!sbWM4;zsU9@OXpUt{f0S3nLc{g x4_`Dn{gx-kReqWJrt Date: Thu, 3 Nov 2016 18:18:14 -0700 Subject: [PATCH 2/2] MYRIAD-244 Addressing review comments --- .../apache/myriad/webapp/HttpConnectorProvider.java | 3 +++ .../src/main/resources/myriad-config-default.yml | 1 + myriad-scheduler/src/test/resources/ssl-server.xml | 12 ++++++++++++ 3 files changed, 16 insertions(+) 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 f4149685..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 @@ -101,6 +101,9 @@ static String getPassword(Configuration conf, String alias) { } 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/resources/ssl-server.xml b/myriad-scheduler/src/test/resources/ssl-server.xml index 950809d7..fa3f46ae 100644 --- a/myriad-scheduler/src/test/resources/ssl-server.xml +++ b/myriad-scheduler/src/test/resources/ssl-server.xml @@ -1,6 +1,18 @@ +