Repository: activemq-artemis Updated Branches: refs/heads/master d0f3f6719 -> b09ea433b
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/MBeanServerFactory.java ---------------------------------------------------------------------- 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 0000000..65ca9be --- /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); + } + } + +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java ---------------------------------------------------------------------- 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 0000000..d867bd8 --- /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<String, Object> 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; + } + } + +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java ---------------------------------------------------------------------- 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 0000000..3ff834e --- /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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/RmiRegistryFactory.java ---------------------------------------------------------------------- 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 0000000..4c5cab2 --- /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); + } + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/JMXAccessControlListTest.java ---------------------------------------------------------------------- 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 0000000..d077f44 --- /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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain.foo:*"), "setSomethingMore"); + Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"}); + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/docs/user-manual/en/management.md ---------------------------------------------------------------------- diff --git a/docs/user-manual/en/management.md b/docs/user-manual/en/management.md index b25a88d..9587d05 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 to disable JMX management for Apache ActiveMQ Artemis --> <jmx-management-enabled>false</jmx-management-enabled> + +#### 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 +<whitelist> + <entry domain="hawtio"/> +</whitelist> +``` +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 +<whitelist> + <entry domain="hawtio" key="type=*"/> +</whitelist> +``` + +The `role-access`defines how roles are mapped to particular mBeans and its attributes and methods, +the default configuration looks like: + +```xml +<role-access> + <match domain="org.apache.activemq.apache"> + <access method="list*" roles="view,update,amq"/> + <access method="get*" roles="view,update,amq"/> + <access method="is*" roles="view,update,amq"/> + <access method="set*" roles="update,amq"/> + <access method="*" roles="amq"/> + </match> +</role-access> +``` +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 +<access method="listMessages" roles="view,update,amq"/> +``` +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 +<match domain="org.apache.activemq.apache" key="subcomponent=queues"> + <access method="list*" roles="view,update,amq"/> + <access method="get*" roles="view,update,amq"/> + <access method="is*" roles="view,update,amq"/> + <access method="set*" roles="update,amq"/> + <access method="*" roles="amq"/> +</match> +``` +You could also match a specific queue for instance : + +`org.apache.activemq.artemis:broker=<brokerName>,component=addresses,address="exampleAddress",subcomponent=queues,routing-type="anycast",queue="exampleQueue"` + +by configuring: + +```xml +<match domain="org.apache.activemq.apache" key="queue=exampleQueue"> + <access method="list*" roles="view,update,amq"/> + <access method="get*" roles="view,update,amq"/> + <access method="is*" roles="view,update,amq"/> + <access method="set*" roles="update,amq"/> + <access method="*" roles="amq"/> +</match> +``` + +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** +> +> 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 +<connector connector-port="1099"/> +``` + +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** > -> 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`). +> 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 <!-- use a specific JMX domain for ActiveMQ Artemis MBeans --> <jmx-domain>my.org.apache.activemq</jmx-domain> -#### 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 http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/examples/features/standard/jmx/pom.xml ---------------------------------------------------------------------- diff --git a/examples/features/standard/jmx/pom.xml b/examples/features/standard/jmx/pom.xml index bb168d6..dae00fd 100644 --- a/examples/features/standard/jmx/pom.xml +++ b/examples/features/standard/jmx/pom.xml @@ -72,6 +72,7 @@ under the License. <goal>cli</goal> </goals> <configuration> + <debug>true</debug> <ignore>${noServer}</ignore> <spawn>true</spawn> <testURI>tcp://localhost:61616</testURI> http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/examples/features/standard/jmx/readme.html ---------------------------------------------------------------------- diff --git a/examples/features/standard/jmx/readme.html b/examples/features/standard/jmx/readme.html index 2dfc466..fe981c6 100644 --- a/examples/features/standard/jmx/readme.html +++ b/examples/features/standard/jmx/readme.html @@ -34,16 +34,12 @@ under the License. <h2>Example configuration</h2> <p>ActiveMQ Artemis exposes its managed resources by default on the platform MBeanServer.</p> - <p>To access this MBeanServer remotely, the Java Virtual machine must be started with system properties: + <p>To access this MBeanServer remotely, add the following to the management.xml configuration: <pre class="prettyprint"> - <code>-Dcom.sun.management.jmxremote - -Dcom.sun.management.jmxremote.port=3000 - -Dcom.sun.management.jmxremote.ssl=false - -Dcom.sun.management.jmxremote.authenticate=false</code> - </pre> - <p>These properties are explained in the Java <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html#gdenl">management guide</a> - (please note that for this example, we will disable user authentication for simplicity sake).</p> - <p>With these properties, ActiveMQ Artemis server will be manageable remotely using standard JMX URL on port <code>3000</code>.</p> + <code><connector connector-port="1099" connector-host="127.0.0.1"/></code> + </pre> + <p>If the example does not work then try changing the host to the ip address of the machine running this example on</p> + <p>With these properties, ActiveMQ Artemis server will be manageable remotely using standard JMX URL on port <code>1099</code>.</p> </p> <h2>Example step-by-step</h2> http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/examples/features/standard/jmx/src/main/java/org/apache/activemq/artemis/jms/example/JMXExample.java ---------------------------------------------------------------------- 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 73658ed..e8beee9 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(); http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/examples/features/standard/jmx/src/main/resources/activemq/server0/management.xml ---------------------------------------------------------------------- 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 0000000..cbcd3d8 --- /dev/null +++ b/examples/features/standard/jmx/src/main/resources/activemq/server0/management.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<management-context xmlns="http://activemq.org/schema"> + <connector connector-port="1099" connector-host="127.0.0.1"/> + <authorisation> + <whitelist> + <entry domain="hawtio"/> + </whitelist> + <default-access> + <access method="list*" roles="view,update,amq"/> + <access method="get*" roles="view,update,amq"/> + <access method="is*" roles="view,update,amq"/> + <access method="set*" roles="update,amq"/> + <access method="*" roles="amq"/> + </default-access> + <role-access> + <match domain="org.apache.activemq.apache"> + <access method="list*" roles="view,update,amq"/> + <access method="get*" roles="view,update,amq"/> + <access method="is*" roles="view,update,amq"/> + <access method="set*" roles="update,amq"/> + <access method="*" roles="amq"/> + </match> + <!--example of how to configure a specific object--> + <!--<match domain="org.apache.activemq.apache" key="subcomponent=queues"> + <access method="list*" roles="view,update,amq"/> + <access method="get*" roles="view,update,amq"/> + <access method="is*" roles="view,update,amq"/> + <access method="set*" roles="update,amq"/> + <access method="*" roles="amq"/> + </match>--> + </role-access> + </authorisation> +</management-context> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/DummyLoginModule.java ---------------------------------------------------------------------- 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 0000000..1ecf824 --- /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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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<String, ?> sharedState; + private Map<String, ?> options; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> 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; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/62a2b14d/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementTestBase.java ---------------------------------------------------------------------- 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 ac09745..8836ba8 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(); }