All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.util.SecurityConfigCreator Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

The newest version!
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.util;

import static oracle.kv.impl.admin.param.SecurityParams.TRANS_TYPE_SSL;
import static oracle.kv.impl.admin.param.SecurityParams.TRANS_TYPE_CLEAR;
import static oracle.kv.impl.security.PasswordManager.FILE_STORE_MANAGER_CLASS;
import static oracle.kv.impl.security.PasswordManager.WALLET_MANAGER_CLASS;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KeyTab;

import oracle.kv.KVSecurityConstants;
import oracle.kv.impl.admin.param.SecurityParams;
import oracle.kv.impl.param.ParameterState;
import oracle.kv.impl.param.ParameterState.Info;
import oracle.kv.impl.security.AuthenticatorManager;
import oracle.kv.impl.security.AuthenticatorManager.SystemAuthMethod;
import oracle.kv.impl.security.PasswordManager;
import oracle.kv.impl.security.PasswordStore;
import oracle.kv.impl.security.PasswordStoreException;
import oracle.kv.impl.security.ssl.SSLConfig;
import oracle.kv.impl.security.util.ConsolePasswordReader;
import oracle.kv.impl.security.util.PasswordReader;
import oracle.kv.impl.security.util.SecurityUtils;
import oracle.kv.impl.security.util.SecurityUtils.KadminSetting;
import oracle.kv.impl.security.util.SecurityUtils.Krb5Config;
import oracle.kv.util.shell.Shell;

/**
 * SecurityConfigCreator implements the core operations for creating a
 * security configuration.  It is referenced by both makebootconfig and
 * securityconfig.
 */
public class SecurityConfigCreator {

    /* This is a Java-specified requirement */
    private static final int MIN_STORE_PASSPHRASE_LEN = 6;
    private String kvRoot;
    private final IOHelper ioHelper;
    private final ParsedConfig config;
    /* derived from config */
    private final String pwdMgrClass;

    private static final String SHARED_KEY_ALIAS = "shared";
    private static final String SSL_CERT_DN = "CN=NoSQL";
    private static final String SSL_CLIENT_PEER = "CN=NoSQL";
    private static final String SSL_SERVER_PEER = "CN=NoSQL";
    private static final String INTERNAL_AUTH_SSL = "ssl";
    private static final String CERT_MODE_SHARED = "shared";
    private static final String CERT_MODE_SERVER = "server";
    private static final String PWD_ALIAS_KEYSTORE = "keystore";
    private static final String NOT_CREATING_KEYTAB_MESSAGE =
        "The kadmin path was specified as NONE, so not creating " +
        "a keytab for the database server. The keytab must " +
        "be generated and copied to the security configuration " +
        "directory manually.";

    /**
     * Configuration class.
     */
    public static class ParsedConfig {
        private String pwdmgr;
        private String secDir;
        private char[] ksPassword;
        private String certMode;
        private String userExternalAuth;
        private String krbConf;
        private String princInstanceName;
        private boolean printCreatedFiles = true;
        private boolean nossl = false;

        /* Settings used for connecting kadmin */
        private KadminSetting kadminSetting = new KadminSetting();

        /* User-defined parameters */
        private List userParams = new ArrayList<>();

        /* User-defined Kerberos configuration properties */
        private Properties princConfigProperties =
            SecurityUtils.getDefaultKrbPrincipalProperties();

        public class ParamSetting {
            final ParameterState pstate;
            final String transportName;
            final String paramName;
            final String paramValue;

            ParamSetting(ParameterState pstate,
                         String transportName,
                         String paramName,
                         String paramValue) {
                this.pstate = pstate;
                this.transportName = transportName;
                this.paramName = paramName;
                this.paramValue = paramValue;
            }

            public ParameterState getParameterState() {
                return pstate;
            }

            public String getTransportName() {
                return transportName;
            }

            public String getParamName() {
                return paramName;
            }

            public String getParamValue() {
                return paramValue;
            }
        }

        public void setPwdmgr(String pwdmgrVal) {
            pwdmgr = pwdmgrVal;
        }

        public String getPwdmgr() {
            return pwdmgr;
        }

        public void setPrintCreatedFiles(boolean value) {
            printCreatedFiles = value;
        }

        public boolean isPrintCreatedFiles() {
            return printCreatedFiles;
        }

        public void setKeystorePassword(char[] ksPwdVal) {
            ksPassword = ksPwdVal;
        }

        public char[] getKeystorePassword() {
            return ksPassword;
        }

        public void setSecurityDir(String securityDir) {
            secDir = securityDir;
        }

        public String getSecurityDir() {
            return secDir;
        }

        public void setNoSSL(boolean value) {
            nossl = value;
        }

        public boolean noSSL() {
            return nossl;
        }

