You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by jb...@apache.org on 2020/11/30 17:12:43 UTC

[activemq-artemis] branch master updated: ARTEMIS-3014 Fix JMX RBAC guard

This is an automated email from the ASF dual-hosted git repository.

jbertram pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/master by this push:
     new 7eb22c1  ARTEMIS-3014 Fix JMX RBAC guard
     new f64dfea  This closes #3364
7eb22c1 is described below

commit 7eb22c18db4bd81ef79c619173579cb54442fa9e
Author: Domenico Francesco Bruscino <br...@apache.org>
AuthorDate: Sat Nov 28 23:10:05 2020 +0100

    ARTEMIS-3014 Fix JMX RBAC guard
---
 .../artemis/cli/factory/jmx/ManagementFactory.java |   2 +
 .../apache/activemq/artemis/util/ServerUtil.java   |  12 +-
 .../core/server/management/ManagementContext.java  |  18 +--
 tests/smoke-tests/pom.xml                          |  20 +++
 .../main/resources/servers/jmx-rbac/management.xml |  52 ++++++++
 .../artemis/tests/smoke/jmxrbac/JmxRBACTest.java   | 137 +++++++++++++++++++++
 6 files changed, 231 insertions(+), 10 deletions(-)

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
index 826dc8e..b41a1ef 100644
--- 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
@@ -134,6 +134,8 @@ public class ManagementFactory {
          context.setSecurityManager(securityManager);
       }
 
