/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.ambari.server.credentialapi; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.hadoop.security.alias.CredentialShell; import org.apache.hadoop.security.alias.JavaKeyStoreProvider; import org.apache.hadoop.util.Tool; import org.apache.hadoop.util.ToolRunner; /** * Command line utility that wraps over CredentialShell. Extends the * create command to overwrite a credential if it exists. Also * provides the ability to get the decrypted password. * * The CLI structure and code is the same as CredentialShell. */ public class CredentialUtil extends Configured implements Tool { /** * List of supported commands. */ final static private String COMMANDS = " [--help]\n" + " [" + CreateCommand.USAGE + "]\n" + " [" + DeleteCommand.USAGE + "]\n" + " [" + ListCommand.USAGE + "]\n" + " [" + GetCommand.USAGE + "]\n"; /** * JCEKS provider prefix. */ public static final String jceksPrefix = JavaKeyStoreProvider.SCHEME_NAME + "://file"; /** * Local JCEKS provider prefix. */ public static final String localJceksPrefix = "localjceks://file"; /** * Password alias */ private String alias = null; /** * Password specified using the -value option */ private String value = null; /** * Provider specified using the -provider option */ protected CredentialProvider provider; /** * When creating a credential, overwrite the credential if it exists. * If -n option is specified, this will be set to false. */ private boolean overwrite = true; /** * Prompt for user confirmation before deleting/overwriting a credential. * If -f option is specified, it will be set to false. In the case * of a create command, it will be set to false if -n or -f is specified. */ private boolean interactive = true; /** * One of the supported credential commands. */ private Command command = null; /** * Main program. * * @param args Command line arguments * @throws Exception */ public static void main(String[] args) throws Exception { int res = ToolRunner.run(new Configuration(), new CredentialUtil(), args); System.exit(res); } /** * Called by ToolRunner.run(). This is the entry point to the tool. * Parses the command line arguments and executes the appropriate command. * * @param args - Arguments supplied by the user. * @return - 0 if successful. 1 in case of a failure. * @throws Exception - If something goes wrong during command execution. */ @Override public int run(String[] args) throws Exception { int exitCode = 1; for (int i = 0; i < args.length; ++i) { if (args[i].equals("create")) { if (i == args.length - 1) { return 1; } command = new CreateCommand(); alias = args[++i]; if (alias.equals("-h") || alias.equals("-help")) { printUsage(); return 0; } } else if (args[i].equals("get")) { if (i == args.length - 1) { return 1; } command = new GetCommand(); alias = args[++i]; if (alias.equals("-h") || alias.equals("-help")) { printUsage(); return 0; } } else if (args[i].equals("delete")) { if (i == args.length - 1) { printUsage(); return 1; } command = new DeleteCommand(); alias = args[++i]; if (alias.equals("-help")) { printUsage(); return 0; } } else if (args[i].equals("list")) { if (i < args.length - 1) { alias = args[i + 1]; } command = new ListCommand(); if (alias.equals("-h") || alias.equals("-help")) { printUsage(); return 0; } alias = "not required"; } else if (args[i].equals("-provider")) { if (i == args.length - 1) { return 1; } String providerPath = getNormalizedPath(args[++i]); getConf().set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, providerPath); provider = getCredentialProvider(); } else if (args[i].equals("-f") || args[i].equals("-force")) { interactive = false; overwrite = true; } else if (args[i].equals("-n")) { interactive = false; overwrite = false; } else if (args[i].equals("-v") || args[i].equals("-value")) { value = args[++i]; } else if (args[i].equals("-h") || args[i].equals("-help")) { printUsage(); return 0; } else { printUsage(); ToolRunner.printGenericCommandUsage(System.err); return 1; } } if (command == null) { printUsage(); } else if (command.validate()) { exitCode = command.execute(); } return exitCode; } /** * Prints a command specific usage or overall tool usage. */ protected void printUsage() { System.out.println(getUsagePrefix() + COMMANDS); if (command != null) { System.out.println(command.getUsage()); } else { System.out.println("=========================================================" + "======"); System.out.println(CreateCommand.USAGE + ":\n\n" + CreateCommand.DESC); System.out.println("=========================================================" + "======"); System.out.println(DeleteCommand.USAGE + ":\n\n" + DeleteCommand.DESC); System.out.println("=========================================================" + "======"); System.out.println(ListCommand.USAGE + ":\n\n" + ListCommand.DESC); System.out.println("=========================================================" + "======"); System.out.println(GetCommand.USAGE + ":\n\n" + GetCommand.DESC); } } /** * Overridden by the command line driver to provide name of the tool. * * @return - CLI specific information, like tool name, year, copyright, etc. */ protected String getUsagePrefix() { return "Usage: "; } /* * Normalize the providerPath to jceks://file/<file_path> or localjceks://file/<file_path> */ private static String getNormalizedPath(String providerPath) { if (providerPath != null) { String jceksPath; if (providerPath.startsWith("/")) { providerPath = providerPath.substring(1); } jceksPath = StringUtils.lowerCase(providerPath.trim()); if (!jceksPath.startsWith(StringUtils.lowerCase(jceksPrefix)) && !jceksPath.startsWith(localJceksPrefix)) { providerPath = jceksPrefix + "/" + providerPath; } } return providerPath; } /** * Gets the provider object for the user specified provider. * * @return - A credential provider. */ private CredentialProvider getCredentialProvider() { CredentialProvider provider = null; List<CredentialProvider> providers; try { providers = CredentialProviderFactory.getProviders(getConf()); provider = providers.get(0); } catch (IOException e) { e.printStackTrace(System.err); } return provider; } /** * CredentialCommand base class */ private abstract class Command { /** * Validates the user input. * * @return - True if inputs are valid. False otherwise. */ public boolean validate() { boolean rc = true; if (alias == null || alias.isEmpty()) { System.out.println("There is no alias specified. Please provide the" + "mandatory <alias>. See the usage description with -help."); rc = false; } if (provider == null) { System.out.println("There are no valid CredentialProviders configured." + "\nCredential will not be created.\n" + "Consider using the -provider option to indicate the provider" + " to use."); rc = false; } return rc; } /** * Gets command usage and description. * * @return */ public abstract String getUsage(); /** * Called by run(). Implemented by the concrete command classes. * * @return - 0 if successful. 1 on failure. * @throws Exception - If something goes wrong. */ public abstract int execute() throws Exception; } /** * Gets the credential for the specified alias from the * specified provider. */ private class GetCommand extends Command { public static final String USAGE = "get <alias> [-provider provider-path]"; public static final String DESC = "The get subcommand gets the credential for the specified alias\n" + "from the provider specified through the -provider argument.\n"; /** * Executes the get command. Prints the clear text password on the command line. * * @return - 0 on success; 1 on failure. * @throws IOException */ @Override public int execute() throws IOException { int exitCode = 0; try { String credential = getCredential(); if (credential == null) { exitCode = 1; } else { System.out.println(credential); } } catch (IOException ex) { System.out.println("Cannot get the credential for the specified alias." + ": " + ex.getMessage()); throw ex; } return exitCode; } /** * Gets the clear text password from the credential provider. * * @return - Decrypted password for the specified alias. * @throws IOException */ private String getCredential() throws IOException { String credential = null; CredentialProvider.CredentialEntry credEntry = provider.getCredentialEntry(alias); if (credEntry != null) { char[] password = credEntry.getCredential(); if (password != null) { credential = String.valueOf(password); } } return credential; } /** * Usage and description. * * @return */ @Override public String getUsage() { return USAGE + ":\n\n" + DESC; } } /** * Creates a new credential for the alias specified or overwrites an * existing credential */ private class CreateCommand extends Command { /** * Usage summary */ public static final String USAGE = "create <alias> [-value credential] [-provider provider-path] [-f | -n]"; /** * Command description */ public static final String DESC = "The create subcommand creates a new credential or overwrites\n" + "an existing credential for the name specified\n" + "as the <alias> argument within the provider indicated through\n" + "the -provider argument. The command asks for confirmation to\n" + "overwrite the existing credential unless the -f option is specified.\n" + "Specify -n to not overwrite if the credential exists.\nThe option specified last wins."; /** * Creates or updates the specified credential. * * @return - 0 on success; 1 on failure. * @throws Exception */ @Override public int execute() throws Exception { int exitCode = 0; CredentialProvider.CredentialEntry credEntry = provider.getCredentialEntry(alias); if (credEntry != null) { /* * If credential already exists, overwrite if -f flag was specified. * overwrite is true if -f was specified. * overwrite is false if -n was specified. * if neither options were specified, prompt the user. */ if (interactive) { // prompt the user to confirm or reject the overwrite overwrite = ToolRunner .confirmPrompt("You are about to OVERWRITE the credential " + alias + " from CredentialProvider " + provider.toString() + ". Continue? "); } if (overwrite) { // delete the existing credential DeleteCommand deleteCommand = new DeleteCommand(); exitCode = deleteCommand.execute(); } else { // nothing to do return 0; } } // create new or overwrite existing credential if delete succeeded if (exitCode == 0) { exitCode = createCredential(); } return exitCode; } /** * Usage and description. * @return */ @Override public String getUsage() { return USAGE + ":\n\n" + DESC; } /** * Creates the specified credential. A credential with the same alias * should not exist. It must be deleted before this method is called. * * @return - 0 on success; 1 on failure. * @throws Exception - If the alias already exists. */ private int createCredential() throws Exception { int exitCode; List<String> args = new ArrayList<>(); args.add("create"); args.add(alias); if (value != null) { args.add("-value"); args.add(value); } String[] toolArgs = args.toArray(new String[args.size()]); exitCode = ToolRunner.run(getConf(), new CredentialShell(), toolArgs); return exitCode; } } /** * Deletes the credential specified by the alias from the * specified provider. */ private class DeleteCommand extends Command { public static final String USAGE = "delete <alias> [-f] [-provider provider-path]"; public static final String DESC = "The delete subcommand deletes the credential specified\n" + "as the <alias> argument from within the provider indicated\n" + "through the -provider argument. The command asks for\n" + "confirmation unless the -f option is specified."; /** * Deletes the specified alias. Prompts for user confirmation * if -f option is not specified. * @return * @throws Exception */ @Override public int execute() throws Exception { int exitCode; List<String> args = new ArrayList<>(); args.add("delete"); args.add(alias); if (!interactive) { args.add("-f"); } String[] toolArgs = args.toArray(new String[args.size()]); exitCode = ToolRunner.run(getConf(), new CredentialShell(), toolArgs); return exitCode; } /** * Usage and description. * * @return */ @Override public String getUsage() { return USAGE + ":\n\n" + DESC; } } /** * Lists all the aliases contained in the specified provider. */ private class ListCommand extends Command { /** * Command usage */ public static final String USAGE = "list [-provider provider-path]"; /** * Command description */ public static final String DESC = "The list subcommand displays the aliases contained within \n" + "a particular provider - as configured in core-site.xml or\n " + "indicated through the -provider argument."; /** * Executes the list command. * * @return - 0 if successful; 1 otherwise. * @throws Exception - If something goes wrong. */ @Override public int execute() throws Exception { int exitCode; List<String> args = new ArrayList<>(); args.add("list"); String[] toolArgs = args.toArray(new String[args.size()]); exitCode = ToolRunner.run(getConf(), new CredentialShell(), toolArgs); return exitCode; } /** * Usage and description. * * @return */ @Override public String getUsage() { return USAGE + ":\n\n" + DESC; } } }