        public void setCertMode(String certModeVal) {
            if (certModeVal != null &&
                !(CERT_MODE_SHARED.equals(certModeVal)) &&
                !(CERT_MODE_SERVER.equals(certModeVal))) {
                throw new IllegalArgumentException(
                    "The value '" + certModeVal + "' is not a valid " +
                    "certificate mode.  Only " + CERT_MODE_SHARED + " and " +
                    CERT_MODE_SERVER + " are allowed.");
            }
            certMode = certModeVal;
        }

        public String getCertMode() {
            return certMode;
        }

        public void setUserExternalAuth(String externalAuthVal) {
            /*
             * We support multiple external authentication methods, but only
             * one at at time.
             */
            if (externalAuthVal != null) {
                userExternalAuth = externalAuthVal.toUpperCase(Locale.ENGLISH);
                final String[] extAuths = userExternalAuth.split(",");

                for (String extAuth : extAuths) {
                    if (!AuthenticatorManager.isValidAuthMethod(extAuth) &&
                        !AuthenticatorManager.noneAuthMethod(extAuth)) {
                        throw new IllegalArgumentException(
                            "The value '" + extAuth + "' is not a valid " +
                            "external authentication method. " +
                            "Only " +
                            Arrays.toString(SystemAuthMethod.values()) +
                            " are allowed.");
                    }
                }

                if (SecurityUtils.hasKerberos(extAuths) &&
                    SecurityUtils.hasIDCSOAuth(extAuths)) {
                    throw new IllegalArgumentException(
                        "The value '" + extAuths +"' is not valid, cannot " +
                        "enable more than one authentication methods");
                }
            }
            userExternalAuth = externalAuthVal;
        }

        public String getUserExternalAuth() {
            return userExternalAuth;
        }

        public void setKrbConf(String krbConfVal) {
            krbConf = krbConfVal;
        }

        public String getKrbConf() {
            return krbConf;
        }

        public KadminSetting getKadminSetting() {
            return kadminSetting;
        }

        public void setInstanceName(String instanceNameVal) {
            princInstanceName = instanceNameVal;
        }

        public String getInstanceName() {
            return princInstanceName;
        }

        public void setKadminPath(String kadminPath) {
            kadminSetting.setKrbAdminPath(kadminPath);
        }

        public void setAdminPrinc(String adminPrinc) {
            kadminSetting.setKrbAdminPrinc(adminPrinc);
        }

        public void setKadminKeytab(String adminKeytab) {
            kadminSetting.setKrbAdminKeytab(adminKeytab);
        }

        public void setKadminCcache(String adminCcache) {
            kadminSetting.setKrbAdminCcache(adminCcache);
        }

        /**
         * @throws IllegalArgumentException if the parameter setting is not
         * valid.
         */
        public void addParam(String paramSetting) {

            final int equalIdx = paramSetting.indexOf("=");
            if (equalIdx < 0) {
                throw new IllegalArgumentException(
                    "Invalid parameter setting - missing '='");
            }

            final String param = paramSetting.substring(0, equalIdx);
            final String value = paramSetting.substring(equalIdx + 1);
            final String[] paramSplit = param.split(":");

            if (paramSplit.length > 2) {
                throw new IllegalArgumentException(
                    "Invalid parameter name format: " + param);
            }

            final String paramName = paramSplit[paramSplit.length - 1];
            final String transport =
                (paramSplit.length > 1) ? paramSplit[0] : null;
            final ParameterState pstate = ParameterState.lookup(paramName);

            if (pstate == null) {
                throw new IllegalArgumentException(
                    "The name " + paramName + " is not a valid parameter name");
            }

            if (!(pstate.appliesTo(Info.SECURITY) ||
                  pstate.appliesTo(Info.TRANSPORT))) {
                throw new IllegalArgumentException(
                    "The name " + paramName + " is not a valid parameter for " +
                    "a security configuration");
            }

            if (transport != null) {
                if (!pstate.appliesTo(Info.TRANSPORT)) {
                    throw new IllegalArgumentException(
                        paramName + " is not a transport parameter");
                }

                if (!(ParameterState.SECURITY_TRANSPORT_CLIENT.equals(
                          transport) ||
                      ParameterState.SECURITY_TRANSPORT_INTERNAL.equals(
                          transport) ||
                      ParameterState.SECURITY_TRANSPORT_JE_HA.equals(
                          transport))) {
                    throw new IllegalArgumentException(
                        transport + " is not a valid transport name");
                }
            }
            userParams.add(
                new ParamSetting(pstate, transport, paramName, value));
        }

        public List getUserParams() {
            return userParams;
        }

        public void addKrbProperty(String paramSetting) {
            final int equalIdx = paramSetting.indexOf("=");
            if (equalIdx < 0) {
                throw new IllegalArgumentException(
                    "Invalid parameter setting - missing '='");
            }

            final String param = paramSetting.substring(0, equalIdx);
            final String value = paramSetting.substring(equalIdx + 1);

            if (princConfigProperties.get(param) == null) {
                throw new IllegalArgumentException("The name " + param +
                    " is not a valid Kerberos configuration parameter");
            }
            princConfigProperties.put(param, value);
        }

