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 019package org.apache.hadoop.security.alias; 020 021import java.io.Console; 022import java.io.IOException; 023import java.io.PrintStream; 024import java.security.InvalidParameterException; 025import java.security.NoSuchAlgorithmException; 026import java.util.Arrays; 027import java.util.List; 028 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.conf.Configured; 031import org.apache.hadoop.util.Tool; 032import org.apache.hadoop.util.ToolRunner; 033 034/** 035 * This program is the CLI utility for the CredentialProvider facilities in 036 * Hadoop. 037 */ 038public 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 = true; 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] [-f] 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 if (i == args.length - 1) { 101 printCredShellUsage(); 102 return 1; 103 } 104 String alias = args[++i]; 105 command = new CreateCommand(alias); 106 if (alias.equals("-help")) { 107 printCredShellUsage(); 108 return 0; 109 } 110 } else if (args[i].equals("delete")) { 111 if (i == args.length - 1) { 112 printCredShellUsage(); 113 return 1; 114 } 115 String alias = args[++i]; 116 command = new DeleteCommand(alias); 117 if (alias.equals("-help")) { 118 printCredShellUsage(); 119 return 0; 120 } 121 } else if (args[i].equals("list")) { 122 command = new ListCommand(); 123 } else if (args[i].equals("-provider")) { 124 if (i == args.length - 1) { 125 printCredShellUsage(); 126 return 1; 127 } 128 userSuppliedProvider = true; 129 getConf().set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, 130 args[++i]); 131 } else if (args[i].equals("-f") || (args[i].equals("-force"))) { 132 interactive = false; 133 } else if (args[i].equals("-v") || (args[i].equals("-value"))) { 134 value = args[++i]; 135 } else if (args[i].equals("-help")) { 136 printCredShellUsage(); 137 return 0; 138 } else { 139 printCredShellUsage(); 140 ToolRunner.printGenericCommandUsage(System.err); 141 return 1; 142 } 143 } 144 return 0; 145 } 146 147 private void printCredShellUsage() { 148 out.println(USAGE_PREFIX + COMMANDS); 149 if (command != null) { 150 out.println(command.getUsage()); 151 } 152 else { 153 out.println("=========================================================" + 154 "======"); 155 out.println(CreateCommand.USAGE + ":\n\n" + CreateCommand.DESC); 156 out.println("=========================================================" + 157 "======"); 158 out.println(DeleteCommand.USAGE + ":\n\n" + DeleteCommand.DESC); 159 out.println("=========================================================" + 160 "======"); 161 out.println(ListCommand.USAGE + ":\n\n" + ListCommand.DESC); 162 } 163 } 164 165 private abstract class Command { 166 protected CredentialProvider provider = null; 167 168 public boolean validate() { 169 return true; 170 } 171 172 protected CredentialProvider getCredentialProvider() { 173 CredentialProvider provider = null; 174 List<CredentialProvider> providers; 175 try { 176 providers = CredentialProviderFactory.getProviders(getConf()); 177 if (userSuppliedProvider) { 178 provider = providers.get(0); 179 } 180 else { 181 for (CredentialProvider p : providers) { 182 if (!p.isTransient()) { 183 provider = p; 184 break; 185 } 186 } 187 } 188 } catch (IOException e) { 189 e.printStackTrace(err); 190 } 191 return provider; 192 } 193 194 protected void printProviderWritten() { 195 out.println(provider.getClass().getName() + " has been updated."); 196 } 197 198 protected void warnIfTransientProvider() { 199 if (provider.isTransient()) { 200 out.println("WARNING: you are modifying a transient provider."); 201 } 202 } 203 204 public abstract void execute() throws Exception; 205 206 public abstract String getUsage(); 207 } 208 209 private class ListCommand extends Command { 210 public static final String USAGE = "list [-provider provider-path]"; 211 public static final String DESC = 212 "The list subcommand displays the aliases contained within \n" + 213 "a particular provider - as configured in core-site.xml or " + 214 "indicated\nthrough the -provider argument."; 215 216 public boolean validate() { 217 boolean rc = true; 218 provider = getCredentialProvider(); 219 if (provider == null) { 220 out.println("There are no non-transient CredentialProviders configured.\n" 221 + "Consider using the -provider option to indicate the provider\n" 222 + "to use. If you want to list a transient provider then you\n" 223 + "you MUST use the -provider argument."); 224 rc = false; 225 } 226 return rc; 227 } 228 229 public void execute() throws IOException { 230 List<String> aliases; 231 try { 232 aliases = provider.getAliases(); 233 out.println("Listing aliases for CredentialProvider: " + provider.toString()); 234 for (String alias : aliases) { 235 out.println(alias); 236 } 237 } catch (IOException e) { 238 out.println("Cannot list aliases for CredentialProvider: " + provider.toString() 239 + ": " + e.getMessage()); 240 throw e; 241 } 242 } 243 244 @Override 245 public String getUsage() { 246 return USAGE + ":\n\n" + DESC; 247 } 248 } 249 250 private class DeleteCommand extends Command { 251 public static final String USAGE = 252 "delete <alias> [-f] [-provider provider-path]"; 253 public static final String DESC = 254 "The delete subcommand deletes the credential\n" + 255 "specified as the <alias> argument from within the provider\n" + 256 "indicated through the -provider argument. The command asks for\n" + 257 "confirmation unless the -f option is specified."; 258 259 String alias = null; 260 boolean cont = true; 261 262 public DeleteCommand(String alias) { 263 this.alias = alias; 264 } 265 266 @Override 267 public boolean validate() { 268 provider = getCredentialProvider(); 269 if (provider == null) { 270 out.println("There are no valid CredentialProviders configured.\n" 271 + "Nothing will be deleted.\n" 272 + "Consider using the -provider option to indicate the provider" 273 + " to use."); 274 return false; 275 } 276 if (alias == null) { 277 out.println("There is no alias specified. Please provide the" + 278 "mandatory <alias>. See the usage description with -help."); 279 return false; 280 } 281 if (interactive) { 282 try { 283 cont = ToolRunner 284 .confirmPrompt("You are about to DELETE the credential " + 285 alias + " from CredentialProvider " + provider.toString() + 286 ". Continue? "); 287 if (!cont) { 288 out.println("Nothing has been be deleted."); 289 } 290 return cont; 291 } catch (IOException e) { 292 out.println(alias + " will not be deleted."); 293 e.printStackTrace(err); 294 } 295 } 296 return true; 297 } 298 299 public void execute() throws IOException { 300 warnIfTransientProvider(); 301 out.println("Deleting credential: " + alias + " from CredentialProvider: " 302 + provider.toString()); 303 if (cont) { 304 try { 305 provider.deleteCredentialEntry(alias); 306 out.println(alias + " has been successfully deleted."); 307 provider.flush(); 308 printProviderWritten(); 309 } catch (IOException e) { 310 out.println(alias + " has NOT been deleted."); 311 throw e; 312 } 313 } 314 } 315 316 @Override 317 public String getUsage() { 318 return USAGE + ":\n\n" + DESC; 319 } 320 } 321 322 private class CreateCommand extends Command { 323 public static final String USAGE = 324 "create <alias> [-provider provider-path]"; 325 public static final String DESC = 326 "The create subcommand creates a new credential for the name specified\n" + 327 "as the <alias> argument within the provider indicated through\n" + 328 "the -provider argument."; 329 330 String alias = null; 331 332 public CreateCommand(String alias) { 333 this.alias = alias; 334 } 335 336 public boolean validate() { 337 boolean rc = true; 338 provider = getCredentialProvider(); 339 if (provider == null) { 340 out.println("There are no valid CredentialProviders configured." + 341 "\nCredential will not be created.\n" 342 + "Consider using the -provider option to indicate the provider" + 343 " to use."); 344 rc = false; 345 } 346 if (alias == null) { 347 out.println("There is no alias specified. Please provide the" + 348 "mandatory <alias>. See the usage description with -help."); 349 rc = false; 350 } 351 return rc; 352 } 353 354 public void execute() throws IOException, NoSuchAlgorithmException { 355 warnIfTransientProvider(); 356 try { 357 char[] credential = null; 358 if (value != null) { 359 // testing only 360 credential = value.toCharArray(); 361 } 362 else { 363 credential = promptForCredential(); 364 } 365 provider.createCredentialEntry(alias, credential); 366 out.println(alias + " has been successfully created."); 367 provider.flush(); 368 printProviderWritten(); 369 } catch (InvalidParameterException e) { 370 out.println(alias + " has NOT been created. " + e.getMessage()); 371 throw e; 372 } catch (IOException e) { 373 out.println(alias + " has NOT been created. " + e.getMessage()); 374 throw e; 375 } 376 } 377 378 @Override 379 public String getUsage() { 380 return USAGE + ":\n\n" + DESC; 381 } 382 } 383 384 protected char[] promptForCredential() throws IOException { 385 PasswordReader c = getPasswordReader(); 386 if (c == null) { 387 throw new IOException("No console available for prompting user."); 388 } 389 390 char[] cred = null; 391 392 boolean noMatch; 393 do { 394 char[] newPassword1 = c.readPassword("Enter password: "); 395 char[] newPassword2 = c.readPassword("Enter password again: "); 396 noMatch = !Arrays.equals(newPassword1, newPassword2); 397 if (noMatch) { 398 if (newPassword1 != null) Arrays.fill(newPassword1, ' '); 399 c.format("Passwords don't match. Try again.%n"); 400 } else { 401 cred = newPassword1; 402 } 403 if (newPassword2 != null) Arrays.fill(newPassword2, ' '); 404 } while (noMatch); 405 return cred; 406 } 407 408 public PasswordReader getPasswordReader() { 409 if (passwordReader == null) { 410 passwordReader = new PasswordReader(); 411 } 412 return passwordReader; 413 } 414 415 public void setPasswordReader(PasswordReader reader) { 416 passwordReader = reader; 417 } 418 419 // to facilitate testing since Console is a final class... 420 public static class PasswordReader { 421 public char[] readPassword(String prompt) { 422 Console console = System.console(); 423 char[] pass = console.readPassword(prompt); 424 return pass; 425 } 426 427 public void format(String message) { 428 Console console = System.console(); 429 console.format(message); 430 } 431 } 432 433 434 /** 435 * Main program. 436 * 437 * @param args 438 * Command line arguments 439 * @throws Exception 440 */ 441 public static void main(String[] args) throws Exception { 442 int res = ToolRunner.run(new Configuration(), new CredentialShell(), args); 443 System.exit(res); 444 } 445}