summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--doc/requirements.txt6
-rwxr-xr-xdoc/source/conf.py2
-rw-r--r--oslo_utils/strutils.py46
-rw-r--r--oslo_utils/tests/test_strutils.py57
-rw-r--r--releasenotes/notes/mask-dict-passwords-99357ffb7972fb0b.yaml9
-rw-r--r--releasenotes/notes/mask-password-patterns-f41524069b8ae488.yaml8
-rw-r--r--releasenotes/source/conf.py16
-rw-r--r--tox.ini7
9 files changed, 110 insertions, 42 deletions
diff --git a/.gitignore b/.gitignore
index adf36be..dc1c1fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@
# Packages
*.egg
*.egg-info
+*.eggs
dist
build
eggs
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 20f0c92..4bf9a83 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -2,8 +2,8 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
# this is required for the docs build jobs
-sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD
-sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD
-openstackdocstheme>=1.18.1 # Apache-2.0
+sphinx>=1.8.0,<2.0.0;python_version=='2.7' # BSD
+sphinx>=1.8.0,!=2.1.0;python_version>='3.4' # BSD
+openstackdocstheme>=1.20.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 25a0146..004256b 100755
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -63,8 +63,6 @@ pygments_style = 'sphinx'
# html_static_path = ['static']
html_theme = 'openstackdocs'
-html_last_updated_fmt = '%Y-%m-%d %H:%M'
-
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
diff --git a/oslo_utils/strutils.py b/oslo_utils/strutils.py
index 936e4d5..6295bde 100644
--- a/oslo_utils/strutils.py
+++ b/oslo_utils/strutils.py
@@ -54,12 +54,19 @@ SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
-# NOTE(flaper87): The following globals are used by `mask_password`
-_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password',
+# NOTE(flaper87): The following globals are used by `mask_password` and
+# `mask_dict_password`. They must all be lowercase.
+_SANITIZE_KEYS = ['adminpass', 'admin_pass', 'password', 'admin_password',
'auth_token', 'new_pass', 'auth_password', 'secret_uuid',
'secret', 'sys_pswd', 'token', 'configdrive',
- 'CHAPPASSWORD', 'encrypted_key', 'private_key',
- 'encryption_key_id']
+ 'chappassword', 'encrypted_key', 'private_key',
+ 'encryption_key_id', 'fernetkey', 'sslkey', 'passphrase',
+ 'cephclusterfsid', 'octaviaheartbeatkey', 'rabbitcookie',
+ 'cephmanilaclientkey', 'pacemakerremoteauthkey',
+ 'designaterndckey', 'cephadminkey', 'heatauthencryptionkey',
+ 'cephclientkey', 'keystonecredential',
+ 'barbicansimplecryptokek', 'cephrgwkey', 'swifthashsuffix',
+ 'migrationsshkey', 'cephmdskey', 'cephmonkey']
# 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
@@ -70,17 +77,18 @@ _SANITIZE_PATTERNS_1 = {}
# NOTE(amrith): Some regular expressions have only one parameter, some
# have two parameters. Use different lists of patterns here.
-_FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+']
-_FORMAT_PATTERNS_2 = [r'(%(key)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*)']
+_FORMAT_PATTERNS_1 = [r'(%(key)s[0-9]*\s*[=]\s*)[^\s^\'^\"]+']
+_FORMAT_PATTERNS_2 = [r'(%(key)s[0-9]*\s*[=]\s*[\"\'])[^\"\']*([\"\'])',
+ r'(%(key)s[0-9]*\s+[\"\'])[^\"\']*([\"\'])',
+ r'([-]{2}%(key)s[0-9]*\s+)[^\'^\"^=^\s]+([\s]*)',
+ r'(<%(key)s[0-9]*>)[^<]*(</%(key)s[0-9]*>)',
+ r'([\"\']%(key)s[0-9]*[\"\']\s*:\s*[\"\'])[^\"\']*'
+ r'([\"\'])',
+ r'([\'"][^"\']*%(key)s[0-9]*[\'"]\s*:\s*u?[\'"])[^\"\']*'
+ r'([\'"])',
+ r'([\'"][^\'"]*%(key)s[0-9]*[\'"]\s*,\s*\'--?[A-z]+'
+ r'\'\s*,\s*u?[\'"])[^\"\']*([\'"])',
+ r'(%(key)s[0-9]*\s*--?[A-z]+\s*)\S+(\s*)']
# NOTE(dhellmann): Keep a separate list of patterns by key so we only
# need to apply the substitutions for keys we find using a quick "in"
@@ -90,11 +98,11 @@ for key in _SANITIZE_KEYS:
_SANITIZE_PATTERNS_2[key] = []
for pattern in _FORMAT_PATTERNS_2:
- reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
+ reg_ex = re.compile(pattern % {'key': key}, re.DOTALL | re.IGNORECASE)
_SANITIZE_PATTERNS_2[key].append(reg_ex)
for pattern in _FORMAT_PATTERNS_1:
- reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
+ reg_ex = re.compile(pattern % {'key': key}, re.DOTALL | re.IGNORECASE)
_SANITIZE_PATTERNS_1[key].append(reg_ex)
@@ -329,7 +337,7 @@ def mask_password(message, secret="***"): # nosec
# specified in _SANITIZE_KEYS, if not then just return the message since
# we don't have to mask any passwords.
for key in _SANITIZE_KEYS:
- if key in message:
+ if key in message.lower():
for pattern in _SANITIZE_PATTERNS_2[key]:
message = re.sub(pattern, substitute2, message)
for pattern in _SANITIZE_PATTERNS_1[key]:
@@ -405,7 +413,7 @@ def mask_dict_password(dictionary, secret="***"): # nosec
k_matched = False
if isinstance(k, six.string_types):
for sani_key in _SANITIZE_KEYS:
- if sani_key in k:
+ if sani_key in k.lower():
out[k] = secret
k_matched = True
break
diff --git a/oslo_utils/tests/test_strutils.py b/oslo_utils/tests/test_strutils.py
index f7efcd9..25e974c 100644
--- a/oslo_utils/tests/test_strutils.py
+++ b/oslo_utils/tests/test_strutils.py
@@ -296,6 +296,12 @@ StringToBytesTest.generate_scenarios()
class MaskPasswordTestCase(test_base.BaseTestCase):
+ def test_sanitize_keys(self):
+
+ lowered = [k.lower() for k in strutils._SANITIZE_KEYS]
+ message = "The _SANITIZE_KEYS must all be lowercase."
+ self.assertEqual(strutils._SANITIZE_KEYS, lowered, message)
+
def test_json(self):
# Test 'adminPass' w/o spaces
payload = """{'adminPass':'TL0EfN33'}"""
@@ -353,6 +359,43 @@ class MaskPasswordTestCase(test_base.BaseTestCase):
payload = """{ 'token' : 'token' }"""
expected = """{ 'token' : '***' }"""
self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'fernetkey'
+ payload = """{ 'fernetkey' : 'token' }"""
+ expected = """{ 'fernetkey' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'FernetKey'
+ payload = """{ 'FernetKey' : 'token' }"""
+ expected = """{ 'FernetKey' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'sslkey'
+ payload = """{ 'sslkey' : 'token' }"""
+ expected = """{ 'sslkey' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'SslKey'
+ payload = """{ 'SslKey' : 'token' }"""
+ expected = """{ 'SslKey' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'passphrase'
+ payload = """{ 'passphrase' : 'token' }"""
+ expected = """{ 'passphrase' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'PassPhrase'
+ payload = """{ 'PassPhrase' : 'token' }"""
+ expected = """{ 'PassPhrase' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Some real-life cases
+ # Test 'KeystoneFernetKey1'
+ payload = """{ 'KeystoneFernetKey1' : 'token' }"""
+ expected = """{ 'KeystoneFernetKey1' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'OctaviaCaKeyPassword'
+ payload = """{ 'OctaviaCaKeyPassword' : 'token' }"""
+ expected = """{ 'OctaviaCaKeyPassword' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'OctaviaCaKeyPassphrase'
+ payload = """{ 'OctaviaCaKeyPassphrase' : 'token' }"""
+ expected = """{ 'OctaviaCaKeyPassphrase' : '***' }"""
+ self.assertEqual(expected, strutils.mask_password(payload))
def test_xml(self):
# Test 'adminPass' w/o spaces
@@ -395,6 +438,10 @@ class MaskPasswordTestCase(test_base.BaseTestCase):
</password>"""
expected = """<password>***</password>"""
self.assertEqual(expected, strutils.mask_password(payload))
+ # Test 'Password1' - case-insensitive + number
+ payload = """<Password1>TL0EfN33</Password1>"""
+ expected = """<Password1>***</Password1>"""
+ self.assertEqual(expected, strutils.mask_password(payload))
def test_xml_attribute(self):
# Test 'adminPass' w/o spaces
@@ -676,6 +723,16 @@ class MaskDictionaryPasswordTestCase(test_base.BaseTestCase):
self.assertEqual(expected,
strutils.mask_dict_password(payload))
+ payload = {'passwords': {'KeystoneFernetKey1': 'c5FijjS'}}
+ expected = {'passwords': {'KeystoneFernetKey1': '***'}}
+ self.assertEqual(expected,
+ strutils.mask_dict_password(payload))
+
+ payload = {'passwords': {'keystonecredential0': 'c5FijjS'}}
+ expected = {'passwords': {'keystonecredential0': '***'}}
+ self.assertEqual(expected,
+ strutils.mask_dict_password(payload))
+
def test_do_no_harm(self):
payload = {}
expected = {}
diff --git a/releasenotes/notes/mask-dict-passwords-99357ffb7972fb0b.yaml b/releasenotes/notes/mask-dict-passwords-99357ffb7972fb0b.yaml
new file mode 100644
index 0000000..6303534
--- /dev/null
+++ b/releasenotes/notes/mask-dict-passwords-99357ffb7972fb0b.yaml
@@ -0,0 +1,9 @@
+---
+security:
+ - |
+ This patch ensures that we mask sensitive data when masking dicts, even if
+ the case doesn't match. This means the behaviour of mask_password and
+ mask_dict_password is now the same.
+ - |
+ Additional password names were included from real world logs that contained
+ sensitive information. \ No newline at end of file
diff --git a/releasenotes/notes/mask-password-patterns-f41524069b8ae488.yaml b/releasenotes/notes/mask-password-patterns-f41524069b8ae488.yaml
new file mode 100644
index 0000000..d9a455a
--- /dev/null
+++ b/releasenotes/notes/mask-password-patterns-f41524069b8ae488.yaml
@@ -0,0 +1,8 @@
+---
+security:
+ - This patch ensures we actually mask sensitive data, even if case doesn't
+ match the static entry we have in the patterns.
+ - It also ensures that some fancy names with a common base, but added
+ number are actually taken care of.
+fixes:
+ - https://bugs.launchpad.net/tripleo/+bug/1850843
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 261c648..7d4741d 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -57,7 +57,6 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
-project = u'oslo.utils Release Notes'
copyright = u'2016, oslo.utils Developers'
# Release notes do not need a version in the title, they span
@@ -146,10 +145,6 @@ html_static_path = ['_static']
# directly to the root of the documentation.
# html_extra_path = []
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-html_last_updated_fmt = '%Y-%m-%d %H:%M'
-
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
@@ -193,17 +188,6 @@ htmlhelp_basename = 'oslo.utilsReleaseNotesDoc'
# -- Options for LaTeX output ---------------------------------------------
-latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- # 'papersize': 'letterpaper',
-
- # The font size ('10pt', '11pt' or '12pt').
- # 'pointsize': '10pt',
-
- # Additional stuff for the LaTeX preamble.
- # 'preamble': '',
-}
-
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
diff --git a/tox.ini b/tox.ini
index b4e4e41..68f7b83 100644
--- a/tox.ini
+++ b/tox.ini
@@ -30,7 +30,7 @@ whitelist_externals = rm
deps = -r{toxinidir}/doc/requirements.txt
commands =
rm -fr doc/build
- sphinx-build -W -b html doc/source doc/build/html
+ sphinx-build -W --keep-going -b html doc/source doc/build/html
[testenv:cover]
basepython = python3
@@ -48,8 +48,11 @@ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py
[testenv:releasenotes]
basepython = python3
+whitelist_externals = rm
deps = -r{toxinidir}/doc/requirements.txt
-commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+commands =
+ rm -rf releasenotes/build
+ sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html
[testenv:lower-constraints]
basepython = python3