Sync latest process and str utils from oslo

This sync required changes to fix these issues:
* Make execute method clean password in exception
* Make sure mask_password works properly

------------------------------------------------
The sync pulls in the following changes (newest to oldest):

6a60f842 - Mask passwords in exceptions and error messages (SSH)
63c99a0f - Mask passwords in exceptions and error messages
66142c34 - Make strutils.mask_password more secure

-----------------------------------------------

Closes-Bug: 1343604
Closes-Bug: 1345233
SecurityImpact

Change-Id: I3b49b1d667f6ade9ae3f6765d735440a3e838917
This commit is contained in:
Tristan Cacqueray 2014-09-14 19:18:06 +00:00
parent e24bd6ba1b
commit 9672744f09
2 changed files with 47 additions and 25 deletions

View File

@ -150,12 +150,12 @@ def execute(*cmd, **kwargs):
cmd = shlex.split(root_helper) + list(cmd) cmd = shlex.split(root_helper) + list(cmd)
cmd = map(str, cmd) cmd = map(str, cmd)
sanitized_cmd = strutils.mask_password(' '.join(cmd))
while attempts > 0: while attempts > 0:
attempts -= 1 attempts -= 1
try: try:
LOG.log(loglevel, 'Running cmd (subprocess): %s', LOG.log(loglevel, _('Running cmd (subprocess): %s'), sanitized_cmd)
strutils.mask_password(' '.join(cmd)))
_PIPE = subprocess.PIPE # pylint: disable=E1101 _PIPE = subprocess.PIPE # pylint: disable=E1101
if os.name == 'nt': if os.name == 'nt':
@ -192,16 +192,18 @@ def execute(*cmd, **kwargs):
LOG.log(loglevel, 'Result was %s' % _returncode) LOG.log(loglevel, 'Result was %s' % _returncode)
if not ignore_exit_code and _returncode not in check_exit_code: if not ignore_exit_code and _returncode not in check_exit_code:
(stdout, stderr) = result (stdout, stderr) = result
sanitized_stdout = strutils.mask_password(stdout)
sanitized_stderr = strutils.mask_password(stderr)
raise ProcessExecutionError(exit_code=_returncode, raise ProcessExecutionError(exit_code=_returncode,
stdout=stdout, stdout=sanitized_stdout,
stderr=stderr, stderr=sanitized_stderr,
cmd=' '.join(cmd)) cmd=sanitized_cmd)
return result return result
except ProcessExecutionError: except ProcessExecutionError:
if not attempts: if not attempts:
raise raise
else: else:
LOG.log(loglevel, '%r failed. Retrying.', cmd) LOG.log(loglevel, _('%r failed. Retrying.'), sanitized_cmd)
if delay_on_retry: if delay_on_retry:
greenthread.sleep(random.randint(20, 200) / 100.0) greenthread.sleep(random.randint(20, 200) / 100.0)
finally: finally:
@ -240,7 +242,8 @@ def trycmd(*args, **kwargs):
def ssh_execute(ssh, cmd, process_input=None, def ssh_execute(ssh, cmd, process_input=None,
addl_env=None, check_exit_code=True): addl_env=None, check_exit_code=True):
LOG.debug('Running cmd (SSH): %s', cmd) sanitized_cmd = strutils.mask_password(cmd)
LOG.debug('Running cmd (SSH): %s', sanitized_cmd)
if addl_env: if addl_env:
raise InvalidArgumentError(_('Environment not supported over SSH')) raise InvalidArgumentError(_('Environment not supported over SSH'))
@ -254,7 +257,10 @@ def ssh_execute(ssh, cmd, process_input=None,
# NOTE(justinsb): This seems suspicious... # NOTE(justinsb): This seems suspicious...
# ...other SSH clients have buffering issues with this approach # ...other SSH clients have buffering issues with this approach
stdout = stdout_stream.read() stdout = stdout_stream.read()
sanitized_stdout = strutils.mask_password(stdout)
stderr = stderr_stream.read() stderr = stderr_stream.read()
sanitized_stderr = strutils.mask_password(stderr)
stdin_stream.close() stdin_stream.close()
exit_status = channel.recv_exit_status() exit_status = channel.recv_exit_status()
@ -264,11 +270,11 @@ def ssh_execute(ssh, cmd, process_input=None,
LOG.debug('Result was %s' % exit_status) LOG.debug('Result was %s' % exit_status)
if check_exit_code and exit_status != 0: if check_exit_code and exit_status != 0:
raise ProcessExecutionError(exit_code=exit_status, raise ProcessExecutionError(exit_code=exit_status,
stdout=stdout, stdout=sanitized_stdout,
stderr=stderr, stderr=sanitized_stderr,
cmd=cmd) cmd=sanitized_cmd)
return (stdout, stderr) return (sanitized_stdout, sanitized_stderr)
def get_worker_count(): def get_worker_count():

View File

@ -50,26 +50,37 @@ SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
# NOTE(flaper87): The following 3 globals are used by `mask_password` # NOTE(flaper87): The following globals are used by `mask_password`
_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] _SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
# NOTE(ldbragst): Let's build a list of regex objects using the list of # NOTE(ldbragst): Let's build a list of regex objects using the list of
# _SANITIZE_KEYS we already have. This way, we only have to add the new key # _SANITIZE_KEYS we already have. This way, we only have to add the new key
# to the list of _SANITIZE_KEYS and we can generate regular expressions # to the list of _SANITIZE_KEYS and we can generate regular expressions
# for XML and JSON automatically. # for XML and JSON automatically.
_SANITIZE_PATTERNS = [] _SANITIZE_PATTERNS_2 = []
_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', _SANITIZE_PATTERNS_1 = []
r'(<%(key)s>).*?(</%(key)s>)',
r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', # NOTE(amrith): Some regular expressions have only one parameter, some
r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])', # have two parameters. Use different lists of patterns here.
r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?[\'"])' _FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+']
'.*?([\'"])', _FORMAT_PATTERNS_2 = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)'] r'(%(key)s\s+[\"\']).*?([\"\'])',
r'([-]{2}%(key)s\s+)[^\'^\"^=^\s]+([\s]*)',
r'(<%(key)s>).*?(</%(key)s>)',
r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])',
r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?'
'[\'"]).*?([\'"])',
r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)']
for key in _SANITIZE_KEYS: for key in _SANITIZE_KEYS:
for pattern in _FORMAT_PATTERNS: for pattern in _FORMAT_PATTERNS_2:
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
_SANITIZE_PATTERNS.append(reg_ex) _SANITIZE_PATTERNS_2.append(reg_ex)
for pattern in _FORMAT_PATTERNS_1:
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
_SANITIZE_PATTERNS_1.append(reg_ex)
def int_from_bool_as_string(subject): def int_from_bool_as_string(subject):
@ -289,7 +300,12 @@ def mask_password(message, secret="***"):
if not any(key in message for key in _SANITIZE_KEYS): if not any(key in message for key in _SANITIZE_KEYS):
return message return message
secret = r'\g<1>' + secret + r'\g<2>' substitute = r'\g<1>' + secret + r'\g<2>'
for pattern in _SANITIZE_PATTERNS: for pattern in _SANITIZE_PATTERNS_2:
message = re.sub(pattern, secret, message) message = re.sub(pattern, substitute, message)
substitute = r'\g<1>' + secret
for pattern in _SANITIZE_PATTERNS_1:
message = re.sub(pattern, substitute, message)
return message return message