From 6d0641b4385d2189e27eaab43c955239f949567d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0mucr=20Jan?= Date: Wed, 7 Nov 2018 14:49:23 +0100 Subject: [PATCH] ARTEMIS-2169 allow config of JMX RMI registry port Previously the port was always random. This caused problems with remote JMX connections that needed to overcome firewalls. As of this patch it's possible to make the RMI port static and whitelist it in the firewall settings. --- .../cli/factory/jmx/ManagementFactory.java | 3 + .../activemq/artemis/dto/JMXConnectorDTO.java | 7 + docs/user-manual/en/management.md | 8 +- .../integration/jmx/JmxConnectionTest.java | 162 ++++++++++++++++++ .../test/resources/jmx-test-management.xml | 49 ++++++ 5 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jmx/JmxConnectionTest.java create mode 100644 tests/integration-tests/src/test/resources/jmx-test-management.xml 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 79e241e204..7baa0f5a77 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 @@ -96,6 +96,9 @@ public class ManagementFactory { if (jmxConnector.getConnectorHost() != null) { jmxConnectorConfiguration.setConnectorHost(jmxConnector.getConnectorHost()); } + if (jmxConnector.getRmiRegistryPort() != null) { + jmxConnectorConfiguration.setRmiRegistryPort(jmxConnector.getRmiRegistryPort()); + } if (jmxConnector.getJmxRealm() != null) { jmxConnectorConfiguration.setJmxRealm(jmxConnector.getJmxRealm()); } diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java index bd78481d8a..965de9392d 100644 --- a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java +++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java @@ -34,6 +34,9 @@ public class JMXConnectorDTO { @XmlAttribute (name = "connector-port", required = true) Integer connectorPort; + @XmlAttribute (name = "rmi-registry-port") + Integer rmiRegistryPort; + @XmlAttribute (name = "jmx-realm") String jmxRealm; @@ -75,6 +78,10 @@ public class JMXConnectorDTO { return connectorPort; } + public Integer getRmiRegistryPort() { + return rmiRegistryPort; + } + public String getJmxRealm() { return jmxRealm; } diff --git a/docs/user-manual/en/management.md b/docs/user-manual/en/management.md index f7c835223d..60ef2a184f 100644 --- a/docs/user-manual/en/management.md +++ b/docs/user-manual/en/management.md @@ -423,7 +423,13 @@ You can also configure the connector using the following: - `connector-port` The port to expose the agent on. - + +- `rmi-registry-port` + + The port that the RMI registry binds to. If not set, the port is + always random. Set to avoid problems with remote JMX connections + tunnelled through firewall. + - `jmx-realm` The jmx realm to use for authentication, defaults to `activemq` to match the diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jmx/JmxConnectionTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jmx/JmxConnectionTest.java new file mode 100644 index 0000000000..9fd2c3a4f6 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jmx/JmxConnectionTest.java @@ -0,0 +1,162 @@ +/* + * 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.integration.jmx; + +import com.sun.jmx.remote.internal.ProxyRef; +import org.apache.activemq.artemis.cli.Artemis; +import org.apache.activemq.artemis.cli.commands.Run; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import sun.rmi.server.UnicastRef; +import sun.rmi.transport.LiveRef; + +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnection; +import javax.management.remote.rmi.RMIConnector; +import java.io.File; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.rmi.server.RemoteObject; +import java.rmi.server.RemoteRef; +import java.util.concurrent.TimeUnit; + +/** + * This test checks JMX connection to Artemis with both necessary ports set up so that it's easier to tunnel through + * firewalls. + */ +public class JmxConnectionTest extends ActiveMQTestBase { + + private static final String MANAGEMENT_XML = "/jmx-test-management.xml"; + + // Make sure these values are always the same as in the MANAGEMENT_XML configuration file + private static final String JMX_SERVER_HOSTNAME = "127.0.0.1"; + private static final int JMX_SERVER_PORT = 10099; + private static final int RMI_REGISTRY_PORT = 10098; + + @Override + @Before + public void setUp() throws Exception { + + super.setUp(); + + /* This needs to be disabled because otherwise there would be a lot of complains about Artemis' own running threads + * and I suppose that this kind of leaks is most likely tested somewhere else. + */ + disableCheckThread(); + + // Artemis instance dir + File instanceDir = new File(temporaryFolder.getRoot(), "instance"); + + // Create new Artemis instance + Run.setEmbedded(true); + Artemis.main("create", instanceDir.getAbsolutePath(), "--silent", "--no-fsync", "--no-autotune", + "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor"); + + // Configure (this is THE subject of testing) + File managementConfigFile = new File(instanceDir, "etc/management.xml"); + Files.copy(getClass().getResourceAsStream(MANAGEMENT_XML), managementConfigFile.toPath(), + StandardCopyOption.REPLACE_EXISTING); + + // Point the server to the instance directory + System.setProperty("artemis.instance", instanceDir.getAbsolutePath()); + + // 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); + + // Enable guest login module (dunno why this isn't automatic) + System.setProperty("java.security.auth.login.config", instanceDir.getAbsolutePath() + "/etc/login.config"); + + // Run Artemis server + Artemis.internalExecute("run"); + } + + @Test + public void testJmxConnection() throws Exception { + + // 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 { + jmxConnector = JMXConnectorFactory.connect(url); + logAndSystemOut("Successfully connected to: " + urlString); + } catch (Exception e) { + logAndSystemOut("JMX connection failed: " + urlString, e); + Assert.fail(); + return; + } + + try { + + /* Now I need to extract the RMI registry port to make sure it's equal to the configured one. It's gonna be + * messy because I have to use reflection to reach the information. + */ + + Assert.assertTrue(jmxConnector instanceof RMIConnector); + + // 1. RMIConnector::connection is expected to be RMIConnectionImpl_Stub + Field connectionField = RMIConnector.class.getDeclaredField("connection"); + connectionField.setAccessible(true); + RMIConnection rmiConnection = (RMIConnection) connectionField.get(jmxConnector); + + // 2. RMIConnectionImpl_Stub extends RemoteStub which extends RemoteObject + Assert.assertTrue(rmiConnection instanceof RemoteObject); + RemoteObject remoteObject = (RemoteObject) rmiConnection; + + // 3. RemoteObject::getRef is hereby expected to return ProxyRef + RemoteRef remoteRef = remoteObject.getRef(); + Assert.assertTrue(remoteRef instanceof ProxyRef); + ProxyRef proxyRef = (ProxyRef) remoteRef; + + // 4. ProxyRef::ref is expected to contain UnicastRef (UnicastRef2 resp.) + Field refField = ProxyRef.class.getDeclaredField("ref"); + refField.setAccessible(true); + remoteRef = (RemoteRef) refField.get(proxyRef); + Assert.assertTrue(remoteRef instanceof UnicastRef); + + // 5. UnicastRef::getLiveRef returns LiveRef + LiveRef liveRef = ((UnicastRef) remoteRef).getLiveRef(); + + // 6. LiveRef::getPort is expected to be the same as the RMI registry port configured via management.xml + /* Accidentally, it can happen that even with the RMI registry port unconfigured the randomly selected port + * will be the same as expected by the test which will make it succeed. But it's highly unlikely. + */ + Assert.assertEquals(RMI_REGISTRY_PORT, liveRef.getPort()); + + } finally { + jmxConnector.close(); + } + } + + @After + @Override + public void tearDown() throws Exception { + Artemis.internalExecute("stop"); + Run.latchRunning.await(5, TimeUnit.SECONDS); + super.tearDown(); + } + +} diff --git a/tests/integration-tests/src/test/resources/jmx-test-management.xml b/tests/integration-tests/src/test/resources/jmx-test-management.xml new file mode 100644 index 0000000000..ac5ba6d703 --- /dev/null +++ b/tests/integration-tests/src/test/resources/jmx-test-management.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file