        public Properties getKrbPrincProperties() {
            return princConfigProperties;
        }

        public void populateDefaults() {
            if (getSecurityDir() == null) {
                setSecurityDir(FileNames.SECURITY_CONFIG_DIR);
            }
            if (getCertMode() == null) {
                setCertMode(CERT_MODE_SHARED);
            }
            if (getKrbConf() == null) {
                setKrbConf(SecurityUtils.KRB_CONF_FILE);
            }
        }
    }

    /**
     * This functionality wants to be accessed through a couple of paths.
     * provide an intermediate interface that can adapt to either path.
     */
    public interface IOHelper {

        /**
         * Read a password from the user.
         */
        char[] readPassword(String prompt) throws IOException;

        /**
         * Print a line of output.
         */
        void println(String s);
    }

    /**
     * IOHelper for use in the context of a Shell environment.
     */
    static class ShellIOHelper implements IOHelper {
        private Shell shell;
        private PasswordReader passwordReader;

        ShellIOHelper(Shell shell) {
            this.shell = shell;
            passwordReader = null;
            if (shell instanceof SecurityShell) {
                passwordReader = ((SecurityShell) shell).getPasswordReader();
            }
            if (passwordReader == null) {
                passwordReader = new ConsolePasswordReader();
            }
        }

        @Override
        public char[] readPassword(String prompt) throws IOException {
            return passwordReader.readPassword(prompt);
        }

        @Override
        public void println(String s) {
            shell.println(s);
        }
    }

    /**
     * Generic IO Helper
     */
    public static class GenericIOHelper implements IOHelper {
        private PrintStream printStream;
        private PasswordReader passwordReader;

        public GenericIOHelper(PrintStream printStream) {
            this(printStream, new ConsolePasswordReader());
        }

        GenericIOHelper(PrintStream printStream,
                        PasswordReader passwordReader) {
            this.printStream = printStream;
            this.passwordReader = passwordReader;
        }

        @Override
        public char[] readPassword(String prompt) throws IOException {
            return passwordReader.readPassword(prompt);
        }

        @Override
        public void println(String s) {
            printStream.println(s);
        }
    }

    public SecurityConfigCreator(String kvRoot,
                                 ParsedConfig parsedConfig,
                                 IOHelper ioHelper) {
        this.kvRoot = kvRoot;
        this.ioHelper = ioHelper;
        this.config = parsedConfig;
        this.pwdMgrClass =
            SecurityConfigCommand.getPwdmgrClass(config.getPwdmgr());
    }

    /*
     * Create a user login file for secured KVLite.
     */
    public boolean createUserLoginFile(String user,
                                       char[] password,
                                       File securityDir)
        throws PasswordStoreException, Exception {

        final PasswordManager pwdMgr = resolvePwdMgr();

        if (pwdMgr == null) {
            return false;
        }

        final SecurityParams sp = makeSecurityParams();
        final Properties securityProps = sp.getClientAccessProps();
        securityProps.setProperty(
            KVSecurityConstants.AUTH_USERNAME_PROPERTY, user);
        if (pwdMgrClass.equals(WALLET_MANAGER_CLASS)) {
            sp.setWalletDir(FileNames.USER_WALLET_DIR);
            securityProps.setProperty(
                KVSecurityConstants.AUTH_WALLET_PROPERTY,
                FileNames.USER_WALLET_DIR);
        } else if (pwdMgrClass.equals(FILE_STORE_MANAGER_CLASS)) {
            sp.setPasswordFile(FileNames.USER_PASSWD_FILE);
            securityProps.setProperty(
                KVSecurityConstants.AUTH_PWDFILE_PROPERTY,
                FileNames.USER_PASSWD_FILE);
        }

        createUserPasswordStore(securityDir, sp, user, password);

        final File userSecurityFile =
            new File(securityDir.getPath(), FileNames.USER_SECURITY_FILE);

        securityProps.put(
            SSLConfig.TRUSTSTORE_FILE, FileNames.CLIENT_TRUSTSTORE_FILE);

        ConfigUtils.storeProperties(securityProps, null, userSecurityFile);

        ioHelper.println("Generated password for user " + user + ":" +
                         String.valueOf(password));
        ioHelper.println("User login file: " + userSecurityFile.getPath());

        return true;
    }

