Add a mechanism to mask passwords in dictionaries

The widely used mask_password() function will mask passwords in
strings using a list of keys and patterns. This change extends it to
work on dictionaries as well. This allows one to directly invoke
mask_dict_passwords() on a dictionary and this will have the
effect of masking not only the strings (values) but also mask the
values if the keys in the dictionary are part of the list of
sanitization keys.

If the dictionary contains nested dictionaries, those will be
recursively masked as well.

Change-Id: I7ebafdeb671da36e0fdc9d6983a17ac5481b6f28
Closes-Bug: 1526041
This commit is contained in:
Amrith Kumar 2015-12-14 15:25:53 -05:00
parent e46a46ba90
commit 077fee4d43
2 changed files with 122 additions and 0 deletions

View File

@ -287,6 +287,79 @@ def mask_password(message, secret="***"): # nosec
return message
def mask_dict_password(dictionary, secret="***"): # nosec
"""Replace password with *secret* in a dictionary recursively.
:param dictionary: The dictionary which includes secret information.
:param secret: value with which to replace secret information.
:returns: The dictionary with string substitutions.
A dictionary (which may contain nested dictionaries) contains
information (such as passwords) which should not be revealed, and
this function helps detect and replace those with the 'secret'
provided (or '***' if none is provided).
Substitution is performed in one of three situations:
If the key is something that is considered to be indicative of a
secret, then the corresponding value is replaced with the secret
provided (or '***' if none is provided).
If a value in the dictionary is a string, then it is masked
using the mask_password() function.
Finally, if a value is a dictionary, this function will
recursively mask that dictionary as well.
For example:
>>> mask_dict_password({'password': 'd81juxmEW_',
>>> 'user': 'admin',
>>> 'home-dir': '/home/admin'},
>>> '???')
{'password': '???', 'user': 'admin', 'home-dir': '/home/admin'}
For example (the value is masked using mask_password())
>>> mask_dict_password({'password': '--password d81juxmEW_',
>>> 'user': 'admin',
>>> 'home-dir': '/home/admin'},
>>> '???')
{'password': '--password ???', 'user': 'admin',
'home-dir': '/home/admin'}
For example (a nested dictionary is masked):
>>> mask_dict_password({"nested": {'password': 'd81juxmEW_',
>>> 'user': 'admin',
>>> 'home': '/home/admin'}},
>>> '???')
{"nested": {'password': '???', 'user': 'admin', 'home': '/home/admin'}}
.. versionadded:: 3.4
"""
if not isinstance(dictionary, dict):
raise TypeError("Expected a dictionary, got %s instead."
% type(dictionary))
out = {}
for k, v in dictionary.items():
if isinstance(v, dict):
v = mask_dict_password(v, secret=secret)
elif k in _SANITIZE_KEYS:
v = secret
elif isinstance(v, six.string_types):
v = mask_password(v, secret=secret)
out[k] = v
return out
def is_int_like(val):
"""Check if a value looks like an integer with base 10.

View File

@ -587,6 +587,55 @@ class MaskPasswordTestCase(test_base.BaseTestCase):
self.assertEqual(expected, strutils.mask_password(payload))
class MaskDictionaryPasswordTestCase(test_base.BaseTestCase):
def test_dictionary(self):
payload = {'password': 'mypassword'}
expected = {'password': '***'}
self.assertEqual(expected,
strutils.mask_dict_password(payload))
payload = {'user': 'admin', 'password': 'mypassword'}
expected = {'user': 'admin', 'password': '***'}
self.assertEqual(expected,
strutils.mask_dict_password(payload))
payload = {'strval': 'somestring',
'dictval': {'user': 'admin', 'password': 'mypassword'}}
expected = {'strval': 'somestring',
'dictval': {'user': 'admin', 'password': '***'}}
self.assertEqual(expected,
strutils.mask_dict_password(payload))
payload = {'strval': '--password abc',
'dont_change': 'this is fine',
'dictval': {'user': 'admin', 'password': b'mypassword'}}
expected = {'strval': '--password ***',
'dont_change': 'this is fine',
'dictval': {'user': 'admin', 'password': '***'}}
self.assertEqual(expected,
strutils.mask_dict_password(payload))
def test_do_no_harm(self):
payload = {}
expected = {}
self.assertEqual(expected,
strutils.mask_dict_password(payload))
payload = {'somekey': 'somevalue',
'anotherkey': 'anothervalue'}
expected = {'somekey': 'somevalue',
'anotherkey': 'anothervalue'}
self.assertEqual(expected,
strutils.mask_dict_password(payload))
def test_mask_values(self):
payload = {'somekey': 'test = cmd --password my\xe9\x80\x80pass'}
expected = {'somekey': 'test = cmd --password ***'}
self.assertEqual(expected,
strutils.mask_dict_password(payload))
class IsIntLikeTestCase(test_base.BaseTestCase):
def test_is_int_like_true(self):
self.assertTrue(strutils.is_int_like(1))