From 2eba2624723e2258bddef72d05a1e90576082315 Mon Sep 17 00:00:00 2001 From: hongdongdong Date: Thu, 24 Oct 2019 18:48:27 +0100 Subject: [PATCH] HADOOP-16615. Add password check for credential provider, Contributed by hongdongdong. Change-Id: Iaac01bc8594860064a80c822a0e47981243ab7e1 --- .../security/alias/CredentialShell.java | 94 ++++++++++++++++++- .../src/site/markdown/CommandsManual.md | 3 + .../hadoop/security/alias/TestCredShell.java | 31 ++++-- 3 files changed, 121 insertions(+), 7 deletions(-) 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 5696118c162..603772444bc 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 f39a92d9e31..0bda253fc8b 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 569fe738a01..bf72b52b320 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 {