11/*
2- * Copyright (c) 2022, 2022 , Oracle and/or its affiliates. All rights reserved.
3- * Copyright (c) 2022, 2022 , Red Hat Inc. All rights reserved.
2+ * Copyright (c) 2022, 2025 , Oracle and/or its affiliates. All rights reserved.
3+ * Copyright (c) 2022, 2025 , Red Hat Inc. All rights reserved.
44 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
55 *
66 * This code is free software; you can redistribute it and/or modify it
2525 */
2626package com .oracle .svm .test .jmx ;
2727
28+ import static org .junit .Assert .assertEquals ;
29+ import static org .junit .Assert .assertFalse ;
30+ import static org .junit .Assert .assertNotNull ;
2831import static org .junit .Assert .assertTrue ;
2932import static org .junit .Assume .assumeTrue ;
3033
3942import java .lang .management .OperatingSystemMXBean ;
4043import java .lang .management .RuntimeMXBean ;
4144import java .lang .management .ThreadMXBean ;
45+ import java .nio .file .Files ;
46+ import java .nio .file .Path ;
47+ import java .nio .file .attribute .PosixFilePermission ;
4248import java .util .HashMap ;
4349import java .util .List ;
4450import java .util .Map ;
51+ import java .util .Set ;
52+ import java .util .concurrent .TimeUnit ;
4553
4654import javax .management .MBeanServer ;
4755import javax .management .MBeanServerConnection ;
5058import javax .management .remote .JMXConnector ;
5159import javax .management .remote .JMXConnectorFactory ;
5260import javax .management .remote .JMXServiceURL ;
61+ import javax .rmi .ssl .SslRMIClientSocketFactory ;
5362
5463import org .graalvm .nativeimage .ImageInfo ;
5564import org .junit .Assert ;
5867
5968import com .oracle .svm .core .VMInspectionOptions ;
6069import com .oracle .svm .core .jdk .management .ManagementAgentStartupHook ;
70+ import com .oracle .svm .hosted .c .util .FileUtils ;
6171import com .oracle .svm .test .AddExports ;
6272
6373import jdk .management .jfr .FlightRecorderMXBean ;
6474
6575@ AddExports ("jdk.management.agent/jdk.internal.agent" )
6676public class JmxTest {
6777 static final String PORT_PROPERTY = "com.sun.management.jmxremote.port" ;
78+ static final String RMI_PORT_PROPERTY = "com.sun.management.jmxremote.rmi.port" ;
6879 static final String AUTH_PROPERTY = "com.sun.management.jmxremote.authenticate" ;
80+ static final String CLIENT_AUTH_PROPERTY = "com.sun.management.jmxremote.ssl.need.client.auth" ;
81+ static final String ACCESS_PROPERTY = "com.sun.management.jmxremote.access.file" ;
82+ static final String PASSWORD_PROPERTY = "com.sun.management.jmxremote.password.file" ;
6983 static final String SSL_PROPERTY = "com.sun.management.jmxremote.ssl" ;
84+ static final String KEYSTORE_FILENAME = "clientkeystore" ;
85+ static final String KEYSTORE_PASSWORD = "clientpass" ;
86+ static final String KEYSTORE_PASSWORD_PROPERTY = "javax.net.ssl.keyStorePassword" ;
87+ static final String KEYSTORE_PROPERTY = "javax.net.ssl.keyStore" ;
88+ static final String TRUSTSTORE_FILENAME = "servertruststore" ;
89+ static final String TRUSTSTORE_PASSWORD = "servertrustpass" ;
90+ static final String TRUSTSTORE_PASSWORD_PROPERTY = "javax.net.ssl.trustStorePassword" ;
91+ static final String TRUSTSTORE_PROPERTY = "javax.net.ssl.trustStore" ;
92+ static final String REGISTRY_SSL_PROPERTY = "com.sun.management.jmxremote.registry.ssl" ;
93+ static final String SOCKET_FACTORY_PROPERTY = "com.sun.jndi.rmi.factory.socket" ;
7094 static final String TEST_PORT = "12345" ;
71- static final String FALSE = "false" ;
95+ static final String TEST_ROLE = "myTestRole" ;
96+ static final String TEST_ROLE_PASSWORD = "MYTESTP@SSWORD" ;
97+ static final String TRUE = "true" ;
7298
7399 @ BeforeClass
74- public static void checkForJFR () {
100+ public static void setup () throws IOException {
75101 assumeTrue ("skipping JMX tests" , !ImageInfo .inImageCode () ||
76102 (VMInspectionOptions .hasJmxClientSupport () && VMInspectionOptions .hasJmxServerSupport ()));
77103
78104 System .setProperty (PORT_PROPERTY , TEST_PORT );
79- System .setProperty (AUTH_PROPERTY , FALSE );
80- System .setProperty (SSL_PROPERTY , FALSE );
105+ System .setProperty (RMI_PORT_PROPERTY , TEST_PORT );
106+ System .setProperty (AUTH_PROPERTY , TRUE );
107+ System .setProperty (CLIENT_AUTH_PROPERTY , TRUE );
108+ System .setProperty (SSL_PROPERTY , TRUE );
109+ System .setProperty (REGISTRY_SSL_PROPERTY , TRUE );
110+
111+ // Prepare temp directory with files required for testing authentication.
112+ Path tempDirectory = Files .createTempDirectory ("jmxtest" );
113+ Path jmxRemoteAccess = tempDirectory .resolve ("jmxremote.access" );
114+ Path jmxRemotePassword = tempDirectory .resolve ("jmxremote.password" );
115+ Path clientKeyStore = tempDirectory .resolve (KEYSTORE_FILENAME );
116+ Path serverTrustStore = tempDirectory .resolve (TRUSTSTORE_FILENAME );
117+
118+ // Generate SSL keystore, client cert, and truststore for testing SSL connection.
119+ createClientKey (tempDirectory );
120+ createClientCert (tempDirectory );
121+ assertTrue ("Failed to create " + KEYSTORE_FILENAME , Files .exists (clientKeyStore ));
122+ System .setProperty (KEYSTORE_PROPERTY , clientKeyStore .toString ());
123+ System .setProperty (KEYSTORE_PASSWORD_PROPERTY , KEYSTORE_PASSWORD );
124+ createServerTrustStore (tempDirectory );
125+ assertTrue ("Failed to create " + TRUSTSTORE_FILENAME , Files .exists (serverTrustStore ));
126+ System .setProperty (TRUSTSTORE_PROPERTY , serverTrustStore .toString ());
127+ System .setProperty (TRUSTSTORE_PASSWORD_PROPERTY , TRUSTSTORE_PASSWORD );
128+
129+ // The following are dummy access and password files required for testing authentication.
130+ Files .writeString (jmxRemoteAccess , TEST_ROLE + " readwrite" );
131+ System .setProperty (ACCESS_PROPERTY , jmxRemoteAccess .toString ());
132+ Files .writeString (jmxRemotePassword , TEST_ROLE + " " + TEST_ROLE_PASSWORD );
133+ System .setProperty (PASSWORD_PROPERTY , jmxRemotePassword .toString ());
134+
135+ // Password file must have restricted access.
136+ Files .setPosixFilePermissions (jmxRemotePassword , Set .of (PosixFilePermission .OWNER_READ , PosixFilePermission .OWNER_WRITE ));
137+
81138 try {
82139 // We need to rerun the startup hook with the correct properties set.
83140 ManagementAgentStartupHook startupHook = new ManagementAgentStartupHook ();
84141 startupHook .execute (false );
85142 } catch (Exception e ) {
86- Assert .fail ("Failed to start server Cause: " + e .getMessage ());
143+ Assert .fail ("Failed to start server. Cause: " + e .getMessage ());
144+ }
145+ }
146+
147+ private static void createClientKey (Path tempDirectory ) throws IOException {
148+ runCommand (tempDirectory , List .of ("keytool" , "-genkey" ,
149+ "-keystore" , KEYSTORE_FILENAME ,
150+ "-alias" , "clientkey" ,
151+ "-storepass" , KEYSTORE_PASSWORD ,
152+ "-keypass" , KEYSTORE_PASSWORD ,
153+ "-dname" , "CN=test, OU=test, O=test, L=test, ST=test, C=test, EMAILADDRESS=test" ,
154+ "-validity" , "99999" ,
155+ "-keyalg" , "rsa" ));
156+ }
157+
158+ private static void createClientCert (Path tempDirectory ) throws IOException {
159+ runCommand (tempDirectory , List .of ("keytool" , "-exportcert" ,
160+ "-keystore" , KEYSTORE_FILENAME ,
161+ "-alias" , "clientkey" ,
162+ "-storepass" , KEYSTORE_PASSWORD ,
163+ "-file" , "client.cer" ));
164+ }
165+
166+ private static void createServerTrustStore (Path tempDirectory ) throws IOException {
167+ runCommand (tempDirectory , List .of ("keytool" , "-importcert" ,
168+ "-noprompt" ,
169+ "-file" , "client.cer" ,
170+ "-keystore" , TRUSTSTORE_FILENAME ,
171+ "-storepass" , TRUSTSTORE_PASSWORD ));
172+ }
173+
174+ private static void runCommand (Path tempDirectory , List <String > command ) throws IOException {
175+ ProcessBuilder pb = new ProcessBuilder ().command (command );
176+ pb .directory (tempDirectory .toFile ());
177+ final Process process = pb .start ();
178+ try {
179+ process .waitFor (5 , TimeUnit .SECONDS );
180+ } catch (InterruptedException e ) {
181+ throw new IOException ("Keytool execution error" );
182+ }
183+ if (process .exitValue () > 0 ) {
184+ final String processError = String .join (" \\ " , FileUtils .readAllLines (process .getErrorStream ()));
185+ final String processOutput = String .join (" \\ " , FileUtils .readAllLines (process .getInputStream ()));
186+ throw new IOException (
187+ "Keytool execution error: " + processError + ", output: " + processOutput + ", command: " + command );
87188 }
88189 }
89190
90191 private static MBeanServerConnection getLocalMBeanServerConnectionStatic () {
91192 try {
92193 JMXServiceURL jmxUrl = new JMXServiceURL ("service:jmx:rmi:///jndi/rmi://" + "localhost" + ":" + TEST_PORT + "/jmxrmi" );
93194 Map <String , Object > env = new HashMap <>();
94-
195+ String [] credentials = {TEST_ROLE , TEST_ROLE_PASSWORD };
196+ env .put (JMXConnector .CREDENTIALS , credentials );
197+ // Include below if protecting registry with SSL
198+ env .put (SOCKET_FACTORY_PROPERTY , new SslRMIClientSocketFactory ());
95199 JMXConnector connector = JMXConnectorFactory .connect (jmxUrl , env );
96200 return connector .getMBeanServerConnection ();
97201 } catch (IOException e ) {
@@ -104,8 +208,8 @@ private static MBeanServerConnection getLocalMBeanServerConnectionStatic() {
104208 public void testConnection () throws Exception {
105209 // This simply tests that we can establish a connection between client and server
106210 MBeanServerConnection mbsc = getLocalMBeanServerConnectionStatic ();
107- assertTrue ("Connection should not be null" , mbsc != null );
108- assertTrue ("Connection default domain should not be empty" , ! mbsc .getDefaultDomain ().isEmpty ());
211+ assertNotNull ("Connection should not be null" , mbsc );
212+ assertFalse ("Connection default domain should not be empty" , mbsc .getDefaultDomain ().isEmpty ());
109213 }
110214
111215 @ Test
@@ -138,7 +242,7 @@ public void testRuntimeMXBeanProxy() {
138242 }
139243
140244 assertTrue ("PID should be positive." , runtimeMXBean .getPid () > 0 );
141- assertTrue ("Class Path should not be null: " , runtimeMXBean .getClassPath () != null );
245+ assertNotNull ("Class Path should not be null: " , runtimeMXBean .getClassPath ());
142246 assertTrue ("Start time should be positive" , runtimeMXBean .getStartTime () > 0 );
143247 }
144248
@@ -149,7 +253,7 @@ public void testRuntimeMXBeanDirect() throws MalformedObjectNameException {
149253 ObjectName objectName = new ObjectName ("java.lang:type=Runtime" );
150254 try {
151255 assertTrue ("Uptime should be positive. " , (long ) mbsc .getAttribute (objectName , "Pid" ) > 0 );
152- assertTrue ("Class Path should not be null: " , mbsc .getAttribute (objectName , "ClassPath" ) != null );
256+ assertNotNull ("Class Path should not be null: " , mbsc .getAttribute (objectName , "ClassPath" ));
153257 assertTrue ("Start time should be positive" , (long ) mbsc .getAttribute (objectName , "StartTime" ) > 0 );
154258 } catch (Exception e ) {
155259 Assert .fail ("Remote invocations failed : " + e .getMessage ());
@@ -168,7 +272,7 @@ public void testClassLoadingMXBeanProxy() {
168272 Assert .fail ("Failed to get ClassLoadingMXBean. : " + e .getMessage ());
169273 }
170274 if (ImageInfo .inImageRuntimeCode ()) {
171- assertTrue ("Loaded Class count should be 0 (hardcoded at 0): " , classLoadingMXBean .getLoadedClassCount () == 0 );
275+ assertEquals ("Loaded Class count should be 0 (hardcoded at 0): " , 0 , classLoadingMXBean .getLoadedClassCount ());
172276 } else {
173277 assertTrue ("If in java mode, number of loaded classes should be positive: " , classLoadingMXBean .getLoadedClassCount () > 0 );
174278 }
@@ -246,7 +350,7 @@ public void testGarbageCollectorMXBeanProxy() {
246350 Assert .fail ("Failed to get GarbageCollectorMXBean. : " + e .getMessage ());
247351 }
248352 for (GarbageCollectorMXBean gcBean : garbageCollectorMXBeans ) {
249- assertTrue ("GC object name should not be null" , gcBean .getObjectName () != null );
353+ assertNotNull ("GC object name should not be null" , gcBean .getObjectName ());
250354 assertTrue ("Number of GC should not be negative" , gcBean .getCollectionCount () >= 0 );
251355 }
252356 }
@@ -263,7 +367,7 @@ public void testOperatingSystemMXBeanProxy() {
263367 } catch (Exception e ) {
264368 Assert .fail ("Failed to get OperatingSystemMXBean. : " + e .getMessage ());
265369 }
266- assertTrue ("OS version can't be null. " , operatingSystemMXBean .getVersion () != null );
370+ assertNotNull ("OS version can't be null. " , operatingSystemMXBean .getVersion ());
267371 }
268372
269373 @ Test
@@ -272,7 +376,7 @@ public void testOperatingSystemMXBeanDirect() throws MalformedObjectNameExceptio
272376 MBeanServerConnection mbsc = getLocalMBeanServerConnectionStatic ();
273377 ObjectName objectName = new ObjectName ("java.lang:type=OperatingSystem" );
274378 try {
275- assertTrue ("OS version can't be null. " , mbsc .getAttribute (objectName , "Version" ) != null );
379+ assertNotNull ("OS version can't be null. " , mbsc .getAttribute (objectName , "Version" ));
276380 } catch (Exception e ) {
277381 Assert .fail ("Remote invokations failed : " + e .getMessage ());
278382 }
@@ -290,7 +394,7 @@ public void testMemoryManagerMXBeanProxy() {
290394 Assert .fail ("Failed to get MemoryManagerMXBean. : " + e .getMessage ());
291395 }
292396 for (MemoryManagerMXBean memoryManagerMXBean : memoryManagerMXBeans ) {
293- assertTrue ("Memory pool names should not be null. " , memoryManagerMXBean .getMemoryPoolNames () != null );
397+ assertNotNull ("Memory pool names should not be null. " , memoryManagerMXBean .getMemoryPoolNames ());
294398 }
295399 }
296400
@@ -307,7 +411,7 @@ public void testMemoryPoolMXBeanProxy() {
307411 Assert .fail ("Failed to get MemoryPoolMXBean. : " + e .getMessage ());
308412 }
309413 for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans ) {
310- assertTrue ("Memory Pool name should not be null " , memoryPoolMXBean .getName () != null );
414+ assertNotNull ("Memory Pool name should not be null " , memoryPoolMXBean .getName ());
311415 }
312416 }
313417
@@ -324,7 +428,7 @@ public void testFlightRecorderMXBeanProxy() {
324428 Assert .fail ("Failed to get FlightRecorderMXBean. : " + e .getMessage ());
325429 }
326430 flightRecorderMXBean .newRecording ();
327- assertTrue ("Flight recordings should be available because we just created one." , ! flightRecorderMXBean .getRecordings ().isEmpty ());
431+ assertFalse ("Flight recordings should be available because we just created one." , flightRecorderMXBean .getRecordings ().isEmpty ());
328432 }
329433
330434 @ Test
@@ -336,7 +440,7 @@ public void testFlightRecorderMXBeanDirect() throws MalformedObjectNameException
336440 mbsc .invoke (objectName , "startRecording" , new Object []{recording }, new String []{"long" });
337441 mbsc .invoke (objectName , "stopRecording" , new Object []{recording }, new String []{"long" });
338442 } catch (Exception e ) {
339- Assert .fail ("Remote invokations failed : " + e .getMessage ());
443+ Assert .fail ("Remote invocations failed : " + e .getMessage ());
340444 }
341445 }
342446}
0 commit comments