summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmrith Kumar <amrith@tesora.com>2015-12-14 15:25:53 -0500
committerAmrith Kumar <amrith@tesora.com>2016-01-05 14:04:09 -0500
commit077fee4d439a6ecfa95e4e5626e4c5a3f9e7a72d (patch)
tree377f3656742c6e24c832005b78488139f1aa34d3
parente46a46ba90741987f1147afc56876e3d0d27e8a2 (diff)
downloadoslo-utils-077fee4d439a6ecfa95e4e5626e4c5a3f9e7a72d.tar.gz
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
-rw-r--r--oslo_utils/strutils.py73
-rw-r--r--oslo_utils/tests/test_strutils.py49
2 files changed, 122 insertions, 0 deletions
diff --git a/oslo_utils/strutils.py b/oslo_utils/strutils.py
index 342b398..af55d9d 100644
--- a/oslo_utils/strutils.py
+++ b/oslo_utils/strutils.py
@@ -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.
diff --git a/oslo_utils/tests/test_strutils.py b/oslo_utils/tests/test_strutils.py
index 7864c39..eba10c9 100644
--- a/oslo_utils/tests/test_strutils.py
+++ b/oslo_utils/tests/test_strutils.py
@@ -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))