Skip to content

HADOOP-16615. Add password check for credential provider #1614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public class CredentialShell extends CommandShell {
" [-help]\n" +
" [" + CreateCommand.USAGE + "]\n" +
" [" + DeleteCommand.USAGE + "]\n" +
" [" + ListCommand.USAGE + "]\n";
" [" + ListCommand.USAGE + "]\n" +
" [" + CheckCommand.USAGE + "]\n";
@VisibleForTesting
public static final String NO_VALID_PROVIDERS =
"There are no valid (non-transient) providers configured.\n" +
Expand All @@ -66,6 +67,7 @@ public class CredentialShell extends CommandShell {
* <pre>
* % hadoop credential create alias [-provider providerPath]
* % hadoop credential list [-provider providerPath]
* % hadoop credential check alias [-provider providerPath]
* % hadoop credential delete alias [-provider providerPath] [-f]
* </pre>
* @param args
Expand All @@ -86,6 +88,11 @@ protected int init(String[] args) throws IOException {
return 1;
}
setSubCommand(new CreateCommand(args[++i]));
} else if (args[i].equals("check")) {
if (i == args.length - 1) {
return 1;
}
setSubCommand(new CheckCommand(args[++i]));
} else if (args[i].equals("delete")) {
if (i == args.length - 1) {
return 1;
Expand Down Expand Up @@ -293,6 +300,91 @@ public String getUsage() {
}
}

private class CheckCommand extends Command {
public static final String USAGE = "check <alias> [-value alias-value] " +
"[-provider provider-path] [-strict]";
public static final String DESC =
"The check subcommand check a password for the name\n" +
"specified as the <alias> argument within the provider indicated\n" +
"through the -provider argument. If -strict is supplied, fail\n" +
"immediately if the provider requires a password and none is given.\n" +
"If -value is provided, use that for the value of the credential\n" +
"instead of prompting the user.";

private String alias = null;

CheckCommand(String alias) {
this.alias = alias;
}

public boolean validate() {
if (alias == null) {
getOut().println("There is no alias specified. Please provide the" +
"mandatory <alias>. See the usage description with -help.");
return false;
}
if (alias.equals("-help")) {
return true;
}
try {
provider = getCredentialProvider();
if (provider == null) {
return false;
} else if (provider.needsPassword()) {
if (strict) {
getOut().println(provider.noPasswordError());
return false;
} else {
getOut().println(provider.noPasswordWarning());
}
}
} catch (IOException e) {
e.printStackTrace(getErr());
}
return true;
}

public void execute() throws IOException, NoSuchAlgorithmException {
if (alias.equals("-help")) {
doHelp();
return;
}
warnIfTransientProvider();
getOut().println("Checking aliases for CredentialProvider: " +
provider.toString());
try {
PasswordReader c = getPasswordReader();
if (c == null) {
throw new IOException("No console available for checking user.");
}

char[] password = null;
if (value != null) {
// testing only
password = value.toCharArray();
} else {
password = c.readPassword("Enter alias password: ");
}
char[] storePassword =
provider.getCredentialEntry(alias).getCredential();
String beMatch =
Arrays.equals(storePassword, password) ? "success" : "failed";

getOut().println("Password match " + beMatch + " for " + alias + ".");
} catch (IOException e) {
getOut().println("Cannot check aliases for CredentialProvider: " +
provider.toString()
+ ": " + e.getMessage());
throw e;
}
}

@Override
public String getUsage() {
return USAGE + ":\n\n" + DESC;
}
}

private class CreateCommand extends Command {
public static final String USAGE = "create <alias> [-value alias-value] " +
"[-provider provider-path] [-strict]";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ Usage: `hadoop credential <subcommand> [options]`
| create *alias* [-provider *provider-path*] [-strict] [-value *credential-value*] | Prompts the user for a credential to be stored as the given alias. The *hadoop.security.credential.provider.path* within the core-site.xml file will be used unless a `-provider` is indicated. The `-strict` flag will cause the command to fail if the provider uses a default password. Use `-value` flag to supply the credential value (a.k.a. the alias password) instead of being prompted. |
| delete *alias* [-provider *provider-path*] [-strict] [-f] | Deletes the credential with the provided alias. The *hadoop.security.credential.provider.path* within the core-site.xml file will be used unless a `-provider` is indicated. The `-strict` flag will cause the command to fail if the provider uses a default password. The command asks for confirmation unless `-f` is specified |
| list [-provider *provider-path*] [-strict] | Lists all of the credential aliases The *hadoop.security.credential.provider.path* within the core-site.xml file will be used unless a `-provider` is indicated. The `-strict` flag will cause the command to fail if the provider uses a default password. |
| check *alias* [-provider *provider-path*] [-strict] | Check the password for the given alias. The *hadoop.security.credential.provider.path* within the core-site.xml file will be used unless a `-provider` is indicated. The `-strict` flag will cause the command to fail if the provider uses a default password. |

Command to manage credentials, passwords and secrets within credential providers.

Expand Down Expand Up @@ -221,6 +222,8 @@ Usage: `hadoop key <subcommand> [options]`
| roll *keyname* [-provider *provider*] [-strict] [-help] | Creates a new version for the specified key within the provider indicated using the `-provider` argument. The `-strict` flag will cause the command to fail if the provider uses a default password. |
| delete *keyname* [-provider *provider*] [-strict] [-f] [-help] | Deletes all versions of the key specified by the *keyname* argument from within the provider specified by `-provider`. The `-strict` flag will cause the command to fail if the provider uses a default password. The command asks for user confirmation unless `-f` is specified. |
| list [-provider *provider*] [-strict] [-metadata] [-help] | Displays the keynames contained within a particular provider as configured in core-site.xml or specified with the `-provider` argument. The `-strict` flag will cause the command to fail if the provider uses a default password. `-metadata` displays the metadata. |
| check *keyname* [-provider *provider*] [-strict] [-help] | Check password of the *keyname* contained within a particular provider as configured in core-site.xml or specified with the `-provider` argument. The `-strict` flag will cause the command to fail if the provider uses a default password. |

| -help | Prints usage of this command |

Manage keys via the KeyProvider. For details on KeyProviders, see the [Transparent Encryption Guide](../hadoop-hdfs/TransparentEncryption.html).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.ProviderUtils;
import org.apache.hadoop.test.GenericTestUtils;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;

Expand All @@ -43,6 +44,11 @@ public class TestCredShell {
/* The default JCEKS provider - for testing purposes */
private String jceksProvider;

private void assertOutputContains(String expected) {
Assertions.assertThat(outContent.toString())
.contains(expected);
}

@Before
public void setup() throws Exception {
System.setOut(new PrintStream(outContent));
Expand Down Expand Up @@ -172,15 +178,28 @@ public void testPromptForCredential() throws Exception {
shell.setPasswordReader(new MockPasswordReader(passwords));
rc = shell.run(args1);
assertEquals(0, rc);
assertTrue(outContent.toString().contains("credential1 has been successfully " +
"created."));

String[] args2 = {"delete", "credential1", "-f", "-provider",
assertOutputContains("credential1 has been successfully created.");

String[] args2 = {"check", "credential1", "-provider",
jceksProvider};
ArrayList<String> password = new ArrayList<String>();
password.add("p@ssw0rd");
shell.setPasswordReader(new MockPasswordReader(password));
rc = shell.run(args2);
assertEquals(0, rc);
assertTrue(outContent.toString().contains("credential1 has been successfully " +
"deleted."));
assertOutputContains("Password match success for credential1.");
ArrayList<String> passwordError = new ArrayList<String>();
passwordError.add("p@ssw0rderr");
shell.setPasswordReader(new MockPasswordReader(password));
rc = shell.run(args2);
assertEquals(0, rc);
assertOutputContains("Password match failed for credential1.");

String[] args3 = {"delete", "credential1", "-f", "-provider",
jceksProvider};
rc = shell.run(args3);
assertEquals(0, rc);
assertOutputContains("credential1 has been successfully deleted.");
}

public class MockPasswordReader extends CredentialShell.PasswordReader {
Expand Down