001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2017 ForgeRock AS. 016 */ 017package org.opends.server.config; 018 019import static org.opends.messages.AdminMessages.*; 020 021import java.io.File; 022import java.io.FileWriter; 023import java.io.PrintWriter; 024import java.net.InetAddress; 025import java.util.List; 026import java.util.SortedSet; 027 028import javax.naming.ldap.Rdn; 029 030import org.forgerock.i18n.LocalizableMessage; 031import org.forgerock.i18n.slf4j.LocalizedLogger; 032import org.forgerock.opendj.config.server.ConfigChangeResult; 033import org.forgerock.opendj.config.server.ConfigException; 034import org.forgerock.opendj.config.server.ConfigurationChangeListener; 035import org.forgerock.opendj.ldap.AddressMask; 036import org.forgerock.opendj.ldap.DN; 037import org.forgerock.opendj.server.config.meta.LDAPConnectionHandlerCfgDefn.SSLClientAuthPolicy; 038import org.forgerock.opendj.server.config.server.AdministrationConnectorCfg; 039import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg; 040import org.forgerock.opendj.server.config.server.FileBasedKeyManagerProviderCfg; 041import org.forgerock.opendj.server.config.server.FileBasedTrustManagerProviderCfg; 042import org.forgerock.opendj.server.config.server.KeyManagerProviderCfg; 043import org.forgerock.opendj.server.config.server.LDAPConnectionHandlerCfg; 044import org.forgerock.opendj.server.config.server.RootCfg; 045import org.forgerock.opendj.server.config.server.TrustManagerProviderCfg; 046import org.opends.server.core.DirectoryServer; 047import org.opends.server.core.ServerContext; 048import org.opends.server.core.SynchronousStrategy; 049import org.opends.server.protocols.ldap.LDAPConnectionHandler; 050import org.opends.server.types.DirectoryException; 051import org.opends.server.types.FilePermission; 052import org.opends.server.types.InitializationException; 053import org.opends.server.util.CertificateManager; 054import org.opends.server.util.Platform.KeyType; 055import org.opends.server.util.SetupUtils; 056 057/** 058 * This class is a wrapper on top of LDAPConnectionHandler to manage 059 * the administration connector, which is an LDAPConnectionHandler 060 * with specific (limited) configuration properties. 061 */ 062public final class AdministrationConnector implements 063 ConfigurationChangeListener<AdministrationConnectorCfg> 064{ 065 066 /** Default Administration Connector port. */ 067 public static final int DEFAULT_ADMINISTRATION_CONNECTOR_PORT = 4444; 068 /** Validity (in days) of the generated certificate. */ 069 public static final int ADMIN_CERT_VALIDITY = 20 * 365; 070 071 /** Friendly name of the administration connector. */ 072 private static final String FRIENDLY_NAME = "Administration Connector"; 073 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 074 075 private LDAPConnectionHandler adminConnectionHandler; 076 private AdministrationConnectorCfg config; 077 078 /** Predefined values for Administration Connector configuration. */ 079 private static final String ADMIN_CLASS_NAME = 080 "org.opends.server.protocols.ldap.LDAPConnectionHandler"; 081 082 private static final boolean ADMIN_ALLOW_LDAP_V2 = false; 083 private static final boolean ADMIN_ALLOW_START_TLS = false; 084 085 private static final boolean ADMIN_ENABLED = true; 086 private static final boolean ADMIN_KEEP_STATS = true; 087 private static final boolean ADMIN_USE_SSL = true; 088 089 private static final int ADMIN_ACCEPT_BACKLOG = 128; 090 private static final boolean ADMIN_ALLOW_TCP_REUSE_ADDRESS = true; 091 092 /** 2mn. */ 093 private static final long ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT = 120000; 094 /** 5 Mb. */ 095 private static final int ADMIN_MAX_REQUEST_SIZE = 5000000; 096 private static final int ADMIN_WRITE_BUFFER_SIZE = 4096; 097 private static final int ADMIN_NUM_REQUEST_HANDLERS = 1; 098 private static final boolean ADMIN_SEND_REJECTION_NOTICE = true; 099 private static final boolean ADMIN_USE_TCP_KEEP_ALIVE = true; 100 private static final boolean ADMIN_USE_TCP_NO_DELAY = true; 101 private static final SSLClientAuthPolicy ADMIN_SSL_CLIENT_AUTH_POLICY = 102 SSLClientAuthPolicy.DISABLED; 103 104 private final ServerContext serverContext; 105 106 /** 107 * Initializes this administration connector provider based on the 108 * information in the provided administration connector 109 * configuration. 110 * 111 * @param configuration 112 * The connection handler configuration that contains the 113 * information to use to initialize this connection 114 * handler. 115 * @throws ConfigException 116 * If an unrecoverable problem arises in the process of 117 * performing the initialization as a result of the server 118 * configuration. 119 * @throws InitializationException 120 * If a problem occurs during initialization that is not 121 * related to the server configuration. 122 */ 123 public void initializeAdministrationConnector( 124 AdministrationConnectorCfg configuration) throws ConfigException, 125 InitializationException 126 { 127 this.config = configuration; 128 129 // Administration Connector uses the LDAP connection handler implementation 130 adminConnectionHandler = new LDAPConnectionHandler( 131 new SynchronousStrategy(), FRIENDLY_NAME); 132 adminConnectionHandler.initializeConnectionHandler(serverContext, new LDAPConnectionCfgAdapter(config)); 133 adminConnectionHandler.setAdminConnectionHandler(); 134 135 // Register this as a change listener. 136 config.addChangeListener(this); 137 } 138 139 140 /** 141 * Creates an instance of the administration connector. 142 * 143 * @param serverContext 144 * The server context. 145 **/ 146 public AdministrationConnector(ServerContext serverContext) 147 { 148 this.serverContext = serverContext; 149 } 150 151 /** 152 * Retrieves the connection handler linked to this administration connector. 153 * 154 * @return The connection handler linked to this administration connector. 155 */ 156 public LDAPConnectionHandler getConnectionHandler() 157 { 158 return adminConnectionHandler; 159 } 160 161 /** {@inheritDoc} */ 162 @Override 163 public boolean isConfigurationChangeAcceptable( 164 AdministrationConnectorCfg configuration, 165 List<LocalizableMessage> unacceptableReasons) 166 { 167 return adminConnectionHandler.isConfigurationAcceptable(new LDAPConnectionCfgAdapter(configuration), 168 unacceptableReasons); 169 } 170 171 /** {@inheritDoc} */ 172 @Override 173 public ConfigChangeResult applyConfigurationChange( 174 AdministrationConnectorCfg configuration) 175 { 176 return adminConnectionHandler.applyConfigurationChange(new LDAPConnectionCfgAdapter(configuration)); 177 } 178 179 180 181 /** 182 * This private class implements a fake LDAP connection Handler configuration. 183 * This allows to re-use the LDAPConnectionHandler as it is. 184 */ 185 private static class LDAPConnectionCfgAdapter implements LDAPConnectionHandlerCfg 186 { 187 private final AdministrationConnectorCfg config; 188 189 public LDAPConnectionCfgAdapter(AdministrationConnectorCfg config) 190 { 191 this.config = config; 192 } 193 194 /** {@inheritDoc} */ 195 @Override 196 public Class<? extends LDAPConnectionHandlerCfg> configurationClass() 197 { 198 return LDAPConnectionHandlerCfg.class; 199 } 200 201 /** {@inheritDoc} */ 202 @Override 203 public void addLDAPChangeListener( 204 ConfigurationChangeListener<LDAPConnectionHandlerCfg> listener) 205 { 206 // do nothing. change listener already added. 207 } 208 209 /** {@inheritDoc} */ 210 @Override 211 public void removeLDAPChangeListener( 212 ConfigurationChangeListener<LDAPConnectionHandlerCfg> listener) 213 { 214 // do nothing. change listener already added. 215 } 216 217 /** {@inheritDoc} */ 218 @Override 219 public int getAcceptBacklog() 220 { 221 return ADMIN_ACCEPT_BACKLOG; 222 } 223 224 /** {@inheritDoc} */ 225 @Override 226 public boolean isAllowLDAPV2() 227 { 228 return ADMIN_ALLOW_LDAP_V2; 229 } 230 231 /** {@inheritDoc} */ 232 @Override 233 public boolean isAllowStartTLS() 234 { 235 return ADMIN_ALLOW_START_TLS; 236 } 237 238 /** {@inheritDoc} */ 239 @Override 240 public boolean isAllowTCPReuseAddress() 241 { 242 return ADMIN_ALLOW_TCP_REUSE_ADDRESS; 243 } 244 245 /** {@inheritDoc} */ 246 @Override 247 public String getJavaClass() 248 { 249 return ADMIN_CLASS_NAME; 250 } 251 252 /** {@inheritDoc} */ 253 @Override 254 public boolean isKeepStats() 255 { 256 return ADMIN_KEEP_STATS; 257 } 258 259 /** {@inheritDoc} */ 260 @Override 261 public String getKeyManagerProvider() 262 { 263 return config.getKeyManagerProvider(); 264 } 265 266 /** {@inheritDoc} */ 267 @Override 268 public DN getKeyManagerProviderDN() 269 { 270 return config.getKeyManagerProviderDN(); 271 } 272 273 /** {@inheritDoc} */ 274 @Override 275 public SortedSet<InetAddress> getListenAddress() 276 { 277 return config.getListenAddress(); 278 } 279 280 /** {@inheritDoc} */ 281 @Override 282 public int getListenPort() 283 { 284 return config.getListenPort(); 285 } 286 287 /** {@inheritDoc} */ 288 @Override 289 public long getMaxBlockedWriteTimeLimit() 290 { 291 return ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT; 292 } 293 294 /** {@inheritDoc} */ 295 @Override 296 public long getMaxRequestSize() 297 { 298 return ADMIN_MAX_REQUEST_SIZE; 299 } 300 301 /** {@inheritDoc} */ 302 @Override 303 public long getBufferSize() 304 { 305 return ADMIN_WRITE_BUFFER_SIZE; 306 } 307 308 /** {@inheritDoc} */ 309 @Override 310 public Integer getNumRequestHandlers() 311 { 312 return ADMIN_NUM_REQUEST_HANDLERS; 313 } 314 315 /** {@inheritDoc} */ 316 @Override 317 public boolean isSendRejectionNotice() 318 { 319 return ADMIN_SEND_REJECTION_NOTICE; 320 } 321 322 /** {@inheritDoc} */ 323 @Override 324 public SortedSet<String> getSSLCertNickname() 325 { 326 return config.getSSLCertNickname(); 327 } 328 329 /** {@inheritDoc} */ 330 @Override 331 public SortedSet<String> getSSLCipherSuite() 332 { 333 return config.getSSLCipherSuite(); 334 } 335 336 /** {@inheritDoc} */ 337 @Override 338 public SSLClientAuthPolicy getSSLClientAuthPolicy() 339 { 340 return ADMIN_SSL_CLIENT_AUTH_POLICY; 341 } 342 343 /** {@inheritDoc} */ 344 @Override 345 public SortedSet<String> getSSLProtocol() 346 { 347 return config.getSSLProtocol(); 348 } 349 350 /** {@inheritDoc} */ 351 @Override 352 public String getTrustManagerProvider() 353 { 354 return config.getTrustManagerProvider(); 355 } 356 357 /** {@inheritDoc} */ 358 @Override 359 public DN getTrustManagerProviderDN() 360 { 361 return config.getTrustManagerProviderDN(); 362 } 363 364 /** {@inheritDoc} */ 365 @Override 366 public boolean isUseSSL() 367 { 368 return ADMIN_USE_SSL; 369 } 370 371 /** {@inheritDoc} */ 372 @Override 373 public boolean isUseTCPKeepAlive() 374 { 375 return ADMIN_USE_TCP_KEEP_ALIVE; 376 } 377 378 /** {@inheritDoc} */ 379 @Override 380 public boolean isUseTCPNoDelay() 381 { 382 return ADMIN_USE_TCP_NO_DELAY; 383 } 384 385 /** {@inheritDoc} */ 386 @Override 387 public void addChangeListener( 388 ConfigurationChangeListener<ConnectionHandlerCfg> listener) 389 { 390 // do nothing. change listener already added. 391 } 392 393 /** {@inheritDoc} */ 394 @Override 395 public void removeChangeListener( 396 ConfigurationChangeListener<ConnectionHandlerCfg> listener) 397 { 398 // do nothing. change listener already added. 399 } 400 401 /** {@inheritDoc} */ 402 @Override 403 public SortedSet<AddressMask> getAllowedClient() 404 { 405 return config.getAllowedClient(); 406 } 407 408 /** {@inheritDoc} */ 409 @Override 410 public SortedSet<AddressMask> getDeniedClient() 411 { 412 return config.getDeniedClient(); 413 } 414 415 /** {@inheritDoc} */ 416 @Override 417 public boolean isEnabled() 418 { 419 return ADMIN_ENABLED; 420 } 421 422 /** {@inheritDoc} */ 423 @Override 424 public DN dn() 425 { 426 return config.dn(); 427 } 428 } 429 430 431 432 /** 433 * Creates a self-signed JKS certificate if needed. 434 * 435 * @param serverContext 436 * The server context. 437 * @throws InitializationException 438 * If an unexpected error occurred whilst trying to create the 439 * certificate. 440 */ 441 public static void createSelfSignedCertificateIfNeeded(ServerContext serverContext) 442 throws InitializationException 443 { 444 try 445 { 446 RootCfg root = serverContext.getRootConfig(); 447 AdministrationConnectorCfg config = root.getAdministrationConnector(); 448 449 // Check if certificate generation is needed 450 final SortedSet<String> certAliases = config.getSSLCertNickname(); 451 KeyManagerProviderCfg keyMgrConfig = root.getKeyManagerProvider(config 452 .getKeyManagerProvider()); 453 TrustManagerProviderCfg trustMgrConfig = root 454 .getTrustManagerProvider(config.getTrustManagerProvider()); 455 456 if (hasDefaultConfigChanged(keyMgrConfig, trustMgrConfig)) 457 { 458 // nothing to do 459 return; 460 } 461 462 FileBasedKeyManagerProviderCfg fbKeyManagerConfig = 463 (FileBasedKeyManagerProviderCfg) keyMgrConfig; 464 String keystorePath = getFullPath(fbKeyManagerConfig.getKeyStoreFile()); 465 FileBasedTrustManagerProviderCfg fbTrustManagerConfig = 466 (FileBasedTrustManagerProviderCfg) trustMgrConfig; 467 String truststorePath = getFullPath(fbTrustManagerConfig 468 .getTrustStoreFile()); 469 String pinFilePath = getFullPath(fbKeyManagerConfig.getKeyStorePinFile()); 470 471 // Check that either we do not have any file, 472 // or we have the 3 required files (keystore, truststore, pin 473 // file) 474 boolean keystore = false; 475 boolean truststore = false; 476 boolean pinFile = false; 477 int nbFiles = 0; 478 if (new File(keystorePath).exists()) 479 { 480 keystore = true; 481 nbFiles++; 482 } 483 if (new File(truststorePath).exists()) 484 { 485 truststore = true; 486 nbFiles++; 487 } 488 if (new File(pinFilePath).exists()) 489 { 490 pinFile = true; 491 nbFiles++; 492 } 493 if (nbFiles == 3) 494 { 495 // nothing to do 496 return; 497 } 498 if (nbFiles != 0) 499 { 500 // 1 or 2 files are missing : error 501 String err = ""; 502 if (!keystore) 503 { 504 err += keystorePath + " "; 505 } 506 if (!truststore) 507 { 508 err += truststorePath + " "; 509 } 510 if (!pinFile) 511 { 512 err += pinFilePath + " "; 513 } 514 LocalizableMessage message = ERR_ADMIN_CERTIFICATE_GENERATION_MISSING_FILES 515 .get(err); 516 logger.error(message); 517 throw new InitializationException(message); 518 } 519 520 // Generate a password 521 String pwd = new String(SetupUtils.createSelfSignedCertificatePwd()); 522 523 // Generate a self-signed certificate 524 CertificateManager certManager = new CertificateManager( 525 getFullPath(fbKeyManagerConfig.getKeyStoreFile()), fbKeyManagerConfig 526 .getKeyStoreType(), pwd); 527 String hostName = 528 SetupUtils.getHostNameForCertificate(DirectoryServer.getServerRoot()); 529 530 // Temporary exported certificate's file 531 String tempCertPath = getFullPath("config" + File.separator 532 + "admin-cert.txt"); 533 534 // Create a new trust store and import the server certificate 535 // into it 536 CertificateManager trustManager = new CertificateManager(truststorePath, 537 CertificateManager.KEY_STORE_TYPE_JKS, pwd); 538 for (String certAlias : certAliases) 539 { 540 final KeyType keyType = KeyType.getTypeOrDefault(certAlias); 541 final String subjectDN = 542 "cn=" + Rdn.escapeValue(hostName) + ",O=" + FRIENDLY_NAME + " " + keyType + " Self-Signed Certificate"; 543 certManager.generateSelfSignedCertificate(keyType, certAlias, subjectDN, ADMIN_CERT_VALIDITY); 544 545 SetupUtils.exportCertificate(certManager, certAlias, tempCertPath); 546 547 // import the server certificate into it 548 final File tempCertFile = new File(tempCertPath); 549 trustManager.addCertificate(certAlias, tempCertFile); 550 tempCertFile.delete(); 551 } 552 553 // Generate a password file 554 if (!new File(pinFilePath).exists()) 555 { 556 try (final FileWriter file = new FileWriter(pinFilePath); 557 final PrintWriter out = new PrintWriter(file)) 558 { 559 out.println(pwd); 560 out.flush(); 561 } 562 } 563 564 // Change the password file permission if possible 565 try 566 { 567 if (!FilePermission.setPermissions(new File(pinFilePath), 568 new FilePermission(0600))) 569 { 570 // Log a warning that the permissions were not set. 571 logger.warn(WARN_ADMIN_SET_PERMISSIONS_FAILED, pinFilePath); 572 } 573 } 574 catch (DirectoryException e) 575 { 576 // Log a warning that the permissions were not set. 577 logger.warn(WARN_ADMIN_SET_PERMISSIONS_FAILED, pinFilePath); 578 } 579 } 580 catch (InitializationException e) 581 { 582 throw e; 583 } 584 catch (Exception e) 585 { 586 throw new InitializationException(ERR_ADMIN_CERTIFICATE_GENERATION.get(e.getMessage()), e); 587 } 588 } 589 590 /** 591 * Check if default configuration for administrator's key manager and trust 592 * manager provider has changed. 593 * 594 * @param keyConfig 595 * key manager provider configuration 596 * @param trustConfig 597 * trust manager provider configuration 598 * @return true if default configuration has changed, false otherwise 599 */ 600 private static boolean hasDefaultConfigChanged( 601 KeyManagerProviderCfg keyConfig, TrustManagerProviderCfg trustConfig) 602 { 603 if (keyConfig.isEnabled() 604 && keyConfig instanceof FileBasedKeyManagerProviderCfg 605 && trustConfig.isEnabled() 606 && trustConfig instanceof FileBasedTrustManagerProviderCfg) 607 { 608 FileBasedKeyManagerProviderCfg fileKeyConfig = 609 (FileBasedKeyManagerProviderCfg) keyConfig; 610 boolean pinIsProvidedByFileOnly = 611 fileKeyConfig.getKeyStorePinFile() != null 612 && fileKeyConfig.getKeyStorePin() == null 613 && fileKeyConfig.getKeyStorePinEnvironmentVariable() == null 614 && fileKeyConfig.getKeyStorePinProperty() == null; 615 return !pinIsProvidedByFileOnly; 616 } 617 return true; 618 } 619 620 private static String getFullPath(String path) 621 { 622 File file = new File(path); 623 if (!file.isAbsolute()) 624 { 625 path = DirectoryServer.getInstanceRoot() + File.separator + path; 626 } 627 628 return path; 629 } 630}