This commit is contained in:
Justin Bertram 2018-11-09 10:05:13 -06:00
commit 366005e44f
5 changed files with 228 additions and 1 deletions

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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

View File

@ -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
* <br>
* http://www.apache.org/licenses/LICENSE-2.0
* <br>
* 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();
}
}

View File

@ -0,0 +1,49 @@
<?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" connector-host="127.0.0.1" rmi-registry-port="10098" />
<authorisation>
<whitelist>
<entry domain="hawtio"/>
</whitelist>
<default-access>
<access method="list*" roles="${role}"/>
<access method="get*" roles="${role}"/>
<access method="is*" roles="${role}"/>
<access method="set*" roles="${role}"/>
<access method="*" roles="${role}"/>
</default-access>
<role-access>
<match domain="org.apache.activemq.artemis">
<access method="list*" roles="${role}"/>
<access method="get*" roles="${role}"/>
<access method="is*" roles="${role}"/>
<access method="set*" roles="${role}"/>
<access method="*" roles="${role}"/>
</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>