blob: ee7e42cb1daf03bbb06f993ded29dfd7acd87519 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.security.alias;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Random;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.Credentials;
import org.apache.hadoop.security.ProviderUtils;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.test.GenericTestUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TestCredentialProviderFactory {
public static final Logger LOG =
LoggerFactory.getLogger(TestCredentialProviderFactory.class);
@Rule
public final TestName test = new TestName();
@Before
public void announce() {
LOG.info("Running test " + test.getMethodName());
}
private static char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'2', '3', '4', '5', '6', '7', '8', '9',};
private static final File tmpDir = GenericTestUtils.getTestDir("creds");
@Test
public void testFactory() throws Exception {
Configuration conf = new Configuration();
final String userUri = UserProvider.SCHEME_NAME + ":///";
final Path jksPath = new Path(tmpDir.toString(), "test.jks");
final String jksUri = JavaKeyStoreProvider.SCHEME_NAME +
"://file" + jksPath.toUri();
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH,
userUri + "," + jksUri);
List<CredentialProvider> providers =
CredentialProviderFactory.getProviders(conf);
assertEquals(2, providers.size());
assertEquals(UserProvider.class, providers.get(0).getClass());
assertEquals(JavaKeyStoreProvider.class, providers.get(1).getClass());
assertEquals(userUri, providers.get(0).toString());
assertEquals(jksUri, providers.get(1).toString());
}
@Test
public void testFactoryErrors() throws Exception {
Configuration conf = new Configuration();
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, "unknown:///");
try {
List<CredentialProvider> providers =
CredentialProviderFactory.getProviders(conf);
assertTrue("should throw!", false);
} catch (IOException e) {
assertEquals("No CredentialProviderFactory for unknown:/// in " +
CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH,
e.getMessage());
}
}
@Test
public void testUriErrors() throws Exception {
Configuration conf = new Configuration();
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, "unkn@own:/x/y");
try {
List<CredentialProvider> providers =
CredentialProviderFactory.getProviders(conf);
assertTrue("should throw!", false);
} catch (IOException e) {
assertEquals("Bad configuration of " +
CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH +
" at unkn@own:/x/y", e.getMessage());
}
}
private static char[] generatePassword(int length) {
StringBuffer sb = new StringBuffer();
Random r = new Random();
for (int i = 0; i < length; i++) {
sb.append(chars[r.nextInt(chars.length)]);
}
return sb.toString().toCharArray();
}
static void checkSpecificProvider(Configuration conf,
String ourUrl) throws Exception {
CredentialProvider provider =
CredentialProviderFactory.getProviders(conf).get(0);
char[] passwd = generatePassword(16);
// ensure that we get nulls when the key isn't there
assertEquals(null, provider.getCredentialEntry("no-such-key"));
assertEquals(null, provider.getCredentialEntry("key"));
// create a new key
try {
provider.createCredentialEntry("pass", passwd);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
// make sure we get back the right key
assertArrayEquals(passwd, provider.getCredentialEntry("pass").getCredential());
// try recreating pass
try {
provider.createCredentialEntry("pass", passwd);
assertTrue("should throw", false);
} catch (IOException e) {
assertEquals("Credential pass already exists in " + ourUrl, e.getMessage());
}
provider.deleteCredentialEntry("pass");
try {
provider.deleteCredentialEntry("pass");
assertTrue("should throw", false);
} catch (IOException e) {
assertEquals("Credential pass does not exist in " + ourUrl, e.getMessage());
}
char[] passTwo = new char[]{'1', '2', '3'};
provider.createCredentialEntry("pass", passwd);
provider.createCredentialEntry("pass2", passTwo);
assertArrayEquals(passTwo,
provider.getCredentialEntry("pass2").getCredential());
// write them to disk so that configuration.getPassword will find them
provider.flush();
// configuration.getPassword should get this from provider
assertArrayEquals(passTwo, conf.getPassword("pass2"));
// configuration.getPassword should get this from config
conf.set("onetwothree", "123");
assertArrayEquals(passTwo, conf.getPassword("onetwothree"));
// configuration.getPassword should NOT get this from config since
// we are disabling the fallback to clear text config
conf.set(CredentialProvider.CLEAR_TEXT_FALLBACK, "false");
assertArrayEquals(null, conf.getPassword("onetwothree"));
// get a new instance of the provider to ensure it was saved correctly
provider = CredentialProviderFactory.getProviders(conf).get(0);
assertTrue(provider != null);
assertArrayEquals(new char[]{'1', '2', '3'},
provider.getCredentialEntry("pass2").getCredential());
assertArrayEquals(passwd, provider.getCredentialEntry("pass").getCredential());
List<String> creds = provider.getAliases();
assertTrue("Credentials should have been returned.", creds.size() == 2);
assertTrue("Returned Credentials should have included pass.", creds.contains("pass"));
assertTrue("Returned Credentials should have included pass2.", creds.contains("pass2"));
}
@Test
public void testUserProvider() throws Exception {
Configuration conf = new Configuration();
final String ourUrl = UserProvider.SCHEME_NAME + ":///";
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl);
checkSpecificProvider(conf, ourUrl);
// see if the credentials are actually in the UGI
Credentials credentials =
UserGroupInformation.getCurrentUser().getCredentials();
assertArrayEquals(new byte[]{'1', '2', '3'},
credentials.getSecretKey(new Text("pass2")));
}
@Test
public void testJksProvider() throws Exception {
Configuration conf = new Configuration();
final Path jksPath = new Path(tmpDir.toString(), "test.jks");
final String ourUrl =
JavaKeyStoreProvider.SCHEME_NAME + "://file" + jksPath.toUri();
File file = new File(tmpDir, "test.jks");
file.delete();
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl);
checkSpecificProvider(conf, ourUrl);
Path path = ProviderUtils.unnestUri(new URI(ourUrl));
FileSystem fs = path.getFileSystem(conf);
FileStatus s = fs.getFileStatus(path);
assertEquals("rw-------", s.getPermission().toString());
assertTrue(file + " should exist", file.isFile());
// check permission retention after explicit change
fs.setPermission(path, new FsPermission("777"));
checkPermissionRetention(conf, ourUrl, path);
}
@Test
public void testLocalJksProvider() throws Exception {
Configuration conf = new Configuration();
final Path jksPath = new Path(tmpDir.toString(), "test.jks");
final String ourUrl =
LocalJavaKeyStoreProvider.SCHEME_NAME + "://file" + jksPath.toUri();
File file = new File(tmpDir, "test.jks");
file.delete();
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl);
checkSpecificProvider(conf, ourUrl);
Path path = ProviderUtils.unnestUri(new URI(ourUrl));
FileSystem fs = path.getFileSystem(conf);
FileStatus s = fs.getFileStatus(path);
assertEquals("Unexpected permissions: " + s.getPermission().toString(),
"rw-------", s.getPermission().toString());
assertTrue(file + " should exist", file.isFile());
// check permission retention after explicit change
fs.setPermission(path, new FsPermission("777"));
checkPermissionRetention(conf, ourUrl, path);
}
public void checkPermissionRetention(Configuration conf, String ourUrl,
Path path) throws Exception {
CredentialProvider provider = CredentialProviderFactory.getProviders(conf).get(0);
// let's add a new credential and flush and check that permissions are still set to 777
char[] cred = new char[32];
for(int i =0; i < cred.length; ++i) {
cred[i] = (char) i;
}
// create a new key
try {
provider.createCredentialEntry("key5", cred);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
provider.flush();
// get a new instance of the provider to ensure it was saved correctly
provider = CredentialProviderFactory.getProviders(conf).get(0);
assertArrayEquals(cred, provider.getCredentialEntry("key5").getCredential());
FileSystem fs = path.getFileSystem(conf);
FileStatus s = fs.getFileStatus(path);
assertEquals("Permissions should have been retained from the preexisting " +
"keystore.", "rwxrwxrwx", s.getPermission().toString());
}
}