More on ARTEMIS-594: support HTTPS access to hawtio

Remove the keystore.jks in distribution
  Add documentation
  Add cli options
This commit is contained in:
Howard Gao 2016-06-30 09:56:04 +08:00
parent 880539a960
commit 3522979bda
5 changed files with 173 additions and 6 deletions

View File

@ -58,7 +58,7 @@ public class Create extends InputAbstract {
private static final Integer HQ_PORT = 5445;
private static final Integer HTTP_PORT = 8161;
public static final Integer HTTP_PORT = 8161;
private static final Integer MQTT_PORT = 1883;
@ -72,7 +72,6 @@ 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";
@ -103,6 +102,21 @@ public class Create extends InputAbstract {
@Option(name = "--http-port", description = "The port number to use for embedded web server (Default: 8161)")
int httpPort = HTTP_PORT;
@Option(name = "--ssl-key", description = "The key store path for embedded web server")
String sslKey;
@Option(name = "--ssl-key-password", description = "The key store password")
String sslKeyPassword;
@Option(name = "--use-client-auth", description = "If the embedded server requires client authentication")
boolean useClientAuth;
@Option(name = "--ssl-trust", description = "The trust store path in case of client authentication")
String sslTrust;
@Option(name = "--ssl-trust-password", description = "The trust store password")
String sslTrustPassword;
@Option(name = "--name", description = "The name of the broker (Default: same as host)")
String name;
@ -347,6 +361,27 @@ public class Create extends InputAbstract {
return clusterPassword;
}
public String getSslKeyPassword() {
if (sslKeyPassword == null) {
sslKeyPassword = inputPassword("--ssl-key-password", "Please enter the keystore password:", "password");
}
return sslKeyPassword;
}
public String getSslTrust() {
if (sslTrust == null) {
sslTrust = input("--ssl-trust", "Please enter the trust store path:", "/etc/truststore.jks");
}
return sslTrust;
}
public String getSslTrustPassword() {
if (sslTrustPassword == null) {
sslTrustPassword = inputPassword("--ssl-key-password", "Please enter the keystore password:", "password");
}
return sslTrustPassword;
}
public void setClusterPassword(String clusterPassword) {
this.clusterPassword = clusterPassword;
}
@ -522,6 +557,21 @@ public class Create extends InputAbstract {
filters.put("${journal.settings}", "ASYNCIO");
}
if (sslKey != null) {
filters.put("${web.protocol}", "https");
getSslKeyPassword();
String extraWebAttr = " keyStorePath=\"" + sslKey + "\" keyStorePassword=\"" + sslKeyPassword + "\"";
if (useClientAuth) {
getSslTrust();
getSslTrustPassword();
extraWebAttr += " clientAuth=\"true\" trustStorePath=\"" + sslTrust + "\" trustStorePassword=\"" + sslTrustPassword + "\"";
}
filters.put("${extra.web.attributes}", extraWebAttr);
}
else {
filters.put("${web.protocol}", "http");
filters.put("${extra.web.attributes}", "");
}
filters.put("${user}", System.getProperty("user.name", ""));
filters.put("${default.port}", String.valueOf(defaultPort + portOffset));
filters.put("${amqp.port}", String.valueOf(AMQP_PORT + portOffset));
@ -625,9 +675,6 @@ 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}", "");
}

View File

@ -1,4 +1,4 @@
<!-- The web server is only bound to loalhost by default -->
<web bind="http://localhost:${http.port}" path="web">
<web bind="${web.protocol}://localhost:${http.port}" path="web"${extra.web.attributes}>
<app url="jolokia" war="jolokia-war-1.3.3.war"/>
</web>

View File

