ARTEMIS-3574 multiple bindings for embedded webserver

* Add BindingDTO to allow configuring multiple addresses to listen on
* Start a new ServerConnector for each binding and deploy the corresponding web-applications
* Update documentation and tests
* Add tests to verify old and new configuration style produce equal results
This commit is contained in:
Marlon Müller 2021-10-27 15:21:04 +02:00 committed by clebertsuconic
parent 7d129b36e9
commit 182334359c
21 changed files with 642 additions and 283 deletions

View File

@ -1,6 +1,8 @@
<!-- The web server is only bound to localhost by default --> <!-- The web server is only bound to localhost by default -->
<web bind="${web.protocol}://${http.host}:${http.port}" path="web"${extra.web.attributes}> <web path="web"${extra.web.attributes}>
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="${web.protocol}://${http.host}:${http.port}">
<app url="artemis-plugin" war="artemis-plugin.war"/> <app url="activemq-branding" war="activemq-branding.war"/>
<app url="console" war="console.war"/> <app url="artemis-plugin" war="artemis-plugin.war"/>
<app url="console" war="console.war"/>
</binding>
</web> </web>

View File

@ -254,8 +254,9 @@ public class ArtemisTest extends CliTestBase {
assertTrue(bootstrapFile.exists()); assertTrue(bootstrapFile.exists());
Document config = parseXml(bootstrapFile); Document config = parseXml(bootstrapFile);
Element webElem = (Element) config.getElementsByTagName("web").item(0); Element webElem = (Element) config.getElementsByTagName("web").item(0);
Element bindingElem = (Element) webElem.getElementsByTagName("binding").item(0);
String bindAttr = webElem.getAttribute("bind"); String bindAttr = bindingElem.getAttribute("uri");
String bindStr = "http://" + Create.HTTP_HOST + ":" + Create.HTTP_PORT; String bindStr = "http://" + Create.HTTP_HOST + ":" + Create.HTTP_PORT;
assertEquals(bindAttr, bindStr); assertEquals(bindAttr, bindStr);
@ -273,8 +274,9 @@ public class ArtemisTest extends CliTestBase {
assertTrue(bootstrapFile.exists()); assertTrue(bootstrapFile.exists());
config = parseXml(bootstrapFile); config = parseXml(bootstrapFile);
webElem = (Element) config.getElementsByTagName("web").item(0); webElem = (Element) config.getElementsByTagName("web").item(0);
bindingElem = (Element) webElem.getElementsByTagName("binding").item(0);
bindAttr = webElem.getAttribute("bind"); bindAttr = bindingElem.getAttribute("uri");
bindStr = "https://localhost:" + Create.HTTP_PORT; bindStr = "https://localhost:" + Create.HTTP_PORT;
assertEquals(bindAttr, bindStr); assertEquals(bindAttr, bindStr);
@ -299,8 +301,9 @@ public class ArtemisTest extends CliTestBase {
config = parseXml(bootstrapFile); config = parseXml(bootstrapFile);
webElem = (Element) config.getElementsByTagName("web").item(0); webElem = (Element) config.getElementsByTagName("web").item(0);
bindingElem = (Element) webElem.getElementsByTagName("binding").item(0);
bindAttr = webElem.getAttribute("bind"); bindAttr = bindingElem.getAttribute("uri");
bindStr = "https://localhost:" + Create.HTTP_PORT; bindStr = "https://localhost:" + Create.HTTP_PORT;
assertEquals(bindAttr, bindStr); assertEquals(bindAttr, bindStr);

View File

@ -0,0 +1,135 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.dto;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "binding")
@XmlAccessorType(XmlAccessType.FIELD)
public class BindingDTO {
@XmlAttribute
public String uri;
@XmlElementRef
public List<AppDTO> apps;
@XmlAttribute
public Boolean clientAuth;
@XmlAttribute
public String passwordCodec;
@XmlAttribute
public String keyStorePath;
@XmlAttribute
public String trustStorePath;
@XmlAttribute
private String includedTLSProtocols;
@XmlAttribute
private String excludedTLSProtocols;
@XmlAttribute
private String includedCipherSuites;
@XmlAttribute
private String excludedCipherSuites;
@XmlAttribute
private String keyStorePassword;
@XmlAttribute
private String trustStorePassword;
public String getKeyStorePassword() throws Exception {
return getPassword(this.keyStorePassword);
}
private String getPassword(String password) throws Exception {
return PasswordMaskingUtil.resolveMask(password, this.passwordCodec);
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public String getTrustStorePassword() throws Exception {
return getPassword(this.trustStorePassword);
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
public String[] getIncludedTLSProtocols() {
return unmarshalArray(includedTLSProtocols);
}
public void setIncludedTLSProtocols(String... protocols) {
includedTLSProtocols = marshalArray(protocols);
}
public String[] getExcludedTLSProtocols() {
return unmarshalArray(excludedTLSProtocols);
}
public void setExcludedTLSProtocols(String... protocols) {
excludedTLSProtocols = marshalArray(protocols);
}
public String[] getIncludedCipherSuites() {
return unmarshalArray(includedCipherSuites);
}
public void setIncludedCipherSuites(String... cipherSuites) {
includedCipherSuites = marshalArray(cipherSuites);
}
public String[] getExcludedCipherSuites() {
return unmarshalArray(excludedCipherSuites);
}
public void setExcludedCipherSuites(String... cipherSuites) {
excludedCipherSuites = marshalArray(cipherSuites);
}
private String[] unmarshalArray(String text) {
if (text == null) {
return null;
}
return text.split(",");
}
private String marshalArray(String[] array) {
if (array == null || (array.length == 1 && array[0] == null)) {
return null;
}
return String.join(",", array);
}
}

View File

@ -16,61 +16,75 @@
*/ */
package org.apache.activemq.artemis.dto; package org.apache.activemq.artemis.dto;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.util.Collections;
import java.util.List; import java.util.List;
@XmlRootElement(name = "web") @XmlRootElement(name = "web")
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class WebServerDTO extends ComponentDTO { public class WebServerDTO extends ComponentDTO {
@Deprecated
@XmlAttribute @XmlAttribute
public String bind; public String bind;
@XmlAttribute(required = true) @XmlAttribute(required = true)
public String path; public String path;
@Deprecated
@XmlAttribute @XmlAttribute
public Boolean clientAuth; public Boolean clientAuth;
@Deprecated
@XmlAttribute @XmlAttribute
public String passwordCodec; public String passwordCodec;
@Deprecated
@XmlAttribute @XmlAttribute
public String keyStorePath; public String keyStorePath;
@Deprecated
@XmlAttribute @XmlAttribute
public String trustStorePath; public String trustStorePath;
@XmlAttribute @XmlAttribute
public String customizer; public String customizer;
@XmlElementRef
private List<BindingDTO> bindings;
@Deprecated
@XmlElementRef @XmlElementRef
public List<AppDTO> apps; public List<AppDTO> apps;
@XmlElementRef(required = false) @XmlElementRef(required = false)
public RequestLogDTO requestLog; public RequestLogDTO requestLog;
@Deprecated
@XmlAttribute @XmlAttribute
private String keyStorePassword; private String keyStorePassword;
@Deprecated
@XmlAttribute @XmlAttribute
private String trustStorePassword; private String trustStorePassword;
@Deprecated
@XmlAttribute @XmlAttribute
private String includedTLSProtocols; private String includedTLSProtocols;
@Deprecated
@XmlAttribute @XmlAttribute
private String excludedTLSProtocols; private String excludedTLSProtocols;
@Deprecated
@XmlAttribute @XmlAttribute
private String includedCipherSuites; private String includedCipherSuites;
@Deprecated
@XmlAttribute @XmlAttribute
private String excludedCipherSuites; private String excludedCipherSuites;
@ -78,71 +92,35 @@ public class WebServerDTO extends ComponentDTO {
componentClassName = "org.apache.activemq.artemis.component.WebServerComponent"; componentClassName = "org.apache.activemq.artemis.component.WebServerComponent";
} }
public String getKeyStorePassword() throws Exception { public List<BindingDTO> getBindings() {
return getPassword(this.keyStorePassword); if (bindings == null || bindings.isEmpty()) {
} return Collections.singletonList(convertToBindingDTO());
private String getPassword(String password) throws Exception {
return PasswordMaskingUtil.resolveMask(password, this.passwordCodec);
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public String getTrustStorePassword() throws Exception {
return getPassword(this.trustStorePassword);
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
private String[] unmarshalArray(String text) {
if (text == null) {
return null;
} }
return bindings;
return text.split(",");
} }
private String marshalArray(String[] array) { public void setBindings(List<BindingDTO> bindings) {
if (array == null) { this.bindings = bindings;
return null;
}
return String.join(",", array);
} }
public String[] getIncludedTLSProtocols() { private BindingDTO convertToBindingDTO() {
return unmarshalArray(includedTLSProtocols); BindingDTO bindingDTO = new BindingDTO();
bindingDTO.uri = bind;
bindingDTO.apps = apps;
bindingDTO.clientAuth = clientAuth;
bindingDTO.passwordCodec = passwordCodec;
bindingDTO.keyStorePath = keyStorePath;
bindingDTO.setKeyStorePassword(keyStorePassword);
bindingDTO.trustStorePath = trustStorePath;
bindingDTO.setTrustStorePassword(trustStorePassword);
bindingDTO.setIncludedTLSProtocols(includedTLSProtocols);
bindingDTO.setExcludedTLSProtocols(excludedTLSProtocols);
bindingDTO.setIncludedCipherSuites(includedCipherSuites);
bindingDTO.setExcludedCipherSuites(excludedCipherSuites);
return bindingDTO;
} }
public void setIncludedTLSProtocols(String... protocols) { public BindingDTO getDefaultBinding() {
includedTLSProtocols = marshalArray(protocols); return getBindings().get(0);
}
public String[] getExcludedTLSProtocols() {
return unmarshalArray(excludedTLSProtocols);
}
public void setExcludedTLSProtocols(String... protocols) {
excludedTLSProtocols = marshalArray(protocols);
}
public String[] getIncludedCipherSuites() {
return unmarshalArray(includedCipherSuites);
}
public void setIncludedCipherSuites(String... cipherSuites) {
includedCipherSuites = marshalArray(cipherSuites);
}
public String[] getExcludedCipherSuites() {
return unmarshalArray(excludedCipherSuites);
}
public void setExcludedCipherSuites(String... cipherSuites) {
excludedCipherSuites = marshalArray(cipherSuites);
} }
} }

View File

@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.dto.test;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.junit.Assert;
import org.junit.Test;
public class BindingDTOTest extends Assert {
@Test
public void testDefault() {
BindingDTO binding = new BindingDTO();
Assert.assertNull(binding.getIncludedTLSProtocols());
Assert.assertNull(binding.getExcludedTLSProtocols());
Assert.assertNull(binding.getIncludedCipherSuites());
Assert.assertNull(binding.getExcludedCipherSuites());
}
@Test
public void testValues() {
BindingDTO binding = new BindingDTO();
binding.setIncludedTLSProtocols("TLSv1.2");
Assert.assertArrayEquals(new String[] {"TLSv1.2"}, binding.getIncludedTLSProtocols());
binding.setExcludedTLSProtocols("TLSv1,TLSv1.1");
Assert.assertArrayEquals(new String[] {"TLSv1", "TLSv1.1"}, binding.getExcludedTLSProtocols());
binding.setIncludedCipherSuites( "^SSL_.*$");
Assert.assertArrayEquals(new String[] {"^SSL_.*$"}, binding.getIncludedCipherSuites());
binding.setExcludedCipherSuites( "^.*_(MD5|SHA|SHA1)$,^TLS_RSA_.*$,^.*_NULL_.*$,^.*_anon_.*$");
Assert.assertArrayEquals(new String[] {"^.*_(MD5|SHA|SHA1)$", "^TLS_RSA_.*$", "^.*_NULL_.*$", "^.*_anon_.*$"}, binding.getExcludedCipherSuites());
}
@Test
public void testEmptyValues() {
BindingDTO binding = new BindingDTO();
binding.setIncludedTLSProtocols("");
Assert.assertArrayEquals(new String[] {""}, binding.getIncludedTLSProtocols());
binding.setExcludedTLSProtocols("");
Assert.assertArrayEquals(new String[] {""}, binding.getExcludedTLSProtocols());
binding.setIncludedCipherSuites("");
Assert.assertArrayEquals(new String[] {""}, binding.getIncludedCipherSuites());
binding.setExcludedCipherSuites("");
Assert.assertArrayEquals(new String[] {""}, binding.getExcludedCipherSuites());
}
@Test
public void testNullValues() {
BindingDTO binding = new BindingDTO();
binding.setIncludedTLSProtocols(null);
Assert.assertNull(binding.getIncludedTLSProtocols());
binding.setExcludedTLSProtocols(null);
Assert.assertNull(binding.getExcludedTLSProtocols());
binding.setIncludedCipherSuites(null);
Assert.assertNull(binding.getIncludedCipherSuites());
binding.setExcludedCipherSuites(null);
Assert.assertNull(binding.getExcludedCipherSuites());
}
}

View File

@ -16,70 +16,95 @@
*/ */
package org.apache.activemq.artemis.dto.test; package org.apache.activemq.artemis.dto.test;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.apache.activemq.artemis.dto.WebServerDTO; import org.apache.activemq.artemis.dto.WebServerDTO;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
public class WebServerDTOTest extends Assert { import java.util.Collections;
import java.util.List;
public class WebServerDTOTest {
@Test @Test
public void testDefault() { public void testDefault() throws Exception {
WebServerDTO webServer = new WebServerDTO(); WebServerDTO webServer = new WebServerDTO();
Assert.assertNull(webServer.getIncludedTLSProtocols()); Assert.assertNotNull(webServer.getBindings());
Assert.assertNull(webServer.getExcludedTLSProtocols()); Assert.assertEquals(1, webServer.getBindings().size());
Assert.assertNull(webServer.getIncludedCipherSuites()); Assert.assertNotNull(webServer.getDefaultBinding());
Assert.assertNull(webServer.getExcludedCipherSuites());
BindingDTO defaultBinding = webServer.getDefaultBinding();
Assert.assertNull(defaultBinding.uri);
Assert.assertNull(defaultBinding.apps);
Assert.assertNull(defaultBinding.clientAuth);
Assert.assertNull(defaultBinding.passwordCodec);
Assert.assertNull(defaultBinding.keyStorePath);
Assert.assertNull(defaultBinding.trustStorePath);
Assert.assertNull(defaultBinding.getIncludedTLSProtocols());
Assert.assertNull(defaultBinding.getExcludedTLSProtocols());
Assert.assertNull(defaultBinding.getIncludedCipherSuites());
Assert.assertNull(defaultBinding.getExcludedCipherSuites());
Assert.assertNull(defaultBinding.getKeyStorePassword());
Assert.assertNull(defaultBinding.getTrustStorePassword());
} }
@Test @Test
public void testValues() { public void testWebServerConfig() {
WebServerDTO webServer = new WebServerDTO(); WebServerDTO webServer = new WebServerDTO();
webServer.bind = "http://localhost:0";
webServer.setIncludedTLSProtocols("TLSv1.2"); Assert.assertNotNull(webServer.getBindings());
Assert.assertArrayEquals(new String[] {"TLSv1.2"}, webServer.getIncludedTLSProtocols()); Assert.assertEquals(1, webServer.getBindings().size());
Assert.assertNotNull(webServer.getDefaultBinding());
webServer.setExcludedTLSProtocols("TLSv1,TLSv1.1"); Assert.assertEquals("http://localhost:0", webServer.getDefaultBinding().uri);
Assert.assertArrayEquals(new String[] {"TLSv1", "TLSv1.1"}, webServer.getExcludedTLSProtocols());
webServer.setIncludedCipherSuites( "^SSL_.*$");
Assert.assertArrayEquals(new String[] {"^SSL_.*$"}, webServer.getIncludedCipherSuites());
webServer.setExcludedCipherSuites( "^.*_(MD5|SHA|SHA1)$,^TLS_RSA_.*$,^.*_NULL_.*$,^.*_anon_.*$");
Assert.assertArrayEquals(new String[] {"^.*_(MD5|SHA|SHA1)$", "^TLS_RSA_.*$", "^.*_NULL_.*$", "^.*_anon_.*$"}, webServer.getExcludedCipherSuites());
} }
@Test @Test
public void testEmptyValues() { public void testWebServerWithBinding() {
BindingDTO binding = new BindingDTO();
binding.uri = "http://localhost:0";
WebServerDTO webServer = new WebServerDTO(); WebServerDTO webServer = new WebServerDTO();
webServer.setBindings(Collections.singletonList(binding));
webServer.setIncludedTLSProtocols(""); Assert.assertNotNull(webServer.getBindings());
Assert.assertArrayEquals(new String[] {""}, webServer.getIncludedTLSProtocols()); Assert.assertEquals(1, webServer.getBindings().size());
Assert.assertNotNull(webServer.getDefaultBinding());
webServer.setExcludedTLSProtocols(""); Assert.assertEquals("http://localhost:0", webServer.getDefaultBinding().uri);
Assert.assertArrayEquals(new String[] {""}, webServer.getExcludedTLSProtocols());
webServer.setIncludedCipherSuites("");
Assert.assertArrayEquals(new String[] {""}, webServer.getIncludedCipherSuites());
webServer.setExcludedCipherSuites("");
Assert.assertArrayEquals(new String[] {""}, webServer.getExcludedCipherSuites());
} }
@Test @Test
public void testNullValues() { public void testWebServerWithMultipleBindings() {
BindingDTO binding1 = new BindingDTO();
binding1.uri = "http://localhost:0";
BindingDTO binding2 = new BindingDTO();
binding2.uri = "http://localhost:1";
WebServerDTO webServer = new WebServerDTO(); WebServerDTO webServer = new WebServerDTO();
webServer.setBindings(List.of(binding1, binding2));
webServer.setIncludedTLSProtocols(null); Assert.assertNotNull(webServer.getBindings());
Assert.assertNull(webServer.getIncludedTLSProtocols()); Assert.assertEquals(2, webServer.getBindings().size());
Assert.assertNotNull(webServer.getDefaultBinding());
webServer.setExcludedTLSProtocols(null); Assert.assertEquals("http://localhost:0", webServer.getDefaultBinding().uri);
Assert.assertNull(webServer.getExcludedTLSProtocols()); Assert.assertEquals("http://localhost:0", webServer.getBindings().get(0).uri);
Assert.assertEquals("http://localhost:1", webServer.getBindings().get(1).uri);
webServer.setIncludedCipherSuites(null);
Assert.assertNull(webServer.getIncludedCipherSuites());
webServer.setExcludedCipherSuites(null);
Assert.assertNull(webServer.getExcludedCipherSuites());
} }
@Test
public void testWebServerConfigAndBinding() {
BindingDTO binding = new BindingDTO();
binding.uri = "http://localhost:0";
WebServerDTO webServer = new WebServerDTO();
webServer.bind = "http://localhost:1";
webServer.setBindings(Collections.singletonList(binding));
Assert.assertNotNull(webServer.getBindings());
Assert.assertEquals(1, webServer.getBindings().size());
Assert.assertNotNull(webServer.getDefaultBinding());
Assert.assertEquals("http://localhost:0", webServer.getDefaultBinding().uri);
}
} }

View File

@ -16,25 +16,14 @@
*/ */
package org.apache.activemq.artemis.component; package org.apache.activemq.artemis.component;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import org.apache.activemq.artemis.ActiveMQWebLogger; import org.apache.activemq.artemis.ActiveMQWebLogger;
import org.apache.activemq.artemis.components.ExternalComponent; import org.apache.activemq.artemis.components.ExternalComponent;
import org.apache.activemq.artemis.dto.AppDTO; import org.apache.activemq.artemis.dto.AppDTO;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.apache.activemq.artemis.dto.ComponentDTO; import org.apache.activemq.artemis.dto.ComponentDTO;
import org.apache.activemq.artemis.dto.WebServerDTO; import org.apache.activemq.artemis.dto.WebServerDTO;
import org.eclipse.jetty.security.DefaultAuthenticatorFactory; import org.eclipse.jetty.security.DefaultAuthenticatorFactory;
import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NCSARequestLog; import org.eclipse.jetty.server.NCSARequestLog;
@ -53,6 +42,16 @@ import org.eclipse.jetty.webapp.WebAppContext;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import java.io.File;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
public class WebServerComponent implements ExternalComponent { public class WebServerComponent implements ExternalComponent {
@ -61,19 +60,17 @@ public class WebServerComponent implements ExternalComponent {
private Server server; private Server server;
private HandlerList handlers; private HandlerList handlers;
private WebServerDTO webServerConfig; private WebServerDTO webServerConfig;
private URI uri; private final List<String> consoleUrls = new ArrayList<>();
private String consoleUrl; private final List<String> jolokiaUrls = new ArrayList<>();
private List<WebAppContext> webContexts; private List<WebAppContext> webContexts;
private ServerConnector connector; private ServerConnector[] connectors;
private Path artemisHomePath; private Path artemisHomePath;
private Path temporaryWarDir; private Path temporaryWarDir;
@Override @Override
public void configure(ComponentDTO config, String artemisInstance, String artemisHome) throws Exception { public void configure(ComponentDTO config, String artemisInstance, String artemisHome) throws Exception {
webServerConfig = (WebServerDTO) config; webServerConfig = (WebServerDTO) config;
uri = new URI(webServerConfig.bind);
server = new Server(); server = new Server();
String scheme = uri.getScheme();
HttpConfiguration httpConfiguration = new HttpConfiguration(); HttpConfiguration httpConfiguration = new HttpConfiguration();
@ -85,49 +82,63 @@ public class WebServerComponent implements ExternalComponent {
} }
} }
if ("https".equals(scheme)) { List<BindingDTO> bindings = webServerConfig.getBindings();
SslContextFactory.Server sslFactory = new SslContextFactory.Server(); connectors = new ServerConnector[bindings.size()];
sslFactory.setKeyStorePath(webServerConfig.keyStorePath == null ? artemisInstance + "/etc/keystore.jks" : webServerConfig.keyStorePath); String[] virtualHosts = new String[bindings.size()];
sslFactory.setKeyStorePassword(webServerConfig.getKeyStorePassword() == null ? "password" : webServerConfig.getKeyStorePassword());
String[] ips = sslFactory.getIncludeProtocols();
if (webServerConfig.getIncludedTLSProtocols() != null) { for (int i = 0; i < bindings.size(); i++) {
sslFactory.setIncludeProtocols(webServerConfig.getIncludedTLSProtocols()); BindingDTO binding = bindings.get(i);
} URI uri = new URI(binding.uri);
if (webServerConfig.getExcludedTLSProtocols() != null) { String scheme = uri.getScheme();
sslFactory.setExcludeProtocols(webServerConfig.getExcludedTLSProtocols()); ServerConnector connector;
}
if (webServerConfig.getIncludedCipherSuites() != null) { if ("https".equals(scheme)) {
sslFactory.setIncludeCipherSuites(webServerConfig.getIncludedCipherSuites()); SslContextFactory.Server sslFactory = new SslContextFactory.Server();
} sslFactory.setKeyStorePath(binding.keyStorePath == null ? artemisInstance + "/etc/keystore.jks" : binding.keyStorePath);
if (webServerConfig.getExcludedCipherSuites() != null) { sslFactory.setKeyStorePassword(binding.getKeyStorePassword() == null ? "password" : binding.getKeyStorePassword());
sslFactory.setExcludeCipherSuites(webServerConfig.getExcludedCipherSuites());
} if (binding.getIncludedTLSProtocols() != null) {
if (webServerConfig.clientAuth != null) { sslFactory.setIncludeProtocols(binding.getIncludedTLSProtocols());
sslFactory.setNeedClientAuth(webServerConfig.clientAuth);
if (webServerConfig.clientAuth) {
sslFactory.setTrustStorePath(webServerConfig.trustStorePath);
sslFactory.setTrustStorePassword(webServerConfig.getTrustStorePassword());
} }
if (binding.getExcludedTLSProtocols() != null) {
sslFactory.setExcludeProtocols(binding.getExcludedTLSProtocols());
}
if (binding.getIncludedCipherSuites() != null) {
sslFactory.setIncludeCipherSuites(binding.getIncludedCipherSuites());
}
if (binding.getExcludedCipherSuites() != null) {
sslFactory.setExcludeCipherSuites(binding.getExcludedCipherSuites());
}
if (binding.clientAuth != null) {
sslFactory.setNeedClientAuth(binding.clientAuth);
if (binding.clientAuth) {
sslFactory.setTrustStorePath(binding.trustStorePath);
sslFactory.setTrustStorePassword(binding.getTrustStorePassword());
}
}
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslFactory, "HTTP/1.1");
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
httpConfiguration.setSendServerVersion(false);
HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfiguration);
connector = new ServerConnector(server, sslConnectionFactory, httpFactory);
} else {
httpConfiguration.setSendServerVersion(false);
ConnectionFactory connectionFactory = new HttpConnectionFactory(httpConfiguration);
connector = new ServerConnector(server, connectionFactory);
} }
connector.setPort(uri.getPort());
connector.setHost(uri.getHost());
connector.setName("Connector-" + i);
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslFactory, "HTTP/1.1"); connectors[i] = connector;
virtualHosts[i] = "@Connector-" + i;
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
httpConfiguration.setSendServerVersion(false);
HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfiguration);
connector = new ServerConnector(server, sslConnectionFactory, httpFactory);
} else {
httpConfiguration.setSendServerVersion(false);
ConnectionFactory connectionFactory = new HttpConnectionFactory(httpConfiguration);
connector = new ServerConnector(server, connectionFactory);
} }
connector.setPort(uri.getPort());
connector.setHost(uri.getHost());
server.setConnectors(new Connector[]{connector}); server.setConnectors(connectors);
handlers = new HandlerList(); handlers = new HandlerList();
@ -140,18 +151,22 @@ public class WebServerComponent implements ExternalComponent {
Files.createDirectories(temporaryWarDir); Files.createDirectories(temporaryWarDir);
} }
if (webServerConfig.apps != null && webServerConfig.apps.size() > 0) { for (int i = 0; i < bindings.size(); i++) {
webContexts = new ArrayList<>(); BindingDTO binding = bindings.get(i);
for (AppDTO app : webServerConfig.apps) { if (binding.apps != null && binding.apps.size() > 0) {
Path dirToUse = homeWarDir; webContexts = new ArrayList<>();
if (new File(instanceWarDir.toFile().toString() + File.separator + app.war).exists()) { for (AppDTO app : binding.apps) {
dirToUse = instanceWarDir; Path dirToUse = homeWarDir;
} if (new File(instanceWarDir.toFile().toString() + File.separator + app.war).exists()) {
WebAppContext webContext = deployWar(app.url, app.war, dirToUse); dirToUse = instanceWarDir;
webContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false"); }
webContexts.add(webContext); WebAppContext webContext = deployWar(app.url, app.war, dirToUse, virtualHosts[i]);
if (app.war.startsWith("console")) { webContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
consoleUrl = webServerConfig.bind + "/" + app.url; webContexts.add(webContext);
if (app.war.startsWith("console")) {
consoleUrls.add(binding.uri + "/" + app.url);
jolokiaUrls.add(binding.uri + "/" + app.url + "/jolokia");
}
} }
} }
} }
@ -165,6 +180,7 @@ public class WebServerComponent implements ExternalComponent {
homeContext.setContextPath("/"); homeContext.setContextPath("/");
homeContext.setResourceBase(homeWarDir.toString()); homeContext.setResourceBase(homeWarDir.toString());
homeContext.setHandler(homeResourceHandler); homeContext.setHandler(homeResourceHandler);
homeContext.setVirtualHosts(virtualHosts);
homeContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false"); homeContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
ResourceHandler instanceResourceHandler = new ResourceHandler(); ResourceHandler instanceResourceHandler = new ResourceHandler();
@ -176,6 +192,7 @@ public class WebServerComponent implements ExternalComponent {
instanceContext.setContextPath("/"); instanceContext.setContextPath("/");
instanceContext.setResourceBase(instanceWarDir.toString()); instanceContext.setResourceBase(instanceWarDir.toString());
instanceContext.setHandler(instanceResourceHandler); instanceContext.setHandler(instanceResourceHandler);
instanceContext.setVirtualHosts(virtualHosts);
homeContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false"); homeContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
DefaultHandler defaultHandler = new DefaultHandler(); DefaultHandler defaultHandler = new DefaultHandler();
@ -263,12 +280,15 @@ public class WebServerComponent implements ExternalComponent {
} }
cleanupTmp(); cleanupTmp();
server.start(); server.start();
ActiveMQWebLogger.LOGGER.webserverStarted(webServerConfig.bind);
if (consoleUrl != null) { String bindings = webServerConfig.getBindings()
ActiveMQWebLogger.LOGGER.jolokiaAvailable(consoleUrl + "/jolokia"); .stream()
ActiveMQWebLogger.LOGGER.consoleAvailable(consoleUrl); .map(binding -> binding.uri)
} .collect(Collectors.joining(", "));
ActiveMQWebLogger.LOGGER.webserverStarted(bindings);
ActiveMQWebLogger.LOGGER.jolokiaAvailable(String.join(", ", jolokiaUrls));
ActiveMQWebLogger.LOGGER.consoleAvailable(String.join(", ", consoleUrls));
} }
public void internalStop() throws Exception { public void internalStop() throws Exception {
@ -324,11 +344,19 @@ public class WebServerComponent implements ExternalComponent {
/** /**
* @return started server's port number; useful if it was specified as 0 (to use a random port) * @return started server's port number; useful if it was specified as 0 (to use a random port)
*/ */
@Deprecated
public int getPort() { public int getPort() {
return (connector != null) ? connector.getLocalPort() : -1; return getPort(0);
} }
private WebAppContext deployWar(String url, String warFile, Path warDirectory) throws IOException { public int getPort(int connectorIndex) {
if (connectorIndex < connectors.length) {
return connectors[connectorIndex].getLocalPort();
}
return -1;
}
private WebAppContext deployWar(String url, String warFile, Path warDirectory, String virtualHost) {
WebAppContext webapp = new WebAppContext(); WebAppContext webapp = new WebAppContext();
if (url.startsWith("/")) { if (url.startsWith("/")) {
webapp.setContextPath(url); webapp.setContextPath(url);
@ -347,6 +375,8 @@ public class WebServerComponent implements ExternalComponent {
// https://github.com/eclipse/jetty.project/commit/7e91d34177a880ecbe70009e8f200d02e3a0c5dd // https://github.com/eclipse/jetty.project/commit/7e91d34177a880ecbe70009e8f200d02e3a0c5dd
webapp.getSecurityHandler().setAuthenticatorFactory(new DefaultAuthenticatorFactory()); webapp.getSecurityHandler().setAuthenticatorFactory(new DefaultAuthenticatorFactory());
webapp.setVirtualHosts(new String[]{virtualHost});
handlers.addHandler(webapp); handlers.addHandler(webapp);
return webapp; return webapp;
} }

View File

@ -27,6 +27,7 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -62,6 +63,7 @@ import org.apache.activemq.artemis.component.WebServerComponent;
import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport; import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport;
import org.apache.activemq.artemis.core.server.ActiveMQComponent; import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.dto.AppDTO; import org.apache.activemq.artemis.dto.AppDTO;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.apache.activemq.artemis.dto.BrokerDTO; import org.apache.activemq.artemis.dto.BrokerDTO;
import org.apache.activemq.artemis.dto.WebServerDTO; import org.apache.activemq.artemis.dto.WebServerDTO;
import org.apache.activemq.artemis.utils.ThreadLeakCheckRule; import org.apache.activemq.artemis.utils.ThreadLeakCheckRule;
@ -114,8 +116,10 @@ public class WebServerComponentTest extends Assert {
} }
private void internalSimpleServer(boolean useCustomizer) throws Exception { private void internalSimpleServer(boolean useCustomizer) throws Exception {
BindingDTO bindingDTO = new BindingDTO();
bindingDTO.uri = "http://localhost:0";
WebServerDTO webServerDTO = new WebServerDTO(); WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.bind = "http://localhost:0"; webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "webapps"; webServerDTO.path = "webapps";
if (useCustomizer) { if (useCustomizer) {
webServerDTO.customizer = TestCustomizer.class.getName(); webServerDTO.customizer = TestCustomizer.class.getName();
@ -162,8 +166,10 @@ public class WebServerComponentTest extends Assert {
@Test @Test
public void testComponentStopBehavior() throws Exception { public void testComponentStopBehavior() throws Exception {
BindingDTO bindingDTO = new BindingDTO();
bindingDTO.uri = "http://localhost:0";
WebServerDTO webServerDTO = new WebServerDTO(); WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.bind = "http://localhost:0"; webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "webapps"; webServerDTO.path = "webapps";
WebServerComponent webServerComponent = new WebServerComponent(); WebServerComponent webServerComponent = new WebServerComponent();
Assert.assertFalse(webServerComponent.isStarted()); Assert.assertFalse(webServerComponent.isStarted());
@ -206,20 +212,22 @@ public class WebServerComponentTest extends Assert {
@Test @Test
public void simpleSecureServer() throws Exception { public void simpleSecureServer() throws Exception {
WebServerDTO webServerDTO = new WebServerDTO(); BindingDTO bindingDTO = new BindingDTO();
webServerDTO.bind = "https://localhost:0"; bindingDTO.uri = "https://localhost:0";
webServerDTO.path = "webapps"; bindingDTO.keyStorePath = "./src/test/resources/server.keystore";
webServerDTO.keyStorePath = "./src/test/resources/server.keystore"; bindingDTO.setKeyStorePassword("password");
webServerDTO.setKeyStorePassword("password");
if (System.getProperty("java.vendor").contains("IBM")) { if (System.getProperty("java.vendor").contains("IBM")) {
//By default on IBM Java 8 JVM, org.eclipse.jetty.util.ssl.SslContextFactory doesn't include TLSv1.2 //By default on IBM Java 8 JVM, org.eclipse.jetty.util.ssl.SslContextFactory doesn't include TLSv1.2
// while it excludes all TLSv1 and TLSv1.1 cipher suites. // while it excludes all TLSv1 and TLSv1.1 cipher suites.
webServerDTO.setIncludedTLSProtocols("TLSv1.2"); bindingDTO.setIncludedTLSProtocols("TLSv1.2");
// Remove excluded cipher suites matching the prefix `SSL` because the names of the IBM Java 8 JVM cipher suites // Remove excluded cipher suites matching the prefix `SSL` because the names of the IBM Java 8 JVM cipher suites
// have the prefix `SSL` while the `DEFAULT_EXCLUDED_CIPHER_SUITES` of org.eclipse.jetty.util.ssl.SslContextFactory // have the prefix `SSL` while the `DEFAULT_EXCLUDED_CIPHER_SUITES` of org.eclipse.jetty.util.ssl.SslContextFactory
// includes "^SSL_.*$". So all IBM JVM cipher suites are excluded by SslContextFactory using the `DEFAULT_EXCLUDED_CIPHER_SUITES`. // includes "^SSL_.*$". So all IBM JVM cipher suites are excluded by SslContextFactory using the `DEFAULT_EXCLUDED_CIPHER_SUITES`.
webServerDTO.setExcludedCipherSuites(Arrays.stream(new SslContextFactory.Server().getExcludeCipherSuites()).filter(s -> !Pattern.matches(s, "SSL_")).toArray(String[]::new)); bindingDTO.setExcludedCipherSuites(Arrays.stream(new SslContextFactory.Server().getExcludeCipherSuites()).filter(s -> !Pattern.matches(s, "SSL_")).toArray(String[]::new));
} }
WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "webapps";
WebServerComponent webServerComponent = new WebServerComponent(); WebServerComponent webServerComponent = new WebServerComponent();
Assert.assertFalse(webServerComponent.isStarted()); Assert.assertFalse(webServerComponent.isStarted());
@ -230,10 +238,10 @@ public class WebServerComponentTest extends Assert {
// Make the connection attempt. // Make the connection attempt.
SSLContext context = new SSLSupport() SSLContext context = new SSLSupport()
.setKeystorePath(webServerDTO.keyStorePath) .setKeystorePath(bindingDTO.keyStorePath)
.setKeystorePassword(webServerDTO.getKeyStorePassword()) .setKeystorePassword(bindingDTO.getKeyStorePassword())
.setTruststorePath(webServerDTO.keyStorePath) .setTruststorePath(bindingDTO.keyStorePath)
.setTruststorePassword(webServerDTO.getKeyStorePassword()) .setTruststorePassword(bindingDTO.getKeyStorePassword())
.createContext(); .createContext();
SSLEngine engine = context.createSSLEngine(); SSLEngine engine = context.createSSLEngine();
@ -279,23 +287,25 @@ public class WebServerComponentTest extends Assert {
@Test @Test
public void simpleSecureServerWithClientAuth() throws Exception { public void simpleSecureServerWithClientAuth() throws Exception {
WebServerDTO webServerDTO = new WebServerDTO(); BindingDTO bindingDTO = new BindingDTO();
webServerDTO.bind = "https://localhost:0"; bindingDTO.uri = "https://localhost:0";
webServerDTO.path = "webapps"; bindingDTO.keyStorePath = "./src/test/resources/server.keystore";
webServerDTO.keyStorePath = "./src/test/resources/server.keystore"; bindingDTO.setKeyStorePassword("password");
webServerDTO.setKeyStorePassword("password"); bindingDTO.clientAuth = true;
webServerDTO.clientAuth = true; bindingDTO.trustStorePath = "./src/test/resources/server.keystore";
webServerDTO.trustStorePath = "./src/test/resources/server.keystore"; bindingDTO.setTrustStorePassword("password");
webServerDTO.setTrustStorePassword("password");
if (System.getProperty("java.vendor").contains("IBM")) { if (System.getProperty("java.vendor").contains("IBM")) {
//By default on IBM Java 8 JVM, org.eclipse.jetty.util.ssl.SslContextFactory doesn't include TLSv1.2 //By default on IBM Java 8 JVM, org.eclipse.jetty.util.ssl.SslContextFactory doesn't include TLSv1.2
// while it excludes all TLSv1 and TLSv1.1 cipher suites. // while it excludes all TLSv1 and TLSv1.1 cipher suites.
webServerDTO.setIncludedTLSProtocols("TLSv1.2"); bindingDTO.setIncludedTLSProtocols("TLSv1.2");
// Remove excluded cipher suites matching the prefix `SSL` because the names of the IBM Java 8 JVM cipher suites // Remove excluded cipher suites matching the prefix `SSL` because the names of the IBM Java 8 JVM cipher suites
// have the prefix `SSL` while the `DEFAULT_EXCLUDED_CIPHER_SUITES` of org.eclipse.jetty.util.ssl.SslContextFactory // have the prefix `SSL` while the `DEFAULT_EXCLUDED_CIPHER_SUITES` of org.eclipse.jetty.util.ssl.SslContextFactory
// includes "^SSL_.*$". So all IBM JVM cipher suites are excluded by SslContextFactory using the `DEFAULT_EXCLUDED_CIPHER_SUITES`. // includes "^SSL_.*$". So all IBM JVM cipher suites are excluded by SslContextFactory using the `DEFAULT_EXCLUDED_CIPHER_SUITES`.
webServerDTO.setExcludedCipherSuites(Arrays.stream(new SslContextFactory.Server().getExcludeCipherSuites()).filter(s -> !Pattern.matches(s, "SSL_")).toArray(String[]::new)); bindingDTO.setExcludedCipherSuites(Arrays.stream(new SslContextFactory.Server().getExcludeCipherSuites()).filter(s -> !Pattern.matches(s, "SSL_")).toArray(String[]::new));
} }
WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "webapps";
WebServerComponent webServerComponent = new WebServerComponent(); WebServerComponent webServerComponent = new WebServerComponent();
Assert.assertFalse(webServerComponent.isStarted()); Assert.assertFalse(webServerComponent.isStarted());
@ -306,10 +316,10 @@ public class WebServerComponentTest extends Assert {
// Make the connection attempt. // Make the connection attempt.
SSLContext context = new SSLSupport() SSLContext context = new SSLSupport()
.setKeystorePath(webServerDTO.keyStorePath) .setKeystorePath(bindingDTO.keyStorePath)
.setKeystorePassword(webServerDTO.getKeyStorePassword()) .setKeystorePassword(bindingDTO.getKeyStorePassword())
.setTruststorePath(webServerDTO.trustStorePath) .setTruststorePath(bindingDTO.trustStorePath)
.setTruststorePassword(webServerDTO.getTrustStorePassword()) .setTruststorePassword(bindingDTO.getTrustStorePassword())
.createContext(); .createContext();
SSLEngine engine = context.createSSLEngine(); SSLEngine engine = context.createSSLEngine();
@ -358,7 +368,7 @@ public class WebServerComponentTest extends Assert {
XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler(); XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler();
BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI()); BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI());
assertNotNull(broker.web); assertNotNull(broker.web);
assertNull(broker.web.passwordCodec); assertNull(broker.web.getDefaultBinding().passwordCodec);
} }
@Test @Test
@ -370,8 +380,8 @@ public class WebServerComponentTest extends Assert {
XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler(); XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler();
BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI()); BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI());
assertNotNull(broker.web); assertNotNull(broker.web);
assertEquals(keyPassword, broker.web.getKeyStorePassword()); assertEquals(keyPassword, broker.web.getDefaultBinding().getKeyStorePassword());
assertEquals(trustPassword, broker.web.getTrustStorePassword()); assertEquals(trustPassword, broker.web.getDefaultBinding().getTrustStorePassword());
} }
@Test @Test
@ -383,24 +393,44 @@ public class WebServerComponentTest extends Assert {
XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler(); XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler();
BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI()); BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI());
assertNotNull(broker.web); assertNotNull(broker.web);
assertNotNull("password codec not picked up!", broker.web.passwordCodec); assertNotNull("password codec not picked up!", broker.web.getDefaultBinding().passwordCodec);
assertEquals(keyPassword, broker.web.getKeyStorePassword()); assertEquals(keyPassword, broker.web.getDefaultBinding().getKeyStorePassword());
assertEquals(trustPassword, broker.web.getTrustStorePassword()); assertEquals(trustPassword, broker.web.getDefaultBinding().getTrustStorePassword());
}
@Test
public void testOldConfigurationStyle() throws Exception {
final String keyPassword = "keypass";
final String trustPassword = "trustpass";
File bootstrap = new File("./target/test-classes/bootstrap_web_old_config.xml");
File brokerHome = new File("./target");
XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler();
BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI());
assertNotNull(broker.web);
assertNotNull("password codec not picked up!", broker.web.getDefaultBinding().passwordCodec);
assertEquals("http://localhost:8161", broker.web.getDefaultBinding().uri);
assertEquals(keyPassword, broker.web.getDefaultBinding().getKeyStorePassword());
assertEquals(trustPassword, broker.web.getDefaultBinding().getTrustStorePassword());
} }
@Test @Test
public void testServerCleanupBeforeStart() throws Exception { public void testServerCleanupBeforeStart() throws Exception {
final String warName = "simple-app.war"; final String warName = "simple-app.war";
createTestWar(warName); createTestWar(warName);
WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.bind = "http://localhost:0";
webServerDTO.path = "";
webServerDTO.apps = new ArrayList<>();
AppDTO app = new AppDTO(); AppDTO app = new AppDTO();
app.url = "simple-app/"; app.url = "simple-app/";
app.war = warName; app.war = warName;
webServerDTO.apps.add(app); BindingDTO bindingDTO = new BindingDTO();
bindingDTO.uri = "http://localhost:0";
bindingDTO.apps = new ArrayList<>();
bindingDTO.apps.add(app);
WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "";
WebServerComponent webServerComponent = new WebServerComponent(); WebServerComponent webServerComponent = new WebServerComponent();
Assert.assertFalse(webServerComponent.isStarted()); Assert.assertFalse(webServerComponent.isStarted());
testedComponents.add(webServerComponent); testedComponents.add(webServerComponent);

View File

@ -25,8 +25,10 @@
<server configuration="${artemis.URI.instance}/etc/broker.xml"/> <server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<!-- The web server is only bound to localhost by default --> <!-- The web server is only bound to localhost by default -->
<web bind="https://localhost:8443" path="web" keyStorePassword="ENC(-5a2376c61c668aaf)" trustStorePassword="ENC(3d617352d12839eb71208edf41d66b34)"> <web path="web">
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="https://localhost:8443" keyStorePassword="ENC(-5a2376c61c668aaf)" trustStorePassword="ENC(3d617352d12839eb71208edf41d66b34)">
<app url="activemq-branding" war="activemq-branding.war"/>
</binding>
</web> </web>

View File

@ -25,8 +25,10 @@
<server configuration="${artemis.URI.instance}/etc/broker.xml"/> <server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<!-- The web server is only bound to localhost by default --> <!-- The web server is only bound to localhost by default -->
<web bind="http://localhost:8161" path="web"> <web path="web">
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="http://localhost:8161">
<app url="activemq-branding" war="activemq-branding.war"/>
</binding>
</web> </web>

View File

@ -25,8 +25,10 @@
<server configuration="${artemis.URI.instance}/etc/broker.xml"/> <server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<!-- The web server is only bound to localhost by default --> <!-- The web server is only bound to localhost by default -->
<web bind="https://localhost:8443" path="web" passwordCodec="org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec" keyStorePassword="ENC(youneverknow)" trustStorePassword="ENC(youcanguess)"> <web path="web">
<app url="console" war="console.war"/> <binding uri="https://localhost:8443" passwordCodec="org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec" keyStorePassword="ENC(youneverknow)" trustStorePassword="ENC(youcanguess)">
<app url="console" war="console.war"/>
</binding>
</web> </web>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<broker xmlns="http://activemq.org/schema">
<jaas-security domain="activemq"/>
<!-- artemis.URI.instance is parsed from artemis.instance by the CLI startup.
This is to avoid situations where you could have spaces or special characters on this URI -->
<server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<!-- The web server is only bound to localhost by default -->
<web path="web" bind="http://localhost:8161" passwordCodec="org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec" keyStorePassword="ENC(youneverknow)" trustStorePassword="ENC(youcanguess)">
<app url="activemq-branding" war="activemq-branding.war"/>
</web>
</broker>

View File

@ -224,10 +224,12 @@ You can also set the `passwordCodec` attribute if you want to use a password
codec other than the default one. For example codec other than the default one. For example
```xml ```xml
<web bind="https://localhost:8443" path="web" <web path="web">
keyStorePassword="ENC(-5a2376c61c668aaf)" <binding uri="https://localhost:8443"
trustStorePassword="ENC(3d617352d12839eb71208edf41d66b34)"> keyStorePassword="ENC(-5a2376c61c668aaf)"
<app url="activemq-branding" war="activemq-branding.war"/> trustStorePassword="ENC(3d617352d12839eb71208edf41d66b34)">
<app url="activemq-branding" war="activemq-branding.war"/>
</binding>
</web> </web>
``` ```

View File

@ -1363,8 +1363,10 @@ documentation via an embedded server. By default the web access is plain HTTP.
It is configured in `bootstrap.xml`: It is configured in `bootstrap.xml`:
```xml ```xml
<web bind="http://localhost:8161" path="web"> <web path="web">
<app url="console" war="console.war"/> <binding uri="http://localhost:8161">
<app url="console" war="console.war"/>
</binding>
</web> </web>
``` ```
@ -1372,11 +1374,12 @@ Alternatively you can edit the above configuration to enable secure access
using HTTPS protocol. e.g.: using HTTPS protocol. e.g.:
```xml ```xml
<web bind="https://localhost:8443" <web path="web">
path="web" <binding uri="https://localhost:8443"
keyStorePath="${artemis.instance}/etc/keystore.jks" keyStorePath="${artemis.instance}/etc/keystore.jks"
keyStorePassword="password"> keyStorePassword="password">
<app url="jolokia" war="jolokia-war-1.3.5.war"/> <app url="jolokia" war="jolokia-war-1.3.5.war"/>
</binding>
</web> </web>
``` ```
@ -1425,14 +1428,15 @@ keytool -storetype pkcs12 -keystore client-truststore.p12 -storepass securepass
- Enable secure access using HTTPS protocol with client authentication, - Enable secure access using HTTPS protocol with client authentication,
use the truststore file created in the previous step to set the trustStorePath and trustStorePassword: use the truststore file created in the previous step to set the trustStorePath and trustStorePassword:
```xml ```xml
<web bind="https://localhost:8443" <web path="web">
path="web" <binding uri="https://localhost:8443"
keyStorePath="${artemis.instance}/etc/server-keystore.p12" keyStorePath="${artemis.instance}/etc/server-keystore.p12"
keyStorePassword="password" keyStorePassword="password"
clientAuth="true" clientAuth="true"
trustStorePath="${artemis.instance}/etc/client-truststore.p12" trustStorePath="${artemis.instance}/etc/client-truststore.p12"
trustStorePassword="password"> trustStorePassword="password">
<app url="jolokia" war="jolokia-war-1.3.5.war"/> <app url="jolokia" war="jolokia-war-1.3.5.war"/>
</binding>
</web> </web>
``` ```

View File

@ -457,10 +457,12 @@ The `bootstrap.xml` file is very simple. Let's take a look at an example:
<server configuration="file:/path/to/broker.xml"/> <server configuration="file:/path/to/broker.xml"/>
<web bind="http://localhost:8161" path="web"> <web path="web">
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="http://localhost:8161">
<app url="artemis-plugin" war="artemis-plugin.war"/> <app url="activemq-branding" war="activemq-branding.war"/>
<app url="console" war="console.war"/> <app url="artemis-plugin" war="artemis-plugin.war"/>
<app url="console" war="console.war"/>
</binding>
</web> </web>
</broker> </broker>
``` ```

View File

@ -12,21 +12,28 @@ The embedded Jetty instance is configured in `etc/bootstrap.xml` via the `web`
element, e.g.: element, e.g.:
```xml ```xml
<web bind="http://localhost:8161" path="web"> <web path="web">
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="http://localhost:8161">
<app url="artemis-plugin" war="artemis-plugin.war"/> <app url="activemq-branding" war="activemq-branding.war"/>
<app url="console" war="console.war"/> <app url="artemis-plugin" war="artemis-plugin.war"/>
<app url="console" war="console.war"/>
</binding>
</web> </web>
``` ```
The `web` element has the following attributes: The `web` element has the following attributes:
- `bind` The protocol to use (i.e. `http` or `https`) as well as the host and
port on which to listen.
- `path` The name of the subdirectory in which to find the web application - `path` The name of the subdirectory in which to find the web application
archives (i.e. WAR files). This is a subdirectory of the broker's home or archives (i.e. WAR files). This is a subdirectory of the broker's home or
instance directory. instance directory.
- `customizer` The name of customizer class to load. - `customizer` The name of customizer class to load.
The `web` element should contain at least one `binding` element to configure how
clients can connect to the web-server. A `binding` element has the following
attributes:
- `uri` The protocol to use (i.e. `http` or `https`) as well as the host and
port on which to listen. This attribute is required.
- `clientAuth` Whether or not clients should present an SSL certificate when - `clientAuth` Whether or not clients should present an SSL certificate when
they connect. Only applicable when using `https`. they connect. Only applicable when using `https`.
- `passwordCodec` The custom coded to use for unmasking the `keystorePassword` - `passwordCodec` The custom coded to use for unmasking the `keystorePassword`
@ -51,8 +58,8 @@ The `web` element has the following attributes:
- `excludedCipherSuites` A comma seperated list of excluded cipher suites. - `excludedCipherSuites` A comma seperated list of excluded cipher suites.
Only applicable when using `https`. Only applicable when using `https`.
Each web application should be defined in an `app` element. The `app` element Each web application should be defined in an `app` element inside an `binding` element.
has the following attributes: The `app` element has the following attributes:
- `url` The context to use for the web application. - `url` The context to use for the web application.
- `war` The name of the web application archive on disk. - `war` The name of the web application archive on disk.
@ -82,10 +89,12 @@ instance. Default values are based on this implementation.
Here is an example configuration: Here is an example configuration:
```xml ```xml
<web bind="http://localhost:8161" path="web"> <web path="web">
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="http://localhost:8161">
<app url="artemis-plugin" war="artemis-plugin.war"/> <app url="activemq-branding" war="activemq-branding.war"/>
<app url="console" war="console.war"/> <app url="artemis-plugin" war="artemis-plugin.war"/>
<app url="console" war="console.war"/>
</binding>
<request-log filename="${artemis.instance}/log/http-access-yyyy_MM_dd.log" append="true" extended="true"/> <request-log filename="${artemis.instance}/log/http-access-yyyy_MM_dd.log" append="true" extended="true"/>
</web> </web>
``` ```
@ -99,9 +108,11 @@ customizer to handle `X-Forwarded` headers.
Set the `customizer` attribute via the `web` element to enable the [`ForwardedRequestCustomizer`](https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/server/ForwardedRequestCustomizer.html) customizer, ie: Set the `customizer` attribute via the `web` element to enable the [`ForwardedRequestCustomizer`](https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/server/ForwardedRequestCustomizer.html) customizer, ie:
```xml ```xml
<web bind="http://localhost:8161" path="web" customizer="org.eclipse.jetty.server.ForwardedRequestCustomizer"> <web path="web" customizer="org.eclipse.jetty.server.ForwardedRequestCustomizer">
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="http://localhost:8161">
<app url="artemis-plugin" war="artemis-plugin.war"/> <app url="activemq-branding" war="activemq-branding.war"/>
<app url="console" war="console.war"/> <app url="artemis-plugin" war="artemis-plugin.war"/>
<app url="console" war="console.war"/>
</binding>
</web> </web>
``` ```

View File

@ -22,9 +22,11 @@
<server configuration="${artemis.URI.instance}/etc/broker.xml"/> <server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<web bind="http://localhost:8080" path="web"> <web path="web">
<!-- ${war} is defined in the example's pom.xml --> <binding uri="http://localhost:8080">
<app url="camel" war="WARFILE"/> <!-- ${war} is defined in the example's pom.xml -->
<app url="camel" war="WARFILE"/>
</binding>
</web> </web>
</broker> </broker>

View File

@ -126,7 +126,8 @@ http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/came
6) Edit `$ARTEMIS_INSTANCE/etc/bootstrap.xml` so that the embedded web broker runs on a different port that the 5.x broker (e.g. 8162): 6) Edit `$ARTEMIS_INSTANCE/etc/bootstrap.xml` so that the embedded web broker runs on a different port that the 5.x broker (e.g. 8162):
```xml ```xml
<web bind="http://localhost:8162" path="web"> <web path="web">
<binding uri="http://localhost:8162"/>
``` ```
## Testing ## Testing

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
@ -34,6 +35,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ServiceComponent; import org.apache.activemq.artemis.core.server.ServiceComponent;
import org.apache.activemq.artemis.core.server.cluster.ha.ReplicatedPolicy; import org.apache.activemq.artemis.core.server.cluster.ha.ReplicatedPolicy;
import org.apache.activemq.artemis.dto.AppDTO; import org.apache.activemq.artemis.dto.AppDTO;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.apache.activemq.artemis.dto.WebServerDTO; import org.apache.activemq.artemis.dto.WebServerDTO;
import org.apache.activemq.artemis.tests.util.Wait; import org.apache.activemq.artemis.tests.util.Wait;
import org.junit.Assert; import org.junit.Assert;
@ -162,13 +164,15 @@ public class ReplicatedFailoverTest extends FailoverTest {
os.close(); os.close();
} }
}); });
WebServerDTO wdto = new WebServerDTO();
AppDTO appDTO = new AppDTO(); AppDTO appDTO = new AppDTO();
appDTO.war = "console.war"; appDTO.war = "console.war";
appDTO.url = "console"; appDTO.url = "console";
wdto.apps = new ArrayList<AppDTO>(); BindingDTO bindingDTO = new BindingDTO();
wdto.apps.add(appDTO); bindingDTO.uri = "http://localhost:0";
wdto.bind = "http://localhost:0"; bindingDTO.apps = new ArrayList<AppDTO>();
bindingDTO.apps.add(appDTO);
WebServerDTO wdto = new WebServerDTO();
wdto.setBindings(Collections.singletonList(bindingDTO));
wdto.path = "console"; wdto.path = "console";
WebServerComponent webServerComponent = new WebServerComponent(); WebServerComponent webServerComponent = new WebServerComponent();
webServerComponent.configure(wdto, ".", "."); webServerComponent.configure(wdto, ".", ".");

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
@ -35,6 +36,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.NodeManager; import org.apache.activemq.artemis.core.server.NodeManager;
import org.apache.activemq.artemis.core.server.ServiceComponent; import org.apache.activemq.artemis.core.server.ServiceComponent;
import org.apache.activemq.artemis.dto.AppDTO; import org.apache.activemq.artemis.dto.AppDTO;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.apache.activemq.artemis.dto.WebServerDTO; import org.apache.activemq.artemis.dto.WebServerDTO;
import org.apache.activemq.artemis.quorum.MutableLong; import org.apache.activemq.artemis.quorum.MutableLong;
import org.apache.activemq.artemis.quorum.file.FileBasedPrimitiveManager; import org.apache.activemq.artemis.quorum.file.FileBasedPrimitiveManager;
@ -127,7 +129,6 @@ public class PluggableQuorumNettyNoGroupNameReplicatedFailoverTest extends Failo
@Test @Test
public void testReplicatedFailbackBackupFromLiveBackToBackup() throws Exception { public void testReplicatedFailbackBackupFromLiveBackToBackup() throws Exception {
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8787); InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8787);
HttpServer httpServer = HttpServer.create(address, 100); HttpServer httpServer = HttpServer.create(address, 100);
httpServer.start(); httpServer.start();
@ -143,13 +144,15 @@ public class PluggableQuorumNettyNoGroupNameReplicatedFailoverTest extends Failo
os.close(); os.close();
} }
}); });
WebServerDTO wdto = new WebServerDTO();
AppDTO appDTO = new AppDTO(); AppDTO appDTO = new AppDTO();
appDTO.war = "console.war"; appDTO.war = "console.war";
appDTO.url = "console"; appDTO.url = "console";
wdto.apps = new ArrayList<AppDTO>(); BindingDTO bindingDTO = new BindingDTO();
wdto.apps.add(appDTO); bindingDTO.uri = "http://localhost:0";
wdto.bind = "http://localhost:0"; bindingDTO.apps = new ArrayList<AppDTO>();
bindingDTO.apps.add(appDTO);
WebServerDTO wdto = new WebServerDTO();
wdto.setBindings(Collections.singletonList(bindingDTO));
wdto.path = "console"; wdto.path = "console";
WebServerComponent webServerComponent = new WebServerComponent(); WebServerComponent webServerComponent = new WebServerComponent();
webServerComponent.configure(wdto, ".", "."); webServerComponent.configure(wdto, ".", ".");

View File

@ -26,10 +26,12 @@
<server configuration="file:/var/lib/artemis-instance/./etc//broker.xml"/> <server configuration="file:/var/lib/artemis-instance/./etc//broker.xml"/>
<!-- The web server is only bound to localhost by default --> <!-- The web server is only bound to localhost by default -->
<web bind="http://localhost:8161" path="web"> <web path="web">
<app url="activemq-branding" war="activemq-branding.war"/> <binding uri="http://localhost:8161">
<app url="artemis-plugin" war="artemis-plugin.war"/> <app url="activemq-branding" war="activemq-branding.war"/>
<app url="console" war="console.war"/> <app url="artemis-plugin" war="artemis-plugin.war"/>
<app url="console" war="console.war"/>
</binding>
</web> </web>