Allow specifying of a global --password option

This fixes bz#1108742 by providing a new global parameter
"--default-password", that will be the default for all other password
parameters if set. Each individual password parameter can override the
default global, and if none are set, a random password will be used as
before.

As part of the change, process_param_value() has been updated, to avoid
leaking passwords when they are modified by a processor function.

Change-Id: Ic5947567599c8b221b7a9e60acb4708429507741
This commit is contained in:
Javier Pena
2014-09-17 16:15:03 +02:00
parent a45eaf6128
commit 512bdce979
18 changed files with 181 additions and 62 deletions

View File

@@ -85,5 +85,5 @@ ERR_FAILURE="General failure"
ERR_NO_ANSWER_FILE="Error: Could not find file %s"
ERR_ONLY_1_FLAG="Error: The %s flag is mutually exclusive to all other command line options"
ERR_REMOVE_REMOTE_VAR="Error: Failed to remove directory %s on %s, it contains sensitive data and should be removed"
ERR_REMOVE_TMP_FILE="Error: Failed to remove temporary file %s, it contains sensitive data and should be removed"
#

View File

@@ -2,6 +2,7 @@
import netaddr
import os
import uuid
from .utils import ScriptRunner, force_ip
from .exceptions import ParamProcessingError, NetworkError
@@ -11,7 +12,7 @@ __all__ = ('ParamProcessingError', 'process_cidr', 'process_host',
'process_ssh_key')
def process_cidr(param, process_args=None):
def process_cidr(param, param_name, process_args=None):
"""
Corrects given CIDR if necessary.
"""
@@ -24,7 +25,7 @@ def process_cidr(param, process_args=None):
raise ParamProcessingError(str(ex))
def process_host(param, process_args=None):
def process_host(param, param_name, process_args=None):
"""
Tries to change given parameter to IP address, if it is in hostname
format
@@ -37,7 +38,7 @@ def process_host(param, process_args=None):
raise ParamProcessingError(str(ex))
def process_ssh_key(param, process_args=None):
def process_ssh_key(param, param_name, process_args=None):
"""
Generates SSH key if given key in param doesn't exist. In case param
is an empty string it generates default SSH key ($HOME/.ssh/id_rsa).
@@ -63,7 +64,7 @@ def process_ssh_key(param, process_args=None):
return param
def process_add_quotes_around_values(param, process_args=None):
def process_add_quotes_around_values(param, param_name, process_args=None):
"""
Add a single quote character around each element of a comma
separated list of values
@@ -77,3 +78,33 @@ def process_add_quotes_around_values(param, process_args=None):
params_list[index] = elem
param = ','.join(params_list)
return param
def process_password(param, param_name, process_args=None):
"""
Process passwords, checking the following:
1- If there is a user-entered password, use it
2- Otherwise, check for a global default password, and use it if available
3- As a last resort, generate a random password
"""
if not hasattr(process_password,"pw_dict"):
process_password.pw_dict = {}
if param == "PW_PLACEHOLDER":
if process_args["CONFIG_DEFAULT_PASSWORD"] != "":
param = process_args["CONFIG_DEFAULT_PASSWORD"]
else:
# We need to make sure we store the random password we provide
# and return it once we are asked for it again
if param_name.endswith("_CONFIRMED"):
unconfirmed_param = param_name[:-10]
if unconfirmed_param in process_password.pw_dict:
param = process_password.pw_dict[unconfirmed_param]
else:
param = uuid.uuid4().hex[:16]
process_password.pw_dict[unconfirmed_param] = param
elif not param_name in process_password.pw_dict:
param = uuid.uuid4().hex[:16]
process_password.pw_dict[param_name] = param
else:
param = process_password.pw_dict[param_name]
return param

View File

@@ -30,7 +30,7 @@ commandLineValues = {}
# List to hold all values to be masked in logging (i.e. passwords and sensitive data)
#TODO: read default values from conf_param?
masked_value_set = set()
tmpfiles = []
def initLogging (debug):
global logFile
@@ -147,8 +147,7 @@ def input_param(param):
confirmedParamName = param.CONF_NAME + "_CONFIRMED"
confirmedParam.CONF_NAME = confirmedParamName
confirmedParam.PROMPT = output_messages.INFO_CONF_PARAMS_PASSWD_CONFIRM_PROMPT
confirmedParam.VALIDATORS = [validators.validate_not_empty]
# Now get both values from user (with existing validations
# Now get both values from user (with existing validations)
while True:
_getInputFromUser(param)
_getInputFromUser(confirmedParam)
@@ -274,10 +273,11 @@ def process_param_value(param, value):
logging.debug("Processing value of parameter "
"%s." % param.CONF_NAME)
try:
new_value = proc_func(_value, controller.CONF)
new_value = proc_func(_value, param.CONF_NAME, controller.CONF)
if new_value != _value:
msg = output_messages.INFO_CHANGED_VALUE
print msg % (_value, new_value)
if param.MASK_INPUT == False:
msg = output_messages.INFO_CHANGED_VALUE
print msg % (_value, new_value)
_value = new_value
else:
logging.debug("Processor returned the original "
@@ -429,6 +429,19 @@ def _getanswerfilepath():
controller.MESSAGES.append(msg)
return path
def _gettmpanswerfilepath():
path = None
msg = "Could not find a suitable path on which to create the temporary answerfile"
ts = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
p = os.path.expanduser("~/")
if os.access(p, os.W_OK):
path = os.path.abspath(os.path.join(p, "tmp-packstack-answers-%s.txt"%ts))
tmpfiles.append(path)
return path
def _handleInteractiveParams():
try:
logging.debug("Groups: %s" % ', '.join([x.GROUP_NAME for x in controller.getAllGroups()]))
@@ -474,13 +487,8 @@ def _handleInteractiveParams():
else:
logging.debug("no post condition check for group %s" % group.GROUP_NAME)
path = _getanswerfilepath()
_displaySummary()
if path:
generateAnswerFile(path)
except KeyboardInterrupt:
logging.error("keyboard interrupt caught")
raise Exception(output_messages.ERR_EXP_KEYBOARD_INTERRUPT)
@@ -589,6 +597,11 @@ def _main(configFile=None):
# Get parameters
_handleParams(configFile)
# Generate answer file
path = _getanswerfilepath()
if path:
generateAnswerFile(path)
# Update masked_value_list with user input values
_updateMaskedValueSet()
@@ -634,6 +647,20 @@ def remove_remote_var_dirs():
logging.exception(e)
controller.MESSAGES.append(utils.color_text(msg, 'red'))
def remove_temp_files():
"""
Removes any temporary files generated during
configuration
"""
for myfile in tmpfiles:
try:
os.unlink(myfile)
except Exception as e:
msg = output_messages.ERR_REMOVE_TMP_FILE % (myfile)
logging.error(msg)
logging.exception(e)
controller.MESSAGES.append(utils.color_text(msg, 'red'))
def generateAnswerFile(outputFile, overrides={}):
sep = os.linesep
@@ -688,7 +715,7 @@ def single_step_aio_install(options):
single_step_install(options)
def single_step_install(options):
answerfilepath = _getanswerfilepath()
answerfilepath = _gettmpanswerfilepath()
if not answerfilepath:
_printAdditionalMessages()
return
@@ -892,6 +919,12 @@ def main():
if options.gen_answer_file:
# Make sure only --gen-answer-file was supplied
validateSingleFlag(options, "gen_answer_file")
answerfilepath = _gettmpanswerfilepath()
if not answerfilepath:
_printAdditionalMessages()
return
generateAnswerFile(answerfilepath)
_handleParams(answerfilepath)
generateAnswerFile(options.gen_answer_file)
# Are we installing an all in one
elif options.allinone:
@@ -926,6 +959,7 @@ def main():
finally:
remove_remote_var_dirs()
remove_temp_files()
# Always print user params to log
_printAdditionalMessages()