Skip to content

Commit

Permalink
HADOOP-10607. Create API to separate credential/password storage from
Browse files Browse the repository at this point in the history
applications. (Larry McCay via omalley)


git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1603491 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
omalley committed Jun 18, 2014
1 parent 1adec79 commit c797284
Show file tree
Hide file tree
Showing 18 changed files with 1,656 additions and 34 deletions.
3 changes: 3 additions & 0 deletions hadoop-common-project/hadoop-common/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ Trunk (Unreleased)

HADOOP-10485. Remove dead classes in hadoop-streaming. (wheat9)

HADOOP-10607. Create API to separate credential/password storage from
applications. (Larry McCay via omalley)

BUG FIXES

HADOOP-9451. Fault single-layer config if node group topology is enabled.
Expand Down
2 changes: 2 additions & 0 deletions hadoop-common-project/hadoop-common/src/main/bin/hadoop
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ case $COMMAND in
elif [ "$COMMAND" = "archive" ] ; then
CLASS=org.apache.hadoop.tools.HadoopArchives
CLASSPATH=${CLASSPATH}:${TOOL_PATH}
elif [ "$COMMAND" = "credential" ] ; then
CLASS=org.apache.hadoop.security.alias.CredentialShell
elif [[ "$COMMAND" = -* ]] ; then
# class and package names cannot begin with a -
echo "Error: No command named \`$COMMAND' was found. Perhaps you meant \`hadoop ${COMMAND#-}'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableUtils;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.alias.CredentialProvider;
import org.apache.hadoop.security.alias.CredentialProvider.CredentialEntry;
import org.apache.hadoop.security.alias.CredentialProviderFactory;
import org.apache.hadoop.util.ReflectionUtils;
import org.apache.hadoop.util.StringInterner;
import org.apache.hadoop.util.StringUtils;
Expand Down Expand Up @@ -1767,6 +1770,79 @@ public void setStrings(String name, String... values) {
set(name, StringUtils.arrayToString(values));
}

/**
* Get the value for a known password configuration element.
* In order to enable the elimination of clear text passwords in config,
* this method attempts to resolve the property name as an alias through
* the CredentialProvider API and conditionally fallsback to config.
* @param name property name
* @return password
*/
public char[] getPassword(String name) throws IOException {
char[] pass = null;

pass = getPasswordFromCredenitalProviders(name);

if (pass == null) {
pass = getPasswordFromConfig(name);
}

return pass;
}

/**
* Try and resolve the provided element name as a credential provider
* alias.
* @param name alias of the provisioned credential
* @return password or null if not found
* @throws IOException
*/
protected char[] getPasswordFromCredenitalProviders(String name)
throws IOException {
char[] pass = null;
try {
List<CredentialProvider> providers =
CredentialProviderFactory.getProviders(this);

if (providers != null) {
for (CredentialProvider provider : providers) {
try {
CredentialEntry entry = provider.getCredentialEntry(name);
if (entry != null) {
pass = entry.getCredential();
break;
}
}
catch (IOException ioe) {
throw new IOException("Can't get key " + name + " from key provider" +
"of type: " + provider.getClass().getName() + ".", ioe);
}
}
}
}
catch (IOException ioe) {
throw new IOException("Configuration problem with provider path.", ioe);
}

return pass;
}

/**
* Fallback to clear text passwords in configuration.
* @param name
* @return clear text password or null
*/
protected char[] getPasswordFromConfig(String name) {
char[] pass = null;
if (getBoolean(CredentialProvider.CLEAR_TEXT_FALLBACK, true)) {
String passStr = get(name);
if (passStr != null) {
pass = passStr.toCharArray();
}
}
return pass;
}

/**
* Get the socket address for <code>name</code> property as a
* <code>InetSocketAddress</code>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.security.ProviderUtils;

import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -101,7 +103,7 @@ public class JavaKeyStoreProvider extends KeyProvider {

private JavaKeyStoreProvider(URI uri, Configuration conf) throws IOException {
this.uri = uri;
path = unnestUri(uri);
path = ProviderUtils.unnestUri(uri);
fs = path.getFileSystem(conf);
// Get the password file from the conf, if not present from the user's
// environment var
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,33 +488,6 @@ protected static String buildVersionName(String name, int version) {
return name + "@" + version;
}

/**
* Convert a nested URI to decode the underlying path. The translation takes
* the authority and parses it into the underlying scheme and authority.
* For example, "myscheme://hdfs@nn/my/path" is converted to
* "hdfs://nn/my/path".
* @param nestedUri the URI from the nested URI
* @return the unnested path
*/
public static Path unnestUri(URI nestedUri) {
String[] parts = nestedUri.getAuthority().split("@", 2);
StringBuilder result = new StringBuilder(parts[0]);
result.append("://");
if (parts.length == 2) {
result.append(parts[1]);
}
result.append(nestedUri.getPath());
if (nestedUri.getQuery() != null) {
result.append("?");
result.append(nestedUri.getQuery());
}
if (nestedUri.getFragment() != null) {
result.append("#");
result.append(nestedUri.getFragment());
}
return new Path(result.toString());
}