@ -20,7 +20,12 @@ import javax.jms.Connection;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.api.core.SimpleString;
@ -43,6 +48,9 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
/**
* Test to validate that the CLI doesn't throw improper exceptions when invoked.
@ -115,6 +123,81 @@ public class ArtemisTest {
}
@Test
public void testWebConfig() throws Exception {
Run.setEmbedded(true);
//instance1: default using http
File instance1 = new File(temporaryFolder.getRoot(), "instance1");
Artemis.main("create", instance1.getAbsolutePath(), "--silent");
File bootstrapFile = new File(new File(instance1, "etc"), "bootstrap.xml");
Assert.assertTrue(bootstrapFile.exists());
Document config = parseXml(bootstrapFile);
Element webElem = (Element)config.getElementsByTagName("web").item(0);
String bindAttr = webElem.getAttribute("bind");
String bindStr = "http://localhost:" + Create.HTTP_PORT;
Assert.assertEquals(bindAttr, bindStr);
//no any of those
Assert.assertFalse(webElem.hasAttribute("keyStorePath"));
Assert.assertFalse(webElem.hasAttribute("keyStorePassword"));
Assert.assertFalse(webElem.hasAttribute("clientAuth"));
Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
//instance2: https
File instance2 = new File(temporaryFolder.getRoot(), "instance2");
Artemis.main("create", instance2.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore", "--ssl-key-password", "password1");
bootstrapFile = new File(new File(instance2, "etc"), "bootstrap.xml");
Assert.assertTrue(bootstrapFile.exists());
config = parseXml(bootstrapFile);
webElem = (Element)config.getElementsByTagName("web").item(0);
bindAttr = webElem.getAttribute("bind");
bindStr = "https://localhost:" + Create.HTTP_PORT;
Assert.assertEquals(bindAttr, bindStr);
String keyStr = webElem.getAttribute("keyStorePath");
Assert.assertEquals("etc/keystore", keyStr);
String keyPass = webElem.getAttribute("keyStorePassword");
Assert.assertEquals("password1", keyPass);
Assert.assertFalse(webElem.hasAttribute("clientAuth"));
Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
//instance3: https with clientAuth
File instance3 = new File(temporaryFolder.getRoot(), "instance3");
Artemis.main("create", instance3.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore",
"--ssl-key-password", "password1",
"--use-client-auth", "--ssl-trust", "etc/truststore", "--ssl-trust-password", "password2");
bootstrapFile = new File(new File(instance3, "etc"), "bootstrap.xml");
Assert.assertTrue(bootstrapFile.exists());
byte[] contents = Files.readAllBytes(bootstrapFile.toPath());
String cfgText = new String(contents);
System.out.println("confg: " + cfgText);
config = parseXml(bootstrapFile);
webElem = (Element)config.getElementsByTagName("web").item(0);
bindAttr = webElem.getAttribute("bind");
bindStr = "https://localhost:" + Create.HTTP_PORT;
Assert.assertEquals(bindAttr, bindStr);
keyStr = webElem.getAttribute("keyStorePath");
Assert.assertEquals("etc/keystore", keyStr);
keyPass = webElem.getAttribute("keyStorePassword");
Assert.assertEquals("password1", keyPass);
String clientAuthAttr = webElem.getAttribute("clientAuth");
Assert.assertEquals("true", clientAuthAttr);
String trustPathAttr = webElem.getAttribute("trustStorePath");
Assert.assertEquals("etc/truststore", trustPathAttr);
String trustPass = webElem.getAttribute("trustStorePassword");
Assert.assertEquals("password2", trustPass);
}
@Test
public void testSimpleRun() throws Exception {
String queues = "q1,t2";
@ -230,5 +313,10 @@ public class ArtemisTest {
Assert.assertEquals(0, LibaioContext.getTotalMaxIO());
}
private static Document parseXml(File xmlFile) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
return domBuilder.parse(xmlFile);
}
}

View File

@ -648,3 +648,35 @@ they use for this should always be changed from the installation default
to prevent a security risk.
Please see [Management](management.md) for instructions on how to do this.
## Securing the console
Artemis comes with a web console that allows user to browse Artemis documentation via an embedded server. By default the
web access is plain HTTP. It is configured in `bootstrap.xml`:
<web bind="http://localhost:8161" path="web">
<app url="jolokia" war="jolokia-war-1.3.3.war"/>
</web>
Alternatively you can edit the above configuration to enable secure access using HTTPS protocol. e.g.:
<web bind="https://localhost:8443"
path="web"
keyStorePath="${artemis.instance}/etc/keystore.jks"
keyStorePassword="password">
<app url="jolokia" war="jolokia-war-1.3.3.war"/>
</web>
As shown in the example, to enable https the first thing to do is config the `bind` to be an `https` url. In addition,
You will have to configure a few extra properties desribed as below.
- `keyStorePath` - The path of the key store file.
- `keyStorePassword` - The key store's password.
- `clientAuth` - The boolean flag indicates whether or not client authentication is required. Default is `false`.
- `trustStorePath` - The path of the trust store file. This is needed only if `clientAuth` is `true`.
- `trustStorePassword` - The trust store's password.