From 23475caca97558f33afe2b4af35fabe4d989a2c6 Mon Sep 17 00:00:00 2001 From: Howard Gao Date: Fri, 24 Jun 2016 14:04:57 +0800 Subject: [PATCH] ARTEMIS-594 support HTTPS access to hawtio --- .../activemq/artemis/cli/commands/Create.java | 4 + .../artemis/cli/commands/etc/keystore.jks | Bin 0 -> 2236 bytes .../activemq/artemis/dto/WebServerDTO.java | 15 +++ .../artemis/component/WebServerComponent.java | 33 ++++- .../cli/test/WebServerComponentTest.java | 119 ++++++++++++++++++ .../src/test/resources/server.keystore | Bin 0 -> 2236 bytes 6 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/keystore.jks create mode 100644 artemis-web/src/test/resources/server.keystore diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java index 84587c05f4..991bd69c70 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java @@ -72,6 +72,7 @@ public class Create extends InputAbstract { public static final String ETC_LOGGING_PROPERTIES = "etc/logging.properties"; public static final String ETC_BOOTSTRAP_XML = "etc/bootstrap.xml"; public static final String ETC_BROKER_XML = "etc/broker.xml"; + public static final String ETC_WEB_KEYSTORE = "etc/keystore.jks"; public static final String ETC_ARTEMIS_ROLES_PROPERTIES = "etc/artemis-roles.properties"; public static final String ETC_ARTEMIS_USERS_PROPERTIES = "etc/artemis-users.properties"; @@ -624,6 +625,9 @@ public class Create extends InputAbstract { filters.put("${bootstrap-web-settings}", applyFilters(readTextFile(ETC_BOOTSTRAP_WEB_SETTINGS_TXT), filters)); } + //keystore + write(ETC_WEB_KEYSTORE); + if (noAmqpAcceptor) { filters.put("${amqp-acceptor}", ""); } diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/keystore.jks b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..f5a67608fa13052b11b230cc95554de6125243c3 GIT binary patch literal 2236 zcmchY=|9v98^-51n_e8wa)x9XWY3y)>}xU%*#<*r5F=?USwm${)@02V zgVLg;j2O$Y=OE#6p6BVsbN+zm#r@)YeXbYx=eqCDeIKnJt%5)x@JRvxE?5BG4~st` zD_GCNw;&J7MXO+6o%i8BXPa|*s8f?eU4EYV`-SK0#^Gl+SS5a--_b}* zZuc(Qew+u@8lgwLYJZ|A0Y=j{JPsEVs-=FuH7(ZutAw;xg1=m6Y^L#|YFU`d?vjzA zEzE7HH<=^eYx8%$GAYHBq|0_g zhIbXK)I7c}u#w>Xu~gBYK^~psv>L1%wh?>SuS22aIcmO)Pd(E5$$TJV_PJ2Jn*OI> zZbbR=wUmR$*OpPEU&9kTDW$mP>M^4~sNhYuXV^>RKN0wF1Eb)~L zy4t4Zve0j~2TUmrf-JMVc<0cJtLXyH0n9%_AaxEKr_33JU%FwNHQZ!vAUJxL^R&c*wx@Z_()&<#z3E7 z)$G#HHa+F^kx3VKi^(#eY>|r{sC~kRVPMM3jQFPC##rED53t}WQfgg73wp~j4UB08 z?{MDs)AxnMs5sjCaa!`sIG?z$HNv(#i0NyNc}@;jWpzN&1}r=5bom4iB4KXZKd4|2 z)!h(k;*;51+p=BzdH5XpZl+%NIdYUkW1R%v8$n~DcCc;FZ`{65Q%s1JBxg;7p%1!s zqLqskwScA{%8B`-woIx{r~C36Bcp_$s_D{KAw6S6$ob4K*SA$exF3EjR#bGa4XKDL zh5r2L?{+Uk@QQwm^B{DBm(QbPDEHHw#T_9r4kc%5evON*KXb67=`PwsT7Gp&?@AVC zfq#`T=C3rsdMoamNB!7{;moVf$>?{7>E&$nxg?PTxoXWAn|*OZ-I>f_Jk5fwyQ%yt zc$7FI@#=dHwfTd9>A?(!za;T^lyz#Ks1R$A?c0-P66`aS{`?8Bp9yMh$DpJFtqbjv zBzK;{H)`5Nx7MVZ+ZnMQ^BlpBQxO>7^Mo#aH`SSsqq;`*Oe5c*e1^g#zbBE3?E1{6 z`Dq=iy3L*%-p$GkFI;`fBBXz8N|L7;nG&B58A&Y48r15h_@T0xpT9v@fqvRK`C{It~x+z%?{ak7djlBxsw7w}Cz>2c3H6xJ9xa zd}LqzX@p%Nh;k=ChN#K{TbPvH<+b5onQM3eI)@B=PpPlCF@X2LXiJF*adOxURM|bw z&gY}L+6SsXRo*0YI#7eyi9Oj1Mx;w}OJPQgGZAB9sWCOE_z3P*yiEq?(c60D2<1q?{A zF4HH8S;)}odFls>Y6HQ zjrgwF8^&iLADSba%*>z{i)dk_G3kSQx1hSnv11Z#KsHb z)5@tVcAZLE4W#|k^S&1t7Mv`0dL_l{TvP?O&>q6w*XfCDFd2ohk)m|Aoso-HQP+@0 zZ-q31CK{NH+;_(_)9?xMqjgStUvP$$voGyfE1jF}xh8jmApi&{6+sE11W(2&$O`3w za!GTlH=+lLP8bq88fnkL(WMDC`^Pq&gb08xnY+0h3f;;TjC&mSyDiI0q+5ky6k4%% zaH7n(PA2@$tp{hLFB`9%(e)t@PYtyac7M;B8R>S8tvp(^l!{=H^q-9xPij%7&>yr6 zq0~(IBU3APlDO{obEi5&ZMywri2A-5Q2C(?W@^;{WkbMvY;)(L@av~d&Td=HB# zjD54S(fvA9&s4&lhvgB+7AbyHzcc#?c-@>8B|Io=??;*|CQX_4sbh$SlcF2Qx8yxR wmnD0kCr^3FdyP+Rn!oYeVZLsB&wGlW`-hIuH%^$Kc;wq>Es-cUV=MH(0Jc=fB>(^b literal 0 HcmV?d00001 diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java index 036e7ecb0a..4553e0aa6e 100644 --- a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java +++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java @@ -33,6 +33,21 @@ public class WebServerDTO extends ComponentDTO { @XmlAttribute(required = true) public String path; + @XmlAttribute + public Boolean clientAuth; + + @XmlAttribute + public String keyStorePath; + + @XmlAttribute + public String keyStorePassword; + + @XmlAttribute + public String trustStorePath; + + @XmlAttribute + public String trustStorePassword; + @XmlElementRef public List apps; diff --git a/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java index 396c2d4748..b857d5bb30 100644 --- a/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java +++ b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java @@ -22,11 +22,16 @@ import org.apache.activemq.artemis.dto.AppDTO; import org.apache.activemq.artemis.dto.ComponentDTO; import org.apache.activemq.artemis.dto.WebServerDTO; 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.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.webapp.WebAppContext; import java.io.IOException; @@ -47,7 +52,33 @@ public class WebServerComponent implements ExternalComponent { webServerConfig = (WebServerDTO) config; uri = new URI(webServerConfig.bind); server = new Server(); - ServerConnector connector = new ServerConnector(server); + String scheme = uri.getScheme(); + ServerConnector connector = null; + + if ("https".equals(scheme)) { + SslContextFactory sslFactory = new SslContextFactory(); + sslFactory.setKeyStorePath(webServerConfig.keyStorePath == null ? artemisInstance + "/etc/keystore.jks" : webServerConfig.keyStorePath); + sslFactory.setKeyStorePassword(webServerConfig.keyStorePassword == null ? "password" : webServerConfig.keyStorePassword); + if (webServerConfig.clientAuth != null) { + sslFactory.setNeedClientAuth(webServerConfig.clientAuth); + if (webServerConfig.clientAuth) { + sslFactory.setTrustStorePath(webServerConfig.trustStorePath); + sslFactory.setTrustStorePassword(webServerConfig.trustStorePassword); + } + } + + SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslFactory, "HTTP/1.1"); + + HttpConfiguration https = new HttpConfiguration(); + https.addCustomizer(new SecureRequestCustomizer()); + HttpConnectionFactory httpFactory = new HttpConnectionFactory(https); + + connector = new ServerConnector(server, sslConnectionFactory, httpFactory); + + } + else { + connector = new ServerConnector(server); + } connector.setPort(uri.getPort()); connector.setHost(uri.getHost()); diff --git a/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java b/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java index 723e4fff78..587c76f39a 100644 --- a/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java +++ b/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java @@ -37,16 +37,22 @@ import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.ssl.SslHandler; import io.netty.util.CharsetUtil; import org.apache.activemq.artemis.component.WebServerComponent; +import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport; import org.apache.activemq.artemis.dto.WebServerDTO; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + public class WebServerComponentTest extends Assert { static final String URL = System.getProperty("url", "http://localhost:8161/WebServerComponentTest.txt"); + static final String SECURE_URL = System.getProperty("url", "https://localhost:8448/WebServerComponentTest.txt"); private Bootstrap bootstrap; private EventLoopGroup group; @@ -94,6 +100,119 @@ public class WebServerComponentTest extends Assert { Assert.assertFalse(webServerComponent.isStarted()); } + @Test + public void simpleSecureServer() throws Exception { + WebServerDTO webServerDTO = new WebServerDTO(); + webServerDTO.bind = "https://localhost:8448"; + webServerDTO.path = "webapps"; + webServerDTO.keyStorePath = "./src/test/resources/server.keystore"; + webServerDTO.keyStorePassword = "password"; + + WebServerComponent webServerComponent = new WebServerComponent(); + Assert.assertFalse(webServerComponent.isStarted()); + webServerComponent.configure(webServerDTO, "./src/test/resources/", "./src/test/resources/"); + webServerComponent.start(); + // Make the connection attempt. + String keyStoreProvider = "JKS"; + + SSLContext context = SSLSupport.createContext(keyStoreProvider, + webServerDTO.keyStorePath, + webServerDTO.keyStorePassword, + keyStoreProvider, + webServerDTO.keyStorePath, + webServerDTO.keyStorePassword); + + SSLEngine engine = context.createSSLEngine(); + engine.setUseClientMode(true); + engine.setWantClientAuth(true); + final SslHandler sslHandler = new SslHandler(engine); + + CountDownLatch latch = new CountDownLatch(1); + final ClientHandler clientHandler = new ClientHandler(latch); + bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(sslHandler); + ch.pipeline().addLast(new HttpClientCodec()); + ch.pipeline().addLast(clientHandler); + } + }); + Channel ch = bootstrap.connect("localhost", 8448).sync().channel(); + + URI uri = new URI(SECURE_URL); + // Prepare the HTTP request. + HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath()); + request.headers().set(HttpHeaders.Names.HOST, "localhost"); + + // Send the HTTP request. + ch.writeAndFlush(request); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(clientHandler.body, "12345"); + // Wait for the server to close the connection. + ch.close(); + Assert.assertTrue(webServerComponent.isStarted()); + webServerComponent.stop(); + Assert.assertFalse(webServerComponent.isStarted()); + } + + @Test + public void simpleSecureServerWithClientAuth() throws Exception { + WebServerDTO webServerDTO = new WebServerDTO(); + webServerDTO.bind = "https://localhost:8448"; + webServerDTO.path = "webapps"; + webServerDTO.keyStorePath = "./src/test/resources/server.keystore"; + webServerDTO.keyStorePassword = "password"; + webServerDTO.clientAuth = true; + webServerDTO.trustStorePath = "./src/test/resources/server.keystore"; + webServerDTO.trustStorePassword = "password"; + + WebServerComponent webServerComponent = new WebServerComponent(); + Assert.assertFalse(webServerComponent.isStarted()); + webServerComponent.configure(webServerDTO, "./src/test/resources/", "./src/test/resources/"); + webServerComponent.start(); + // Make the connection attempt. + String keyStoreProvider = "JKS"; + + SSLContext context = SSLSupport.createContext(keyStoreProvider, + webServerDTO.keyStorePath, + webServerDTO.keyStorePassword, + keyStoreProvider, + webServerDTO.trustStorePath, + webServerDTO.trustStorePassword); + + SSLEngine engine = context.createSSLEngine(); + engine.setUseClientMode(true); + engine.setWantClientAuth(true); + final SslHandler sslHandler = new SslHandler(engine); + + CountDownLatch latch = new CountDownLatch(1); + final ClientHandler clientHandler = new ClientHandler(latch); + bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(sslHandler); + ch.pipeline().addLast(new HttpClientCodec()); + ch.pipeline().addLast(clientHandler); + } + }); + Channel ch = bootstrap.connect("localhost", 8448).sync().channel(); + + URI uri = new URI(SECURE_URL); + // Prepare the HTTP request. + HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath()); + request.headers().set(HttpHeaders.Names.HOST, "localhost"); + + // Send the HTTP request. + ch.writeAndFlush(request); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(clientHandler.body, "12345"); + // Wait for the server to close the connection. + ch.close(); + Assert.assertTrue(webServerComponent.isStarted()); + webServerComponent.stop(); + Assert.assertFalse(webServerComponent.isStarted()); + } + class ClientHandler extends SimpleChannelInboundHandler { private CountDownLatch latch; diff --git a/artemis-web/src/test/resources/server.keystore b/artemis-web/src/test/resources/server.keystore new file mode 100644 index 0000000000000000000000000000000000000000..f5a67608fa13052b11b230cc95554de6125243c3 GIT binary patch literal 2236 zcmchY=|9v98^-51n_e8wa)x9XWY3y)>}xU%*#<*r5F=?USwm${)@02V zgVLg;j2O$Y=OE#6p6BVsbN+zm#r@)YeXbYx=eqCDeIKnJt%5)x@JRvxE?5BG4~st` zD_GCNw;&J7MXO+6o%i8BXPa|*s8f?eU4EYV`-SK0#^Gl+SS5a--_b}* zZuc(Qew+u@8lgwLYJZ|A0Y=j{JPsEVs-=FuH7(ZutAw;xg1=m6Y^L#|YFU`d?vjzA zEzE7HH<=^eYx8%$GAYHBq|0_g zhIbXK)I7c}u#w>Xu~gBYK^~psv>L1%wh?>SuS22aIcmO)Pd(E5$$TJV_PJ2Jn*OI> zZbbR=wUmR$*OpPEU&9kTDW$mP>M^4~sNhYuXV^>RKN0wF1Eb)~L zy4t4Zve0j~2TUmrf-JMVc<0cJtLXyH0n9%_AaxEKr_33JU%FwNHQZ!vAUJxL^R&c*wx@Z_()&<#z3E7 z)$G#HHa+F^kx3VKi^(#eY>|r{sC~kRVPMM3jQFPC##rED53t}WQfgg73wp~j4UB08 z?{MDs)AxnMs5sjCaa!`sIG?z$HNv(#i0NyNc}@;jWpzN&1}r=5bom4iB4KXZKd4|2 z)!h(k;*;51+p=BzdH5XpZl+%NIdYUkW1R%v8$n~DcCc;FZ`{65Q%s1JBxg;7p%1!s zqLqskwScA{%8B`-woIx{r~C36Bcp_$s_D{KAw6S6$ob4K*SA$exF3EjR#bGa4XKDL zh5r2L?{+Uk@QQwm^B{DBm(QbPDEHHw#T_9r4kc%5evON*KXb67=`PwsT7Gp&?@AVC zfq#`T=C3rsdMoamNB!7{;moVf$>?{7>E&$nxg?PTxoXWAn|*OZ-I>f_Jk5fwyQ%yt zc$7FI@#=dHwfTd9>A?(!za;T^lyz#Ks1R$A?c0-P66`aS{`?8Bp9yMh$DpJFtqbjv zBzK;{H)`5Nx7MVZ+ZnMQ^BlpBQxO>7^Mo#aH`SSsqq;`*Oe5c*e1^g#zbBE3?E1{6 z`Dq=iy3L*%-p$GkFI;`fBBXz8N|L7;nG&B58A&Y48r15h_@T0xpT9v@fqvRK`C{It~x+z%?{ak7djlBxsw7w}Cz>2c3H6xJ9xa zd}LqzX@p%Nh;k=ChN#K{TbPvH<+b5onQM3eI)@B=PpPlCF@X2LXiJF*adOxURM|bw z&gY}L+6SsXRo*0YI#7eyi9Oj1Mx;w}OJPQgGZAB9sWCOE_z3P*yiEq?(c60D2<1q?{A zF4HH8S;)}odFls>Y6HQ zjrgwF8^&iLADSba%*>z{i)dk_G3kSQx1hSnv11Z#KsHb z)5@tVcAZLE4W#|k^S&1t7Mv`0dL_l{TvP?O&>q6w*XfCDFd2ohk)m|Aoso-HQP+@0 zZ-q31CK{NH+;_(_)9?xMqjgStUvP$$voGyfE1jF}xh8jmApi&{6+sE11W(2&$O`3w za!GTlH=+lLP8bq88fnkL(WMDC`^Pq&gb08xnY+0h3f;;TjC&mSyDiI0q+5ky6k4%% zaH7n(PA2@$tp{hLFB`9%(e)t@PYtyac7M;B8R>S8tvp(^l!{=H^q-9xPij%7&>yr6 zq0~(IBU3APlDO{obEi5&ZMywri2A-5Q2C(?W@^;{WkbMvY;)(L@av~d&Td=HB# zjD54S(fvA9&s4&lhvgB+7AbyHzcc#?c-@>8B|Io=??;*|CQX_4sbh$SlcF2Qx8yxR wmnD0kCr^3FdyP+Rn!oYeVZLsB&wGlW`-hIuH%^$Kc;wq>Es-cUV=MH(0Jc=fB>(^b literal 0 HcmV?d00001