From a609b7548903e9fda39300bb04d4e44e6c52f709 Mon Sep 17 00:00:00 2001 From: Anmol Saxena Date: Thu, 19 Feb 2026 04:37:44 +0530 Subject: [PATCH 1/3] AMQ-9857 : 1699 : ManagementContext should support sslContext injection. --- .../apache/activemq/broker/BrokerService.java | 6 ++ .../broker/jmx/ManagementContext.java | 72 ++++++++++++++++- .../ManagementContextXBeanConfigTest.java | 77 +++++++++++++++++-- ...ment-context-test-ssl-context-explicit.xml | 50 ++++++++++++ .../management-context-test-ssl-context.xml | 42 ++++++++++ 5 files changed, 236 insertions(+), 11 deletions(-) create mode 100644 activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context-explicit.xml create mode 100644 activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context.xml diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java index e51d5214804..13af6315bfd 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java @@ -2624,6 +2624,12 @@ public ConnectionContext getAdminConnectionContext() throws Exception { protected void startManagementContext() throws Exception { getManagementContext().setBrokerName(brokerName); + // Reuse the broker-level SSL context for JMX by default. + // This avoids duplicating SSL config in activemq.xml while still allowing an + // explicit managementContext sslContext to override when one is needed + if (getManagementContext().getSslContext() == null && getSslContext() != null) { + getManagementContext().setSslContext(getSslContext()); + } getManagementContext().start(); adminView = new BrokerView(this, null); ObjectName objectName = getBrokerObjectName(); diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/ManagementContext.java b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/ManagementContext.java index 28c6f8c3d09..a92028a214c 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/ManagementContext.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/ManagementContext.java @@ -17,6 +17,7 @@ package org.apache.activemq.broker.jmx; import java.io.IOException; +import java.io.Serializable; import java.lang.management.ManagementFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -27,14 +28,19 @@ import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.net.ServerSocket; +import java.security.GeneralSecurityException; import javax.management.Attribute; import javax.management.InstanceNotFoundException; @@ -50,8 +56,12 @@ import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnectorServer; import javax.management.remote.rmi.RMIJRMPServerImpl; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocketFactory; +import javax.rmi.ssl.SslRMIClientSocketFactory; import org.apache.activemq.Service; +import org.apache.activemq.broker.SslContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -115,6 +125,7 @@ public class ManagementContext implements Service { private List suppressMBeanList; private Remote serverStub; private RMIJRMPServerImpl server; + private SslContext sslContext; public ManagementContext() { this(null); @@ -282,6 +293,15 @@ public void setBrokerName(String brokerName) { this.brokerName = brokerName; } + public SslContext getSslContext() { + return sslContext; + } + + public void setSslContext(SslContext sslContext) { + this.sslContext = sslContext; + } + + /** * @return Returns the jmxDomainName. */ @@ -578,13 +598,35 @@ private void createConnector(MBeanServer mbeanServer) throws IOException { // This is handy to use if you have a firewall and need to force JMX to use fixed ports. rmiServer = ""+getConnectorHost()+":" + rmiServerPort; } + + Map connectorEnvironment = new HashMap<>(); + if (environment != null) { + connectorEnvironment.putAll(environment); + } + + RMIClientSocketFactory clientSocketFactory = null; + RMIServerSocketFactory serverSocketFactory = null; + + if (sslContext != null) { + try { + SSLContext context = sslContext.getSSLContext(); + clientSocketFactory = new SslRMIClientSocketFactory(); + serverSocketFactory = new SslContextServerSocketFactory(context); + + connectorEnvironment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientSocketFactory); + connectorEnvironment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, serverSocketFactory); + } catch (GeneralSecurityException e) { + throw new IOException("Failed to initialize JMX SSLContext", e); + } + } - server = new RMIJRMPServerImpl(connectorPort, null, null, environment); + server = new RMIJRMPServerImpl(connectorPort, clientSocketFactory, serverSocketFactory, connectorEnvironment); - final String serviceURL = "service:jmx:rmi://" + rmiServer + "/jndi/rmi://" +getConnectorHost()+":" + connectorPort + connectorPath; + final String serviceURL = + "service:jmx:rmi://" + rmiServer + "/jndi/rmi://" + getConnectorHost() + ":" + connectorPort + connectorPath; final JMXServiceURL url = new JMXServiceURL(serviceURL); - connectorServer = new RMIConnectorServer(url, environment, server, ManagementFactory.getPlatformMBeanServer()); + connectorServer = new RMIConnectorServer(url, connectorEnvironment, server, mbeanServer); LOG.debug("Created JMXConnectorServer {}", connectorServer); } @@ -710,4 +752,28 @@ private Registry jmxRegistry(final int port) throws RemoteException { } })); } + + private static final class SslContextServerSocketFactory implements RMIServerSocketFactory, Serializable { + private static final long serialVersionUID = 1L; + private final SSLServerSocketFactory delegate; + + private SslContextServerSocketFactory(SSLContext context) { + this.delegate = context.getServerSocketFactory(); + } + + @Override + public ServerSocket createServerSocket(int port) throws IOException { + return delegate.createServerSocket(port); + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj.getClass() == SslContextServerSocketFactory.class; + } + + @Override + public int hashCode() { + return SslContextServerSocketFactory.class.hashCode(); + } + } } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/xbean/ManagementContextXBeanConfigTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/xbean/ManagementContextXBeanConfigTest.java index e3ba66b003a..5c7551ee64d 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/xbean/ManagementContextXBeanConfigTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/xbean/ManagementContextXBeanConfigTest.java @@ -16,6 +16,8 @@ */ package org.apache.activemq.xbean; +import java.io.IOException; +import java.net.ServerSocket; import java.net.URI; import java.util.HashMap; import java.util.Hashtable; @@ -45,6 +47,11 @@ public class ManagementContextXBeanConfigTest extends TestCase { private static final String MANAGEMENT_CONTEXT_TEST_XML = "org/apache/activemq/xbean/management-context-test.xml"; private static final String MANAGEMENT_CONTEXT_TEST_XML_CONNECTOR_PATH = "org/apache/activemq/xbean/management-context-test-connector-path.xml"; + private static final String MANAGEMENT_CONTEXT_TEST_XML_SSL_CONTEXT = + "org/apache/activemq/xbean/management-context-test-ssl-context.xml"; + private static final String MANAGEMENT_CONTEXT_TEST_XML_SSL_CONTEXT_EXPLICIT = + "org/apache/activemq/xbean/management-context-test-ssl-context-explicit.xml"; + private static final String TEST_MANAGEMENT_CONNECTOR_PORT_PROPERTY = "test.management.connector.port"; private static final transient Logger LOG = LoggerFactory.getLogger(ManagementContextXBeanConfigTest.class); @@ -101,19 +108,72 @@ public void testFailAuthentication() throws Exception { fail("Should have thrown an exception"); } + private int findOpenPort() throws IOException { + ServerSocket socket = new ServerSocket(0); + try { + return socket.getLocalPort(); + } finally { + socket.close(); + } + } + + public void testManagementContextUsesBrokerSslContextByDefault() throws Exception { + int port = findOpenPort(); + System.setProperty(TEST_MANAGEMENT_CONNECTOR_PORT_PROPERTY, Integer.toString(port)); + brokerService = getBrokerService(MANAGEMENT_CONTEXT_TEST_XML_SSL_CONTEXT); + brokerService.start(); + + assertNotNull("ManagementContext SSL context should be inherited from BrokerService", + brokerService.getManagementContext().getSslContext()); + assertTrue("JMX connector should be started when broker SSL context is configured", + brokerService.getManagementContext().isConnectorStarted()); + + assertNotNull("ManagementContext should retain SSL context after start", + brokerService.getManagementContext().getSslContext()); + } + + public void testManagementContextUsesExplicitSslContext() throws Exception { + int port = findOpenPort(); + System.setProperty(TEST_MANAGEMENT_CONNECTOR_PORT_PROPERTY, Integer.toString(port)); + brokerService = getBrokerService(MANAGEMENT_CONTEXT_TEST_XML_SSL_CONTEXT_EXPLICIT); + brokerService.start(); + + assertNotNull("BrokerService SSL context should be configured", + brokerService.getSslContext()); + assertNotNull("ManagementContext explicit SSL context should be configured", + brokerService.getManagementContext().getSslContext()); + assertNotSame("ManagementContext should use explicit SSL context instead of broker fallback", + brokerService.getSslContext(), brokerService.getManagementContext().getSslContext()); + assertTrue("JMX connector should be started when explicit management SSL context is configured", + brokerService.getManagementContext().isConnectorStarted()); + + assertNotNull("ManagementContext should retain SSL context after start", + brokerService.getManagementContext().getSslContext()); + } + public void assertAuthentication(JMXConnector connector) throws Exception { - connector.connect(); - MBeanServerConnection connection = connector.getMBeanServerConnection(); - ObjectName name = new ObjectName("test.domain:type=Broker,brokerName=localhost"); - BrokerViewMBean mbean = MBeanServerInvocationHandler - .newProxyInstance(connection, name, BrokerViewMBean.class, true); - LOG.info("Broker " + mbean.getBrokerId() + " - " + mbean.getBrokerName()); + try { + connector.connect(); + MBeanServerConnection connection = connector.getMBeanServerConnection(); + ObjectName name = new ObjectName("test.domain:type=Broker,brokerName=localhost"); + BrokerViewMBean mbean = MBeanServerInvocationHandler + .newProxyInstance(connection, name, BrokerViewMBean.class, true); + LOG.info("Broker " + mbean.getBrokerId() + " - " + mbean.getBrokerName()); + } finally { + connector.close(); + } } @Override protected void tearDown() throws Exception { - if (brokerService != null) { - brokerService.stop(); + try { + if (brokerService != null) { + brokerService.stop(); + brokerService.waitUntilStopped(); + brokerService = null; + } + } finally { + System.clearProperty(TEST_MANAGEMENT_CONNECTOR_PORT_PROPERTY); } } @@ -122,3 +182,4 @@ private BrokerService getBrokerService(String uri) throws Exception { } } + diff --git a/activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context-explicit.xml b/activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context-explicit.xml new file mode 100644 index 00000000000..880fc3129a4 --- /dev/null +++ b/activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context-explicit.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context.xml b/activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context.xml new file mode 100644 index 00000000000..d99311b94dc --- /dev/null +++ b/activemq-unit-tests/src/test/resources/org/apache/activemq/xbean/management-context-test-ssl-context.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + From 8a57c071da504ba07376a40b2bd209735766210a Mon Sep 17 00:00:00 2001 From: Anmol Saxena Date: Mon, 23 Feb 2026 16:37:21 +0530 Subject: [PATCH 2/3] BrokerService-changes --- .../main/java/org/apache/activemq/broker/BrokerService.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java index 13af6315bfd..e51d5214804 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java @@ -2624,12 +2624,6 @@ public ConnectionContext getAdminConnectionContext() throws Exception { protected void startManagementContext() throws Exception { getManagementContext().setBrokerName(brokerName); - // Reuse the broker-level SSL context for JMX by default. - // This avoids duplicating SSL config in activemq.xml while still allowing an - // explicit managementContext sslContext to override when one is needed - if (getManagementContext().getSslContext() == null && getSslContext() != null) { - getManagementContext().setSslContext(getSslContext()); - } getManagementContext().start(); adminView = new BrokerView(this, null); ObjectName objectName = getBrokerObjectName(); From 505bfc664b1189147454463326154ed8877f977c Mon Sep 17 00:00:00 2001 From: Anmol Saxena Date: Mon, 23 Feb 2026 17:21:33 +0530 Subject: [PATCH 3/3] AMQ-9857: Support broker-level SSL fallback in ManagementContext to eliminate duplicate SSL configuration --- .../main/java/org/apache/activemq/broker/BrokerService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java index e51d5214804..ca07ffea569 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java @@ -2624,6 +2624,12 @@ public ConnectionContext getAdminConnectionContext() throws Exception { protected void startManagementContext() throws Exception { getManagementContext().setBrokerName(brokerName); + // Reuse the broker-level SSL context for JMX by default + // This avoids duplicating SSL config in activemq.xml while still allowing an + // explicit managementContext sslContext to override when one is needed + if (getManagementContext().getSslContext() == null && getSslContext() != null) { + getManagementContext().setSslContext(getSslContext()); + } getManagementContext().start(); adminView = new BrokerView(this, null); ObjectName objectName = getBrokerObjectName();