+      context.init();
+
       return context;
    }
 }
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/util/ServerUtil.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/util/ServerUtil.java
index 7c8675b..6d7cf8f 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/util/ServerUtil.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/util/ServerUtil.java
@@ -105,14 +105,22 @@ public class ServerUtil {
    }
 
    public static boolean waitForServerToStart(int id, int timeout) throws InterruptedException {
-      return waitForServerToStart("tcp://localhost:" + (61616 + id), timeout);
+      return waitForServerToStart(id, null, null, timeout);
+   }
+
+   public static boolean waitForServerToStart(int id, String username, String password, int timeout) throws InterruptedException {
+      return waitForServerToStart("tcp://localhost:" + (61616 + id), username, password, timeout);
    }
 
    public static boolean waitForServerToStart(String uri, long timeout) throws InterruptedException {
+      return waitForServerToStart(uri, null, null, timeout);
+   }
+
+   public static boolean waitForServerToStart(String uri, String username, String password, long timeout) throws InterruptedException {
       long realTimeout = System.currentTimeMillis() + timeout;
       while (System.currentTimeMillis() < realTimeout) {
          try (ActiveMQConnectionFactory cf = ActiveMQJMSClient.createConnectionFactory(uri, null)) {
-            cf.createConnection().close();
+            cf.createConnection(username, password).close();
             System.out.println("server " + uri + " started");
          } catch (Exception e) {
             System.out.println("awaiting server " + uri + " start at ");
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
index f518c2d..db308ac 100644
--- 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
@@ -34,6 +34,16 @@ public class ManagementContext implements ServiceComponent {
    private ArtemisMBeanServerGuard guardHandler;
    private ActiveMQSecurityManager securityManager;
 
+   public void init() {
+      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());
+         guardHandler = new ArtemisMBeanServerGuard();
+         guardHandler.setJMXAccessControlList(accessControlList);
+         ArtemisMBeanServerBuilder.setGuard(guardHandler);
+      }
+   }
+
    @Override
    public void start() throws Exception {
       if (isStarted) {
@@ -44,14 +54,6 @@ public class ManagementContext implements ServiceComponent {
             return;
          }
          isStarted = true;
-         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());
-            guardHandler = new ArtemisMBeanServerGuard();
-            guardHandler.setJMXAccessControlList(accessControlList);
-            ArtemisMBeanServerBuilder.setGuard(guardHandler);
-         }
-
          if (jmxConnectorConfiguration != null) {
             mBeanServer = new ManagementConnector(jmxConnectorConfiguration, securityManager);
             mBeanServer.start();
diff --git a/tests/smoke-tests/pom.xml b/tests/smoke-tests/pom.xml
index 5bf24ce..3c5bd43 100644
--- a/tests/smoke-tests/pom.xml
+++ b/tests/smoke-tests/pom.xml
@@ -406,6 +406,26 @@
                </execution>
                <execution>
                   <phase>test-compile</phase>
+                  <id>create-createJMXRBAC</id>
+                  <goals>
+                     <goal>create</goal>
+                  </goals>
+                  <configuration>
+                     <role>amq</role>
+                     <user>admin</user>
+                     <password>admin</password>
+                     <allowAnonymous>false</allowAnonymous>
+                     <instance>${basedir}/target/jmx-rbac</instance>
+                     <configuration>${basedir}/target/classes/servers/jmx-rbac</configuration>
+                     <args>
+                        <!-- this is needed to run the server remotely -->
+                        <arg>--java-options</arg>
+                        <arg>-Djava.rmi.server.hostname=localhost</arg>
+                     </args>
+                  </configuration>
+               </execution>
+               <execution>
+                  <phase>test-compile</phase>
                   <id>create-createAuditLogging</id>
                   <goals>
                      <goal>create</goal>
diff --git a/tests/smoke-tests/src/main/resources/servers/jmx-rbac/management.xml b/tests/smoke-tests/src/main/resources/servers/jmx-rbac/management.xml
new file mode 100644
index 0000000..44e491e
--- /dev/null
+++ b/tests/smoke-tests/src/main/resources/servers/jmx-rbac/management.xml
@@ -0,0 +1,52 @@
+<?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="10099"/>
+   <authorisation>
+      <whitelist>
+         <entry domain="hawtio"/>
+      </whitelist>
+      <default-access>
+         <access method="list*" roles="amq"/>
+         <access method="get*" roles="amq"/>
+         <access method="is*" roles="amq"/>
+         <access method="set*" roles="amq"/>
+         <access method="*" roles="amq"/>
+      </default-access>
+      <role-access>
+         <match domain="java.lang" key="type=Memory">
+            <access method="gc" roles="amq-user"/>
+         </match>
+         <match domain="org.apache.activemq.artemis">
+            <access method="list*" roles="amq"/>
+            <access method="get*" roles="amq"/>
+            <access method="is*" roles="amq"/>
+            <access method="set*" roles="amq"/>
+            <access method="*" roles="amq"/>
+         </match>
+         <!--example of how to configure a specific object-->
+         <!--<match domain="org.apache.activemq.artemis" 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
diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/jmxrbac/JmxRBACTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/jmxrbac/JmxRBACTest.java
new file mode 100644
index 0000000..c25eced
--- /dev/null
+++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/jmxrbac/JmxRBACTest.java
@@ -0,0 +1,137 @@
+/**
+ * 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.smoke.jmxrbac;
+
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import java.util.Collections;
+
+import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
+import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl;
+import org.apache.activemq.artemis.api.core.management.ObjectNameBuilder;
+import org.apache.activemq.artemis.tests.smoke.common.SmokeTestBase;
+import org.apache.activemq.artemis.util.ServerUtil;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class JmxRBACTest extends SmokeTestBase {
+   // This test will use a smoke created by the pom on this project (smoke-tsts)
+
+   private static final String JMX_SERVER_HOSTNAME = "localhost";
+   private static final int JMX_SERVER_PORT = 10099;
+
+   public static final String SERVER_NAME_0 = "jmx-rbac";
+
+   public static final String SERVER_ADMIN = "admin";
+   public static final String SERVER_USER = "user";
+
+
+   @Before
+   public void before() throws Exception {
+      cleanupData(SERVER_NAME_0);
+      disableCheckThread();
+      startServer(SERVER_NAME_0, 0, 0);
+      ServerUtil.waitForServerToStart(0, SERVER_ADMIN, SERVER_ADMIN, 30000);
+   }
+
+   @Test
+   public void testManagementRoleAccess() throws Exception {
+      // Without this, the RMI server would bind to the default interface IP (the user's local IP mostly)
+      System.setProperty("java.rmi.server.hostname", JMX_SERVER_HOSTNAME);
+
+      // I don't specify both ports here manually on purpose. See actual RMI registry connection port extraction below.
+      String urlString = "service:jmx:rmi:///jndi/rmi://" + JMX_SERVER_HOSTNAME + ":" + JMX_SERVER_PORT + "/jmxrmi";
+
+      JMXServiceURL url = new JMXServiceURL(urlString);
+      JMXConnector jmxConnector;
+
+      try {
+         //Connect using the admin.
+         jmxConnector = JMXConnectorFactory.connect(url, Collections.singletonMap(
+            "jmx.remote.credentials", new String[] {SERVER_ADMIN, SERVER_ADMIN}));
+         System.out.println("Successfully connected to: " + urlString);
+      } catch (Exception e) {
+         jmxConnector = null;
+         e.printStackTrace();
+         Assert.fail(e.getMessage());
+      }
+
+      try {
+         //Create an user.
+         MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
+         String brokerName = "0.0.0.0";  // configured e.g. in broker.xml <broker-name> element
+         ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true);
+         ActiveMQServerControl activeMQServerControl = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, objectNameBuilder.getActiveMQServerObjectName(), ActiveMQServerControl.class, false);
+         ObjectName memoryObjectName = new ObjectName("java.lang:type=Memory");
+
+         try {
+            activeMQServerControl.removeUser(SERVER_USER);
+         } catch (Exception ignore) {
+         }
+         activeMQServerControl.addUser(SERVER_USER, SERVER_USER, "amq-user", true);
+
+         activeMQServerControl.getVersion();
+
+         try {
+            mBeanServerConnection.invoke(memoryObjectName, "gc", null, null);
+            Assert.fail(SERVER_ADMIN + " should not access to " + memoryObjectName);
+         } catch (Exception e) {
+            Assert.assertEquals(SecurityException.class, e.getClass());
+         }
+      } finally {
+         jmxConnector.close();
+      }
+
+      try {
+         //Connect using an user.
+         jmxConnector = JMXConnectorFactory.connect(url, Collections.singletonMap(
+            "jmx.remote.credentials", new String[] {SERVER_USER, SERVER_USER}));
+         System.out.println("Successfully connected to: " + urlString);
+      } catch (Exception e) {
+         jmxConnector = null;
+         e.printStackTrace();
+         Assert.fail(e.getMessage());
+      }
+
+
+      try {
+         MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
+         String brokerName = "0.0.0.0";  // configured e.g. in broker.xml <broker-name> element
+         ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true);
+         ActiveMQServerControl activeMQServerControl = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, objectNameBuilder.getActiveMQServerObjectName(), ActiveMQServerControl.class, false);
+         ObjectName memoryObjectName = new ObjectName("java.lang:type=Memory");
+
+         mBeanServerConnection.invoke(memoryObjectName, "gc", null, null);
+
+         try {
+            activeMQServerControl.getVersion();
+            Assert.fail(SERVER_USER + " should not access to " + objectNameBuilder.getActiveMQServerObjectName());
+         } catch (Exception e) {
+            Assert.assertEquals(SecurityException.class, e.getClass());
+         }
+      } finally {
+         jmxConnector.close();
+      }
+   }
+}