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