blob: 52325d4e4bffada10c335b8d8ee64e1a61b08f60 [file] [log] [blame]
/*
* 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 +
". 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;
}
}
}