1
1
/*
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.
4
4
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5
5
*
6
6
* This code is free software; you can redistribute it and/or modify it
25
25
*/
26
26
package com .oracle .svm .test .jmx ;
27
27
28
+ import static org .junit .Assert .assertEquals ;
29
+ import static org .junit .Assert .assertFalse ;
30
+ import static org .junit .Assert .assertNotNull ;
28
31
import static org .junit .Assert .assertTrue ;
29
32
import static org .junit .Assume .assumeTrue ;
30
33
39
42
import java .lang .management .OperatingSystemMXBean ;
40
43
import java .lang .management .RuntimeMXBean ;
41
44
import java .lang .management .ThreadMXBean ;
45
+ import java .nio .file .Files ;
46
+ import java .nio .file .Path ;
47
+ import java .nio .file .attribute .PosixFilePermission ;
42
48
import java .util .HashMap ;
43
49
import java .util .List ;
44
50
import java .util .Map ;
51
+ import java .util .Set ;
52
+ import java .util .concurrent .TimeUnit ;
45
53
46
54
import javax .management .MBeanServer ;
47
55
import javax .management .MBeanServerConnection ;
50
58
import javax .management .remote .JMXConnector ;
51
59
import javax .management .remote .JMXConnectorFactory ;
52
60
import javax .management .remote .JMXServiceURL ;
61
+ import javax .rmi .ssl .SslRMIClientSocketFactory ;
53
62
54
63
import org .graalvm .nativeimage .ImageInfo ;
55
64
import org .junit .Assert ;
58
67
59
68
import com .oracle .svm .core .VMInspectionOptions ;
60
69
import com .oracle .svm .core .jdk .management .ManagementAgentStartupHook ;
70
+ import com .oracle .svm .hosted .c .util .FileUtils ;
61
71
import com .oracle .svm .test .AddExports ;
62
72
63
73
import jdk .management .jfr .FlightRecorderMXBean ;
64
74
65
75
@ AddExports ("jdk.management.agent/jdk.internal.agent" )
66
76
public class JmxTest {
67
77
static final String PORT_PROPERTY = "com.sun.management.jmxremote.port" ;
78
+ static final String RMI_PORT_PROPERTY = "com.sun.management.jmxremote.rmi.port" ;
68
79
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" ;
69
83
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" ;
70
94
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" ;
72
98
73
99
@ BeforeClass
74
- public static void checkForJFR () {
100
+ public static void setup () throws IOException {
75
101
assumeTrue ("skipping JMX tests" , !ImageInfo .inImageCode () ||
76
102
(VMInspectionOptions .hasJmxClientSupport () && VMInspectionOptions .hasJmxServerSupport ()));
77
103
78
104
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
+
81
138
try {
82
139
// We need to rerun the startup hook with the correct properties set.
83
140
ManagementAgentStartupHook startupHook = new ManagementAgentStartupHook ();
84
141
startupHook .execute (false );
85
142
} 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 );
87
188
}
88
189
}
89
190
90
191
private static MBeanServerConnection getLocalMBeanServerConnectionStatic () {
91
192
try {
92
193
JMXServiceURL jmxUrl = new JMXServiceURL ("service:jmx:rmi:///jndi/rmi://" + "localhost" + ":" + TEST_PORT + "/jmxrmi" );
93
194
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 ());
95
199
JMXConnector connector = JMXConnectorFactory .connect (jmxUrl , env );
96
200
return connector .getMBeanServerConnection ();
97
201
} catch (IOException e ) {
@@ -104,8 +208,8 @@ private static MBeanServerConnection getLocalMBeanServerConnectionStatic() {
104
208
public void testConnection () throws Exception {
105
209
// This simply tests that we can establish a connection between client and server
106
210
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 ());
109
213
}
110
214
111
215
@ Test
@@ -138,7 +242,7 @@ public void testRuntimeMXBeanProxy() {
138
242
}
139
243
140
244
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 ());
142
246
assertTrue ("Start time should be positive" , runtimeMXBean .getStartTime () > 0 );
143
247
}
144
248
@@ -149,7 +253,7 @@ public void testRuntimeMXBeanDirect() throws MalformedObjectNameException {
149
253
ObjectName objectName = new ObjectName ("java.lang:type=Runtime" );
150
254
try {
151
255
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" ));
153
257
assertTrue ("Start time should be positive" , (long ) mbsc .getAttribute (objectName , "StartTime" ) > 0 );
154
258
} catch (Exception e ) {
155
259
Assert .fail ("Remote invocations failed : " + e .getMessage ());
@@ -168,7 +272,7 @@ public void testClassLoadingMXBeanProxy() {
168
272
Assert .fail ("Failed to get ClassLoadingMXBean. : " + e .getMessage ());
169
273
}
170
274
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 ());
172
276
} else {
173
277
assertTrue ("If in java mode, number of loaded classes should be positive: " , classLoadingMXBean .getLoadedClassCount () > 0 );
174
278
}
@@ -246,7 +350,7 @@ public void testGarbageCollectorMXBeanProxy() {
246
350
Assert .fail ("Failed to get GarbageCollectorMXBean. : " + e .getMessage ());
247
351
}
248
352
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 ());
250
354
assertTrue ("Number of GC should not be negative" , gcBean .getCollectionCount () >= 0 );
251
355
}
252
356
}
@@ -263,7 +367,7 @@ public void testOperatingSystemMXBeanProxy() {
263
367
} catch (Exception e ) {
264
368
Assert .fail ("Failed to get OperatingSystemMXBean. : " + e .getMessage ());
265
369
}
266
- assertTrue ("OS version can't be null. " , operatingSystemMXBean .getVersion () != null );
370
+ assertNotNull ("OS version can't be null. " , operatingSystemMXBean .getVersion ());
267
371
}
268
372
269
373
@ Test
@@ -272,7 +376,7 @@ public void testOperatingSystemMXBeanDirect() throws MalformedObjectNameExceptio
272
376
MBeanServerConnection mbsc = getLocalMBeanServerConnectionStatic ();
273
377
ObjectName objectName = new ObjectName ("java.lang:type=OperatingSystem" );
274
378
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" ));
276
380
} catch (Exception e ) {
277
381
Assert .fail ("Remote invokations failed : " + e .getMessage ());
278
382
}
@@ -290,7 +394,7 @@ public void testMemoryManagerMXBeanProxy() {
290
394
Assert .fail ("Failed to get MemoryManagerMXBean. : " + e .getMessage ());
291
395
}
292
396
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 ());
294
398
}
295
399
}
296
400
@@ -307,7 +411,7 @@ public void testMemoryPoolMXBeanProxy() {
307
411
Assert .fail ("Failed to get MemoryPoolMXBean. : " + e .getMessage ());
308
412
}
309
413
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 ());
311
415
}
312
416
}
313
417
@@ -324,7 +428,7 @@ public void testFlightRecorderMXBeanProxy() {
324
428
Assert .fail ("Failed to get FlightRecorderMXBean. : " + e .getMessage ());
325
429
}
326
430
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 ());
328
432
}
329
433
330
434
@ Test
@@ -336,7 +440,7 @@ public void testFlightRecorderMXBeanDirect() throws MalformedObjectNameException
336
440
mbsc .invoke (objectName , "startRecording" , new Object []{recording }, new String []{"long" });
337
441
mbsc .invoke (objectName , "stopRecording" , new Object []{recording }, new String []{"long" });
338
442
} catch (Exception e ) {
339
- Assert .fail ("Remote invokations failed : " + e .getMessage ());
443
+ Assert .fail ("Remote invocations failed : " + e .getMessage ());
340
444
}
341
445
}
342
446
}
0 commit comments