/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.admin; import static org.opends.messages.AdminMessages.*; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.net.InetAddress; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import javax.naming.ldap.Rdn; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.AddressMask; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.server.ServerManagementContext; import org.opends.server.admin.std.meta.LDAPConnectionHandlerCfgDefn.SSLClientAuthPolicy; import org.opends.server.admin.std.server.AdministrationConnectorCfg; import org.opends.server.admin.std.server.ConnectionHandlerCfg; import org.opends.server.admin.std.server.FileBasedKeyManagerProviderCfg; import org.opends.server.admin.std.server.FileBasedTrustManagerProviderCfg; import org.opends.server.admin.std.server.KeyManagerProviderCfg; import org.opends.server.admin.std.server.LDAPConnectionHandlerCfg; import org.opends.server.admin.std.server.RootCfg; import org.opends.server.admin.std.server.TrustManagerProviderCfg; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ServerContext; import org.opends.server.core.SynchronousStrategy; import org.opends.server.protocols.ldap.LDAPConnectionHandler; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.FilePermission; import org.opends.server.types.InitializationException; import org.opends.server.util.CertificateManager; import org.opends.server.util.Platform.KeyType; import org.opends.server.util.SetupUtils; /** * This class is a wrapper on top of LDAPConnectionHandler to manage * the administration connector, which is an LDAPConnectionHandler * with specific (limited) configuration properties. */ public final class AdministrationConnector implements ConfigurationChangeListener { /** Default Administration Connector port. */ public static final int DEFAULT_ADMINISTRATION_CONNECTOR_PORT = 4444; /** Validity (in days) of the generated certificate. */ public static final int ADMIN_CERT_VALIDITY = 20 * 365; /** Friendly name of the administration connector. */ private static final String FRIENDLY_NAME = "Administration Connector"; private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private LDAPConnectionHandler adminConnectionHandler; private AdministrationConnectorCfg config; /** Predefined values for Administration Connector configuration. */ private static final String ADMIN_CLASS_NAME = "org.opends.server.protocols.ldap.LDAPConnectionHandler"; private static final boolean ADMIN_ALLOW_LDAP_V2 = false; private static final boolean ADMIN_ALLOW_START_TLS = false; private static final SortedSet ADMIN_ALLOWED_CLIENT = new TreeSet<>(); private static final SortedSet ADMIN_DENIED_CLIENT = new TreeSet<>(); private static final boolean ADMIN_ENABLED = true; private static final boolean ADMIN_KEEP_STATS = true; private static final boolean ADMIN_USE_SSL = true; private static final int ADMIN_ACCEPT_BACKLOG = 128; private static final boolean ADMIN_ALLOW_TCP_REUSE_ADDRESS = true; /** 2mn. */ private static final long ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT = 120000; /** 5 Mb. */ private static final int ADMIN_MAX_REQUEST_SIZE = 5000000; private static final int ADMIN_WRITE_BUFFER_SIZE = 4096; private static final int ADMIN_NUM_REQUEST_HANDLERS = 1; private static final boolean ADMIN_SEND_REJECTION_NOTICE = true; private static final boolean ADMIN_USE_TCP_KEEP_ALIVE = true; private static final boolean ADMIN_USE_TCP_NO_DELAY = true; private static final SSLClientAuthPolicy ADMIN_SSL_CLIENT_AUTH_POLICY = SSLClientAuthPolicy.DISABLED; private final ServerContext serverContext; /** * Initializes this administration connector provider based on the * information in the provided administration connector * configuration. * * @param configuration * The connection handler configuration that contains the * information to use to initialize this connection * handler. * @throws ConfigException * If an unrecoverable problem arises in the process of * performing the initialization as a result of the server * configuration. * @throws InitializationException * If a problem occurs during initialization that is not * related to the server configuration. */ public void initializeAdministrationConnector( AdministrationConnectorCfg configuration) throws ConfigException, InitializationException { this.config = configuration; // Administration Connector uses the LDAP connection handler implementation adminConnectionHandler = new LDAPConnectionHandler( new SynchronousStrategy(), FRIENDLY_NAME); adminConnectionHandler.initializeConnectionHandler(serverContext, new LDAPConnectionCfgAdapter(config)); adminConnectionHandler.setAdminConnectionHandler(); // Register this as a change listener. config.addChangeListener(this); } /** * Creates an instance of the administration connector. * * @param serverContext * The server context. **/ public AdministrationConnector(ServerContext serverContext) { this.serverContext = serverContext; } /** * Retrieves the connection handler linked to this administration connector. * * @return The connection handler linked to this administration connector. */ public LDAPConnectionHandler getConnectionHandler() { return adminConnectionHandler; } /** {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( AdministrationConnectorCfg configuration, List unacceptableReasons) { return adminConnectionHandler.isConfigurationAcceptable(new LDAPConnectionCfgAdapter(configuration), unacceptableReasons); } /** {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( AdministrationConnectorCfg configuration) { return adminConnectionHandler.applyConfigurationChange(new LDAPConnectionCfgAdapter(configuration)); } /** * This private class implements a fake LDAP connection Handler configuration. * This allows to re-use the LDAPConnectionHandler as it is. */ private static class LDAPConnectionCfgAdapter implements LDAPConnectionHandlerCfg { private final AdministrationConnectorCfg config; public LDAPConnectionCfgAdapter(AdministrationConnectorCfg config) { this.config = config; } /** {@inheritDoc} */ @Override public Class configurationClass() { return LDAPConnectionHandlerCfg.class; } /** {@inheritDoc} */ @Override public void addLDAPChangeListener( ConfigurationChangeListener listener) { // do nothing. change listener already added. } /** {@inheritDoc} */ @Override public void removeLDAPChangeListener( ConfigurationChangeListener listener) { // do nothing. change listener already added. } /** {@inheritDoc} */ @Override public int getAcceptBacklog() { return ADMIN_ACCEPT_BACKLOG; } /** {@inheritDoc} */ @Override public boolean isAllowLDAPV2() { return ADMIN_ALLOW_LDAP_V2; } /** {@inheritDoc} */ @Override public boolean isAllowStartTLS() { return ADMIN_ALLOW_START_TLS; } /** {@inheritDoc} */ @Override public boolean isAllowTCPReuseAddress() { return ADMIN_ALLOW_TCP_REUSE_ADDRESS; } /** {@inheritDoc} */ @Override public String getJavaClass() { return ADMIN_CLASS_NAME; } /** {@inheritDoc} */ @Override public boolean isKeepStats() { return ADMIN_KEEP_STATS; } /** {@inheritDoc} */ @Override public String getKeyManagerProvider() { return config.getKeyManagerProvider(); } /** {@inheritDoc} */ @Override public DN getKeyManagerProviderDN() { return config.getKeyManagerProviderDN(); } /** {@inheritDoc} */ @Override public SortedSet getListenAddress() { return config.getListenAddress(); } /** {@inheritDoc} */ @Override public int getListenPort() { return config.getListenPort(); } /** {@inheritDoc} */ @Override public long getMaxBlockedWriteTimeLimit() { return ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT; } /** {@inheritDoc} */ @Override public long getMaxRequestSize() { return ADMIN_MAX_REQUEST_SIZE; } /** {@inheritDoc} */ @Override public long getBufferSize() { return ADMIN_WRITE_BUFFER_SIZE; } /** {@inheritDoc} */ @Override public Integer getNumRequestHandlers() { return ADMIN_NUM_REQUEST_HANDLERS; } /** {@inheritDoc} */ @Override public boolean isSendRejectionNotice() { return ADMIN_SEND_REJECTION_NOTICE; } /** {@inheritDoc} */ @Override public SortedSet getSSLCertNickname() { return config.getSSLCertNickname(); } /** {@inheritDoc} */ @Override public SortedSet getSSLCipherSuite() { return config.getSSLCipherSuite(); } /** {@inheritDoc} */ @Override public SSLClientAuthPolicy getSSLClientAuthPolicy() { return ADMIN_SSL_CLIENT_AUTH_POLICY; } /** {@inheritDoc} */ @Override public SortedSet getSSLProtocol() { return config.getSSLProtocol(); } /** {@inheritDoc} */ @Override public String getTrustManagerProvider() { return config.getTrustManagerProvider(); } /** {@inheritDoc} */ @Override public DN getTrustManagerProviderDN() { return config.getTrustManagerProviderDN(); } /** {@inheritDoc} */ @Override public boolean isUseSSL() { return ADMIN_USE_SSL; } /** {@inheritDoc} */ @Override public boolean isUseTCPKeepAlive() { return ADMIN_USE_TCP_KEEP_ALIVE; } /** {@inheritDoc} */ @Override public boolean isUseTCPNoDelay() { return ADMIN_USE_TCP_NO_DELAY; } /** {@inheritDoc} */ @Override public void addChangeListener( ConfigurationChangeListener listener) { // do nothing. change listener already added. } /** {@inheritDoc} */ @Override public void removeChangeListener( ConfigurationChangeListener listener) { // do nothing. change listener already added. } /** {@inheritDoc} */ @Override public SortedSet getAllowedClient() { return ADMIN_ALLOWED_CLIENT; } /** {@inheritDoc} */ @Override public SortedSet getDeniedClient() { return ADMIN_DENIED_CLIENT; } /** {@inheritDoc} */ @Override public boolean isEnabled() { return ADMIN_ENABLED; } /** {@inheritDoc} */ @Override public DN dn() { return config.dn(); } } /** * Creates a self-signed JKS certificate if needed. * * @param serverContext * The server context. * @throws InitializationException * If an unexpected error occurred whilst trying to create the * certificate. */ public static void createSelfSignedCertificateIfNeeded(ServerContext serverContext) throws InitializationException { try { RootCfg root = ServerManagementContext.getInstance() .getRootConfiguration(); AdministrationConnectorCfg config = root.getAdministrationConnector(); // Check if certificate generation is needed final SortedSet certAliases = config.getSSLCertNickname(); KeyManagerProviderCfg keyMgrConfig = root.getKeyManagerProvider(config .getKeyManagerProvider()); TrustManagerProviderCfg trustMgrConfig = root .getTrustManagerProvider(config.getTrustManagerProvider()); if (hasDefaultConfigChanged(keyMgrConfig, trustMgrConfig)) { // nothing to do return; } FileBasedKeyManagerProviderCfg fbKeyManagerConfig = (FileBasedKeyManagerProviderCfg) keyMgrConfig; String keystorePath = getFullPath(fbKeyManagerConfig.getKeyStoreFile()); FileBasedTrustManagerProviderCfg fbTrustManagerConfig = (FileBasedTrustManagerProviderCfg) trustMgrConfig; String truststorePath = getFullPath(fbTrustManagerConfig .getTrustStoreFile()); String pinFilePath = getFullPath(fbKeyManagerConfig.getKeyStorePinFile()); // Check that either we do not have any file, // or we have the 3 required files (keystore, truststore, pin // file) boolean keystore = false; boolean truststore = false; boolean pinFile = false; int nbFiles = 0; if (new File(keystorePath).exists()) { keystore = true; nbFiles++; } if (new File(truststorePath).exists()) { truststore = true; nbFiles++; } if (new File(pinFilePath).exists()) { pinFile = true; nbFiles++; } if (nbFiles == 3) { // nothing to do return; } if (nbFiles != 0) { // 1 or 2 files are missing : error String err = ""; if (!keystore) { err += keystorePath + " "; } if (!truststore) { err += truststorePath + " "; } if (!pinFile) { err += pinFilePath + " "; } LocalizableMessage message = ERR_ADMIN_CERTIFICATE_GENERATION_MISSING_FILES .get(err); logger.error(message); throw new InitializationException(message); } // Generate a password String pwd = new String(SetupUtils.createSelfSignedCertificatePwd()); // Generate a self-signed certificate CertificateManager certManager = new CertificateManager( getFullPath(fbKeyManagerConfig.getKeyStoreFile()), fbKeyManagerConfig .getKeyStoreType(), pwd); String hostName = SetupUtils.getHostNameForCertificate(DirectoryServer.getServerRoot()); // Temporary exported certificate's file String tempCertPath = getFullPath("config" + File.separator + "admin-cert.txt"); // Create a new trust store and import the server certificate // into it CertificateManager trustManager = new CertificateManager(truststorePath, CertificateManager.KEY_STORE_TYPE_JKS, pwd); for (String certAlias : certAliases) { final KeyType keyType = KeyType.getTypeOrDefault(certAlias); final String subjectDN = "cn=" + Rdn.escapeValue(hostName) + ",O=" + FRIENDLY_NAME + " " + keyType + " Self-Signed Certificate"; certManager.generateSelfSignedCertificate(keyType, certAlias, subjectDN, ADMIN_CERT_VALIDITY); SetupUtils.exportCertificate(certManager, certAlias, tempCertPath); // import the server certificate into it final File tempCertFile = new File(tempCertPath); trustManager.addCertificate(certAlias, tempCertFile); tempCertFile.delete(); } // Generate a password file if (!new File(pinFilePath).exists()) { try (final FileWriter file = new FileWriter(pinFilePath); final PrintWriter out = new PrintWriter(file)) { out.println(pwd); out.flush(); } } // Change the password file permission if possible try { if (!FilePermission.setPermissions(new File(pinFilePath), new FilePermission(0600))) { // Log a warning that the permissions were not set. logger.warn(WARN_ADMIN_SET_PERMISSIONS_FAILED, pinFilePath); } } catch (DirectoryException e) { // Log a warning that the permissions were not set. logger.warn(WARN_ADMIN_SET_PERMISSIONS_FAILED, pinFilePath); } } catch (InitializationException e) { throw e; } catch (Exception e) { throw new InitializationException(ERR_ADMIN_CERTIFICATE_GENERATION.get(e.getMessage()), e); } } /** * Check if default configuration for administrator's key manager and trust * manager provider has changed. * * @param keyConfig * key manager provider configuration * @param trustConfig * trust manager provider configuration * @return true if default configuration has changed, false otherwise */ private static boolean hasDefaultConfigChanged( KeyManagerProviderCfg keyConfig, TrustManagerProviderCfg trustConfig) { if (keyConfig.isEnabled() && keyConfig instanceof FileBasedKeyManagerProviderCfg && trustConfig.isEnabled() && trustConfig instanceof FileBasedTrustManagerProviderCfg) { FileBasedKeyManagerProviderCfg fileKeyConfig = (FileBasedKeyManagerProviderCfg) keyConfig; boolean pinIsProvidedByFileOnly = fileKeyConfig.getKeyStorePinFile() != null && fileKeyConfig.getKeyStorePin() == null && fileKeyConfig.getKeyStorePinEnvironmentVariable() == null && fileKeyConfig.getKeyStorePinProperty() == null; return !pinIsProvidedByFileOnly; } return true; } private static String getFullPath(String path) { File file = new File(path); if (!file.isAbsolute()) { path = DirectoryServer.getInstanceRoot() + File.separator + path; } return path; } }