/*
* 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.geode.management;
import static org.apache.geode.distributed.ConfigurationProperties.*;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.distributed.LocatorLauncher;
import org.apache.geode.internal.AvailablePortHelper;
import org.apache.geode.internal.security.SecurableCommunicationChannel;
import org.apache.geode.test.dunit.DistributedTestCase;
import org.apache.geode.test.dunit.DistributedTestUtils;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.NetworkUtils;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.rules.DistributedRestoreSystemProperties;
import org.apache.geode.test.junit.categories.FlakyTest;
import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
import org.apache.geode.util.test.TestUtil;
public class JMXMBeanDUnitTest extends DistributedTestCase {
private Host host;
private VM locator;
private VM jmxClient;
private String serverHostName;
private int locatorPort;
private int jmxPort;
private static LocatorLauncher locatorLauncher;
@Rule
public DistributedRestoreSystemProperties distributedRestoreSystemProperties =
new DistributedRestoreSystemProperties();
@Rule
public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
@Before
public void before() {
host = Host.getHost(0);
locator = host.getVM(0);
jmxClient = host.getVM(1);
int[] randomAvailableTCPPorts = AvailablePortHelper.getRandomAvailableTCPPorts(2);
locatorPort = randomAvailableTCPPorts[0];
jmxPort = randomAvailableTCPPorts[1];
serverHostName = NetworkUtils.getServerHostName(host);
}
@Test
public void testJMXOverSSLWithoutJMXAlias() throws Exception {
Properties properties =
configureLocatorProperties(new Properties(), jmxPort, serverHostName, true, false, true);
locator.invoke("Configure and start Locator", () -> {
System.setProperty("javax.ssl.debug", "true");
configureAndStartLocator(locatorPort, jmxPort, serverHostName, properties);
});
jmxClient.invoke("Configure and start JMX Client", () -> {
System.setProperty("javax.ssl.debug", "true");
connectAndValidateAsJmxClient(jmxPort, serverHostName, true, true);
});
}
@Test
public void testJMXOverSSLWithJMXAlias() throws Exception {
Properties properties =
configureLocatorProperties(new Properties(), jmxPort, serverHostName, true, false, true);
locator.invoke("Configure and start Locator", () -> {
System.setProperty("javax.ssl.debug", "true");
configureAndStartLocator(locatorPort, jmxPort, serverHostName, properties);
});
jmxClient.invoke("Configure and start JMX Client", () -> {
System.setProperty("javax.ssl.debug", "true");
connectAndValidateAsJmxClient(jmxPort, serverHostName, true, true);
});
}
@Test
public void testJMXOverSSL() throws Exception {
Properties properties =
configureLocatorProperties(new Properties(), jmxPort, serverHostName, true, false, true);
locator.invoke("Configure and start Locator", () -> {
System.setProperty("javax.ssl.debug", "true");
configureAndStartLocator(locatorPort, jmxPort, serverHostName, properties);
});
jmxClient.invoke("Configure and start JMX Client", () -> {
System.setProperty("javax.ssl.debug", "true");
connectAndValidateAsJmxClient(jmxPort, serverHostName, true);
});
}
@Test
@Category(FlakyTest.class)
// To be fixed in GEODE-1716
public void testJMXOverLegacySSL() throws Exception {
Properties properties =
configureLocatorProperties(new Properties(), jmxPort, serverHostName, true, true, false);
locator.invoke("Configure and start Locator", () -> {
System.setProperty("javax.ssl.debug", "true");
configureAndStartLocator(locatorPort, jmxPort, serverHostName, properties);
});
jmxClient.invoke("Configure and start JMX Client", () -> {
System.setProperty("javax.ssl.debug", "true");
connectAndValidateAsJmxClient(jmxPort, serverHostName, true);
});
}
@Test
public void testJMXOverNonSSL() throws Exception {
Properties properties =
configureLocatorProperties(new Properties(), jmxPort, serverHostName, false, false, false);
locator.invoke("Configure and start Locator",
() -> configureAndStartLocator(locatorPort, jmxPort, serverHostName, properties));
jmxClient.invoke("Configure and start JMX Client",
() -> connectAndValidateAsJmxClient(jmxPort, serverHostName, false));
}
@Test
public void testJMXOverNonSSLWithClientUsingIncorrectPort() throws Exception {
Properties properties =
configureLocatorProperties(new Properties(), jmxPort, serverHostName, false, false, false);
locator.invoke("Configure and start Locator",
() -> configureAndStartLocator(locatorPort, jmxPort, serverHostName, properties));
assertThatThrownBy(() -> jmxClient.invoke("Configure and start JMX Client",
() -> connectAndValidateAsJmxClient(9999, serverHostName, false)))
.hasCauseExactlyInstanceOf(IOException.class)
.hasRootCauseExactlyInstanceOf(java.net.ConnectException.class);
}
private void connectAndValidateAsJmxClient(final int jmxPort, final String serverHostName,
final boolean useSSL) throws Exception {
connectAndValidateAsJmxClient(jmxPort, serverHostName, useSSL, false);
}
private void connectAndValidateAsJmxClient(final int jmxPort, final String serverHostName,
final boolean useSSL, final boolean useMulti) throws Exception {
// JMX RMI
Map<String, Object> environment = new HashMap();
if (useSSL) {
System.setProperty("javax.net.ssl.keyStore",
useMulti ? getMultiKeyKeystore() : getSimpleSingleKeyKeystore());
System.setProperty("javax.net.ssl.keyStoreType", "JKS");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
System.setProperty("javax.net.ssl.trustStore",
useMulti ? getMultiKeyTruststore() : getSimpleSingleKeyKeystore());
System.setProperty("javax.net.ssl.trustStoreType", "JKS");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
environment.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory());
}
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://" + serverHostName + ":" + jmxPort
+ "/jndi/rmi://" + serverHostName + ":" + jmxPort + "/jmxrmi");
JMXConnector jmxConnector = JMXConnectorFactory.connect(url, environment);
try {
MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
ObjectName mbeanName = new ObjectName("GemFire:service=System,type=Distributed");
// Get MBean proxy instance that will be used to make calls to registered MBean
DistributedSystemMXBean distributedSystemMXBean =
JMX.newMBeanProxy(mbeanServerConnection, mbeanName, DistributedSystemMXBean.class, true);
assertEquals(1, distributedSystemMXBean.getMemberCount());
assertEquals(1, distributedSystemMXBean.getLocatorCount());
} finally {
jmxConnector.close();
}
}
private void configureAndStartLocator(final int locatorPort, final int jmxPort,
final String serverHostName, final Properties properties) throws IOException {
configureAndStartLocator(locatorPort, serverHostName, properties);
}
private void configureAndStartLocator(final int locatorPort, final String serverHostName,
final Properties properties) throws IOException {
DistributedTestUtils.deleteLocatorStateFile();
final String memberName = getUniqueName() + "-locator";
final File workingDirectory = temporaryFolder.newFolder(memberName);
LocatorLauncher.Builder builder = new LocatorLauncher.Builder();
for (String propertyName : properties.stringPropertyNames()) {
builder.set(propertyName, properties.getProperty(propertyName));
}
locatorLauncher = builder.setBindAddress(serverHostName).setHostnameForClients(serverHostName)
.setMemberName(memberName).setPort(locatorPort)
.setWorkingDirectory(workingDirectory.getCanonicalPath()).build();
locatorLauncher.start();
}
private Properties configureJMXSSLProperties(final Properties properties, final boolean isLegacy,
final boolean useMultiKey) {
if (isLegacy) {
properties.setProperty(JMX_MANAGER_SSL_CIPHERS, "any");
properties.setProperty(JMX_MANAGER_SSL_PROTOCOLS, "any");
properties.setProperty(JMX_MANAGER_SSL_ENABLED, "true");
properties.setProperty(JMX_MANAGER_SSL_KEYSTORE, getSimpleSingleKeyKeystore());
properties.setProperty(JMX_MANAGER_SSL_KEYSTORE_PASSWORD, "password");
properties.setProperty(JMX_MANAGER_SSL_TRUSTSTORE, getSimpleSingleKeyKeystore());
properties.setProperty(JMX_MANAGER_SSL_TRUSTSTORE_PASSWORD, "password");
} else {
{
properties.setProperty(SSL_CIPHERS, "any");
properties.setProperty(SSL_PROTOCOLS, "any");
properties.setProperty(SSL_KEYSTORE_PASSWORD, "password");
properties.setProperty(SSL_TRUSTSTORE_PASSWORD, "password");
properties.setProperty(SSL_KEYSTORE, getSimpleSingleKeyKeystore());
properties.setProperty(SSL_TRUSTSTORE, getSimpleSingleKeyKeystore());
properties.setProperty(SSL_ENABLED_COMPONENTS,
SecurableCommunicationChannel.JMX.getConstant());
if (useMultiKey) {
properties.setProperty(SSL_KEYSTORE, getMultiKeyKeystore());
properties.setProperty(SSL_TRUSTSTORE, getMultiKeyTruststore());
properties.setProperty(SSL_JMX_ALIAS, "jmxkey");
}
}
}
return properties;
}
private String getSimpleSingleKeyKeystore() {
return TestUtil.getResourcePath(getClass(), "/ssl/trusted.keystore");
}
private String getMultiKeyKeystore() {
return TestUtil.getResourcePath(getClass(), "/org/apache/geode/internal/net/multiKey.jks");
}
private String getMultiKeyTruststore() {
return TestUtil.getResourcePath(getClass(), "/org/apache/geode/internal/net/multiKeyTrust.jks");
}
private Properties configureLocatorProperties(final Properties properties, final int jmxPort,
final String serverHostName, final boolean useSSL, final boolean useLegacySSL,
final boolean useMultiKeyKeystore) {
configureCommonProperties(properties);
properties.setProperty(JMX_MANAGER, "true");
properties.setProperty(JMX_MANAGER_START, "true");
properties.setProperty(JMX_MANAGER_PORT, String.valueOf(jmxPort));
properties.setProperty(JMX_MANAGER_BIND_ADDRESS, serverHostName);
properties.setProperty(JMX_MANAGER_HOSTNAME_FOR_CLIENTS, serverHostName);
if (useSSL) {
configureJMXSSLProperties(properties, useLegacySSL, useMultiKeyKeystore);
}
return properties;
}
private Properties configureCommonProperties(final Properties properties) {
properties.setProperty(MCAST_PORT, "0");
properties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
properties.setProperty(USE_CLUSTER_CONFIGURATION, "false");
return properties;
}
}