/**
* Find the provider with the given key.
* @param providerList the list of providers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.hadoop.crypto.key.KeyProvider;
import org.apache.hadoop.crypto.key.KeyProviderFactory;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.ProviderUtils;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.client.PseudoAuthenticator;
Expand Down Expand Up @@ -147,7 +148,7 @@ public String toString() {
}

public KMSClientProvider(URI uri, Configuration conf) throws IOException {
Path path = unnestUri(uri);
Path path = ProviderUtils.unnestUri(uri);
URL url = path.toUri().toURL();
kmsUrl = createServiceURL(url);
if ("https".equalsIgnoreCase(url.getProtocol())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* 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;

import java.net.URI;

import org.apache.hadoop.fs.Path;

public class ProviderUtils {
/**
* Convert a nested URI to decode the underlying path. The translation takes
* the authority and parses it into the underlying scheme and authority.
* For example, "myscheme://hdfs@nn/my/path" is converted to
* "hdfs://nn/my/path".
* @param nestedUri the URI from the nested URI
* @return the unnested path
*/
public static Path unnestUri(URI nestedUri) {
String[] parts = nestedUri.getAuthority().split("@", 2);
StringBuilder result = new StringBuilder(parts[0]);
result.append("://");
if (parts.length == 2) {
result.append(parts[1]);
}
result.append(nestedUri.getPath());
if (nestedUri.getQuery() != null) {
result.append("?");
result.append(nestedUri.getQuery());
}
if (nestedUri.getFragment() != null) {
result.append("#");
result.append(nestedUri.getFragment());
}
return new Path(result.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* 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.IOException;
import java.util.List;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;

/**
* A provider of credentials or password for Hadoop applications. Provides an
* abstraction to separate credential storage from users of them. It
* is intended to support getting or storing passwords in a variety of ways,
* including third party bindings.
*/
@InterfaceAudience.Public
@InterfaceStability.Unstable
public abstract class CredentialProvider {
public static final String CLEAR_TEXT_FALLBACK
= "hadoop.security.credential.clear-text-fallback";

/**
* The combination of both the alias and the actual credential value.
*/
public static class CredentialEntry {
private final String alias;
private final char[] credential;

protected CredentialEntry(String alias,
char[] credential) {
this.alias = alias;
this.credential = credential;
}

public String getAlias() {
return alias;
}

public char[] getCredential() {
return credential;
}

public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("alias(");
buf.append(alias);
buf.append(")=");
if (credential == null) {
buf.append("null");
} else {
for(char c: credential) {
buf.append(c);
}
}
return buf.toString();
}
}

/**
* Indicates whether this provider represents a store
* that is intended for transient use - such as the UserProvider
* is. These providers are generally used to provide job access to
* passwords rather than for long term storage.
* @return true if transient, false otherwise
*/
public boolean isTransient() {
return false;
}

/**
* Ensures that any changes to the credentials are written to persistent store.
* @throws IOException
*/
public abstract void flush() throws IOException;

/**
* Get the credential entry for a specific alias.
* @param alias the name of a specific credential
* @return the credentialEntry
* @throws IOException
*/
public abstract CredentialEntry getCredentialEntry(String alias)
throws IOException;

/**
* Get the aliases for all credentials.
* @return the list of alias names
* @throws IOException
*/
public abstract List<String> getAliases() throws IOException;

/**
* Create a new credential. The given alias must not already exist.
* @param name the alias of the credential
* @param credential the credential value for the alias.
* @throws IOException
*/
public abstract CredentialEntry createCredentialEntry(String name,
char[] credential) throws IOException;

/**
* Delete the given credential.
* @param name the alias of the credential to delete
* @throws IOException
*/
public abstract void deleteCredentialEntry(String name) throws IOException;
}
Loading

0 comments on commit c797284

Please sign in to comment.