diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Configurable.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Configurable.java
index 9046c8f3c4..909906854b 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Configurable.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Configurable.java
@@ -27,9 +27,11 @@ import io.airlift.airline.model.CommandGroupMetadata;
import io.airlift.airline.model.CommandMetadata;
import io.airlift.airline.model.GlobalMetadata;
import org.apache.activemq.artemis.cli.factory.BrokerFactory;
+import org.apache.activemq.artemis.cli.factory.jmx.ManagementFactory;
import org.apache.activemq.artemis.core.config.FileDeploymentManager;
import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
import org.apache.activemq.artemis.dto.BrokerDTO;
+import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.integration.bootstrap.ActiveMQBootstrapLogger;
import org.apache.activemq.artemis.jms.server.config.impl.FileJMSConfiguration;
@@ -116,6 +118,11 @@ public abstract class Configurable extends ActionAbstract {
return brokerDTO;
}
+ protected ManagementContextDTO getManagementDTO() throws Exception {
+ String configuration = getManagementConfiguration();
+ return ManagementFactory.createJmxAclConfiguration(configuration, getBrokerHome(), getBrokerInstance(), getBrokerURIInstance());
+ }
+
protected String getConfiguration() {
if (configuration == null) {
File xmlFile = new File(new File(new File(getBrokerInstance()), "etc"), "bootstrap.xml");
@@ -130,4 +137,14 @@ public abstract class Configurable extends ActionAbstract {
return configuration;
}
+ protected String getManagementConfiguration() {
+ File xmlFile = new File(new File(new File(getBrokerInstance()), "etc"), "management.xml");
+ String configuration = "xml:" + xmlFile.toURI().toString().substring("file:".length());
+
+ // To support Windows paths as explained above.
+ configuration = configuration.replace("\\", "/");
+
+ return configuration;
+ }
+
}
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 2897f6d93a..400bdc3463 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
@@ -82,6 +82,7 @@ public class Create extends InputAbstract {
public static final String ETC_ARTEMIS_PROFILE = "etc/artemis.profile";
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_MANAGEMENT_XML = "etc/management.xml";
public static final String ETC_BROKER_XML = "etc/broker.xml";
public static final String ETC_ARTEMIS_ROLES_PROPERTIES = "etc/artemis-roles.properties";
@@ -689,6 +690,7 @@ public class Create extends InputAbstract {
// we want this variable to remain unchanged so that it will use the value set in the profile
filters.remove("${artemis.instance}");
write(ETC_BOOTSTRAP_XML, filters, false);
+ write(ETC_MANAGEMENT_XML, filters, false);
write(ETC_JOLOKIA_ACCESS_XML, filters, false);
context.out.println("");
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java
index 229bd5b3ad..12c00dbfe4 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java
@@ -25,10 +25,13 @@ import io.airlift.airline.Option;
import org.apache.activemq.artemis.cli.Artemis;
import org.apache.activemq.artemis.cli.commands.tools.LockAbstract;
import org.apache.activemq.artemis.cli.factory.BrokerFactory;
+import org.apache.activemq.artemis.cli.factory.jmx.ManagementFactory;
import org.apache.activemq.artemis.cli.factory.security.SecurityManagerFactory;
import org.apache.activemq.artemis.components.ExternalComponent;
+import org.apache.activemq.artemis.core.server.management.ManagementContext;
import org.apache.activemq.artemis.dto.BrokerDTO;
import org.apache.activemq.artemis.dto.ComponentDTO;
+import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.integration.Broker;
import org.apache.activemq.artemis.integration.bootstrap.ActiveMQBootstrapLogger;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
@@ -44,6 +47,8 @@ public class Run extends LockAbstract {
public static final ReusableLatch latchRunning = new ReusableLatch(0);
+ private ManagementContext managementContext;
+
/**
* This will disable the System.exit at the end of the server.stop, as that means there are other things
* happening on the same VM.
@@ -60,6 +65,9 @@ public class Run extends LockAbstract {
public Object execute(ActionContext context) throws Exception {
super.execute(context);
+ ManagementContextDTO managementDTO = getManagementDTO();
+ managementContext = ManagementFactory.create(managementDTO);
+
Artemis.printBanner();
BrokerDTO broker = getBrokerDTO();
@@ -70,6 +78,7 @@ public class Run extends LockAbstract {
server = BrokerFactory.createServer(broker.server, security);
+ managementContext.start();
server.start();
if (broker.web != null) {
@@ -121,11 +130,7 @@ public class Run extends LockAbstract {
}
if (file.exists()) {
try {
- try {
- server.stop(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
+ stop();
timer.cancel();
} finally {
System.out.println("Server stopped!");
@@ -142,13 +147,20 @@ public class Run extends LockAbstract {
Runtime.getRuntime().addShutdownHook(new Thread("shutdown-hook") {
@Override
public void run() {
- try {
- server.stop(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
+ Run.this.stop();
}
});
}
+
+ protected void stop() {
+ try {
+ server.stop(true);
+ if (managementContext != null) {
+ managementContext.stop();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/JmxAclHandler.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/JmxAclHandler.java
new file mode 100644
index 0000000000..3b13c40738
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/JmxAclHandler.java
@@ -0,0 +1,25 @@
+/**
+ * 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.cli.factory.jmx;
+
+import org.apache.activemq.artemis.dto.ManagementContextDTO;
+
+import java.net.URI;
+
+public interface JmxAclHandler {
+ ManagementContextDTO createJmxAcl(URI configURI, String artemisHome, String artemisInstance, URI artemisURIInstance) throws Exception;
+}
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/ManagementFactory.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/ManagementFactory.java
new file mode 100644
index 0000000000..235cdf6044
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/ManagementFactory.java
@@ -0,0 +1,134 @@
+/**
+ * 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.cli.factory.jmx;
+
+
+import org.apache.activemq.artemis.cli.ConfigurationException;
+import org.apache.activemq.artemis.core.config.JMXConnectorConfiguration;
+import org.apache.activemq.artemis.core.server.management.ManagementContext;
+import org.apache.activemq.artemis.dto.AccessDTO;
+import org.apache.activemq.artemis.dto.AuthorisationDTO;
+import org.apache.activemq.artemis.dto.EntryDTO;
+import org.apache.activemq.artemis.dto.JMXConnectorDTO;
+import org.apache.activemq.artemis.dto.ManagementContextDTO;
+import org.apache.activemq.artemis.dto.MatchDTO;
+import org.apache.activemq.artemis.core.server.management.JMXAccessControlList;
+import org.apache.activemq.artemis.utils.FactoryFinder;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+
+public class ManagementFactory {
+
+ private static ManagementContextDTO createJmxAclConfiguration(URI configURI,
+ String artemisHome,
+ String artemisInstance,
+ URI artemisURIInstance) throws Exception {
+ if (configURI.getScheme() == null) {
+ throw new ConfigurationException("Invalid configuration URI, no scheme specified: " + configURI);
+ }
+
+ JmxAclHandler factory = null;
+ try {
+ FactoryFinder finder = new FactoryFinder("META-INF/services/org/apache/activemq/artemis/broker/jmx/");
+ factory = (JmxAclHandler) finder.newInstance(configURI.getScheme());
+ } catch (IOException ioe) {
+ throw new ConfigurationException("Invalid configuration URI, can't find configuration scheme: " + configURI.getScheme());
+ }
+ return factory.createJmxAcl(configURI, artemisHome, artemisInstance, artemisURIInstance);
+ }
+
+ public static ManagementContextDTO createJmxAclConfiguration(String configuration,
+ String artemisHome,
+ String artemisInstance,
+ URI artemisURIInstance) throws Exception {
+ return createJmxAclConfiguration(new URI(configuration), artemisHome, artemisInstance, artemisURIInstance);
+ }
+
+ public static ManagementContext create(ManagementContextDTO config) {
+ ManagementContext context = new ManagementContext();
+
+ if (config.getAuthorisation() != null) {
+ AuthorisationDTO authorisation = config.getAuthorisation();
+ JMXAccessControlList accessControlList = new JMXAccessControlList();
+ List entries = authorisation.getWhiteList().getEntries();
+ for (EntryDTO entry : entries) {
+ accessControlList.addToWhiteList(entry.domain, entry.key);
+ }
+
+ List accessList = authorisation.getDefaultAccess().getAccess();
+ for (AccessDTO access : accessList) {
+ String[] split = access.roles.split(",");
+ accessControlList.addToDefaultAccess(access.method, split);
+ }
+
+ List matches = authorisation.getRoleAccess().getMatch();
+ for (MatchDTO match : matches) {
+ List accesses = match.getAccess();
+ for (AccessDTO access : accesses) {
+ String[] split = access.roles.split(",");
+ accessControlList.addToRoleAccess(match.getDomain(), match.getKey(), access.method, split);
+ }
+ }
+ context.setAccessControlList(accessControlList);
+ }
+
+ if (config.getJmxConnector() != null) {
+ JMXConnectorDTO jmxConnector = config.getJmxConnector();
+ JMXConnectorConfiguration jmxConnectorConfiguration = new JMXConnectorConfiguration();
+
+ jmxConnectorConfiguration.setConnectorPort(jmxConnector.getConnectorPort());
+ if (jmxConnector.getConnectorHost() != null) {
+ jmxConnectorConfiguration.setConnectorHost(jmxConnector.getConnectorHost());
+ }
+ if (jmxConnector.getJmxRealm() != null) {
+ jmxConnectorConfiguration.setJmxRealm(jmxConnector.getJmxRealm());
+ }
+ if (jmxConnector.getAuthenticatorType() != null) {
+ jmxConnectorConfiguration.setAuthenticatorType(jmxConnector.getAuthenticatorType());
+ }
+ if (jmxConnector.getKeyStorePath() != null) {
+ jmxConnectorConfiguration.setKeyStorePath(jmxConnector.getKeyStorePath());
+ }
+ if (jmxConnector.getKeyStoreProvider() != null) {
+ jmxConnectorConfiguration.setKeyStoreProvider(jmxConnector.getKeyStoreProvider());
+ }
+ if (jmxConnector.getKeyStorePassword() != null) {
+ jmxConnectorConfiguration.setKeyStorePassword(jmxConnector.getKeyStorePassword());
+ }
+ if (jmxConnector.getTrustStorePath() != null) {
+ jmxConnectorConfiguration.setTrustStorePath(jmxConnector.getTrustStorePath());
+ }
+ if (jmxConnector.getTrustStoreProvider() != null) {
+ jmxConnectorConfiguration.setTrustStoreProvider(jmxConnector.getTrustStoreProvider());
+ }
+ if (jmxConnector.getTrustStorePassword() != null) {
+ jmxConnectorConfiguration.setTrustStorePassword(jmxConnector.getTrustStorePassword());
+ }
+ if (jmxConnector.getObjectName() != null) {
+ jmxConnectorConfiguration.setObjectName(jmxConnector.getObjectName());
+ }
+ if (jmxConnector.isSecured() != null) {
+ jmxConnectorConfiguration.setSecured(jmxConnector.isSecured());
+ }
+ context.setJmxConnectorConfiguration(jmxConnectorConfiguration);
+ }
+
+ return context;
+ }
+}
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/XmlJmxAclHandler.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/XmlJmxAclHandler.java
new file mode 100644
index 0000000000..2ce8205d06
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/XmlJmxAclHandler.java
@@ -0,0 +1,36 @@
+/**
+ * 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.cli.factory.jmx;
+
+
+import org.apache.activemq.artemis.cli.ConfigurationException;
+import org.apache.activemq.artemis.dto.ManagementContextDTO;
+import org.apache.activemq.artemis.dto.XmlUtil;
+
+import java.io.File;
+import java.net.URI;
+
+public class XmlJmxAclHandler implements JmxAclHandler {
+ @Override
+ public ManagementContextDTO createJmxAcl(URI configURI, String artemisHome, String artemisInstance, URI artemisURIInstance) throws Exception {
+ File file = new File(configURI.getSchemeSpecificPart());
+ if (!file.exists()) {
+ throw new ConfigurationException("Invalid configuration URI, can't find file: " + file.getName());
+ }
+ return XmlUtil.decode(ManagementContextDTO.class, file, artemisHome, artemisInstance, artemisURIInstance);
+ }
+}
diff --git a/artemis-cli/src/main/resources/META-INF/services/org/apache/activemq/artemis/broker/jmx/xml b/artemis-cli/src/main/resources/META-INF/services/org/apache/activemq/artemis/broker/jmx/xml
new file mode 100644
index 0000000000..3d2dd012fe
--- /dev/null
+++ b/artemis-cli/src/main/resources/META-INF/services/org/apache/activemq/artemis/broker/jmx/xml
@@ -0,0 +1,17 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+class=org.apache.activemq.artemis.cli.factory.jmx.XmlJmxAclHandler
\ No newline at end of file
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/management.xml b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/management.xml
new file mode 100644
index 0000000000..1dd751098f
--- /dev/null
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/management.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java
index c802fb2503..fcf1e67ece 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java
@@ -39,6 +39,7 @@ public class StreamClassPathTest {
openStream(Create.ETC_ARTEMIS_PROFILE);
openStream(Create.ETC_LOGGING_PROPERTIES);
openStream(Create.ETC_BOOTSTRAP_XML);
+ openStream(Create.ETC_MANAGEMENT_XML);
openStream(Create.ETC_BROKER_XML);
openStream(Create.ETC_ARTEMIS_ROLES_PROPERTIES);
openStream(Create.ETC_ARTEMIS_USERS_PROPERTIES);
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/AccessDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/AccessDTO.java
new file mode 100644
index 0000000000..dc1f6144a5
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/AccessDTO.java
@@ -0,0 +1,34 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "access")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class AccessDTO {
+
+ @XmlAttribute
+ public String method;
+
+ @XmlAttribute
+ public String roles;
+}
\ No newline at end of file
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/AuthorisationDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/AuthorisationDTO.java
new file mode 100644
index 0000000000..dd302467b1
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/AuthorisationDTO.java
@@ -0,0 +1,49 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "authorisation")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class AuthorisationDTO {
+
+ @XmlElementRef
+ WhiteListDTO whitelist;
+
+ @XmlElementRef
+ DefaultAccessDTO defaultAccess;
+
+ @XmlElementRef
+ RoleAccessDTO roleAccess;
+
+ public WhiteListDTO getWhiteList() {
+ return whitelist;
+ }
+
+ public DefaultAccessDTO getDefaultAccess() {
+ return defaultAccess;
+ }
+
+ public RoleAccessDTO getRoleAccess() {
+ return roleAccess;
+ }
+}
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/DefaultAccessDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/DefaultAccessDTO.java
new file mode 100644
index 0000000000..c1db6e31bd
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/DefaultAccessDTO.java
@@ -0,0 +1,36 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.List;
+
+@XmlRootElement(name = "default-access")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class DefaultAccessDTO {
+
+ @XmlElementRef
+ List access;
+
+ public List getAccess() {
+ return access;
+ }
+}
\ No newline at end of file
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/EntryDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/EntryDTO.java
new file mode 100644
index 0000000000..e938a42bf0
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/EntryDTO.java
@@ -0,0 +1,34 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "entry")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class EntryDTO {
+
+ @XmlAttribute
+ public String domain;
+
+ @XmlAttribute
+ public String key;
+}
\ No newline at end of file
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java
new file mode 100644
index 0000000000..617a570529
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java
@@ -0,0 +1,112 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "connector")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class JMXConnectorDTO {
+
+ @XmlAttribute (name = "connector-host")
+ String connectorHost;
+
+ @XmlAttribute (name = "connector-port", required = true)
+ Integer connectorPort;
+
+ @XmlAttribute (name = "jmx-realm")
+ String jmxRealm;
+
+ @XmlAttribute (name = "object-name")
+ String objectName;
+
+ @XmlAttribute (name = "authenticator-type")
+ String authenticatorType;
+
+ @XmlAttribute (name = "secured")
+ Boolean secured;
+
+ @XmlAttribute (name = "key-store-provider")
+ String keyStoreProvider;
+
+ @XmlAttribute (name = "key-store-path")
+ String keyStorePath;
+
+ @XmlAttribute (name = "key-store-password")
+ String keyStorePassword;
+
+ @XmlAttribute (name = "trust-store-provider")
+ String trustStoreProvider;
+
+ @XmlAttribute (name = "trust-store-path")
+ String trustStorePath;
+
+ @XmlAttribute (name = "trust-store-password")
+ String trustStorePassword;
+
+ public String getConnectorHost() {
+ return connectorHost;
+ }
+
+ public int getConnectorPort() {
+ return connectorPort;
+ }
+
+ public String getJmxRealm() {
+ return jmxRealm;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public String getAuthenticatorType() {
+ return authenticatorType;
+ }
+
+ public Boolean isSecured() {
+ return secured;
+ }
+
+ public String getKeyStoreProvider() {
+ return keyStoreProvider;
+ }
+
+ public String getKeyStorePath() {
+ return keyStorePath;
+ }
+
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ public String getTrustStoreProvider() {
+ return trustStoreProvider;
+ }
+
+ public String getTrustStorePath() {
+ return trustStorePath;
+ }
+
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+}
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/ManagementContextDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/ManagementContextDTO.java
new file mode 100644
index 0000000000..7594127ca0
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/ManagementContextDTO.java
@@ -0,0 +1,41 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "management-context")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ManagementContextDTO {
+
+ @XmlElementRef (required = false)
+ JMXConnectorDTO jmxConnector;
+
+ @XmlElementRef (required = false)
+ AuthorisationDTO authorisation;
+
+ public JMXConnectorDTO getJmxConnector() {
+ return jmxConnector;
+ }
+
+ public AuthorisationDTO getAuthorisation() {
+ return authorisation;
+ }
+}
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/MatchDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/MatchDTO.java
new file mode 100644
index 0000000000..77e8b01f95
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/MatchDTO.java
@@ -0,0 +1,53 @@
+/**
+ * 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 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 = "match")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class MatchDTO {
+
+
+ @XmlAttribute
+ public String domain;
+
+ @XmlAttribute
+ public String key;
+
+ @XmlElementRef
+ List access;
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public List getAccess() {
+ return access;
+ }
+
+}
\ No newline at end of file
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/RoleAccessDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/RoleAccessDTO.java
new file mode 100644
index 0000000000..ca763d4d9d
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/RoleAccessDTO.java
@@ -0,0 +1,36 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.List;
+
+@XmlRootElement(name = "role-access")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class RoleAccessDTO {
+
+ @XmlElementRef
+ List match;
+
+ public List getMatch() {
+ return match;
+ }
+}
\ No newline at end of file
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WhiteListDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WhiteListDTO.java
new file mode 100644
index 0000000000..70feabee3a
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WhiteListDTO.java
@@ -0,0 +1,36 @@
+/**
+ * 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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.List;
+
+@XmlRootElement(name = "whitelist")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class WhiteListDTO {
+
+ @XmlElementRef
+ List entry;
+
+ public List getEntries() {
+ return entry;
+ }
+}
\ No newline at end of file
diff --git a/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index b/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index
index 94f0b2d2cb..73ff377b0b 100644
--- a/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index
+++ b/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index
@@ -17,4 +17,5 @@
BrokerDTO
SecurityDTO
JaasSecurityDTO
+ManagementContextDTO
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/JMXConnectorConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/JMXConnectorConfiguration.java
new file mode 100644
index 0000000000..786f55415d
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/JMXConnectorConfiguration.java
@@ -0,0 +1,149 @@
+/**
+ * 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.core.config;
+
+public class JMXConnectorConfiguration {
+ private int rmiRegistryPort;
+ private String connectorHost = "localhost";
+ private int connectorPort = 1099;
+
+ private String connectorPath = "/jmxrmi";
+
+ private String jmxRealm = "activemq";
+ private String objectName = "connector:name=rmi";
+ private String authenticatorType = "password";
+ private boolean secured = false;
+ private String keyStoreProvider;
+ private String keyStorePath;
+ private String keyStorePassword;
+ private String trustStoreProvider;
+ private String trustStorePath;
+ private String trustStorePassword;
+
+ public int getRmiRegistryPort() {
+ return rmiRegistryPort;
+ }
+
+ public void setRmiRegistryPort(int rmiRegistryPort) {
+ this.rmiRegistryPort = rmiRegistryPort;
+ }
+
+ public String getConnectorHost() {
+ return connectorHost;
+ }
+
+ public void setConnectorHost(String connectorHost) {
+ this.connectorHost = connectorHost;
+ }
+
+ public int getConnectorPort() {
+ return connectorPort;
+ }
+
+ public void setConnectorPort(int connectorPort) {
+ this.connectorPort = connectorPort;
+ }
+
+ public String getJmxRealm() {
+ return jmxRealm;
+ }
+
+ public void setJmxRealm(String jmxRealm) {
+ this.jmxRealm = jmxRealm;
+ }
+
+ public String getServiceUrl() {
+ String rmiServer = "";
+ if (rmiRegistryPort != 0) {
+ // This is handy to use if you have a firewall and need to force JMX to use fixed ports.
+ rmiServer = "" + getConnectorHost() + ":" + rmiRegistryPort;
+ }
+ return "service:jmx:rmi://" + rmiServer + "/jndi/rmi://" + getConnectorHost() + ":" + connectorPort + connectorPath;
+ }
+
+ public String getAuthenticatorType() {
+ return authenticatorType;
+ }
+
+ public void setAuthenticatorType(String authenticatorType) {
+ this.authenticatorType = authenticatorType;
+ }
+
+ public boolean isSecured() {
+ return secured;
+ }
+
+ public String getKeyStoreProvider() {
+ return keyStoreProvider;
+ }
+
+ public void setKeyStoreProvider(String keyStoreProvider) {
+ this.keyStoreProvider = keyStoreProvider;
+ }
+
+ public String getKeyStorePath() {
+ return keyStorePath;
+ }
+
+ public void setKeyStorePath(String keyStorePath) {
+ this.keyStorePath = keyStorePath;
+ }
+
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ public void setKeyStorePassword(String keyStorePassword) {
+ this.keyStorePassword = keyStorePassword;
+ }
+
+ public String getTrustStoreProvider() {
+ return trustStoreProvider;
+ }
+
+ public void setTrustStoreProvider(String trustStoreProvider) {
+ this.trustStoreProvider = trustStoreProvider;
+ }
+
+ public String getTrustStorePath() {
+ return trustStorePath;
+ }
+
+ public void setTrustStorePath(String trustStorePath) {
+ this.trustStorePath = trustStorePath;
+ }
+
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+
+ public void setTrustStorePassword(String trustStorePassword) {
+ this.trustStorePassword = trustStorePassword;
+ }
+
+ public void setObjectName(String objectName) {
+ this.objectName = objectName;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public void setSecured(Boolean secured) {
+ this.secured = secured;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerBuilder.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerBuilder.java
new file mode 100644
index 0000000000..e4e743bced
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerBuilder.java
@@ -0,0 +1,90 @@
+/*
+ * 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.core.server.management;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerBuilder;
+import javax.management.MBeanServerDelegate;
+
+public class ArtemisMBeanServerBuilder extends MBeanServerBuilder {
+
+ private static volatile InvocationHandler guard;
+
+ public static void setGuard(InvocationHandler guardHandler) {
+ guard = guardHandler;
+ }
+
+ public static ArtemisMBeanServerGuard getArtemisMBeanServerGuard() {
+ return (ArtemisMBeanServerGuard) guard;
+ }
+
+ @Override
+ public MBeanServer newMBeanServer(String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) {
+ InvocationHandler handler = new MBeanInvocationHandler(super.newMBeanServer(defaultDomain, outer, delegate));
+ return (MBeanServer) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{MBeanServer.class}, handler);
+ }
+
+ private static final class MBeanInvocationHandler implements InvocationHandler {
+
+ private final MBeanServer wrapped;
+ private final List guarded = Collections.unmodifiableList(Arrays.asList("invoke", "getAttribute", "getAttributes", "setAttribute", "setAttributes"));
+
+ MBeanInvocationHandler(MBeanServer mbeanServer) {
+ wrapped = mbeanServer;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (guarded.contains(method.getName())) {
+ if (ArtemisMBeanServerBuilder.guard == null) {
+ throw new IllegalStateException("ArtemisMBeanServerBuilder not initialized");
+ }
+ guard.invoke(proxy, method, args);
+ }
+ if (method.getName().equals("queryMBeans")) {
+ guard.invoke(wrapped, method, args);
+ }
+ if (method.getName().equals("equals")
+ && method.getParameterTypes().length == 1
+ && method.getParameterTypes()[0] == Object.class) {
+ Object target = args[0];
+ if (target != null && Proxy.isProxyClass(target.getClass())) {
+ InvocationHandler handler = Proxy.getInvocationHandler(target);
+ if (handler instanceof MBeanInvocationHandler) {
+ args[0] = ((MBeanInvocationHandler) handler).wrapped;
+ }
+ }
+ } else if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) {
+ // special case finalize, don't route through to delegate because that will get its own call
+ return null;
+ }
+ try {
+ return method.invoke(wrapped, args);
+ } catch (InvocationTargetException ite) {
+ throw ite.getCause();
+ }
+ }
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuard.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuard.java
new file mode 100644
index 0000000000..115b31aaf1
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuard.java
@@ -0,0 +1,162 @@
+/*
+ * 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.core.server.management;
+
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.JMException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.security.auth.Subject;
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.List;
+
+public class ArtemisMBeanServerGuard implements InvocationHandler {
+
+ private JMXAccessControlList jmxAccessControlList = JMXAccessControlList.createDefaultList();
+
+ public void init() {
+ ArtemisMBeanServerBuilder.setGuard(this);
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getParameterTypes().length == 0)
+ return null;
+
+ if (!ObjectName.class.isAssignableFrom(method.getParameterTypes()[0]))
+ return null;
+
+ ObjectName objectName = (ObjectName) args[0];
+ if ("getAttribute".equals(method.getName())) {
+ handleGetAttribute((MBeanServer) proxy, objectName, (String) args[1]);
+ } else if ("getAttributes".equals(method.getName())) {
+ handleGetAttributes((MBeanServer) proxy, objectName, (String[]) args[1]);
+ } else if ("setAttribute".equals(method.getName())) {
+ handleSetAttribute((MBeanServer) proxy, objectName, (Attribute) args[1]);
+ } else if ("setAttributes".equals(method.getName())) {
+ handleSetAttributes((MBeanServer) proxy, objectName, (AttributeList) args[1]);
+ } else if ("invoke".equals(method.getName())) {
+ handleInvoke(objectName, (String) args[1], (Object[]) args[2], (String[]) args[3]);
+ }
+
+ return null;
+ }
+
+ private void handleGetAttribute(MBeanServer proxy, ObjectName objectName, String attributeName) throws JMException, IOException {
+ MBeanInfo info = proxy.getMBeanInfo(objectName);
+ String prefix = null;
+ for (MBeanAttributeInfo attr : info.getAttributes()) {
+ if (attr.getName().equals(attributeName)) {
+ prefix = attr.isIs() ? "is" : "get";
+ }
+ }
+ if (prefix == null) {
+ //ActiveMQServerLogger.LOGGER.debug("Attribute " + attributeName + " can not be found for MBean " + objectName.toString());
+ } else {
+ handleInvoke(objectName, prefix + attributeName, new Object[]{}, new String[]{});
+ }
+ }
+
+ private void handleGetAttributes(MBeanServer proxy, ObjectName objectName, String[] attributeNames) throws JMException, IOException {
+ for (String attr : attributeNames) {
+ handleGetAttribute(proxy, objectName, attr);
+ }
+ }
+
+ private void handleSetAttribute(MBeanServer proxy, ObjectName objectName, Attribute attribute) throws JMException, IOException {
+ String dataType = null;
+ MBeanInfo info = proxy.getMBeanInfo(objectName);
+ for (MBeanAttributeInfo attr : info.getAttributes()) {
+ if (attr.getName().equals(attribute.getName())) {
+ dataType = attr.getType();
+ break;
+ }
+ }
+
+ if (dataType == null)
+ throw new IllegalStateException("Attribute data type can not be found");
+
+ handleInvoke(objectName, "set" + attribute.getName(), new Object[]{attribute.getValue()}, new String[]{dataType});
+ }
+
+ private void handleSetAttributes(MBeanServer proxy, ObjectName objectName, AttributeList attributes) throws JMException, IOException {
+ for (Attribute attr : attributes.asList()) {
+ handleSetAttribute(proxy, objectName, attr);
+ }
+ }
+
+ private boolean canBypassRBAC(ObjectName objectName) {
+ return jmxAccessControlList.isInWhiteList(objectName);
+ }
+
+ void handleInvoke(ObjectName objectName, String operationName, Object[] params, String[] signature) throws IOException {
+ if (canBypassRBAC(objectName)) {
+ return;
+ }
+ List requiredRoles = getRequiredRoles(objectName, operationName, params, signature);
+ for (String role : requiredRoles) {
+ if (currentUserHasRole(role))
+ return;
+ }
+ throw new SecurityException("Insufficient roles/credentials for operation");
+ }
+
+ List getRequiredRoles(ObjectName objectName, String methodName, Object[] params, String[] signature) throws IOException {
+ return jmxAccessControlList.getRolesForObject(objectName, methodName);
+ }
+
+ public void setJMXAccessControlList(JMXAccessControlList JMXAccessControlList) {
+ this.jmxAccessControlList = JMXAccessControlList;
+ }
+
+ public static boolean currentUserHasRole(String requestedRole) {
+
+ String clazz;
+ String role;
+ int index = requestedRole.indexOf(':');
+ if (index > 0) {
+ clazz = requestedRole.substring(0, index);
+ role = requestedRole.substring(index + 1);
+ } else {
+ clazz = "org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal";
+ role = requestedRole;
+ }
+ AccessControlContext acc = AccessController.getContext();
+ if (acc == null) {
+ return false;
+ }
+ Subject subject = Subject.getSubject(acc);
+ if (subject == null) {
+ return false;
+ }
+ for (Principal p : subject.getPrincipals()) {
+ if (clazz.equals(p.getClass().getName()) && role.equals(p.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ConnectorServerFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ConnectorServerFactory.java
new file mode 100644
index 0000000000..2c66acbb95
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ConnectorServerFactory.java
@@ -0,0 +1,282 @@
+/*
+ * 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.core.server.management;
+
+import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.util.Map;
+
+public class ConnectorServerFactory {
+
+ public void setkeyStoreProvider(String keyStoreProvider) {
+ this.keyStoreProvider = keyStoreProvider;
+ }
+
+ public void setKeyStorePassword(String keyStorePassword) {
+ this.keyStorePassword = keyStorePassword;
+ }
+
+ public void setTrustStorePath(String trustStorePath) {
+ this.trustStorePath = trustStorePath;
+ }
+
+ public void setTrustStoreProvider(String trustStoreProvider) {
+ this.trustStoreProvider = trustStoreProvider;
+ }
+
+ public void setTrustStorePassword(String trustStorePassword) {
+ this.trustStorePassword = trustStorePassword;
+ }
+
+ private enum AuthenticatorType { NONE, PASSWORD, CERTIFICATE };
+
+ private MBeanServer server;
+ private String serviceUrl;
+ private String rmiServerHost;
+ private Map environment;
+ private ObjectName objectName;
+ private JMXConnectorServer connectorServer;
+
+ private AuthenticatorType authenticatorType = AuthenticatorType.PASSWORD;
+
+ private boolean secured;
+
+ private String keyStoreProvider;
+
+ private String keyStorePath;
+
+ private String keyStorePassword;
+
+ private String trustStoreProvider;
+
+ private String trustStorePath;
+
+ private String trustStorePassword;
+
+
+ public String getKeyStoreProvider() {
+ return keyStoreProvider;
+ }
+
+ public String getKeyStorePath() {
+ return keyStorePath;
+ }
+
+ public void setKeyStorePath(String keyStorePath) {
+ this.keyStorePath = keyStorePath;
+ }
+
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ public String getTrustStoreProvider() {
+ return trustStoreProvider;
+ }
+
+ public String getTrustStorePath() {
+ return trustStorePath;
+ }
+
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+
+ public MBeanServer getServer() {
+ return server;
+ }
+
+ public void setServer(MBeanServer server) {
+ this.server = server;
+ }
+
+ public String getServiceUrl() {
+ return serviceUrl;
+ }
+
+ public void setServiceUrl(String serviceUrl) {
+ this.serviceUrl = serviceUrl;
+ }
+
+ public String getRmiServerHost() {
+ return this.rmiServerHost;
+ }
+
+ public void setRmiServerHost(String rmiServerHost) {
+ this.rmiServerHost = rmiServerHost;
+ }
+
+ public Map getEnvironment() {
+ return environment;
+ }
+
+ public void setEnvironment(Map environment) {
+ this.environment = environment;
+ }
+
+ public ObjectName getObjectName() {
+ return objectName;
+ }
+
+ public void setObjectName(ObjectName objectName) {
+ this.objectName = objectName;
+ }
+
+ public String getAuthenticatorType() {
+ return this.authenticatorType.name().toLowerCase();
+ }
+
+ /**
+ * Authenticator type to use. Acceptable values are "none", "password", and "certificate"
+ *
+ * @param value
+ */
+ public void setAuthenticatorType(String value) {
+ this.authenticatorType = AuthenticatorType.valueOf(value.toUpperCase());
+ }
+
+ public boolean isSecured() {
+ return this.secured;
+ }
+
+ public void setSecured(boolean secured) {
+ this.secured = secured;
+ }
+
+ private boolean isClientAuth() {
+ return this.authenticatorType.equals(AuthenticatorType.CERTIFICATE);
+ }
+
+ public void init() throws Exception {
+
+ if (this.server == null) {
+ throw new IllegalArgumentException("server must be set");
+ }
+ JMXServiceURL url = new JMXServiceURL(this.serviceUrl);
+ setupArtemisRMIServerSocketFactory();
+ if (isClientAuth()) {
+ this.secured = true;
+ }
+
+ if (this.secured) {
+ this.setupSsl();
+ }
+
+ if (!AuthenticatorType.PASSWORD.equals(this.authenticatorType)) {
+ this.environment.remove("jmx.remote.authenticator");
+ }
+
+ this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, this.environment, this.server);
+ if (this.objectName != null) {
+ this.server.registerMBean(this.connectorServer, this.objectName);
+ }
+
+ try {
+ this.connectorServer.start();
+ } catch (Exception ex) {
+ doUnregister(this.objectName);
+ throw ex;
+ }
+ }
+
+ public void destroy() throws Exception {
+ try {
+ if (this.connectorServer != null) {
+ this.connectorServer.stop();
+ }
+ } finally {
+ doUnregister(this.objectName);
+ }
+ }
+
+ protected void doUnregister(ObjectName objectName) {
+ try {
+ if (this.objectName != null && this.server.isRegistered(objectName)) {
+ this.server.unregisterMBean(objectName);
+ }
+ } catch (JMException ex) {
+ // Ignore
+ }
+ }
+
+ //todo fix
+ private void setupSsl() throws Exception {
+ SSLContext context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword);
+ SSLServerSocketFactory sssf = context.getServerSocketFactory();
+ RMIServerSocketFactory rssf = new ArtemisSslRMIServerSocketFactory(sssf, this.isClientAuth(), rmiServerHost);
+ RMIClientSocketFactory rcsf = new SslRMIClientSocketFactory();
+ environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, rssf);
+ environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, rcsf);
+ }
+
+ private void setupArtemisRMIServerSocketFactory() {
+ RMIServerSocketFactory rmiServerSocketFactory = new ArtemisRMIServerSocketFactory(getRmiServerHost());
+ environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, rmiServerSocketFactory);
+ }
+
+ private static class ArtemisSslRMIServerSocketFactory implements RMIServerSocketFactory {
+ private SSLServerSocketFactory sssf;
+ private boolean clientAuth;
+ private String rmiServerHost;
+
+ ArtemisSslRMIServerSocketFactory(SSLServerSocketFactory sssf, boolean clientAuth, String rmiServerHost) {
+ this.sssf = sssf;
+ this.clientAuth = clientAuth;
+ this.rmiServerHost = rmiServerHost;
+ }
+
+ @Override
+ public ServerSocket createServerSocket(int port) throws IOException {
+ SSLServerSocket ss = (SSLServerSocket) sssf.createServerSocket(port, 50, InetAddress.getByName(rmiServerHost));
+ ss.setNeedClientAuth(clientAuth);
+ return ss;
+ }
+ }
+
+ private static class ArtemisRMIServerSocketFactory implements RMIServerSocketFactory {
+ private String rmiServerHost;
+
+ ArtemisRMIServerSocketFactory(String rmiServerHost) {
+ this.rmiServerHost = rmiServerHost;
+ }
+
+ @Override
+ public ServerSocket createServerSocket(int port) throws IOException {
+ ServerSocket serverSocket = (ServerSocket) ServerSocketFactory.getDefault().createServerSocket(port, 50, InetAddress.getByName(rmiServerHost));
+ return serverSocket;
+ }
+ }
+
+
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/JMXAccessControlList.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/JMXAccessControlList.java
new file mode 100644
index 0000000000..0cafeb059a
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/JMXAccessControlList.java
@@ -0,0 +1,203 @@
+/**
+ * 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.core.server.management;
+
+import javax.management.ObjectName;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class JMXAccessControlList {
+
+ private Access defaultAccess = new Access("*");
+ private Map domainAccess = new HashMap<>();
+ private ConcurrentHashMap> whitelist = new ConcurrentHashMap<>();
+
+
+ public void addToWhiteList(String domain, String match) {
+ List list = new ArrayList<>();
+ list = whitelist.putIfAbsent(domain, list);
+ if (list == null) {
+ list = whitelist.get(domain);
+ }
+ list.add(match != null ? match : "*");
+ }
+
+
+ public List getRolesForObject(ObjectName objectName, String methodName) {
+ Hashtable keyPropertyList = objectName.getKeyPropertyList();
+ for (Map.Entry keyEntry : keyPropertyList.entrySet()) {
+ String key = keyEntry.getKey() + "=" + keyEntry.getValue();
+ Access access = domainAccess.get(getObjectID(objectName.getDomain(), key));
+ if (access != null) {
+ return access.getMatchingRolesForMethod(methodName);
+ }
+ }
+ for (Map.Entry keyEntry : keyPropertyList.entrySet()) {
+ String key = keyEntry.getKey() + "=*";
+ Access access = domainAccess.get(getObjectID(objectName.getDomain(), key));
+ if (access != null) {
+ return access.getMatchingRolesForMethod(methodName);
+ }
+ }
+ Access access = domainAccess.get(objectName.getDomain());
+ if (access == null) {
+ access = defaultAccess;
+ }
+ return access.getMatchingRolesForMethod(methodName);
+ }
+
+ public boolean isInWhiteList(ObjectName objectName) {
+ List matches = whitelist.get(objectName.getDomain());
+ if (matches != null) {
+ for (String match : matches) {
+ if (match.equals("*")) {
+ return true;
+ } else {
+ String[] split = match.split("=");
+ String key = split[0];
+ String val = split[1];
+ String propVal = objectName.getKeyProperty(key);
+ if (propVal != null && (val.equals("*") || propVal.equals(val))) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public void addToDefaultAccess(String method, String... roles) {
+ if (roles != null) {
+ if ( method.equals("*")) {
+ defaultAccess.addCatchAll(roles);
+ } else if (method.endsWith("*")) {
+ String prefix = method.replace("*", "");
+ defaultAccess.addMethodsPrefixes(prefix, roles);
+ } else {
+ defaultAccess.addMethods(method, roles);
+ }
+ }
+ }
+
+ public void addToRoleAccess(String domain,String key, String method, String... roles) {
+ String id = getObjectID(domain, key);
+ Access access = domainAccess.get(id);
+ if (access == null) {
+ access = new Access(domain);
+ domainAccess.put(id, access);
+ }
+
+ if (method.endsWith("*")) {
+ String prefix = method.replace("*", "");
+ access.addMethodsPrefixes(prefix, roles);
+ } else {
+ access.addMethods(method, roles);
+ }
+ }
+
+ private String getObjectID(String domain, String key) {
+ String id = domain;
+
+ if (key != null) {
+ String actualKey = key;
+ if (key.endsWith("\"")) {
+ actualKey = actualKey.replace("\"", "");
+ }
+ id += ":" + actualKey;
+ }
+ return id;
+ }
+
+ static class Access {
+ private final String domain;
+ List catchAllRoles = new ArrayList<>();
+ Map> methodRoles = new HashMap<>();
+ Map> methodPrefixRoles = new HashMap<>();
+
+ Access(String domain) {
+ this.domain = domain;
+ }
+
+ public synchronized void addMethods(String prefix, String... roles) {
+ List rolesList = methodRoles.get(prefix);
+ if (rolesList == null) {
+ rolesList = new ArrayList<>();
+ methodRoles.put(prefix, rolesList);
+ }
+ for (String role : roles) {
+ rolesList.add(role);
+ }
+ }
+
+ public synchronized void addMethodsPrefixes(String prefix, String... roles) {
+ List rolesList = methodPrefixRoles.get(prefix);
+ if (rolesList == null) {
+ rolesList = new ArrayList<>();
+ methodPrefixRoles.put(prefix, rolesList);
+ }
+ for (String role : roles) {
+ rolesList.add(role);
+ }
+ }
+
+ public void addCatchAll(String... roles) {
+ for (String role : roles) {
+ catchAllRoles.add(role);
+ }
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public List getMatchingRolesForMethod(String methodName) {
+ List roles = methodRoles.get(methodName);
+ if (roles != null) {
+ return roles;
+ }
+ for (Map.Entry> entry : methodPrefixRoles.entrySet()) {
+ if (methodName.startsWith(entry.getKey())) {
+ return entry.getValue();
+ }
+ }
+ return catchAllRoles;
+ }
+ }
+ public static JMXAccessControlList createDefaultList() {
+ JMXAccessControlList accessControlList = new JMXAccessControlList();
+
+ accessControlList.addToWhiteList("hawtio", "type=*");
+
+ accessControlList.addToRoleAccess("org.apache.activemq.apache", null, "list*", "view", "update", "amq");
+ accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"get*", "view", "update", "amq");
+ accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"is*", "view", "update", "amq");
+ accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"set*","update", "amq");
+ accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"*", "amq");
+
+ accessControlList.addToDefaultAccess("list*", "view", "update", "amq");
+ accessControlList.addToDefaultAccess("get*", "view", "update", "amq");
+ accessControlList.addToDefaultAccess("is*", "view", "update", "amq");
+ accessControlList.addToDefaultAccess("set*", "update", "amq");
+ accessControlList.addToDefaultAccess("*", "amq");
+
+ return accessControlList;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/JaasAuthenticator.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/JaasAuthenticator.java
new file mode 100644
index 0000000000..23167e909f
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/JaasAuthenticator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.core.server.management;
+
+import javax.management.remote.JMXAuthenticator;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+
+public class JaasAuthenticator implements JMXAuthenticator {
+
+ private String realm;
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ @Override
+ public Subject authenticate(Object credentials) throws SecurityException {
+ if (!(credentials instanceof String[])) {
+ throw new IllegalArgumentException("Expected String[2], got "
+ + (credentials != null ? credentials.getClass().getName() : null));
+ }
+
+ final String[] params = (String[]) credentials;
+ if (params.length != 2) {
+ throw new IllegalArgumentException("Expected String[2] but length was " + params.length);
+ }
+ try {
+ Subject subject = new Subject();
+ LoginContext loginContext = new LoginContext(realm, subject, new CallbackHandler() {
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ if (callbacks[i] instanceof NameCallback) {
+ ((NameCallback) callbacks[i]).setName(params[0]);
+ } else if (callbacks[i] instanceof PasswordCallback) {
+ ((PasswordCallback) callbacks[i]).setPassword((params[1].toCharArray()));
+ } else {
+ throw new UnsupportedCallbackException(callbacks[i]);
+ }
+ }
+ }
+ });
+ loginContext.login();
+
+ /* if (subject.getPrincipals().size() == 0) {
+ // there must be some Principals, but which ones required are tested later
+ throw new FailedLoginException("User does not have the required role");
+ }*/
+
+ return subject;
+ } catch (LoginException e) {
+ throw new SecurityException("Authentication failed", e);
+ }
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/MBeanServerFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/MBeanServerFactory.java
new file mode 100644
index 0000000000..65ca9be48e
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/MBeanServerFactory.java
@@ -0,0 +1,100 @@
+/*
+ * 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.core.server.management;
+
+import javax.management.MBeanServer;
+import java.lang.management.ManagementFactory;
+import java.util.List;
+
+public class MBeanServerFactory {
+
+ private boolean locateExistingServerIfPossible = false;
+ private String defaultDomain;
+ private boolean registerWithFactory = true;
+ private MBeanServer server;
+ private boolean newlyRegistered = false;
+
+ public boolean isLocateExistingServerIfPossible() {
+ return locateExistingServerIfPossible;
+ }
+
+ public void setLocateExistingServerIfPossible(boolean locateExistingServerIfPossible) {
+ this.locateExistingServerIfPossible = locateExistingServerIfPossible;
+ }
+
+ public String getDefaultDomain() {
+ return defaultDomain;
+ }
+
+ public void setDefaultDomain(String defaultDomain) {
+ this.defaultDomain = defaultDomain;
+ }
+
+ public boolean isRegisterWithFactory() {
+ return registerWithFactory;
+ }
+
+ public void setRegisterWithFactory(boolean registerWithFactory) {
+ this.registerWithFactory = registerWithFactory;
+ }
+
+ public boolean isNewlyRegistered() {
+ return newlyRegistered;
+ }
+
+ public void setNewlyRegistered(boolean newlyRegistered) {
+ this.newlyRegistered = newlyRegistered;
+ }
+
+ public MBeanServer getServer() throws Exception {
+ if (this.server == null) {
+ init();
+ }
+ return server;
+ }
+
+ public void init() throws Exception {
+ if (this.locateExistingServerIfPossible) {
+ List servers = javax.management.MBeanServerFactory.findMBeanServer(null);
+ if (servers != null && servers.size() > 0) {
+ this.server = (MBeanServer) servers.get(0);
+ }
+ if (this.server == null) {
+ this.server = ManagementFactory.getPlatformMBeanServer();
+ }
+ if (this.server == null) {
+ throw new Exception("Unable to locate MBeanServer");
+ }
+ }
+ if (this.server == null) {
+ if (this.registerWithFactory) {
+ this.server = javax.management.MBeanServerFactory.createMBeanServer(this.defaultDomain);
+ } else {
+ this.server = javax.management.MBeanServerFactory.newMBeanServer(this.defaultDomain);
+ }
+ this.newlyRegistered = this.registerWithFactory;
+ }
+ }
+
+ public void destroy() throws Exception {
+
+ if (this.newlyRegistered) {
+ javax.management.MBeanServerFactory.releaseMBeanServer(this.server);
+ }
+ }
+
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java
new file mode 100644
index 0000000000..d867bd8cc5
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java
@@ -0,0 +1,114 @@
+/*
+ * 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.core.server.management;
+
+import org.apache.activemq.artemis.core.config.JMXConnectorConfiguration;
+import org.apache.activemq.artemis.core.server.ActiveMQComponent;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class ManagementConnector implements ActiveMQComponent {
+
+ private final JMXConnectorConfiguration configuration;
+ private ConnectorServerFactory connectorServerFactory;
+ private RmiRegistryFactory rmiRegistryFactory;
+ private MBeanServerFactory mbeanServerFactory;
+
+ public ManagementConnector(JMXConnectorConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return rmiRegistryFactory != null;
+ }
+
+ @Override
+ public void start() throws Exception {
+ ArtemisMBeanServerGuard guard = new ArtemisMBeanServerGuard();
+ guard.init();
+
+ rmiRegistryFactory = new RmiRegistryFactory();
+ rmiRegistryFactory.setPort(configuration.getConnectorPort());
+ rmiRegistryFactory.init();
+
+ mbeanServerFactory = new MBeanServerFactory();
+ mbeanServerFactory.setLocateExistingServerIfPossible(true);
+ mbeanServerFactory.init();
+
+ MBeanServer mbeanServer = mbeanServerFactory.getServer();
+
+ JaasAuthenticator jaasAuthenticator = new JaasAuthenticator();
+ jaasAuthenticator.setRealm(configuration.getJmxRealm());
+
+ connectorServerFactory = new ConnectorServerFactory();
+ connectorServerFactory.setServer(mbeanServer);
+ connectorServerFactory.setServiceUrl(configuration.getServiceUrl());
+ connectorServerFactory.setRmiServerHost(configuration.getConnectorHost());
+ connectorServerFactory.setObjectName(new ObjectName(configuration.getObjectName()));
+ Map environment = new HashMap<>();
+ environment.put("jmx.remote.authenticator", jaasAuthenticator);
+ try {
+ connectorServerFactory.setEnvironment(environment);
+ connectorServerFactory.setAuthenticatorType(configuration.getAuthenticatorType());
+ connectorServerFactory.setSecured(configuration.isSecured());
+ connectorServerFactory.setKeyStorePath(configuration.getKeyStorePath());
+ connectorServerFactory.setkeyStoreProvider(configuration.getKeyStoreProvider());
+ connectorServerFactory.setKeyStorePassword(configuration.getKeyStorePassword());
+ connectorServerFactory.setTrustStorePath(configuration.getTrustStorePath());
+ connectorServerFactory.setTrustStoreProvider(configuration.getTrustStoreProvider());
+ connectorServerFactory.setTrustStorePassword(configuration.getTrustStorePassword());
+ connectorServerFactory.init();
+ } catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.error("Can't init JMXConnectorServer: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (connectorServerFactory != null) {
+ try {
+ connectorServerFactory.destroy();
+ } catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.warn("Error destroying ConnectorServerFactory", e);
+ }
+ connectorServerFactory = null;
+ }
+ if (mbeanServerFactory != null) {
+ try {
+ mbeanServerFactory.destroy();
+ } catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.warn("Error destroying MBeanServerFactory", e);
+ }
+ mbeanServerFactory = null;
+ }
+ if (rmiRegistryFactory != null) {
+ try {
+ rmiRegistryFactory.destroy();
+ } catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.warn("Error destroying RMIRegistryFactory", e);
+ }
+ rmiRegistryFactory = null;
+ }
+ }
+
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java
new file mode 100644
index 0000000000..3ff834e089
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java
@@ -0,0 +1,74 @@
+/**
+ * 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.core.server.management;
+
+
+import org.apache.activemq.artemis.core.config.JMXConnectorConfiguration;
+import org.apache.activemq.artemis.core.server.ActiveMQComponent;
+
+public class ManagementContext implements ActiveMQComponent {
+ private boolean isStarted = false;
+ private JMXAccessControlList accessControlList;
+ private JMXConnectorConfiguration jmxConnectorConfiguration;
+ private ManagementConnector mBeanServer;
+
+ @Override
+ public void start() throws Exception {
+ if (accessControlList != null) {
+ //if we are configured then assume we want to use the guard so set the system property
+ System.setProperty("javax.management.builder.initial", ArtemisMBeanServerBuilder.class.getCanonicalName());
+ ArtemisMBeanServerGuard guardHandler = new ArtemisMBeanServerGuard();
+ guardHandler.setJMXAccessControlList(accessControlList);
+ ArtemisMBeanServerBuilder.setGuard(guardHandler);
+ }
+
+ if (jmxConnectorConfiguration != null) {
+ mBeanServer = new ManagementConnector(jmxConnectorConfiguration);
+ mBeanServer.start();
+ }
+ isStarted = true;
+ }
+
+ @Override
+ public void stop() throws Exception {
+ isStarted = false;
+ if (mBeanServer != null) {
+ mBeanServer.stop();
+ }
+ }
+
+ @Override
+ public boolean isStarted() {
+ return isStarted;
+ }
+
+ public void setAccessControlList(JMXAccessControlList accessControlList) {
+ this.accessControlList = accessControlList;
+ }
+
+ public JMXAccessControlList getAccessControlList() {
+ return accessControlList;
+ }
+
+ public void setJmxConnectorConfiguration(JMXConnectorConfiguration jmxConnectorConfiguration) {
+ this.jmxConnectorConfiguration = jmxConnectorConfiguration;
+ }
+
+ public JMXConnectorConfiguration getJmxConnectorConfiguration() {
+ return jmxConnectorConfiguration;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/RmiRegistryFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/RmiRegistryFactory.java
new file mode 100644
index 0000000000..4c5cab2e95
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/RmiRegistryFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.core.server.management;
+
+import java.net.UnknownHostException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RmiRegistryFactory {
+
+ private int port = Registry.REGISTRY_PORT;
+ private Registry registry;
+ /**
+ * @return the port
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * @param port the port to set
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public Object getObject() throws Exception {
+ return registry;
+ }
+
+ public void init() throws RemoteException, UnknownHostException {
+ registry = LocateRegistry.createRegistry(port);
+ }
+
+ public void destroy() throws RemoteException {
+ if (registry != null) {
+ UnicastRemoteObject.unexportObject(registry, true);
+ }
+ }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/JMXAccessControlListTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/JMXAccessControlListTest.java
new file mode 100644
index 0000000000..d077f4465d
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/JMXAccessControlListTest.java
@@ -0,0 +1,153 @@
+/**
+ * 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.core.server.management;
+
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.util.List;
+
+public class JMXAccessControlListTest {
+
+ @Test
+ public void testBasicDomain() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToWhiteList("org.myDomain", null);
+ controlList.addToWhiteList("org.myDomain.foo", null);
+ Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:*")));
+ Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain.foo:*")));
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.bar:*")));
+ }
+
+ @Test
+ public void testBasicDomainWithProperty() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToWhiteList("org.myDomain", "type=foo");
+ controlList.addToWhiteList("org.myDomain.foo", "type=bar");
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain:*")));
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.foo:*")));
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.bar:*")));
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain:subType=foo")));
+
+ Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:type=foo")));
+ Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:subType=bar,type=foo")));
+ }
+
+ @Test
+ public void testBasicDomainWithWildCardProperty() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToWhiteList("org.myDomain", "type=*");
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain:*")));
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.foo:*")));
+ Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.bar:*")));
+ Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:type=foo")));
+ }
+
+ @Test
+ public void testBasicRole() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToRoleAccess("org.myDomain", null,"listSomething", "admin");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+
+ @Test
+ public void testBasicRoleWithKey() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToRoleAccess("org.myDomain", "type=foo","listSomething", "admin");
+ controlList.addToRoleAccess("org.myDomain", null,"listSomething", "view");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain:type=foo"), "listSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+
+ @Test
+ public void testBasicRoleWithKeyContainingQuotes() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToRoleAccess("org.myDomain", "type=foo","listSomething", "admin");
+ controlList.addToRoleAccess("org.myDomain", null,"listSomething", "view");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain:type=\"foo\""), "listSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+
+ @Test
+ public void testBasicRoleWithWildcardKey() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToRoleAccess("org.myDomain", "type=*","listSomething", "admin");
+ controlList.addToRoleAccess("org.myDomain", null,"listSomething", "view");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain:type=foo"), "listSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+
+ @Test
+ public void testMutipleBasicRoles() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToRoleAccess("org.myDomain", null, "listSomething", "admin", "view", "update");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin", "view", "update"});
+ }
+
+ @Test
+ public void testBasicRoleWithPrefix() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToRoleAccess("org.myDomain", null,"list*", "admin");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+
+ @Test
+ public void testBasicRoleWithBoth() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToRoleAccess("org.myDomain", null,"listSomething", "admin");
+ controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomethingMore");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"view"});
+ }
+
+ @Test
+ public void testBasicRoleWithDefaultsPrefix() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToDefaultAccess("setSomething","admin");
+ controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain.foo:*"), "setSomething");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+
+ @Test
+ public void testBasicRoleWithDefaultsWildcardPrefix() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToDefaultAccess("setSomething","admin");
+ controlList.addToDefaultAccess("set*","admin");
+ controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain.foo:*"), "setSomethingMore");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+
+ @Test
+ public void testBasicRoleWithDefaultscatchAllPrefix() throws MalformedObjectNameException {
+ JMXAccessControlList controlList = new JMXAccessControlList();
+ controlList.addToDefaultAccess("setSomething","admin");
+ controlList.addToDefaultAccess("*","admin");
+ controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
+ List roles = controlList.getRolesForObject(new ObjectName("org.myDomain.foo:*"), "setSomethingMore");
+ Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
+ }
+}
diff --git a/docs/user-manual/en/management.md b/docs/user-manual/en/management.md
index b25a88dfc8..9587d05064 100644
--- a/docs/user-manual/en/management.md
+++ b/docs/user-manual/en/management.md
@@ -296,16 +296,132 @@ setting `jmx-management-enabled` to `false` in
false
+
+#### Role Based Authentication with JMX
-If JMX is enabled, Apache ActiveMQ Artemis can be managed locally using `jconsole`.
+Although by default Artemis uses the Java Virtual Machine's `Platform MBeanServer`
+this is guarded using role based authentication that leverages Artemis's JAAS plugin support.
+This is configured via the `authorisation` element in the `management.xml` configuration file
+and can be used to restrict access to attributes and methods on mbeans.
+
+There are 3 elements within the `authorisation` element, `whitelist`, `default-access` and
+`role-access`, Lets discuss each in turn.
+
+Whitelist contains a list of mBeans that will by pass the authentication, this is typically
+used for any mbeans that are needed by the console to run etc. The default configuration is:
+
+```xml
+
+
+
+```
+This means that any mbean with the domain `hawtio` will be allowed access without authorisation. for instance
+`hawtio:plugin=artemis`. You can also use wildcards for the mBean properties so the following would also match.
+
+```xml
+
+
+
+```
+
+The `role-access`defines how roles are mapped to particular mBeans and its attributes and methods,
+the default configuration looks like:
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+This contains 1 match and will be applied to any mBean that has the domain `org.apache.activemq.apache`.
+Any access to any mBeans that have this domain are controlled by the `access` elements which contain a
+method and a set of roles. The method being invoked will be used to pick the closest matching method and
+the roles for this will be applied for access. For instance if you try the invoke a method called `listMessages` on an mBean
+with the `org.apache.activemq.apache` domain then this would match the `access` with the method of `list*`.
+You could also explicitly configure this by using the full method name, like so:
+
+```xml
+
+```
+You can also match specific mBeans within a domain by adding a key attribute that is used to match one of the properties
+on the mBean, like:
+
+```xml
+
+
+
+
+
+
+
+```
+You could also match a specific queue for instance :
+
+`org.apache.activemq.artemis:broker=,component=addresses,address="exampleAddress",subcomponent=queues,routing-type="anycast",queue="exampleQueue"`
+
+by configuring:
+
+```xml
+
+
+
+
+
+
+
+```
+
+Access to JMX mBean attributes are converted to method calls so these are controlled via the `set*`, `get*` and `is*`.
+The `*` access is the catch all for everything other method that isnt specifically matched.
+
+The `default-access` element is basically the catch all for every method call that isn't handled via the `role-access` configuration.
+This has teh same semantics as a `match` element.
> **Note**
>
-> Remote connections to JMX are not enabled by default for security
-> reasons. Please refer to [Java Management
-> guide](http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html)
-> to configure the server for remote management (system properties must
-> be set in `artemis.profile`).
+> If JMX is enabled, Apache ActiveMQ Artemis can *not* be managed locally using `jconsole` when connecting as a local process,
+> this is because jconsole does not using any authentication when connecting this way. If you want to use jconsole you will
+either have to disable authentication, by removing the `authentication` element or enable remote access.
+
+#### Configuring remote JMX Access
+
+By default remote JMX access to Artemis is disabled for security reasons.
+
+Artemis has a JMX agent which allows access to JMX mBeans remotely. This is configured via the `connector` element in the
+`management.xml` configuration file. To enable this you simpl ad the following xml:
+
+```xml
+
+```
+
+This exposes the agent remotely on the port 1099. If you were connecting via jconsole you would connect as a remote process
+using the service url `service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi` and an appropriate user name and password.
+
+You can also configure the connector using the following:
+
+- connector-host The host to expose the agent on
+- connector-port The port to expose the agent on
+- jmx-realm The jmx realm to use for authentication, defaults to `activemq` to match the JAAS configuration.
+- object-name The object name to expose the remote connector on, default is `connector:name=rmi`
+
+> **Note**
+>
+> It is important to note that the rmi registry will pick an ip address to bind to, If you have a multi IP addresses/NICs
+> present on the system then you can choose the ip address to use by adding the following to artemis.profile
+> `-Djava.rmi.server.hostname=localhost`
+
+> **Note**
+>
+> Remote connections using the default JVM Agent not enabled by default as Artemis exposes the mBean Server via its own configuration.
+> This is so Artemis can leverage the JAAS authentication layer via JMX. If you want to expose this then you will need to
+> disable both the connector and the authorisation by removing them from the `management.xml` configuration.
+> Please refer to [Java Management guide](http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html)
+> to configure the server for remote management (system properties must be set in `artemis.profile`).
By default, Apache ActiveMQ Artemis server uses the JMX domain "org.apache.activemq.artemis".
To manage several Apache ActiveMQ Artemis servers from the *same* MBeanServer, the JMX
@@ -315,12 +431,6 @@ domain can be configured for each individual Apache ActiveMQ Artemis server by s
my.org.apache.activemq
-#### MBeanServer configuration
-
-When Apache ActiveMQ Artemis is run in standalone, it uses the Java Virtual Machine's
-`Platform MBeanServer` to register its MBeans. By default, [Jolokia](http://www.jolokia.org/)
-is also deployed to allow access to the MBean server via [REST](https://en.wikipedia.org/wiki/Representational_state_transfer).
-
### Example
See the [Examples](examples.md) chapter for an example which shows how to use a remote connection to JMX
@@ -333,12 +443,23 @@ HTTP agent deployed as a Web Application. Jolokia is a remote
JMX-over-HTTP bridge that exposes MBeans. For a full guide as
to how to use it refer to [Jolokia Documentation](http://www.jolokia.org/documentation.html),
however a simple example to query the broker's version would
-be to use a browser and go to the URL [http://localhost:8161/jolokia/read/org.apache.activemq.artemis:broker="0.0.0.0"/Version]().
+be to use a browser and go to the URL [http://username:password@localhost:8161/jolokia/read/org.apache.activemq.artemis:broker="0.0.0.0"/Version]().
This would give you back something like the following:
{"request":{"mbean":"org.apache.activemq.artemis:broker=\"0.0.0.0\"","attribute":"Version","type":"read"},"value":"2.0.0-SNAPSHOT","timestamp":1487017918,"status":200}
+### JMX and the Console
+
+The console that ships with Artemis uses Jolokia under the covers which in turn uses JMX. This will use the authentication
+configuration in the `management.xml` file as described in the previous section. This means that when mBeans are accessed
+via the console the credentials used to log into the console and the roles associated with them. By default access to the
+console is only allow via users with the amq role. This is configured in the `artemis.profile` via the system property `-Dhawtio.role=amq`.
+You can configure multiple roles by changing this to `-Dhawtio.roles=amq,view,update`.
+
+If a user doesn't have the correct role to invoke a specific operation then this will display an authorisation exception
+in the console.
+
## Using Management Via Apache ActiveMQ Artemis API
The management API in ActiveMQ Artemis is accessed by sending Core Client messages
diff --git a/examples/features/standard/jmx/pom.xml b/examples/features/standard/jmx/pom.xml
index bb168d6966..dae00fd5c2 100644
--- a/examples/features/standard/jmx/pom.xml
+++ b/examples/features/standard/jmx/pom.xml
@@ -72,6 +72,7 @@ under the License.
cli
+ true
${noServer}
true
tcp://localhost:61616
diff --git a/examples/features/standard/jmx/readme.html b/examples/features/standard/jmx/readme.html
index 2dfc466472..fe981c60e5 100644
--- a/examples/features/standard/jmx/readme.html
+++ b/examples/features/standard/jmx/readme.html
@@ -34,16 +34,12 @@ under the License.
Example configuration
ActiveMQ Artemis exposes its managed resources by default on the platform MBeanServer.
- To access this MBeanServer remotely, the Java Virtual machine must be started with system properties:
+
To access this MBeanServer remotely, add the following to the management.xml configuration:
- -Dcom.sun.management.jmxremote
- -Dcom.sun.management.jmxremote.port=3000
- -Dcom.sun.management.jmxremote.ssl=false
- -Dcom.sun.management.jmxremote.authenticate=false
+
- These properties are explained in the Java management guide
- (please note that for this example, we will disable user authentication for simplicity sake).
- With these properties, ActiveMQ Artemis server will be manageable remotely using standard JMX URL on port 3000
.
+ If the example does not work then try changing the host to the ip address of the machine running this example on
+ With these properties, ActiveMQ Artemis server will be manageable remotely using standard JMX URL on port 1099
.
Example step-by-step
diff --git a/examples/features/standard/jmx/src/main/java/org/apache/activemq/artemis/jms/example/JMXExample.java b/examples/features/standard/jmx/src/main/java/org/apache/activemq/artemis/jms/example/JMXExample.java
index 73658ed021..e8beee9f8a 100644
--- a/examples/features/standard/jmx/src/main/java/org/apache/activemq/artemis/jms/example/JMXExample.java
+++ b/examples/features/standard/jmx/src/main/java/org/apache/activemq/artemis/jms/example/JMXExample.java
@@ -44,7 +44,7 @@ import org.apache.activemq.artemis.jms.client.ActiveMQTextMessage;
*/
public class JMXExample {
- private static final String JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:3000/jmxrmi";
+ private static final String JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi";
public static void main(final String[] args) throws Exception {
QueueConnection connection = null;
@@ -79,7 +79,11 @@ public class JMXExample {
ObjectName on = ObjectNameBuilder.DEFAULT.getQueueObjectName(SimpleString.toSimpleString(queue.getQueueName()), SimpleString.toSimpleString(queue.getQueueName()), RoutingType.ANYCAST);
// Step 10. Create JMX Connector to connect to the server's MBeanServer
- JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(JMXExample.JMX_URL), new HashMap());
+ HashMap env = new HashMap();
+ String[] creds = {"admin", "password"};
+ env.put(JMXConnector.CREDENTIALS, creds);
+
+ JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(JMXExample.JMX_URL), env);
// Step 11. Retrieve the MBeanServerConnection
MBeanServerConnection mbsc = connector.getMBeanServerConnection();
diff --git a/examples/features/standard/jmx/src/main/resources/activemq/server0/management.xml b/examples/features/standard/jmx/src/main/resources/activemq/server0/management.xml
new file mode 100644
index 0000000000..cbcd3d88c0
--- /dev/null
+++ b/examples/features/standard/jmx/src/main/resources/activemq/server0/management.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/DummyLoginModule.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/DummyLoginModule.java
new file mode 100644
index 0000000000..1ecf824252
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/DummyLoginModule.java
@@ -0,0 +1,94 @@
+/**
+ * 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.tests.integration.management;
+
+
+import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.io.IOException;
+import java.util.Map;
+
+public class DummyLoginModule implements LoginModule {
+ private Subject subject;
+ private CallbackHandler callbackHandler;
+ private Map sharedState;
+ private Map options;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ this.sharedState = sharedState;
+ this.options = options;
+ System.out.println("DummyLoginModule.initialize");
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ Callback[] callbacks = new Callback[2];
+
+ callbacks[0] = new NameCallback("Username: ");
+ callbacks[1] = new PasswordCallback("Password: ", false);
+ try {
+ callbackHandler.handle(callbacks);
+ } catch (IOException ioe) {
+ throw new LoginException(ioe.getMessage());
+ } catch (UnsupportedCallbackException uce) {
+ throw new LoginException(uce.getMessage() + " not available to obtain information from user");
+ }
+ String user = ((NameCallback) callbacks[0]).getName();
+ char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
+ if (tmpPassword == null) {
+ tmpPassword = new char[0];
+ }
+ if (user == null) {
+ throw new FailedLoginException("User is null");
+ }
+ subject.getPrincipals().add(new RolePrincipal("amq"));
+ // String password = users.getProperty(user);
+
+ /*if (password == null) {
+ throw new FailedLoginException("User does not exist: " + user);
+ }*/
+
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return false;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ return false;
+ }
+}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementTestBase.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementTestBase.java
index ac09745e55..8836ba8299 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementTestBase.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementTestBase.java
@@ -76,6 +76,10 @@ public abstract class ManagementTestBase extends ActiveMQTestBase {
public void setUp() throws Exception {
super.setUp();
+ createMBeanServer();
+ }
+
+ protected void createMBeanServer() {
mbeanServer = MBeanServerFactory.createMBeanServer();
}