diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/alias/CredentialShell.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/alias/CredentialShell.java
index 5696118c162b7..603772444bcef 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/alias/CredentialShell.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/alias/CredentialShell.java
@@ -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" +
@@ -66,6 +67,7 @@ public class CredentialShell extends CommandShell {
*
* % hadoop credential create alias [-provider providerPath]
* % hadoop credential list [-provider providerPath]
+ * % hadoop credential check alias [-provider providerPath]
* % hadoop credential delete alias [-provider providerPath] [-f]
*
* @param args
@@ -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;
@@ -293,6 +300,91 @@ public String getUsage() {
}
}
+ private class CheckCommand extends Command {
+ public static final String USAGE = "check [-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 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 . 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 [-value alias-value] " +
"[-provider provider-path] [-strict]";
diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/CommandsManual.md b/hadoop-common-project/hadoop-common/src/site/markdown/CommandsManual.md
index f39a92d9e31af..0bda253fc8b54 100644
--- a/hadoop-common-project/hadoop-common/src/site/markdown/CommandsManual.md
+++ b/hadoop-common-project/hadoop-common/src/site/markdown/CommandsManual.md
@@ -125,6 +125,7 @@ Usage: `hadoop credential [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.
@@ -221,6 +222,8 @@ Usage: `hadoop key [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).
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/alias/TestCredShell.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/alias/TestCredShell.java
index 569fe738a01e1..bf72b52b32066 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/alias/TestCredShell.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/alias/TestCredShell.java
@@ -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;
@@ -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));
@@ -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 password = new ArrayList();
+ 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 passwordError = new ArrayList();
+ 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 {