diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java index a834779c1f..e364723121 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java @@ -17,6 +17,8 @@ package org.apache.activemq.artemis.core.security.impl; import javax.security.auth.Subject; +import java.security.AccessControlContext; +import java.security.AccessController; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -26,6 +28,7 @@ import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.management.CoreNotificationType; import org.apache.activemq.artemis.api.core.management.ManagementHelper; +import org.apache.activemq.artemis.core.management.impl.ManagementRemotingConnection; import org.apache.activemq.artemis.core.remoting.CertificateUtil; import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.Role; @@ -168,6 +171,16 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC subject = cacheEntry.getB(); validatedUser = getUserFromSubject(subject); } + } else { + if (user == null && password == null && connection instanceof ManagementRemotingConnection) { + AccessControlContext accessControlContext = AccessController.getContext(); + if (accessControlContext != null) { + check = false; + userIsValid = true; + subject = Subject.getSubject(accessControlContext); + validatedUser = getUserFromSubject(subject); + } + } } if (check) { if (securityManager instanceof ActiveMQSecurityManager5) { @@ -382,6 +395,14 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC */ private Subject getSubjectForAuthorization(SecurityAuth auth, ActiveMQSecurityManager5 securityManager) { Pair cached = authenticationCache.getIfPresent(createAuthenticationCacheKey(auth.getUsername(), auth.getPassword(), auth.getRemotingConnection())); + + if (cached == null && auth.getUsername() == null && auth.getPassword() == null && auth.getRemotingConnection() instanceof ManagementRemotingConnection) { + AccessControlContext accessControlContext = AccessController.getContext(); + if (accessControlContext != null) { + cached = new Pair<>(true, Subject.getSubject(accessControlContext)); + } + } + /* * We don't need to worry about the cached boolean being false as users always have to * successfully authenticate before requesting authorization for anything. 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 index bc0e1517a4..edd31ed7ba 100644 --- 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 @@ -26,7 +26,11 @@ import javax.management.remote.JMXServiceURL; import java.util.Collections; import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; +import org.apache.activemq.artemis.api.core.Message; +import org.apache.activemq.artemis.api.core.RoutingType; +import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl; +import org.apache.activemq.artemis.api.core.management.AddressControl; 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; @@ -40,11 +44,14 @@ public class JmxRBACTest extends SmokeTestBase { private static final String JMX_SERVER_HOSTNAME = "localhost"; private static final int JMX_SERVER_PORT = 10099; + public static final String BROKER_NAME = "0.0.0.0"; + public static final String SERVER_NAME_0 = "jmx-rbac"; public static final String SERVER_ADMIN = "admin"; public static final String SERVER_USER = "user"; + public static final String ADDRESS_TEST = "TEST"; @Before public void before() throws Exception { @@ -79,8 +86,7 @@ public class JmxRBACTest extends SmokeTestBase { try { //Create an user. MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); - String brokerName = "0.0.0.0"; // configured e.g. in broker.xml element - ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true); + ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), BROKER_NAME, true); ActiveMQServerControl activeMQServerControl = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, objectNameBuilder.getActiveMQServerObjectName(), ActiveMQServerControl.class, false); ObjectName memoryObjectName = new ObjectName("java.lang:type=Memory"); @@ -116,8 +122,7 @@ public class JmxRBACTest extends SmokeTestBase { try { MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); - String brokerName = "0.0.0.0"; // configured e.g. in broker.xml element - ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true); + ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), BROKER_NAME, true); ActiveMQServerControl activeMQServerControl = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, objectNameBuilder.getActiveMQServerObjectName(), ActiveMQServerControl.class, false); ObjectName memoryObjectName = new ObjectName("java.lang:type=Memory"); @@ -133,4 +138,73 @@ public class JmxRBACTest extends SmokeTestBase { jmxConnector.close(); } } + + @Test + public void testSendMessageWithoutUserAndPassword() 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 { + MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); + ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), BROKER_NAME, true); + ActiveMQServerControl activeMQServerControl = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, objectNameBuilder.getActiveMQServerObjectName(), ActiveMQServerControl.class, false); + + activeMQServerControl.createAddress(ADDRESS_TEST, RoutingType.MULTICAST.name()); + AddressControl testAddressControl = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, objectNameBuilder.getAddressObjectName(SimpleString.toSimpleString(ADDRESS_TEST)), AddressControl.class, false); + + testAddressControl.sendMessage(null, Message.TEXT_TYPE, ADDRESS_TEST, true, null, null); + + + try { + activeMQServerControl.removeUser(SERVER_USER); + } catch (Exception ignore) { + } + activeMQServerControl.addUser(SERVER_USER, SERVER_USER, "amq-user", true); + } 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(); + ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), BROKER_NAME, true); + AddressControl testAddressControl = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, objectNameBuilder.getAddressObjectName(SimpleString.toSimpleString("TEST")), AddressControl.class, false); + + try { + testAddressControl.sendMessage(null, Message.TEXT_TYPE, ADDRESS_TEST, true, null, null); + Assert.fail(SERVER_USER + " should not have permissions to send a message to the address " + ADDRESS_TEST); + } catch (Exception e) { + Assert.assertEquals(SecurityException.class, e.getClass()); + } + } finally { + jmxConnector.close(); + } + } }