    /**
     * Gather input from the user and create the requisite security files.
     * @return true if the operation was successful
     */
    public boolean createConfig() throws PasswordStoreException, Exception {

        config.populateDefaults();

        /* Get a PasswordManager instance */
        final PasswordManager pwdMgr = resolvePwdMgr();
        if (pwdMgr == null) {
            return false;
        }

        /* Make sure the security directory exists - create if needed */
        final File securityDir = prepareSecurityDir();
        if (securityDir == null) {
            return false;
        }

        final boolean nossl = config.noSSL();
        char[] keyStorePassword = config.getKeystorePassword();
        if (!nossl) {
            if (keyStorePassword != null) {
                if (!validKeystorePassword(keyStorePassword)) {
                    return false;
                }
            } else {
                /* Get a keystore password - the user may need to know this */
                try {
                    keyStorePassword = promptForKeyStorePassword();
                    if (keyStorePassword == null) {
                        ioHelper.println("No keystore password specified");
                        return false;
                    }
                } catch (IOException ioe) {
                    ioHelper.println("I/O error reading password: " +
                                     ioe.getMessage());
                    return false;
                }
            }
        }

        /* Prepare the SecurityParams object */
        final SecurityParams sp = nossl ?
            makeSecurityParamsWithoutSSL() : makeSecurityParams();

        if (!nossl) {
            /* Create the keystore file */
            final Properties keyStoreProperties = new Properties();
            keyStoreProperties.setProperty(SecurityUtils.KEY_DISTINGUISHED_NAME,
                                           SSL_CERT_DN);
            SecurityUtils.initKeyStore(securityDir,
                                       sp,
                                       keyStorePassword,
                                       keyStoreProperties);

            /*
             * Create the password store, which will hold the password for the
             * keystore.
             */
            createUserPasswordStore(securityDir, sp, PWD_ALIAS_KEYSTORE,
                keyStorePassword);
        }

        /*
         * Create requisite security configuration files of Kerberos
         * authentication method.
         */
        if (SecurityUtils.hasKerberos(config.getUserExternalAuth())) {
            /* Check if Kerberos authentication is supported */
            if (!resolveKerberosAuth()) {
                return false;
            }

            /* Parse and validate Kerberos configuration file */
            final Krb5Config krb5Conf = parseKerberosConfig();
            if (krb5Conf == null) {
                return false;
            }

            /* Validate kadmin settings */
            try {
                config.getKadminSetting().validateKadminSetting();
            } catch (IllegalArgumentException iae) {
                ioHelper.println("kadmin setting error " + iae.getMessage());
                return false;
            }

            sp.setKerberosConfFile(krb5Conf.getConfigFilePath());
            sp.setKerberosRealmName(krb5Conf.getDefaultRealm());

            if (!config.getKadminSetting().doNotPerformKadmin()) {
                SecurityUtils.generateKeyTabFile(securityDir,
                                                 sp,
                                                 config.getKadminSetting(),
                                                 config.getKrbPrincProperties(),
                                                 ioHelper);
            } else {
                ioHelper.println(NOT_CREATING_KEYTAB_MESSAGE);
            }
        }

        /* Check if IDCS OAuth authentication method can be enabled */
        if (SecurityUtils.hasIDCSOAuth(config.getUserExternalAuth())) {
            resolveIDCSOAuthAuth();
        }

        /*
         * Now that the password store and keystore have successfully been
         * created, build the security.xml file that ties it all together.
         */
        final File securityXmlFile =
            new File(securityDir.getPath(), FileNames.SECURITY_CONFIG_FILE);
        ConfigUtils.createSecurityConfig(sp, securityXmlFile);

        /*
         * Now build a security file that captures the salient bits
         * that the customer needs in order to connect to the KVStore.
         */
        final File securityFile =
            new File(securityDir.getPath(), FileNames.CLIENT_SECURITY_FILE);
        final Properties securityProps = sp.getClientAccessProps();

        /*
         * The client access properties have a trustStore setting that
         * references the store.trust file.  Update it to reference the
         * client.trust file.
         */
        final String trustStoreRef =
            securityProps.getProperty(SSLConfig.TRUSTSTORE_FILE);
        if (trustStoreRef != null) {
            /*
             * There is a truststore file that is needed for secure access.
             * Make a copy of it and update the property to refer to the client
             * copy of the file.
             */
            final File srcFile =
                new File(securityDir, trustStoreRef);
            final File destFile =
                new File(securityDir, FileNames.CLIENT_TRUSTSTORE_FILE);

            SecurityUtils.copyOwnerWriteFile(srcFile, destFile);
            securityProps.put(SSLConfig.TRUSTSTORE_FILE,
                              FileNames.CLIENT_TRUSTSTORE_FILE);
        }

        final String securityComment =
            "Security property settings for communication with " +
            "KVStore servers";
        ConfigUtils.storeProperties(securityProps, securityComment,
                                    securityFile);

        /* summarize the configuration for the user */
        if (config.isPrintCreatedFiles()) {
            final List createdFiles = findSecurityFiles(securityDir);
            ioHelper.println("Created files");
            for (File f : createdFiles) {
                ioHelper.println("    " + f.getPath());
            }
        }
        if (nossl) {
            ioHelper.println("It's not recommended to configure " +
                             "Oracle NoSQL Database Security without SSL. " +
                             "All network communication will be in clear text" +
                             " including login credentials, login tokens, " +
                             "and data");
        }
        return true;
    }

