summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-01-06 13:27:26 +0000
committerGerrit Code Review <review@openstack.org>2016-01-06 13:27:26 +0000
commita3e0be7e368fe6cdda1b6cd896b728e51e2ce69f (patch)
tree7f58da17d8faffcd603b87a2379ed8f0ea5ec9a5
parent3c5ce7b992c53f409c0f3780168b42295527b5b1 (diff)
parent077fee4d439a6ecfa95e4e5626e4c5a3f9e7a72d (diff)
downloadoslo-utils-a3e0be7e368fe6cdda1b6cd896b728e51e2ce69f.tar.gz
Merge "Add a mechanism to mask passwords in dictionaries"
-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))