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 0000000000..f5a67608fa Binary files /dev/null and b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/keystore.jks differ 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 0000000000..f5a67608fa Binary files /dev/null and b/artemis-web/src/test/resources/server.keystore differ