    private void createUserPasswordStore(File securityDir,
                                         SecurityParams sp,
                                         String userName,
                                         char[] password) throws IOException {
        final PasswordStore pwdStore = makePasswordStore(securityDir, sp);
        pwdStore.setSecret(userName, password);
        pwdStore.save();
        pwdStore.discard();
    }

    /**
     * Add Kerberos configuration to an existing security configuration.
     *
     * Either adding Kerberos to an existing secure, but non-Kerberos
     * configuration, or adding a new service principal and keytab file to an
     * existing Kerberos configuration.
     */
    public boolean addKerberosConfig() {
        /* Check if Kerberos authentication is supported */
        if (!resolveKerberosAuth()) {
            return false;
        }

        /* Check if kvroot and security directory exist */
        File kvRootDir = new File(kvRoot);
        if (!kvRootDir.exists()) {
            ioHelper.println(
                "The directory " + kvRootDir.getPath() + " does not exist");
            return false;
        }
        if (!kvRootDir.isAbsolute()) {
            kvRootDir = kvRootDir.getAbsoluteFile();
            kvRoot = kvRootDir.getPath();
        }

        final File securityDir = new File(kvRoot, config.getSecurityDir());
        if (!securityDir.exists()) {
            ioHelper.println("The directory " + securityDir.getPath() +
                             " does not exist");
            return false;
        }
        config.populateDefaults();

        /* security.xml file is expected to exist in this case */
        final File secXmlFile =
            new File(securityDir.getPath(), FileNames.SECURITY_CONFIG_FILE);
        if (!secXmlFile.exists()) {
            ioHelper.println("security.xml file does not exist, " +
                "need to run securityconfig create firstly");
            return false;
        }
        final SecurityParams sp = ConfigUtils.getSecurityParams(secXmlFile);
        String oldKeytabName = sp.getKerberosKeytabFile();
        if (oldKeytabName == null) {
            oldKeytabName = FileNames.KERBEROS_KEYTAB_FILE;
        }

        /* Parse and validate Kerberos configuration file */
        final Krb5Config krb5Conf = parseKerberosConfig();
        if (krb5Conf == null) {
            return false;
        }

        try {
            config.getKadminSetting().validateKadminSetting();
        } catch (IllegalArgumentException iae) {
            ioHelper.println("kadmin setting error " + iae.getMessage());
            return false;
        }

        /*
         * Find out if user customized service name or keytab file name
         * in existing configuration
         */
        String newKeytabName = FileNames.KERBEROS_KEYTAB_FILE;
        final List settings = config.getUserParams();
        for (ParsedConfig.ParamSetting setting : settings) {
            if (setting.paramName.equals(
                ParameterState.SEC_KERBEROS_SERVICE_NAME)) {
                sp.setKerberosServiceName(setting.paramValue);
            } else if (setting.paramName.equals(
                ParameterState.SEC_KERBEROS_KEYTAB_FILE)) {
                newKeytabName = setting.paramValue;
            }
        }

        sp.setKerberosKeytabFile(newKeytabName);
        sp.setKerberosConfFile(krb5Conf.getConfigFilePath());
        sp.setKerberosRealmName(krb5Conf.getDefaultRealm());
        sp.setKerberosInstanceName(config.getInstanceName());

        if (config.getKadminSetting().doNotPerformKadmin()) {
            ioHelper.println(NOT_CREATING_KEYTAB_MESSAGE);

            /* Refresh Kerberos-related configuration in security.xml */
            ConfigUtils.createSecurityConfig(sp, secXmlFile);
            return true;
        }
        final String canonicalPrincName =
            SecurityUtils.getCanonicalPrincName(sp);
        final KerberosPrincipal servicePrinc =
            new KerberosPrincipal(canonicalPrincName);

        /*
         * If target keytab file exists, check if it contains keys of the
         * service principal that current configuration is aim to add. If so,
         * do not generate a new keytab file, otherwise the previous keytab
         * would not work correctly since kvno are bumped automatically by kdc.
         */
        final File newKeytab = new File(securityDir, newKeytabName);
        if (newKeytab.exists()) {
            final KeyTab keytab = KeyTab.getInstance(newKeytab);

            if (keytab.getKeys(servicePrinc).length != 0) {
                ioHelper.println("Keytab file " + newKeytabName +
                    " already contains the keys of " + canonicalPrincName +
                    " not adding this Kerberos service principal.");
                return false;
            }

            /* Remove existing keytab file */
            if (!newKeytab.delete()) {
                ioHelper.println("Existing keytab file " + newKeytabName +
                                 " cannot be removed");
                return false;
            }
        }

        /*
         * If existing configuration use different keytab file name, check if
         * its keytab file contains the keys of the service principal current
         * configuration is aim to create. If so, rename the previous keytab
         * file to current one and not extract a new keytab file.
         */
        if (oldKeytabName != null && !oldKeytabName.equals(newKeytabName)) {
            final File oldKeytab = new File(securityDir, oldKeytabName);
            if (oldKeytab.exists()) {
                final KeyTab keytab = KeyTab.getInstance(oldKeytab);

                if (keytab.getKeys(servicePrinc).length != 0) {

                    /*
                     * New configuration use the different keytab file name,
                     * rename to new keytab file name.
                     */
                    ioHelper.println("Existing keytab file " + oldKeytabName +
                        " already contains the keys of " + canonicalPrincName +
                        ", rename keytab file from " +
                        oldKeytabName + " to " + newKeytabName);

                     if (!oldKeytab.renameTo(newKeytab)) {
                         ioHelper.println("Rename keytab file failed");
                         return false;
                     }

                     /*
                      * Refresh Kerberos-related configuration in
                      * security.xml file
                      */
                     ConfigUtils.createSecurityConfig(sp, secXmlFile);
                     return true;
                }

                /* Remove existing keytab file */
                if (!oldKeytab.delete()) {
                    ioHelper.println("Old keytab file " + oldKeytabName +
                                     " cannot be removed");
                    return false;
                }
            }
        }

        /* Add store service principal and extract keytab file */
        if (SecurityUtils.generateKeyTabFile(securityDir,
                                             sp,
                                             config.getKadminSetting(),
                                             config.getKrbPrincProperties(),
                                             ioHelper)) {
            ioHelper.println("Created file: " + newKeytabName);

            /* Refresh Kerberos-related configuration in security.xml */
            ConfigUtils.createSecurityConfig(sp, secXmlFile);
            return true;
        }

        return false;
    }

