001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.security.alias; 020 021 import java.io.Console; 022 import java.io.IOException; 023 import java.io.PrintStream; 024 import java.security.InvalidParameterException; 025 import java.security.NoSuchAlgorithmException; 026 import java.util.Arrays; 027 import java.util.List; 028 029 import org.apache.hadoop.conf.Configuration; 030 import org.apache.hadoop.conf.Configured; 031 import org.apache.hadoop.util.Tool; 032 import org.apache.hadoop.util.ToolRunner; 033 034 /** 035 * This program is the CLI utility for the CredentialProvider facilities in 036 * Hadoop. 037 */ 038 public class CredentialShell extends Configured implements Tool { 039 final static private String USAGE_PREFIX = "Usage: hadoop credential " + 040 "[generic options]\n"; 041 final static private String COMMANDS = 042 " [--help]\n" + 043 " [" + CreateCommand.USAGE + "]\n" + 044 " [" + DeleteCommand.USAGE + "]\n" + 045 " [" + ListCommand.USAGE + "]\n"; 046 047 private boolean interactive = false; 048 private Command command = null; 049 050 /** allows stdout to be captured if necessary */ 051 public PrintStream out = System.out; 052 /** allows stderr to be captured if necessary */ 053 public PrintStream err = System.err; 054 055 private boolean userSuppliedProvider = false; 056 private String value = null; 057 private PasswordReader passwordReader; 058 059 @Override 060 public int run(String[] args) throws Exception { 061 int exitCode = 0; 062 try { 063 exitCode = init(args); 064 if (exitCode != 0) { 065 return exitCode; 066 } 067 if (command.validate()) { 068 command.execute(); 069 } else { 070 exitCode = 1; 071 } 072 } catch (Exception e) { 073 e.printStackTrace(err); 074 return 1; 075 } 076 return exitCode; 077 } 078 079 /** 080 * Parse the command line arguments and initialize the data 081 * <pre> 082 * % hadoop credential create alias [-provider providerPath] 083 * % hadoop credential list [-provider providerPath] 084 * % hadoop credential delete alias [-provider providerPath] [-i] 085 * </pre> 086 * @param args 087 * @return 0 if the argument(s) were recognized, 1 otherwise 088 * @throws IOException 089 */ 090 protected int init(String[] args) throws IOException { 091 // no args should print the help message 092 if (0 == args.length) { 093 printCredShellUsage(); 094 ToolRunner.printGenericCommandUsage(System.err); 095 return 1; 096 } 097 098 for (int i = 0; i < args.length; i++) { // parse command line 099 if (args[i].equals("create")) { 100 String alias = args[++i]; 101 command = new CreateCommand(alias); 102 if (alias.equals("-help")) { 103 printCredShellUsage(); 104 return 0; 105 } 106 } else if (args[i].equals("delete")) { 107 String alias = args[++i]; 108 command = new DeleteCommand(alias); 109 if (alias.equals("-help")) { 110 printCredShellUsage(); 111 return 0; 112 } 113 } else if (args[i].equals("list")) { 114 command = new ListCommand(); 115 } else if (args[i].equals("-provider")) { 116 userSuppliedProvider = true; 117 getConf().set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, 118 args[++i]); 119 } else if (args[i].equals("-i") || (args[i].equals("-interactive"))) { 120 interactive = true; 121 } else if (args[i].equals("-v") || (args[i].equals("-value"))) { 122 value = args[++i]; 123 } else if (args[i].equals("-help")) { 124 printCredShellUsage(); 125 return 0; 126 } else { 127 printCredShellUsage(); 128 ToolRunner.printGenericCommandUsage(System.err); 129 return 1; 130 } 131 } 132 return 0; 133 } 134 135 private void printCredShellUsage() { 136 out.println(USAGE_PREFIX + COMMANDS); 137 if (command != null) { 138 out.println(command.getUsage()); 139 } 140 else { 141 out.println("=========================================================" + 142 "======"); 143 out.println(CreateCommand.USAGE + ":\n\n" + CreateCommand.DESC); 144 out.println("=========================================================" + 145 "======"); 146 out.println(DeleteCommand.USAGE + ":\n\n" + DeleteCommand.DESC); 147 out.println("=========================================================" + 148 "======"); 149 out.println(ListCommand.USAGE + ":\n\n" + ListCommand.DESC); 150 } 151 } 152 153 private abstract class Command { 154 protected CredentialProvider provider = null; 155 156 public boolean validate() { 157 return true; 158 } 159 160 protected CredentialProvider getCredentialProvider() { 161 CredentialProvider provider = null; 162 List<CredentialProvider> providers; 163 try { 164 providers = CredentialProviderFactory.getProviders(getConf()); 165 if (userSuppliedProvider) { 166 provider = providers.get(0); 167 } 168 else { 169 for (CredentialProvider p : providers) { 170 if (!p.isTransient()) { 171 provider = p; 172 break; 173 } 174 } 175 } 176 } catch (IOException e) { 177 e.printStackTrace(err); 178 } 179 return provider; 180 } 181 182 protected void printProviderWritten() { 183 out.println(provider.getClass().getName() + " has been updated."); 184 } 185 186 protected void warnIfTransientProvider() { 187 if (provider.isTransient()) { 188 out.println("WARNING: you are modifying a transient provider."); 189 } 190 } 191 192 public abstract void execute() throws Exception; 193 194 public abstract String getUsage(); 195 } 196 197 private class ListCommand extends Command { 198 public static final String USAGE = "list [-provider] [-help]"; 199 public static final String DESC = 200 "The list subcommand displays the aliases contained within \n" + 201 "a particular provider - as configured in core-site.xml or " + 202 "indicated\nthrough the -provider argument."; 203 204 public boolean validate() { 205 boolean rc = true; 206 provider = getCredentialProvider(); 207 if (provider == null) { 208 out.println("There are no non-transient CredentialProviders configured.\n" 209 + "Consider using the -provider option to indicate the provider\n" 210 + "to use. If you want to list a transient provider then you\n" 211 + "you MUST use the -provider argument."); 212 rc = false; 213 } 214 return rc; 215 } 216 217 public void execute() throws IOException { 218 List<String> aliases; 219 try { 220 aliases = provider.getAliases(); 221 out.println("Listing aliases for CredentialProvider: " + provider.toString()); 222 for (String alias : aliases) { 223 out.println(alias); 224 } 225 } catch (IOException e) { 226 out.println("Cannot list aliases for CredentialProvider: " + provider.toString() 227 + ": " + e.getMessage()); 228 throw e; 229 } 230 } 231 232 @Override 233 public String getUsage() { 234 return USAGE + ":\n\n" + DESC; 235 } 236 } 237 238 private class DeleteCommand extends Command { 239 public static final String USAGE = "delete <alias> [-provider] [-help]"; 240 public static final String DESC = 241 "The delete subcommand deletes the credenital\n" + 242 "specified as the <alias> argument from within the provider\n" + 243 "indicated through the -provider argument"; 244 245 String alias = null; 246 boolean cont = true; 247 248 public DeleteCommand(String alias) { 249 this.alias = alias; 250 } 251 252 @Override 253 public boolean validate() { 254 provider = getCredentialProvider(); 255 if (provider == null) { 256 out.println("There are no valid CredentialProviders configured.\n" 257 + "Nothing will be deleted.\n" 258 + "Consider using the -provider option to indicate the provider" 259 + " to use."); 260 return false; 261 } 262 if (alias == null) { 263 out.println("There is no alias specified. Please provide the" + 264 "mandatory <alias>. See the usage description with -help."); 265 return false; 266 } 267 if (interactive) { 268 try { 269 cont = ToolRunner 270 .confirmPrompt("You are about to DELETE the credential: " + 271 alias + " from CredentialProvider " + provider.toString() + 272 ". Continue?:"); 273 if (!cont) { 274 out.println("Nothing has been be deleted."); 275 } 276 return cont; 277 } catch (IOException e) { 278 out.println(alias + " will not be deleted."); 279 e.printStackTrace(err); 280 } 281 } 282 return true; 283 } 284 285 public void execute() throws IOException { 286 warnIfTransientProvider(); 287 out.println("Deleting credential: " + alias + " from CredentialProvider: " 288 + provider.toString()); 289 if (cont) { 290 try { 291 provider.deleteCredentialEntry(alias); 292 out.println(alias + " has been successfully deleted."); 293 provider.flush(); 294 printProviderWritten(); 295 } catch (IOException e) { 296 out.println(alias + "has NOT been deleted."); 297 throw e; 298 } 299 } 300 } 301 302 @Override 303 public String getUsage() { 304 return USAGE + ":\n\n" + DESC; 305 } 306 } 307 308 private class CreateCommand extends Command { 309 public static final String USAGE = "create <alias> [-provider] [-help]"; 310 public static final String DESC = 311 "The create subcommand creates a new credential for the name specified\n" + 312 "as the <alias> argument within the provider indicated through\n" + 313 "the -provider argument."; 314 315 String alias = null; 316 317 public CreateCommand(String alias) { 318 this.alias = alias; 319 } 320 321 public boolean validate() { 322 boolean rc = true; 323 provider = getCredentialProvider(); 324 if (provider == null) { 325 out.println("There are no valid CredentialProviders configured." + 326 "\nCredential will not be created.\n" 327 + "Consider using the -provider option to indicate the provider" + 328 " to use."); 329 rc = false; 330 } 331 if (alias == null) { 332 out.println("There is no alias specified. Please provide the" + 333 "mandatory <alias>. See the usage description with -help."); 334 rc = false; 335 } 336 return rc; 337 } 338 339 public void execute() throws IOException, NoSuchAlgorithmException { 340 warnIfTransientProvider(); 341 try { 342 char[] credential = null; 343 if (value != null) { 344 // testing only 345 credential = value.toCharArray(); 346 } 347 else { 348 credential = promptForCredential(); 349 } 350 provider.createCredentialEntry(alias, credential); 351 out.println(alias + " has been successfully created."); 352 provider.flush(); 353 printProviderWritten(); 354 } catch (InvalidParameterException e) { 355 out.println(alias + " has NOT been created. " + e.getMessage()); 356 throw e; 357 } catch (IOException e) { 358 out.println(alias + " has NOT been created. " + e.getMessage()); 359 throw e; 360 } 361 } 362 363 @Override 364 public String getUsage() { 365 return USAGE + ":\n\n" + DESC; 366 } 367 } 368 369 protected char[] promptForCredential() throws IOException { 370 PasswordReader c = getPasswordReader(); 371 if (c == null) { 372 throw new IOException("No console available for prompting user."); 373 } 374 375 char[] cred = null; 376 377 boolean noMatch; 378 do { 379 char[] newPassword1 = c.readPassword("Enter password: "); 380 char[] newPassword2 = c.readPassword("Enter password again: "); 381 noMatch = !Arrays.equals(newPassword1, newPassword2); 382 if (noMatch) { 383 if (newPassword1 != null) Arrays.fill(newPassword1, ' '); 384 c.format("Passwords don't match. Try again.%n"); 385 } else { 386 cred = newPassword1; 387 } 388 if (newPassword2 != null) Arrays.fill(newPassword2, ' '); 389 } while (noMatch); 390 return cred; 391 } 392 393 public PasswordReader getPasswordReader() { 394 if (passwordReader == null) { 395 passwordReader = new PasswordReader(); 396 } 397 return passwordReader; 398 } 399 400 public void setPasswordReader(PasswordReader reader) { 401 passwordReader = reader; 402 } 403 404 // to facilitate testing since Console is a final class... 405 public static class PasswordReader { 406 public char[] readPassword(String prompt) { 407 Console console = System.console(); 408 char[] pass = console.readPassword(prompt); 409 return pass; 410 } 411 412 public void format(String message) { 413 Console console = System.console(); 414 console.format(message); 415 } 416 } 417 418 419 /** 420 * Main program. 421 * 422 * @param args 423 * Command line arguments 424 * @throws Exception 425 */ 426 public static void main(String[] args) throws Exception { 427 int res = ToolRunner.run(new Configuration(), new CredentialShell(), args); 428 System.exit(res); 429 } 430 }