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