    private List findSecurityFiles(File securityDir) {
        final List result = new ArrayList();
        findFiles(securityDir, result);
        return result;
    }

    private void findFiles(File securityDir, List found) {
        for (File f : securityDir.listFiles()) {
            if (f.isDirectory()) {
                findFiles(f, found);
            } else {
                found.add(f);
            }
        }
    }

    /**
     * Resolve the pwdMgrClass to a PasswordManager instance.
     * @return an instance of the password manager class, or null if a problem
     * was encountered
     */
    private PasswordManager resolvePwdMgr() {
        try {
            return PasswordManager.load(pwdMgrClass);
        } catch (ClassNotFoundException cnfe) {
            ioHelper.println("Unable to locate password manager class '" +
                             pwdMgrClass + "'");
            return null;
        } catch (Exception exc) {
            /*
             * There are lots of ways this could fail, none of them likely.
             */
            ioHelper.println("Creation of password manager class failed: " +
                             exc.getMessage());
            return null;
        }
    }

    /**
     * Ensure the existence of the security directory based on the kvRoot
     * input.
     * @return a File object identifying the security directory if all
     *   goes according to plan, or null otherwise.
     */
    private File prepareSecurityDir() {
        /* Check out kvRoot */
        File kvRootDir = new File(kvRoot);
        if (!kvRootDir.exists()) {
            ioHelper.println(
                "The directory " + kvRootDir.getPath() + " does not exist");
            return null;
        }
        if (!kvRootDir.isAbsolute()) {
            kvRootDir = kvRootDir.getAbsoluteFile();
            kvRoot = kvRootDir.getPath();
        }

        /* Then create the security directory if needed */
        final File securityDir = new File(kvRoot, config.getSecurityDir());
        if (securityDir.exists()) {
            if (!directoryEmpty(securityDir)) {
                ioHelper.println("The directory " + securityDir.getPath() +
                                 " exists and is not empty");
                return null;
            }
        } else {
            if (!securityDir.mkdir()) {
                ioHelper.println("Unable to create the directory " +
                                 securityDir.getPath());
                return null;
            }
        }
        return securityDir;
    }

    /**
     * Check whether the specified directory is empty.
     * @param dir a directory, which must exist
     * @return true if the directory is empty
     */
    private boolean directoryEmpty(File dir) {
        final String[] entries = dir.list();
        return entries != null && entries.length == 0;
    }

    private PasswordStore makePasswordStore(File securityDir,
                                            SecurityParams sp) {

        PasswordManager pwdMgr = null;
        try {
            pwdMgr = PasswordManager.load(pwdMgrClass);
        } catch (ClassNotFoundException cnfe) {
            /*
             * This should already have been checked, but we have to deal with
             * the exception, so...
             */
            ioHelper.println("Unable to locate password manager class '" +
                             pwdMgrClass + "'");
            return null;
        } catch (Exception exc) {
            /*
             * There are lots of ways this could fail, none of them likely.
             */
            ioHelper.println("Creation of password manager class failed: " +
                                 exc.getMessage());
            return null;
        }

        final File storeLoc =
            new File(securityDir,
                     new File(pwdMgrClass.equals(WALLET_MANAGER_CLASS) ?
                              sp.getWalletDir() :
                              sp.getPasswordFile()).getPath());
        final PasswordStore pwdStore = pwdMgr.getStoreHandle(storeLoc);

        try {
            /* Create password store as auto-login */
            pwdStore.create(null);
            return pwdStore;
        } catch (IOException ioe) {
            ioHelper.println("Error creating password store: " +
                             ioe.getMessage());
        }
        return null;
    }

    private SecurityParams makeSecurityParamsWithoutSSL() {
        final SecurityParams sp = new SecurityParams();

        /* Security is enabled */
        sp.setSecurityEnabled(true);

        if (SecurityUtils.hasKerberos(config.getUserExternalAuth())) {
            if (config.getInstanceName() != null) {
                sp.setKerberosInstanceName(config.getInstanceName());
            }

            if (config.getKrbConf() != null) {
                sp.setKerberosConfFile(config.getKrbConf());
            }
            sp.setKerberosKeytabFile(FileNames.KERBEROS_KEYTAB_FILE);
        }

        /* Set client transport */
        sp.addTransportMap(ParameterState.SECURITY_TRANSPORT_CLIENT);
        sp.setTransType(ParameterState.SECURITY_TRANSPORT_CLIENT,
                        TRANS_TYPE_CLEAR);

        /* Set internal transport */
        sp.addTransportMap(ParameterState.SECURITY_TRANSPORT_INTERNAL);
        sp.setTransType(ParameterState.SECURITY_TRANSPORT_INTERNAL,
                        TRANS_TYPE_CLEAR);

        /* Set JE HA transport */
        sp.addTransportMap(ParameterState.SECURITY_TRANSPORT_JE_HA);
        sp.setTransType(ParameterState.SECURITY_TRANSPORT_JE_HA,
                        TRANS_TYPE_CLEAR);

        /*
         * Apply any user-specified settings
         */
        SecurityUtils.applyParamsChanges(sp, config.getUserParams());
        return sp;
    }

    private SecurityParams makeSecurityParams() {
        final SecurityParams sp = new SecurityParams();

        /* Security is enabled */
        sp.setSecurityEnabled(true);

        if (pwdMgrClass.equals(WALLET_MANAGER_CLASS)) {
            sp.setWalletDir(FileNames.WALLET_DIR);
        } else {
            sp.setPasswordFile(FileNames.PASSWD_FILE);
            sp.setPasswordClass(pwdMgrClass);
        }

        sp.setKeystorePasswordAlias(PWD_ALIAS_KEYSTORE);

        sp.setKeystoreFile(FileNames.KEYSTORE_FILE);
        sp.setTruststoreFile(FileNames.TRUSTSTORE_FILE);

        /* Set the internal authentication mechanism */
        sp.setInternalAuth(INTERNAL_AUTH_SSL);

        /* Set the certificate mode */
        sp.setCertMode(CERT_MODE_SHARED);

        if (SecurityUtils.hasKerberos(config.getUserExternalAuth())) {
            if (config.getInstanceName() != null) {
                sp.setKerberosInstanceName(config.getInstanceName());
            }

            if (config.getKrbConf() != null) {
                sp.setKerberosConfFile(config.getKrbConf());
            }
            sp.setKerberosKeytabFile(FileNames.KERBEROS_KEYTAB_FILE);
        }

        /* Set client transport */
        sp.addTransportMap(ParameterState.SECURITY_TRANSPORT_CLIENT);
        sp.setTransType(
            ParameterState.SECURITY_TRANSPORT_CLIENT,
            TRANS_TYPE_SSL);
        sp.setTransServerKeyAlias(
            ParameterState.SECURITY_TRANSPORT_CLIENT,
            SHARED_KEY_ALIAS);
        sp.setTransServerIdentityAllowed(
            ParameterState.SECURITY_TRANSPORT_CLIENT,
            "dnmatch(" + SSL_SERVER_PEER + ")");

        /*
         * We only set the ClientAllowProtocols because we might need to be
         * accessed by applications that can't set their protocol level,
         * which would prevent them from connecting to us.
         */
        sp.setTransClientAllowProtocols(
            ParameterState.SECURITY_TRANSPORT_CLIENT,
            SecurityUtils.PREFERRED_PROTOCOLS_DEFAULT);

        /* Set internal transport */
        sp.addTransportMap(ParameterState.SECURITY_TRANSPORT_INTERNAL);
        sp.setTransType(
            ParameterState.SECURITY_TRANSPORT_INTERNAL,
            TRANS_TYPE_SSL);
        sp.setTransServerKeyAlias(
            ParameterState.SECURITY_TRANSPORT_INTERNAL,
            SHARED_KEY_ALIAS);
        sp.setTransClientKeyAlias(
            ParameterState.SECURITY_TRANSPORT_INTERNAL,
            SHARED_KEY_ALIAS);
        sp.setTransClientAuthRequired(
            ParameterState.SECURITY_TRANSPORT_INTERNAL,
            true);
        sp.setTransClientIdentityAllowed(
            ParameterState.SECURITY_TRANSPORT_INTERNAL,
            "dnmatch(" + SSL_CLIENT_PEER + ")");
        sp.setTransServerIdentityAllowed(
            ParameterState.SECURITY_TRANSPORT_INTERNAL,
            "dnmatch(" + SSL_SERVER_PEER + ")");
        /* See above for reason why we only apply this on the client side */
        sp.setTransClientAllowProtocols(
            ParameterState.SECURITY_TRANSPORT_INTERNAL,
            SecurityUtils.PREFERRED_PROTOCOLS_DEFAULT);

        /* Set JE HA transport */
        sp.addTransportMap(ParameterState.SECURITY_TRANSPORT_JE_HA);
        sp.setTransType(
            ParameterState.SECURITY_TRANSPORT_JE_HA,
            TRANS_TYPE_SSL);
        sp.setTransServerKeyAlias(
            ParameterState.SECURITY_TRANSPORT_JE_HA,
            SHARED_KEY_ALIAS);
        sp.setTransClientAuthRequired(
            ParameterState.SECURITY_TRANSPORT_JE_HA,
            true);
        sp.setTransClientIdentityAllowed(
            ParameterState.SECURITY_TRANSPORT_JE_HA,
            "dnmatch(" + SSL_CLIENT_PEER + ")");
        sp.setTransServerIdentityAllowed(
            ParameterState.SECURITY_TRANSPORT_JE_HA,
            "dnmatch(" + SSL_SERVER_PEER + ")");

        /*
         * JE doesn't support the notion of client vs. server and we don't
         * have a need to support non-Oracle applications, so set both client
         * and server to use the preferred protocols.
         */
        sp.setTransAllowProtocols(
            ParameterState.SECURITY_TRANSPORT_JE_HA,
            SecurityUtils.PREFERRED_PROTOCOLS_DEFAULT);

        /*
         * Apply any user-specified settings
         */
        SecurityUtils.applyParamsChanges(sp, config.getUserParams());
        return sp;
    }

