diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 1388a62d2a..612aba77fc 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -532,6 +532,9 @@ Release 2.6.0 - UNRELEASED HADOOP-10918. JMXJsonServlet fails when used within Tomcat. (tucu) + HADOOP-10933. FileBasedKeyStoresFactory Should use Configuration.getPassword + for SSL Passwords. (lmccay via tucu) + Release 2.5.0 - UNRELEASED INCOMPATIBLE CHANGES diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java index ea22a881e3..aabb815db3 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java @@ -150,7 +150,7 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory { } String passwordProperty = resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY); - String keystorePassword = conf.get(passwordProperty, ""); + String keystorePassword = getPassword(conf, passwordProperty, ""); if (keystorePassword.isEmpty()) { throw new GeneralSecurityException("The property '" + passwordProperty + "' has not been set in the ssl configuration file."); @@ -160,7 +160,8 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory { // Key password defaults to the same value as store password for // compatibility with legacy configurations that did not use a separate // configuration property for key password. - keystoreKeyPassword = conf.get(keyPasswordProperty, keystorePassword); + keystoreKeyPassword = getPassword( + conf, keyPasswordProperty, keystorePassword); LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation); InputStream is = new FileInputStream(keystoreLocation); @@ -191,7 +192,7 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory { if (!truststoreLocation.isEmpty()) { String passwordProperty = resolvePropertyName(mode, SSL_TRUSTSTORE_PASSWORD_TPL_KEY); - String truststorePassword = conf.get(passwordProperty, ""); + String truststorePassword = getPassword(conf, passwordProperty, ""); if (truststorePassword.isEmpty()) { throw new GeneralSecurityException("The property '" + passwordProperty + "' has not been set in the ssl configuration file."); @@ -217,6 +218,21 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory { } } + String getPassword(Configuration conf, String alias, String defaultPass) { + String password = defaultPass; + try { + char[] passchars = conf.getPassword(alias); + if (passchars != null) { + password = new String(passchars); + } + } + catch (IOException ioe) { + LOG.warn("Exception while trying to get password for alias " + alias + + ": " + ioe.getMessage()); + } + return password; + } + /** * Releases any resources being used. */ diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java index a07faebe03..5f72f9e95e 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java @@ -19,6 +19,10 @@ package org.apache.hadoop.security.ssl; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.alias.CredentialProvider; +import org.apache.hadoop.security.alias.CredentialProviderFactory; +import org.apache.hadoop.security.alias.JavaKeyStoreProvider; + import sun.security.x509.AlgorithmId; import sun.security.x509.CertificateAlgorithmId; import sun.security.x509.CertificateIssuerName; @@ -382,4 +386,41 @@ public class KeyStoreTestUtil { writer.close(); } } + + public static void provisionPasswordsToCredentialProvider() throws Exception { + File testDir = new File(System.getProperty("test.build.data", + "target/test-dir")); + + Configuration conf = new Configuration(); + final String ourUrl = + JavaKeyStoreProvider.SCHEME_NAME + "://file/" + testDir + "/test.jks"; + + File file = new File(testDir, "test.jks"); + file.delete(); + conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl); + + CredentialProvider provider = + CredentialProviderFactory.getProviders(conf).get(0); + char[] keypass = {'k', 'e', 'y', 'p', 'a', 's', 's'}; + char[] storepass = {'s', 't', 'o', 'r', 'e', 'p', 'a', 's', 's'}; + + // create new aliases + try { + provider.createCredentialEntry( + FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, + FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), + storepass); + + provider.createCredentialEntry( + FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, + FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY), + keypass); + + // write out so that it can be found in checks + provider.flush(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } } diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java index d719170d55..3c428fea28 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java @@ -17,8 +17,14 @@ */ package org.apache.hadoop.security.ssl; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.security.alias.CredentialProvider; +import org.apache.hadoop.security.alias.CredentialProviderFactory; +import org.apache.hadoop.security.alias.JavaKeyStoreProvider; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -211,6 +217,13 @@ public class TestSSLFactory { "password", "password", null); } + @Test + public void testServerCredProviderPasswords() throws Exception { + KeyStoreTestUtil.provisionPasswordsToCredentialProvider(); + checkSSLFactoryInitWithPasswords(SSLFactory.Mode.SERVER, + "storepass", "keypass", null, null, true); + } + /** * Checks that SSLFactory initialization is successful with the given * arguments. This is a helper method for writing test cases that cover @@ -218,7 +231,7 @@ public class TestSSLFactory { * It takes care of bootstrapping a keystore, a truststore, and SSL client or * server configuration. Then, it initializes an SSLFactory. If no exception * is thrown, then initialization was successful. - * + * * @param mode SSLFactory.Mode mode to test * @param password String store password to set on keystore * @param keyPassword String key password to set on keystore @@ -231,6 +244,34 @@ public class TestSSLFactory { private void checkSSLFactoryInitWithPasswords(SSLFactory.Mode mode, String password, String keyPassword, String confPassword, String confKeyPassword) throws Exception { + checkSSLFactoryInitWithPasswords(mode, password, keyPassword, + confPassword, confKeyPassword, false); + } + + /** + * Checks that SSLFactory initialization is successful with the given + * arguments. This is a helper method for writing test cases that cover + * different combinations of settings for the store password and key password. + * It takes care of bootstrapping a keystore, a truststore, and SSL client or + * server configuration. Then, it initializes an SSLFactory. If no exception + * is thrown, then initialization was successful. + * + * @param mode SSLFactory.Mode mode to test + * @param password String store password to set on keystore + * @param keyPassword String key password to set on keystore + * @param confPassword String store password to set in SSL config file, or null + * to avoid setting in SSL config file + * @param confKeyPassword String key password to set in SSL config file, or + * null to avoid setting in SSL config file + * @param useCredProvider boolean to indicate whether passwords should be set + * into the config or not. When set to true nulls are set and aliases are + * expected to be resolved through credential provider API through the + * Configuration.getPassword method + * @throws Exception for any error + */ + private void checkSSLFactoryInitWithPasswords(SSLFactory.Mode mode, + String password, String keyPassword, String confPassword, + String confKeyPassword, boolean useCredProvider) throws Exception { String keystore = new File(KEYSTORES_DIR, "keystore.jks").getAbsolutePath(); String truststore = new File(KEYSTORES_DIR, "truststore.jks") .getAbsolutePath(); @@ -249,10 +290,25 @@ public class TestSSLFactory { // Create SSL configuration file, for either server or client. final String sslConfFileName; final Configuration sslConf; + + // if the passwords are provisioned in a cred provider then don't set them + // in the configuration properly - expect them to be resolved through the + // provider + if (useCredProvider) { + confPassword = null; + confKeyPassword = null; + } if (mode == SSLFactory.Mode.SERVER) { sslConfFileName = "ssl-server.xml"; sslConf = KeyStoreTestUtil.createServerSSLConfig(keystore, confPassword, confKeyPassword, truststore); + if (useCredProvider) { + File testDir = new File(System.getProperty("test.build.data", + "target/test-dir")); + final String ourUrl = + JavaKeyStoreProvider.SCHEME_NAME + "://file/" + testDir + "/test.jks"; + sslConf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl); + } } else { sslConfFileName = "ssl-client.xml"; sslConf = KeyStoreTestUtil.createClientSSLConfig(keystore, confPassword,