/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * Licensed 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. * * Initial developer(s): Ludovic Launer * Contributor(s): */ package com.continuent.tungsten.common.security; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.MissingArgumentException; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.cli.UnrecognizedOptionException; import org.apache.log4j.Logger; import com.continuent.tungsten.common.config.cluster.ConfigurationException; import com.continuent.tungsten.common.jmx.ServerRuntimeException; import com.continuent.tungsten.common.security.PasswordManager.ClientApplicationType; /** * Application to manage passwords and users * * @author <a href="mailto:ludovic.launer@continuent.com">Ludovic Launer</a> * @version 1.0 */ public class PasswordManagerCtrl { private static Logger logger = Logger .getLogger(PasswordManagerCtrl.class); private Options helpOptions = new Options(); private Options options = new Options(); private static PasswordManagerCtrl pwd; private static PasswordManager.ClientApplicationType clientApplicationType = null; public PasswordManager passwordManager = null; // --- Options overriding elements in security.properties --- private Boolean useEncryptedPassword = null; private String truststoreLocation = null; private String truststorePassword = null; private String truststorePasswordFileLocation = null; private String keystoreLocation = null; private String keystorePassword = null; private String keystorePasswordFileLocation = null; private String passwordFileLocation = null; private String userPasswordFileLocation = null; // -- Define constants for command line arguments --- private static final String HELP = "help"; private static final String _HELP = "h"; private static final String _AUTHENTICATE = "a"; private static final String AUTHENTICATE = "authenticate"; private static final String CREATE = "create"; private static final String _CREATE = "c"; private static final String DELETE = "delete"; private static final String _DELETE = "d"; private static final String FILE = "file"; private static final String _FILE = "f"; private static final String TARGET_APPLICATION = "target"; private static final String _TARGET_APPLICATION = "t"; private static final String _ENCRYPTED_PASSWORD = "e"; private static final String ENCRYPTED_PASSWORD = "encrypted.password"; private static final String _TRUSTSTORE_LOCATION = "ts"; private static final String TRUSTSTORE_LOCATION = "truststore.location"; private static final String _TRUSTSTORE_PASSWORD = "tsp"; private static final String TRUSTSTORE_PASSWORD = "truststore.password"; private static final String _TRUSTSTORE_PASSWORD_FILE = "tspf"; private static final String TRUSTSTORE_PASSWORD_FILE = "truststore.password.file"; private static final String KEYSTORE_LOCATION = "keystore.location"; private static final String _KEYSTORE_LOCATION = "ks"; private static final String KEYSTORE_PASSWORD = "keystore.password"; private static final String _KEYSTORE_PASSWORD = "ksp"; private static final String KEYSTORE_PASSWORD_FILE = "keystore.password.file"; private static final String _KEYSTORE_PASSWORD_FILE = "kspf"; private static final String _PASSWORD_FILE_LOCATION = "p"; private static final String PASSWORD_FILE_LOCATION = "password.file.location"; private static final String _USER_PASSWORD_FILE_LOCATION = "upf"; private static final String USER_PASSWORD_FILE_LOCATION = "user.password.file.location"; private static final String _LIST_USERS = "l"; private static final String LIST_USERS = "list"; private static Option create; private static Option authenticate; // --- Exit codes --- public enum EXIT_CODE { EXIT_OK(0), EXIT_ERROR(1); final int value; private EXIT_CODE(int value) { this.value = value; } } /** * Setup command line options */ @SuppressWarnings("static-access") private void setupCommandLine() { // --- Options on the command line --- Option help = OptionBuilder.withLongOpt(HELP) .withDescription("Displays this message").create(_HELP); Option listUsers = OptionBuilder.withLongOpt(LIST_USERS) .withDescription("List known users").create(_LIST_USERS); Option file = OptionBuilder.withLongOpt(FILE).withArgName("filename") .hasArgs() .withDescription("Location of the " + SecurityConf.SECURITY_PROPERTIES_FILE_NAME + " file") .create(_FILE); // Mutually excluding options OptionGroup optionGroup = new OptionGroup(); authenticate = OptionBuilder.withLongOpt(AUTHENTICATE).hasArgs(2) .withArgName("username> <password") .withDescription("Authenticates a user with given password") .create(_AUTHENTICATE); create = OptionBuilder.withLongOpt(CREATE).hasArgs(2) .withArgName("username> <password") .withDescription("Creates or Updates a user").create(_CREATE); Option delete = OptionBuilder.withLongOpt(DELETE) .withArgName("username").hasArgs() .withDescription("Deletes a user").create(_DELETE); optionGroup.addOption(listUsers); optionGroup.addOption(authenticate); optionGroup.addOption(create); optionGroup.addOption(delete); optionGroup.setRequired(true); // At least 1 command required Option targetApplication = OptionBuilder.withLongOpt(TARGET_APPLICATION) .withArgName("target").hasArgs() .withDescription("Target application: " + getListOfClientApplicationType()) .create(_TARGET_APPLICATION); // --- Options replacing parameters from security.properties --- Option encryptedPassword = OptionBuilder.withLongOpt(ENCRYPTED_PASSWORD) .withArgName("encrypt password") .withDescription("Encrypts the password") .create(_ENCRYPTED_PASSWORD); Option truststoreLocation = OptionBuilder .withLongOpt(TRUSTSTORE_LOCATION).withArgName("filename") .hasArg().withDescription("Location of the tuststore file") .create(_TRUSTSTORE_LOCATION); Option truststorePassword = OptionBuilder .withLongOpt(TRUSTSTORE_PASSWORD).withArgName("password") .hasArg().withDescription("Password for the truststore file") .create(_TRUSTSTORE_PASSWORD); Option truststorePasswordFile = OptionBuilder .withLongOpt(TRUSTSTORE_PASSWORD_FILE) .withArgName("truststore password file").hasArg() .withDescription(MessageFormat.format( "Hidden password alternative to -{0}", _TRUSTSTORE_PASSWORD)) .create(_TRUSTSTORE_PASSWORD_FILE); Option keystoreLocation = OptionBuilder.withLongOpt(KEYSTORE_LOCATION) .withArgName("filename").hasArg() .withDescription("Location of the keystore file") .create(_KEYSTORE_LOCATION); Option keystorePassword = OptionBuilder.withLongOpt(KEYSTORE_PASSWORD) .withArgName("password").hasArg() .withDescription("Password for the keystore file") .create(_KEYSTORE_PASSWORD); Option keystorePasswordFile = OptionBuilder .withLongOpt(KEYSTORE_PASSWORD_FILE) .withArgName("password file").hasArg() .withDescription(MessageFormat.format( "Hidden password alternative to -{0}", _KEYSTORE_PASSWORD)) .create(_KEYSTORE_PASSWORD_FILE); Option passwordFileLocation = OptionBuilder .withLongOpt(PASSWORD_FILE_LOCATION).withArgName("filename") .hasArg().withDescription("Location of the password file") .create(_PASSWORD_FILE_LOCATION); Option userPasswordFileLocation = OptionBuilder .withLongOpt(USER_PASSWORD_FILE_LOCATION) .withArgName("filename").hasArg() .withDescription("Location of the user password file") .create(_USER_PASSWORD_FILE_LOCATION); // --- Add options to the list --- // --- Help this.helpOptions.addOption(help); // --- Program command line options this.options.addOptionGroup(optionGroup); this.options.addOption(file); this.options.addOption(help); this.options.addOption(encryptedPassword); this.options.addOption(truststoreLocation); this.options.addOption(truststorePassword); this.options.addOption(truststorePasswordFile); this.options.addOption(keystoreLocation); this.options.addOption(keystorePassword); this.options.addOption(keystorePasswordFile); this.options.addOption(passwordFileLocation); this.options.addOption(userPasswordFileLocation); this.options.addOption(targetApplication); } /** * Creates a new <code>PasswordManager</code> object */ public PasswordManagerCtrl() { this.setupCommandLine(); } /** * Password Manager entry point * * @param argv * @throws Exception */ public static void main(String argv[]) throws Exception { pwd = new PasswordManagerCtrl(); // --- Options --- ClientApplicationType clientApplicationType = null; String securityPropertiesFileLocation = null; String username = null; String password = null; CommandLine line = null; try { CommandLineParser parser = new GnuParser(); // --- Parse the command line arguments --- // --- Help line = parser.parse(pwd.helpOptions, argv, true); if (line.hasOption(_HELP)) { DisplayHelpAndExit(EXIT_CODE.EXIT_OK); } // --- Program command line options line = parser.parse(pwd.options, argv); // --- Handle options --- // --- Optional arguments : Get options --- if (line.hasOption(_HELP)) { DisplayHelpAndExit(EXIT_CODE.EXIT_OK); } if (line.hasOption(_TARGET_APPLICATION)) // Target Application { String target = line.getOptionValue(TARGET_APPLICATION); clientApplicationType = PasswordManagerCtrl .getClientApplicationType(target); } if (line.hasOption(_FILE)) // security.properties file location { securityPropertiesFileLocation = line.getOptionValue(_FILE); } if (line.hasOption(_AUTHENTICATE)) { String[] authenticateArgs = line.getOptionValues(_AUTHENTICATE); if (!line.hasOption(_USER_PASSWORD_FILE_LOCATION)) { // Make sure username + password are provided if (authenticateArgs.length < 2) throw new MissingArgumentException(authenticate); else { // Credentials on command line username = authenticateArgs[0]; password = authenticateArgs[1]; } } else { // Credentials on command line and file username = authenticateArgs[0]; pwd.userPasswordFileLocation = line .getOptionValue(USER_PASSWORD_FILE_LOCATION); password = null; } } if (line.hasOption(_CREATE)) { String[] authenticateArgs = line.getOptionValues(_CREATE); if (!line.hasOption(_USER_PASSWORD_FILE_LOCATION)) { // Make sure username + password are provided if (authenticateArgs.length < 2) throw new MissingArgumentException(authenticate); else { // Credentials on command line username = authenticateArgs[0]; password = authenticateArgs[1]; } } else { // Credentials on command line and file username = authenticateArgs[0]; pwd.userPasswordFileLocation = line .getOptionValue(USER_PASSWORD_FILE_LOCATION); password = null; } } // --- Options to replace values in security.properties file --- if (line.hasOption(_ENCRYPTED_PASSWORD)) pwd.useEncryptedPassword = true; if (line.hasOption(_TRUSTSTORE_LOCATION)) pwd.truststoreLocation = line .getOptionValue(_TRUSTSTORE_LOCATION); if (line.hasOption(_TRUSTSTORE_PASSWORD)) pwd.truststorePassword = line .getOptionValue(_TRUSTSTORE_PASSWORD); if (line.hasOption(_TRUSTSTORE_PASSWORD_FILE)) pwd.truststorePasswordFileLocation = line .getOptionValue(_TRUSTSTORE_PASSWORD_FILE); if (line.hasOption(_KEYSTORE_LOCATION)) pwd.keystoreLocation = line.getOptionValue(_KEYSTORE_LOCATION); if (line.hasOption(_KEYSTORE_PASSWORD)) pwd.keystorePassword = line.getOptionValue(_KEYSTORE_PASSWORD); if (line.hasOption(_KEYSTORE_PASSWORD_FILE)) pwd.keystorePasswordFileLocation = line .getOptionValue(_KEYSTORE_PASSWORD_FILE); if (line.hasOption(_PASSWORD_FILE_LOCATION)) pwd.passwordFileLocation = line .getOptionValue(_PASSWORD_FILE_LOCATION); try { pwd.passwordManager = new PasswordManager( securityPropertiesFileLocation, clientApplicationType); AuthenticationInfo authenticationInfo = pwd.passwordManager .getAuthenticationInfo(); // --- Substitute with user provided options if (pwd.userPasswordFileLocation != null) password = pwd .getPassewordFromFile(pwd.userPasswordFileLocation); if (pwd.useEncryptedPassword != null) authenticationInfo .setUseEncryptedPasswords(pwd.useEncryptedPassword); if (pwd.truststoreLocation != null) authenticationInfo .setTruststoreLocation(pwd.truststoreLocation); // Truststore password if (pwd.truststorePasswordFileLocation != null) authenticationInfo .setTruststorePassword(pwd.getPassewordFromFile( pwd.truststorePasswordFileLocation)); else if (pwd.truststorePassword != null) authenticationInfo .setTruststorePassword(pwd.truststorePassword); if (pwd.keystoreLocation != null) authenticationInfo .setKeystoreLocation(pwd.keystoreLocation); // Keystore password if (pwd.keystorePasswordFileLocation != null) pwd.keystorePasswordFileLocation = pwd.getPassewordFromFile( pwd.keystorePasswordFileLocation); else if (pwd.keystorePassword != null) authenticationInfo .setKeystorePassword(pwd.keystorePassword); // Target password file location if (pwd.passwordFileLocation != null) authenticationInfo .setPasswordFileLocation(pwd.passwordFileLocation); // --- Display summary of used parameters --- logger.info("Using parameters: "); logger.info("-----------------"); if (pwd.userPasswordFileLocation != null) logger.info(MessageFormat.format("{0} \t = {1}", USER_PASSWORD_FILE_LOCATION, pwd.userPasswordFileLocation)); if (authenticationInfo .getParentPropertiesFileLocation() != null) logger.info(MessageFormat.format( "security.properties \t\t = {0}", authenticationInfo .getParentPropertiesFileLocation())); logger.info(MessageFormat.format( "password.file.location \t\t = {0}", authenticationInfo.getPasswordFileLocation())); logger.info( MessageFormat.format("encrypted.password \t\t = {0}", authenticationInfo.isUseEncryptedPasswords())); // --- Keystore if (line.hasOption(_AUTHENTICATE)) { logger.info( MessageFormat.format("keystore.location \t\t = {0}", authenticationInfo.getKeystoreLocation())); // Keystore password from command line if (pwd.keystorePasswordFileLocation == null) logger.info(MessageFormat.format( "keystore.password \t\t = {0}", getHiddenPassword(authenticationInfo .getKeystorePassword()))); // Keystore password from file else logger.info(MessageFormat.format("{0} = {1}", KEYSTORE_PASSWORD_FILE, pwd.keystorePasswordFileLocation)); } // --- Truststore if (authenticationInfo.isUseEncryptedPasswords()) { logger.info(MessageFormat.format( "truststore.location \t\t = {0}", authenticationInfo.getTruststoreLocation())); // Truststore password from command line if (pwd.truststorePasswordFileLocation == null) logger.info(MessageFormat.format( "truststore.password \t\t = {0}", getHiddenPassword(authenticationInfo .getTruststorePassword()))); // Truststore password from file else logger.info(MessageFormat.format("{0} = {1}", TRUSTSTORE_PASSWORD_FILE, pwd.truststorePasswordFileLocation)); } logger.info("-----------------"); // --- AuthenticationInfo consistency check // Try to create files if possible pwd.passwordManager.try_createAuthenticationInfoFiles(); authenticationInfo.checkAndCleanAuthenticationInfo(); } catch (ConfigurationException ce) { logger.error(MessageFormat.format( "Could not retrieve configuration information: {0}\nTry to specify a security.properties file location, provide options on the command line, or have the cluster.home variable set.", ce.getMessage())); System.exit(EXIT_CODE.EXIT_ERROR.value); } catch (ServerRuntimeException sre) { logger.error(sre.getLocalizedMessage()); // AuthenticationInfo consistency check : failed Exit(EXIT_CODE.EXIT_ERROR); } catch (Exception e) { logger.error(e); Exit(EXIT_CODE.EXIT_ERROR); } // --- Perform commands --- // ######### List users ########## if (line.hasOption(_LIST_USERS)) { try { logger.info("Listing users by application type:"); HashMap<String, List<String>> mapUsers = new HashMap<String, List<String>>(); // Get users for each application type for (ClientApplicationType applicationType : ClientApplicationType .values()) { List<String> listUsernames = pwd.passwordManager .listUsers(applicationType); mapUsers.put(applicationType.name(), listUsernames); } // Display result for (ClientApplicationType applicationType : ClientApplicationType .values()) { List<String> listUsers = mapUsers .get(applicationType.name()); if (listUsers != null && !listUsers.isEmpty()) { logger.info("\n"); logger.info(MessageFormat.format("[{0}]", applicationType.name().toLowerCase())); logger.info("-----------"); for (String user : listUsers) { logger.info(user); } } } logger.info("\n"); } catch (Exception e) { logger.error(MessageFormat.format( "Error while listing users: {0}", e.getMessage())); Exit(EXIT_CODE.EXIT_ERROR); } } // ######### Authenticate ########## if (line.hasOption(_AUTHENTICATE)) { try { boolean authOK = pwd.passwordManager .authenticateUser(username, password); String msgAuthOK = (authOK) ? "SUCCESS" : "FAILED"; logger.info(MessageFormat.format( "Authenticating {0}:{1} = {2}", username, password, msgAuthOK)); } catch (Exception e) { logger.error(MessageFormat.format( "Error while authenticating user: {0}", e.getMessage())); Exit(EXIT_CODE.EXIT_ERROR); } } // ######### Create ########## if (line.hasOption(_CREATE)) { try { pwd.passwordManager.setPasswordForUser(username, password); logger.info(MessageFormat .format("User created successfuly: {0}", username)); } catch (Exception e) { logger.error(MessageFormat.format( "Error while creating user: {0}", e.getMessage())); Exit(EXIT_CODE.EXIT_ERROR); } } // ########## DELETE ########## else if (line.hasOption(_DELETE)) { username = line.getOptionValue(_DELETE); try { pwd.passwordManager.deleteUser(username); logger.info(MessageFormat .format("User deleted successfuly: {0}", username)); } catch (Exception e) { logger.error(MessageFormat.format( "Error while deleting user: {0}", e.getMessage())); Exit(EXIT_CODE.EXIT_ERROR); } } } catch (ParseException exp) { logger.error(exp.getMessage()); DisplayHelpAndExit(EXIT_CODE.EXIT_ERROR); } catch (Exception e) { // Workaround for Junit test if (e.toString().contains("CheckExitCalled")) { throw e; } else // Normal behaviour { logger.error(e.getMessage()); Exit(EXIT_CODE.EXIT_ERROR); } } Exit(EXIT_CODE.EXIT_OK); } /** * Retrieve password from a 1 line / 1 word file * * @param passwordFileLocation location of the file from which to retrieve * the password * @return * @throws ConfigurationException */ private String getPassewordFromFile(String passwordFileLocation) throws ConfigurationException { String password = null; BufferedReader br = null; try { br = new BufferedReader(new FileReader(passwordFileLocation)); String line = null; if ((line = br.readLine()) != null) password = line; } catch (Exception e) { String errorMessage = MessageFormat.format( "Could not read password from file: {0} -> {1}", passwordFileLocation, e); throw new ConfigurationException(errorMessage); } finally { if (br != null) { try { br.close(); } catch (IOException e1) { // Nothing to do, best effort } } } return password; } /** * Hide password characters by replacing them with a * * * @param clearTextPassword the password to hide * @return */ private static String getHiddenPassword(String clearTextPassword) { String hiddenPassword = null; if (clearTextPassword != null) hiddenPassword = clearTextPassword.replaceAll("(?s).", "*"); return hiddenPassword; } /** * Get the list of ClientApplicationType as a string * * @return String containing all possible lower-cased application types */ private static String getListOfClientApplicationType() { String listApplicationType = ""; for (ClientApplicationType appType : ClientApplicationType.values()) { listApplicationType += ((listApplicationType.isEmpty() ? "" : " | ") + appType.toString().toLowerCase()); } listApplicationType.trim(); return listApplicationType; } /** * Get the application type argument. Populates the * <code>clientApplicationType</code> property * * @param commandLineTargetApplicationType the argument provided on the * command line * @return a ClientApplicationType * @throws UnrecognizedOptionException if the cast could not be made */ private static ClientApplicationType getClientApplicationType( String commandLineTargetApplicationType) throws UnrecognizedOptionException { try { clientApplicationType = ClientApplicationType .fromString(commandLineTargetApplicationType); } catch (IllegalArgumentException iae) { throw new UnrecognizedOptionException(MessageFormat.format( "The target application type does not exist: {0}", commandLineTargetApplicationType)); } return clientApplicationType; } /** * Display the program help and exits */ private static void DisplayHelpAndExit(EXIT_CODE exitCode) { HelpFormatter formatter = new HelpFormatter(); formatter.setWidth(120); formatter.printHelp("tpasswd", pwd.options); Exit(exitCode); } private static void Exit(EXIT_CODE exitCode) { System.exit(exitCode.value); } }