    private char[] promptForKeyStorePassword()
        throws IOException {

        while (true) {
            final char[] pwd = ioHelper.readPassword(
                "Enter a password for the Java KeyStore:");
            if (pwd == null || pwd.length == 0) {
                return null;
            }
            if (!validKeystorePassword(pwd)) {
                continue;
            }
            final char[] pwd2 = ioHelper.readPassword(
                "Re-enter the KeyStore password for verification:");
            if (pwd2 != null && SecurityUtils.passwordsMatch(pwd, pwd2)) {
                return pwd;
            }
            ioHelper.println("The passwords do not match");
        }
    }

    private boolean validKeystorePassword(char[] pwd) {
        /*
         * Standard requirement for keystore implementations is 6 character
         * minimum, though some implementations might add additional
         * requirements.
         */
        if (pwd.length < MIN_STORE_PASSPHRASE_LEN) {
            ioHelper.println("The keystore password must be at least " +
                             MIN_STORE_PASSPHRASE_LEN + " characters long");
            return false;
        }
        return true;
    }

    /**
     * Resolve whether Kerberos authentication method is supported.
     */
    private boolean resolveKerberosAuth() {
        if (AuthenticatorManager.isSupported(
            SecurityUtils.KERBEROS_AUTH_NAME)) {
            return true;
        }
        ioHelper.println(
            "Unable to locate Kerberos authenticator class, " +
            "it is possible you are using NoSQL Database Community Edition " +
            "or Basic Edition. " +
            "Kerberos authentication is only available in " +
            "NoSQL Database Enterprise Edition");
        return false;
    }

    /**
     * Resolve whether IDCS OAuth authentication method is supported.
     */
    private boolean resolveIDCSOAuthAuth() {
        if (AuthenticatorManager.isSupported(
            SecurityUtils.OAUTH_AUTH_NAME)) {
            return true;
        }
        ioHelper.println("IDCS OAuth authentication is not supported");
        return false;
    }

    /**
     * Parse and validate Kerberos configuration file.
     */
    private Krb5Config parseKerberosConfig() {
        final File krb5ConfFile = new File(config.getKrbConf());
        if (!krb5ConfFile.exists()) {
            ioHelper.println("Kerberos configuration file does not exist");
            return null;
        }

        /* Parse krb5.conf file and find default realm and its kdc */
        final Krb5Config krb5Conf = new Krb5Config(krb5ConfFile);
        try {
            krb5Conf.parseConfigFile();
        } catch (IOException ioe) {
            ioHelper.println("Parsing kerberos configuration file " +
                config.getKrbConf() + " error: " + ioe.getMessage());
            return null;
        }

        if (krb5Conf.getDefaultRealm() == null || krb5Conf.getKdc() == null) {
            ioHelper.println("Kerberos configuration file does not specify" +
                             "default realm and its kdc correctly");
            return null;
        }
        return krb5Conf;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy