ARTEMIS-3014 Fix JMX RBAC guard
This commit is contained in:
parent
c64d4d62e3
commit
7eb22c18db
|
@ -134,6 +134,8 @@ public class ManagementFactory {
|
||||||
context.setSecurityManager(securityManager);
|
context.setSecurityManager(securityManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.init();
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,14 +105,22 @@ public class ServerUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean waitForServerToStart(int id, int timeout) throws InterruptedException {
|
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 {
|
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;
|
long realTimeout = System.currentTimeMillis() + timeout;
|
||||||
while (System.currentTimeMillis() < realTimeout) {
|
while (System.currentTimeMillis() < realTimeout) {
|
||||||
try (ActiveMQConnectionFactory cf = ActiveMQJMSClient.createConnectionFactory(uri, null)) {
|
try (ActiveMQConnectionFactory cf = ActiveMQJMSClient.createConnectionFactory(uri, null)) {
|
||||||
cf.createConnection().close();
|
cf.createConnection(username, password).close();
|
||||||
System.out.println("server " + uri + " started");
|
System.out.println("server " + uri + " started");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("awaiting server " + uri + " start at ");
|
System.out.println("awaiting server " + uri + " start at ");
|
||||||
|
|
|
@ -34,6 +34,16 @@ public class ManagementContext implements ServiceComponent {
|
||||||
private ArtemisMBeanServerGuard guardHandler;
|
private ArtemisMBeanServerGuard guardHandler;
|
||||||
private ActiveMQSecurityManager securityManager;
|
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
|
@Override
|
||||||
public void start() throws Exception {
|
public void start() throws Exception {
|
||||||
if (isStarted) {
|
if (isStarted) {
|
||||||
|
@ -44,14 +54,6 @@ public class ManagementContext implements ServiceComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isStarted = true;
|
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) {
|
if (jmxConnectorConfiguration != null) {
|
||||||
mBeanServer = new ManagementConnector(jmxConnectorConfiguration, securityManager);
|
mBeanServer = new ManagementConnector(jmxConnectorConfiguration, securityManager);
|
||||||
mBeanServer.start();
|
mBeanServer.start();
|
||||||
|
|
|
@ -404,6 +404,26 @@
|
||||||
</args>
|
</args>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</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>
|
<execution>
|
||||||
<phase>test-compile</phase>
|
<phase>test-compile</phase>
|
||||||
<id>create-createAuditLogging</id>
|
<id>create-createAuditLogging</id>
|
||||||
|
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue