@ rev 11403 : 6425769: Allow specifying an address to bind JMX remote connector | Reviewed-by: jbachorik, dfuchs
1 /* 2 * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.management.jmxremote; 27 28 import java.io.BufferedInputStream; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.Serializable; 34 import java.lang.management.ManagementFactory; 35 import java.net.InetAddress; 36 import java.net.MalformedURLException; 37 import java.net.Socket; 38 import java.net.ServerSocket; 39 import java.net.UnknownHostException; 40 import java.rmi.NoSuchObjectException; 41 import java.rmi.Remote; 42 import java.rmi.RemoteException; 43 import java.rmi.registry.Registry; 44 import java.rmi.server.RMIClientSocketFactory; 45 import java.rmi.server.RMIServerSocketFactory; 46 import java.rmi.server.RMISocketFactory; 47 import java.rmi.server.RemoteObject; 48 import java.rmi.server.UnicastRemoteObject; 49 import java.security.KeyStore; 50 import java.security.Principal; 51 import java.util.HashMap; 52 import java.util.HashSet; 53 import java.util.Iterator; 54 import java.util.Map; 55 import java.util.Properties; 56 import java.util.Set; 57 import java.util.StringTokenizer; 58 59 import javax.management.MBeanServer; 60 import javax.management.remote.JMXAuthenticator; 61 import javax.management.remote.JMXConnectorServer; 62 import javax.management.remote.JMXConnectorServerFactory; 63 import javax.management.remote.JMXServiceURL; 64 import javax.management.remote.rmi.RMIConnectorServer; 65 import javax.net.ssl.KeyManagerFactory; 66 import javax.net.ssl.SSLContext; 67 import javax.net.ssl.SSLSocket; 68 import javax.net.ssl.SSLSocketFactory; 69 import javax.net.ssl.TrustManagerFactory; 70 import javax.rmi.ssl.SslRMIClientSocketFactory; 71 import javax.rmi.ssl.SslRMIServerSocketFactory; 72 import javax.security.auth.Subject; 73 74 import com.sun.jmx.remote.internal.RMIExporter; 75 import com.sun.jmx.remote.security.JMXPluggableAuthenticator; 76 import com.sun.jmx.remote.util.ClassLogger; 77 78 import sun.management.Agent; 79 import sun.management.AgentConfigurationError; 80 import static sun.management.AgentConfigurationError.*; 81 import sun.management.ConnectorAddressLink; 82 import sun.management.FileSystem; 83 import sun.rmi.server.UnicastRef; 84 import sun.rmi.server.UnicastServerRef; 85 import sun.rmi.server.UnicastServerRef2; 86 87 /** 88 * This class initializes and starts the RMIConnectorServer for JSR 163 89 * JMX Monitoring. 90 **/ 91 public final class ConnectorBootstrap { 92 93 /** 94 * Default values for JMX configuration properties. 95 **/ 96 public static interface DefaultValues { 97 98 public static final String PORT = "0"; 99 public static final String CONFIG_FILE_NAME = "management.properties"; 100 public static final String USE_SSL = "true"; 101 public static final String USE_LOCAL_ONLY = "true"; 102 public static final String USE_REGISTRY_SSL = "false"; 103 public static final String USE_AUTHENTICATION = "true"; 104 public static final String PASSWORD_FILE_NAME = "jmxremote.password"; 105 public static final String ACCESS_FILE_NAME = "jmxremote.access"; 106 public static final String SSL_NEED_CLIENT_AUTH = "false"; 107 } 108 109 /** 110 * Names of JMX configuration properties. 111 **/ 112 public static interface PropertyNames { 113 114 public static final String PORT = 115 "com.sun.management.jmxremote.port"; 116 public static final String HOST = 117 "com.sun.management.jmxremote.host"; 118 public static final String RMI_PORT = 119 "com.sun.management.jmxremote.rmi.port"; 120 public static final String CONFIG_FILE_NAME = 121 "com.sun.management.config.file"; 122 public static final String USE_LOCAL_ONLY = 123 "com.sun.management.jmxremote.local.only"; 124 public static final String USE_SSL = 125 "com.sun.management.jmxremote.ssl"; 126 public static final String USE_REGISTRY_SSL = 127 "com.sun.management.jmxremote.registry.ssl"; 128 public static final String USE_AUTHENTICATION = 129 "com.sun.management.jmxremote.authenticate"; 130 public static final String PASSWORD_FILE_NAME = 131 "com.sun.management.jmxremote.password.file"; 132 public static final String ACCESS_FILE_NAME = 133 "com.sun.management.jmxremote.access.file"; 134 public static final String LOGIN_CONFIG_NAME = 135 "com.sun.management.jmxremote.login.config"; 136 public static final String SSL_ENABLED_CIPHER_SUITES = 137 "com.sun.management.jmxremote.ssl.enabled.cipher.suites"; 138 public static final String SSL_ENABLED_PROTOCOLS = 139 "com.sun.management.jmxremote.ssl.enabled.protocols"; 140 public static final String SSL_NEED_CLIENT_AUTH = 141 "com.sun.management.jmxremote.ssl.need.client.auth"; 142 public static final String SSL_CONFIG_FILE_NAME = 143 "com.sun.management.jmxremote.ssl.config.file"; 144 } 145 146 /** 147 * JMXConnectorServer associated data. 148 */ 149 private static class JMXConnectorServerData { 150 151 public JMXConnectorServerData( 152 JMXConnectorServer jmxConnectorServer, 153 JMXServiceURL jmxRemoteURL) { 154 this.jmxConnectorServer = jmxConnectorServer; 155 this.jmxRemoteURL = jmxRemoteURL; 156 } 157 JMXConnectorServer jmxConnectorServer; 158 JMXServiceURL jmxRemoteURL; 159 } 160 161 /** 162 * <p>Prevents our RMI server objects from keeping the JVM alive.</p> 163 * 164 * <p>We use a private interface in Sun's JMX Remote API implementation 165 * that allows us to specify how to export RMI objects. We do so using 166 * UnicastServerRef, a class in Sun's RMI implementation. This is all 167 * non-portable, of course, so this is only valid because we are inside 168 * Sun's JRE.</p> 169 * 170 * <p>Objects are exported using {@link 171 * UnicastServerRef#exportObject(Remote, Object, boolean)}. The 172 * boolean parameter is called <code>permanent</code> and means 173 * both that the object is not eligible for Distributed Garbage 174 * Collection, and that its continued existence will not prevent 175 * the JVM from exiting. It is the latter semantics we want (we 176 * already have the former because of the way the JMX Remote API 177 * works). Hence the somewhat misleading name of this class.</p> 178 */ 179 private static class PermanentExporter implements RMIExporter { 180 181 public Remote exportObject(Remote obj, 182 int port, 183 RMIClientSocketFactory csf, 184 RMIServerSocketFactory ssf) 185 throws RemoteException { 186 187 synchronized (this) { 188 if (firstExported == null) { 189 firstExported = obj; 190 } 191 } 192 193 final UnicastServerRef ref; 194 if (csf == null && ssf == null) { 195 ref = new UnicastServerRef(port); 196 } else { 197 ref = new UnicastServerRef2(port, csf, ssf); 198 } 199 return ref.exportObject(obj, null, true); 200 } 201 202 // Nothing special to be done for this case 203 public boolean unexportObject(Remote obj, boolean force) 204 throws NoSuchObjectException { 205 return UnicastRemoteObject.unexportObject(obj, force); 206 } 207 Remote firstExported; 208 } 209 210 /** 211 * This JMXAuthenticator wraps the JMXPluggableAuthenticator and verifies 212 * that at least one of the principal names contained in the authenticated 213 * Subject is present in the access file. 214 */ 215 private static class AccessFileCheckerAuthenticator 216 implements JMXAuthenticator { 217 218 public AccessFileCheckerAuthenticator(Map<String, Object> env) throws IOException { 219 environment = env; 220 accessFile = (String) env.get("jmx.remote.x.access.file"); 221 properties = propertiesFromFile(accessFile); 222 } 223 224 public Subject authenticate(Object credentials) { 225 final JMXAuthenticator authenticator = 226 new JMXPluggableAuthenticator(environment); 227 final Subject subject = authenticator.authenticate(credentials); 228 checkAccessFileEntries(subject); 229 return subject; 230 } 231 232 private void checkAccessFileEntries(Subject subject) { 233 if (subject == null) { 234 throw new SecurityException( 235 "Access denied! No matching entries found in " + 236 "the access file [" + accessFile + "] as the " + 237 "authenticated Subject is null"); 238 } 239 final Set<Principal> principals = subject.getPrincipals(); 240 for (Principal p1: principals) { 241 if (properties.containsKey(p1.getName())) { 242 return; 243 } 244 } 245 246 final Set<String> principalsStr = new HashSet<>(); 247 for (Principal p2: principals) { 248 principalsStr.add(p2.getName()); 249 } 250 throw new SecurityException( 251 "Access denied! No entries found in the access file [" + 252 accessFile + "] for any of the authenticated identities " + 253 principalsStr); 254 } 255 256 private static Properties propertiesFromFile(String fname) 257 throws IOException { 258 Properties p = new Properties(); 259 if (fname == null) { 260 return p; 261 } 262 try (FileInputStream fin = new FileInputStream(fname)) { 263 p.load(fin); 264 } 265 return p; 266 } 267 private final Map<String, Object> environment; 268 private final Properties properties; 269 private final String accessFile; 270 } 271 272 // The variable below is here to support stop functionality 273 // It would be overriten if you call startRemoteCommectionServer second 274 // time. It's OK for now as logic in Agent.java forbids mutiple agents 275 private static Registry registry = null; 276 277 public static void unexportRegistry() { 278 // Remove the entry from registry 279 try { 280 if (registry != null) { 281 UnicastRemoteObject.unexportObject(registry, true); 282 registry = null; 283 } 284 } catch(NoSuchObjectException ex) { 285 // This exception can appears only if we attempt 286 // to unexportRegistry second time. So it's safe 287 // to ignore it without additional messages. 288 } 289 } 290 291 /** 292 * Initializes and starts the JMX Connector Server. 293 * If the com.sun.management.jmxremote.port property is not defined, 294 * simply return. Otherwise, attempts to load the config file, and 295 * then calls {@link #startRemoteConnectorServer 296 * (java.lang.String, java.util.Properties)}. 297 * 298 * This method is used by some jtreg tests. 299 **/ 300 public static synchronized JMXConnectorServer initialize() { 301 302 // Load a new management properties 303 final Properties props = Agent.loadManagementProperties(); 304 if (props == null) { 305 return null; 306 } 307 308 final String portStr = props.getProperty(PropertyNames.PORT); 309 return startRemoteConnectorServer(portStr, props); 310 } 311 312 /** 313 * This method is used by some jtreg tests. 314 * 315 * @see #startRemoteConnectorServer 316 * (String portStr, Properties props) 317 */ 318 public static synchronized JMXConnectorServer initialize(String portStr, Properties props) { 319 return startRemoteConnectorServer(portStr, props); 320 } 321 322 /** 323 * Initializes and starts a JMX Connector Server for remote 324 * monitoring and management. 325 **/ 326 public static synchronized JMXConnectorServer startRemoteConnectorServer(String portStr, Properties props) { 327 328 // Get port number 329 final int port; 330 try { 331 port = Integer.parseInt(portStr); 332 } catch (NumberFormatException x) { 333 throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, x, portStr); 334 } 335 if (port < 0) { 336 throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, portStr); 337 } 338 339 // User can specify a port to be used to export rmi object, 340 // in order to simplify firewall rules 341 // if port is not specified random one will be allocated. 342 int rmiPort = 0; 343 String rmiPortStr = props.getProperty(PropertyNames.RMI_PORT); 344 try { 345 if (rmiPortStr != null) { 346 rmiPort = Integer.parseInt(rmiPortStr); 347 } 348 } catch (NumberFormatException x) { 349 throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, x, rmiPortStr); 350 } 351 if (rmiPort < 0) { 352 throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, rmiPortStr); 353 } 354 355 // Do we use authentication? 356 final String useAuthenticationStr = 357 props.getProperty(PropertyNames.USE_AUTHENTICATION, 358 DefaultValues.USE_AUTHENTICATION); 359 final boolean useAuthentication = 360 Boolean.valueOf(useAuthenticationStr).booleanValue(); 361 362 // Do we use SSL? 363 final String useSslStr = 364 props.getProperty(PropertyNames.USE_SSL, 365 DefaultValues.USE_SSL); 366 final boolean useSsl = 367 Boolean.valueOf(useSslStr).booleanValue(); 368 369 // Do we use RMI Registry SSL? 370 final String useRegistrySslStr = 371 props.getProperty(PropertyNames.USE_REGISTRY_SSL, 372 DefaultValues.USE_REGISTRY_SSL); 373 final boolean useRegistrySsl = 374 Boolean.valueOf(useRegistrySslStr).booleanValue(); 375 376 final String enabledCipherSuites = 377 props.getProperty(PropertyNames.SSL_ENABLED_CIPHER_SUITES); 378 String enabledCipherSuitesList[] = null; 379 if (enabledCipherSuites != null) { 380 StringTokenizer st = new StringTokenizer(enabledCipherSuites, ","); 381 int tokens = st.countTokens(); 382 enabledCipherSuitesList = new String[tokens]; 383 for (int i = 0; i < tokens; i++) { 384 enabledCipherSuitesList[i] = st.nextToken(); 385 } 386 } 387 388 final String enabledProtocols = 389 props.getProperty(PropertyNames.SSL_ENABLED_PROTOCOLS); 390 String enabledProtocolsList[] = null; 391 if (enabledProtocols != null) { 392 StringTokenizer st = new StringTokenizer(enabledProtocols, ","); 393 int tokens = st.countTokens(); 394 enabledProtocolsList = new String[tokens]; 395 for (int i = 0; i < tokens; i++) { 396 enabledProtocolsList[i] = st.nextToken(); 397 } 398 } 399 400 final String sslNeedClientAuthStr = 401 props.getProperty(PropertyNames.SSL_NEED_CLIENT_AUTH, 402 DefaultValues.SSL_NEED_CLIENT_AUTH); 403 final boolean sslNeedClientAuth = 404 Boolean.valueOf(sslNeedClientAuthStr).booleanValue(); 405 406 // Read SSL config file name 407 final String sslConfigFileName = 408 props.getProperty(PropertyNames.SSL_CONFIG_FILE_NAME); 409 410 String loginConfigName = null; 411 String passwordFileName = null; 412 String accessFileName = null; 413 414 // Initialize settings when authentication is active 415 if (useAuthentication) { 416 417 // Get non-default login configuration 418 loginConfigName = 419 props.getProperty(PropertyNames.LOGIN_CONFIG_NAME); 420 421 if (loginConfigName == null) { 422 // Get password file 423 passwordFileName = 424 props.getProperty(PropertyNames.PASSWORD_FILE_NAME, 425 getDefaultFileName(DefaultValues.PASSWORD_FILE_NAME)); 426 checkPasswordFile(passwordFileName); 427 } 428 429 // Get access file 430 accessFileName = props.getProperty(PropertyNames.ACCESS_FILE_NAME, 431 getDefaultFileName(DefaultValues.ACCESS_FILE_NAME)); 432 checkAccessFile(accessFileName); 433 } 434 435 final String bindAddress = 436 props.getProperty(PropertyNames.HOST); 437 438 if (log.debugOn()) { 439 log.debug("startRemoteConnectorServer", 440 Agent.getText("jmxremote.ConnectorBootstrap.starting") + 441 "\n\t" + PropertyNames.PORT + "=" + port + 442 (bindAddress == null ? "" : "\n\t" + PropertyNames.HOST + "=" + bindAddress) + 443 "\n\t" + PropertyNames.RMI_PORT + "=" + rmiPort + 444 "\n\t" + PropertyNames.USE_SSL + "=" + useSsl + 445 "\n\t" + PropertyNames.USE_REGISTRY_SSL + "=" + useRegistrySsl + 446 "\n\t" + PropertyNames.SSL_CONFIG_FILE_NAME + "=" + sslConfigFileName + 447 "\n\t" + PropertyNames.SSL_ENABLED_CIPHER_SUITES + "=" + 448 enabledCipherSuites + 449 "\n\t" + PropertyNames.SSL_ENABLED_PROTOCOLS + "=" + 450 enabledProtocols + 451 "\n\t" + PropertyNames.SSL_NEED_CLIENT_AUTH + "=" + 452 sslNeedClientAuth + 453 "\n\t" + PropertyNames.USE_AUTHENTICATION + "=" + 454 useAuthentication + 455 (useAuthentication ? (loginConfigName == null ? ("\n\t" + PropertyNames.PASSWORD_FILE_NAME + "=" + 456 passwordFileName) : ("\n\t" + PropertyNames.LOGIN_CONFIG_NAME + "=" + 457 loginConfigName)) : "\n\t" + 458 Agent.getText("jmxremote.ConnectorBootstrap.noAuthentication")) + 459 (useAuthentication ? ("\n\t" + PropertyNames.ACCESS_FILE_NAME + "=" + 460 accessFileName) : "") + 461 ""); 462 } 463 464 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 465 JMXConnectorServer cs = null; 466 JMXServiceURL url = null; 467 try { 468 final JMXConnectorServerData data = exportMBeanServer( 469 mbs, port, rmiPort, useSsl, useRegistrySsl, 470 sslConfigFileName, enabledCipherSuitesList, 471 enabledProtocolsList, sslNeedClientAuth, 472 useAuthentication, loginConfigName, 473 passwordFileName, accessFileName, bindAddress); 474 cs = data.jmxConnectorServer; 475 url = data.jmxRemoteURL; 476 log.config("startRemoteConnectorServer", 477 Agent.getText("jmxremote.ConnectorBootstrap.ready", 478 url.toString())); 479 } catch (Exception e) { 480 throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString()); 481 } 482 try { 483 // Export remote connector address and associated configuration 484 // properties to the instrumentation buffer. 485 Map<String, String> properties = new HashMap<>(); 486 properties.put("remoteAddress", url.toString()); 487 properties.put("authenticate", useAuthenticationStr); 488 properties.put("ssl", useSslStr); 489 properties.put("sslRegistry", useRegistrySslStr); 490 properties.put("sslNeedClientAuth", sslNeedClientAuthStr); 491 ConnectorAddressLink.exportRemote(properties); 492 } catch (Exception e) { 493 // Remote connector server started but unable to export remote 494 // connector address and associated configuration properties to 495 // the instrumentation buffer - non-fatal error. 496 log.debug("startRemoteConnectorServer", e); 497 } 498 return cs; 499 } 500 501 /* 502 * Creates and starts a RMI Connector Server for "local" monitoring 503 * and management. 504 */ 505 public static JMXConnectorServer startLocalConnectorServer() { 506 // Ensure cryptographically strong random number generater used 507 // to choose the object number - see java.rmi.server.ObjID 508 System.setProperty("java.rmi.server.randomIDs", "true"); 509 510 // This RMI server should not keep the VM alive 511 Map<String, Object> env = new HashMap<>(); 512 env.put(RMIExporter.EXPORTER_ATTRIBUTE, new PermanentExporter()); 513 514 // The local connector server need only be available via the 515 // loopback connection. 516 String localhost = "localhost"; 517 InetAddress lh = null; 518 try { 519 lh = InetAddress.getByName(localhost); 520 localhost = lh.getHostAddress(); 521 } catch (UnknownHostException x) { 522 } 523 524 // localhost unknown or (somehow) didn't resolve to 525 // a loopback address. 526 if (lh == null || !lh.isLoopbackAddress()) { 527 localhost = "127.0.0.1"; 528 } 529 530 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 531 try { 532 JMXServiceURL url = new JMXServiceURL("rmi", localhost, 0); 533 // Do we accept connections from local interfaces only? 534 Properties props = Agent.getManagementProperties(); 535 if (props == null) { 536 props = new Properties(); 537 } 538 String useLocalOnlyStr = props.getProperty( 539 PropertyNames.USE_LOCAL_ONLY, DefaultValues.USE_LOCAL_ONLY); 540 boolean useLocalOnly = Boolean.valueOf(useLocalOnlyStr).booleanValue(); 541 if (useLocalOnly) { 542 env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, 543 new LocalRMIServerSocketFactory()); 544 } 545 JMXConnectorServer server = 546 JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); 547 server.start(); 548 return server; 549 } catch (Exception e) { 550 throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString()); 551 } 552 } 553 554 private static void checkPasswordFile(String passwordFileName) { 555 if (passwordFileName == null || passwordFileName.length() == 0) { 556 throw new AgentConfigurationError(PASSWORD_FILE_NOT_SET); 557 } 558 File file = new File(passwordFileName); 559 if (!file.exists()) { 560 throw new AgentConfigurationError(PASSWORD_FILE_NOT_FOUND, passwordFileName); 561 } 562 563 if (!file.canRead()) { 564 throw new AgentConfigurationError(PASSWORD_FILE_NOT_READABLE, passwordFileName); 565 } 566 567 FileSystem fs = FileSystem.open(); 568 try { 569 if (fs.supportsFileSecurity(file)) { 570 if (!fs.isAccessUserOnly(file)) { 571 final String msg = Agent.getText("jmxremote.ConnectorBootstrap.password.readonly", 572 passwordFileName); 573 log.config("startRemoteConnectorServer", msg); 574 throw new AgentConfigurationError(PASSWORD_FILE_ACCESS_NOT_RESTRICTED, 575 passwordFileName); 576 } 577 } 578 } catch (IOException e) { 579 throw new AgentConfigurationError(PASSWORD_FILE_READ_FAILED, 580 e, passwordFileName); 581 } 582 } 583 584 private static void checkAccessFile(String accessFileName) { 585 if (accessFileName == null || accessFileName.length() == 0) { 586 throw new AgentConfigurationError(ACCESS_FILE_NOT_SET); 587 } 588 File file = new File(accessFileName); 589 if (!file.exists()) { 590 throw new AgentConfigurationError(ACCESS_FILE_NOT_FOUND, accessFileName); 591 } 592 593 if (!file.canRead()) { 594 throw new AgentConfigurationError(ACCESS_FILE_NOT_READABLE, accessFileName); 595 } 596 } 597 598 private static void checkRestrictedFile(String restrictedFileName) { 599 if (restrictedFileName == null || restrictedFileName.length() == 0) { 600 throw new AgentConfigurationError(FILE_NOT_SET); 601 } 602 File file = new File(restrictedFileName); 603 if (!file.exists()) { 604 throw new AgentConfigurationError(FILE_NOT_FOUND, restrictedFileName); 605 } 606 if (!file.canRead()) { 607 throw new AgentConfigurationError(FILE_NOT_READABLE, restrictedFileName); 608 } 609 FileSystem fs = FileSystem.open(); 610 try { 611 if (fs.supportsFileSecurity(file)) { 612 if (!fs.isAccessUserOnly(file)) { 613 final String msg = Agent.getText( 614 "jmxremote.ConnectorBootstrap.file.readonly", 615 restrictedFileName); 616 log.config("startRemoteConnectorServer", msg); 617 throw new AgentConfigurationError( 618 FILE_ACCESS_NOT_RESTRICTED, restrictedFileName); 619 } 620 } 621 } catch (IOException e) { 622 throw new AgentConfigurationError( 623 FILE_READ_FAILED, e, restrictedFileName); 624 } 625 } 626 627 /** 628 * Compute the full path name for a default file. 629 * @param basename basename (with extension) of the default file. 630 * @return ${JRE}/lib/management/${basename} 631 **/ 632 private static String getDefaultFileName(String basename) { 633 final String fileSeparator = File.separator; 634 return System.getProperty("java.home") + fileSeparator + "lib" + 635 fileSeparator + "management" + fileSeparator + 636 basename; 637 } 638 639 private static SslRMIServerSocketFactory createSslRMIServerSocketFactory( 640 String sslConfigFileName, 641 String[] enabledCipherSuites, 642 String[] enabledProtocols, 643 boolean sslNeedClientAuth, 644 String bindAddress) { 645 if (sslConfigFileName == null) { 646 return new HostAwareSslSocketFactory( 647 enabledCipherSuites, 648 enabledProtocols, 649 sslNeedClientAuth, bindAddress); 650 } else { 651 checkRestrictedFile(sslConfigFileName); 652 try { 653 // Load the SSL keystore properties from the config file 654 Properties p = new Properties(); 655 try (InputStream in = new FileInputStream(sslConfigFileName)) { 656 BufferedInputStream bin = new BufferedInputStream(in); 657 p.load(bin); 658 } 659 String keyStore = 660 p.getProperty("javax.net.ssl.keyStore"); 661 String keyStorePassword = 662 p.getProperty("javax.net.ssl.keyStorePassword", ""); 663 String trustStore = 664 p.getProperty("javax.net.ssl.trustStore"); 665 String trustStorePassword = 666 p.getProperty("javax.net.ssl.trustStorePassword", ""); 667 668 char[] keyStorePasswd = null; 669 if (keyStorePassword.length() != 0) { 670 keyStorePasswd = keyStorePassword.toCharArray(); 671 } 672 673 char[] trustStorePasswd = null; 674 if (trustStorePassword.length() != 0) { 675 trustStorePasswd = trustStorePassword.toCharArray(); 676 } 677 678 KeyStore ks = null; 679 if (keyStore != null) { 680 ks = KeyStore.getInstance(KeyStore.getDefaultType()); 681 try (FileInputStream ksfis = new FileInputStream(keyStore)) { 682 ks.load(ksfis, keyStorePasswd); 683 } 684 } 685 KeyManagerFactory kmf = KeyManagerFactory.getInstance( 686 KeyManagerFactory.getDefaultAlgorithm()); 687 kmf.init(ks, keyStorePasswd); 688 689 KeyStore ts = null; 690 if (trustStore != null) { 691 ts = KeyStore.getInstance(KeyStore.getDefaultType()); 692 try (FileInputStream tsfis = new FileInputStream(trustStore)) { 693 ts.load(tsfis, trustStorePasswd); 694 } 695 } 696 TrustManagerFactory tmf = TrustManagerFactory.getInstance( 697 TrustManagerFactory.getDefaultAlgorithm()); 698 tmf.init(ts); 699 700 SSLContext ctx = SSLContext.getInstance("SSL"); 701 ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 702 703 return new HostAwareSslSocketFactory( 704 ctx, 705 enabledCipherSuites, 706 enabledProtocols, 707 sslNeedClientAuth, bindAddress); 708 } catch (Exception e) { 709 throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString()); 710 } 711 } 712 } 713 714 private static JMXConnectorServerData exportMBeanServer( 715 MBeanServer mbs, 716 int port, 717 int rmiPort, 718 boolean useSsl, 719 boolean useRegistrySsl, 720 String sslConfigFileName, 721 String[] enabledCipherSuites, 722 String[] enabledProtocols, 723 boolean sslNeedClientAuth, 724 boolean useAuthentication, 725 String loginConfigName, 726 String passwordFileName, 727 String accessFileName, 728 String bindAddress) 729 throws IOException, MalformedURLException { 730 731 /* Make sure we use non-guessable RMI object IDs. Otherwise 732 * attackers could hijack open connections by guessing their 733 * IDs. */ 734 System.setProperty("java.rmi.server.randomIDs", "true"); 735 736 JMXServiceURL url = new JMXServiceURL("rmi", bindAddress, rmiPort); 737 738 Map<String, Object> env = new HashMap<>(); 739 740 PermanentExporter exporter = new PermanentExporter(); 741 742 env.put(RMIExporter.EXPORTER_ATTRIBUTE, exporter); 743 744 boolean useSocketFactory = bindAddress != null && !useSsl; 745 746 if (useAuthentication) { 747 if (loginConfigName != null) { 748 env.put("jmx.remote.x.login.config", loginConfigName); 749 } 750 if (passwordFileName != null) { 751 env.put("jmx.remote.x.password.file", passwordFileName); 752 } 753 754 env.put("jmx.remote.x.access.file", accessFileName); 755 756 if (env.get("jmx.remote.x.password.file") != null || 757 env.get("jmx.remote.x.login.config") != null) { 758 env.put(JMXConnectorServer.AUTHENTICATOR, 759 new AccessFileCheckerAuthenticator(env)); 760 } 761 } 762 763 RMIClientSocketFactory csf = null; 764 RMIServerSocketFactory ssf = null; 765 766 if (useSsl || useRegistrySsl) { 767 csf = new SslRMIClientSocketFactory(); 768 ssf = createSslRMIServerSocketFactory( 769 sslConfigFileName, enabledCipherSuites, 770 enabledProtocols, sslNeedClientAuth, bindAddress); 771 } 772 773 if (useSsl) { 774 env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, 775 csf); 776 env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, 777 ssf); 778 } 779 780 if (useSocketFactory) { 781 ssf = new HostAwareSocketFactory(bindAddress); 782 env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, 783 ssf); 784 } 785 786 JMXConnectorServer connServer = null; 787 try { 788 connServer = 789 JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); 790 connServer.start(); 791 } catch (IOException e) { 792 if (connServer == null || connServer.getAddress() == null) { 793 throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR, 794 e, url.toString()); 795 } else { 796 throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR, 797 e, connServer.getAddress().toString()); 798 } 799 } 800 801 if (useRegistrySsl) { 802 registry = 803 new SingleEntryRegistry(port, csf, ssf, 804 "jmxrmi", exporter.firstExported); 805 } else if (useSocketFactory) { 806 registry = 807 new SingleEntryRegistry(port, csf, ssf, 808 "jmxrmi", exporter.firstExported); 809 } else { 810 registry = 811 new SingleEntryRegistry(port, 812 "jmxrmi", exporter.firstExported); 813 } 814 815 816 int registryPort = 817 ((UnicastRef) ((RemoteObject) registry).getRef()).getLiveRef().getPort(); 818 String jmxUrlStr = String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", 819 url.getHost(), registryPort); 820 JMXServiceURL remoteURL = new JMXServiceURL(jmxUrlStr); 821 822 /* Our exporter remembers the first object it was asked to 823 export, which will be an RMIServerImpl appropriate for 824 publication in our special registry. We could 825 alternatively have constructed the RMIServerImpl explicitly 826 and then constructed an RMIConnectorServer passing it as a 827 parameter, but that's quite a bit more verbose and pulls in 828 lots of knowledge of the RMI connector. */ 829 830 return new JMXConnectorServerData(connServer, remoteURL); 831 } 832 833 /** 834 * This class cannot be instantiated. 835 **/ 836 private ConnectorBootstrap() { 837 } 838 839 private static final ClassLogger log = 840 new ClassLogger(ConnectorBootstrap.class.getPackage().getName(), 841 "ConnectorBootstrap"); 842 843 private static class HostAwareSocketFactory implements RMIServerSocketFactory { 844 845 private final String bindAddress; 846 847 private HostAwareSocketFactory(String bindAddress) { 848 this.bindAddress = bindAddress; 849 } 850 851 @Override 852 public ServerSocket createServerSocket(int port) throws IOException { 853 if (bindAddress == null) { 854 return new ServerSocket(port); 855 } else { 856 try { 857 InetAddress addr = InetAddress.getByName(bindAddress); 858 return new ServerSocket(port, 0, addr); 859 } catch (UnknownHostException e) { 860 return new ServerSocket(port); 861 } 862 } 863 } 864 } 865 866 private static class HostAwareSslSocketFactory extends SslRMIServerSocketFactory { 867 868 private final String bindAddress; 869 private final String[] enabledCipherSuites; 870 private final String[] enabledProtocols; 871 private final boolean needClientAuth; 872 private final SSLContext context; 873 874 private HostAwareSslSocketFactory(String[] enabledCipherSuites, 875 String[] enabledProtocols, 876 boolean sslNeedClientAuth, 877 String bindAddress) throws IllegalArgumentException { 878 this(null, enabledCipherSuites, enabledProtocols, sslNeedClientAuth, bindAddress); 879 } 880 881 private HostAwareSslSocketFactory(SSLContext ctx, 882 String[] enabledCipherSuites, 883 String[] enabledProtocols, 884 boolean sslNeedClientAuth, 885 String bindAddress) throws IllegalArgumentException { 886 this.context = ctx; 887 this.bindAddress = bindAddress; 888 this.enabledProtocols = enabledProtocols; 889 this.enabledCipherSuites = enabledCipherSuites; 890 this.needClientAuth = sslNeedClientAuth; 891 checkValues(ctx, enabledCipherSuites, enabledProtocols); 892 } 893 894 @Override 895 public ServerSocket createServerSocket(int port) throws IOException { 896 if (bindAddress != null) { 897 try { 898 InetAddress addr = InetAddress.getByName(bindAddress); 899 return new SslServerSocket(port, 0, addr, context, 900 enabledCipherSuites, enabledProtocols, needClientAuth); 901 } catch (UnknownHostException e) { 902 return new SslServerSocket(port, context, 903 enabledCipherSuites, enabledProtocols, needClientAuth); 904 } 905 } else { 906 return new SslServerSocket(port, context, 907 enabledCipherSuites, enabledProtocols, needClientAuth); 908 } 909 } 910 911 private static void checkValues(SSLContext context, 912 String[] enabledCipherSuites, 913 String[] enabledProtocols) throws IllegalArgumentException { 914 // Force the initialization of the default at construction time, 915 // rather than delaying it to the first time createServerSocket() 916 // is called. 917 // 918 final SSLSocketFactory sslSocketFactory = 919 context == null ? 920 (SSLSocketFactory)SSLSocketFactory.getDefault() : context.getSocketFactory(); 921 SSLSocket sslSocket = null; 922 if (enabledCipherSuites != null || enabledProtocols != null) { 923 try { 924 sslSocket = (SSLSocket) sslSocketFactory.createSocket(); 925 } catch (Exception e) { 926 final String msg = "Unable to check if the cipher suites " + 927 "and protocols to enable are supported"; 928 throw (IllegalArgumentException) 929 new IllegalArgumentException(msg).initCause(e); 930 } 931 } 932 933 // Check if all the cipher suites and protocol versions to enable 934 // are supported by the underlying SSL/TLS implementation and if 935 // true create lists from arrays. 936 // 937 if (enabledCipherSuites != null) { 938 sslSocket.setEnabledCipherSuites(enabledCipherSuites); 939 } 940 if (enabledProtocols != null) { 941 sslSocket.setEnabledProtocols(enabledProtocols); 942 } 943 } 944 } 945 946 private static class SslServerSocket extends ServerSocket { 947 948 private static SSLSocketFactory defaultSSLSocketFactory; 949 private final String[] enabledCipherSuites; 950 private final String[] enabledProtocols; 951 private final boolean needClientAuth; 952 private final SSLContext context; 953 954 private SslServerSocket(int port, 955 SSLContext ctx, 956 String[] enabledCipherSuites, 957 String[] enabledProtocols, 958 boolean needClientAuth) throws IOException { 959 super(port); 960 this.enabledProtocols = enabledProtocols; 961 this.enabledCipherSuites = enabledCipherSuites; 962 this.needClientAuth = needClientAuth; 963 this.context = ctx; 964 } 965 966 private SslServerSocket(int port, 967 int backlog, 968 InetAddress bindAddr, 969 SSLContext ctx, 970 String[] enabledCipherSuites, 971 String[] enabledProtocols, 972 boolean needClientAuth) throws IOException { 973 super(port, backlog, bindAddr); 974 this.enabledProtocols = enabledProtocols; 975 this.enabledCipherSuites = enabledCipherSuites; 976 this.needClientAuth = needClientAuth; 977 this.context = ctx; 978 } 979 980 @Override 981 public Socket accept() throws IOException { 982 final SSLSocketFactory sslSocketFactory = 983 context == null ? 984 getDefaultSSLSocketFactory() : context.getSocketFactory(); 985 Socket socket = super.accept(); 986 SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( 987 socket, socket.getInetAddress().getHostName(), 988 socket.getPort(), true); 989 sslSocket.setUseClientMode(false); 990 if (enabledCipherSuites != null) { 991 sslSocket.setEnabledCipherSuites(enabledCipherSuites); 992 } 993 if (enabledProtocols != null) { 994 sslSocket.setEnabledProtocols(enabledProtocols); 995 } 996 sslSocket.setNeedClientAuth(needClientAuth); 997 return sslSocket; 998 } 999 1000 private static synchronized SSLSocketFactory getDefaultSSLSocketFactory() { 1001 if (defaultSSLSocketFactory == null) { 1002 defaultSSLSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); 1003 return defaultSSLSocketFactory; 1004 } else { 1005 return defaultSSLSocketFactory; 1006 } 1007 } 1008 1009 } 1010 } --- EOF ---