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(); }