Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mask rendered data (in logs) #48291

Merged
merged 9 commits into from
Jun 25, 2018
Merged
1 change: 1 addition & 0 deletions salt/roster/flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def targets(tgt, tgt_type='glob', **kwargs):
__opts__['renderer'],
__opts__['renderer_blacklist'],
__opts__['renderer_whitelist'],
mask_value='passw*',
**kwargs)
conditioned_raw = {}
for minion in raw:
Expand Down
15 changes: 11 additions & 4 deletions salt/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import salt.utils.files
import salt.utils.stringio
import salt.utils.versions
import salt.utils.sanitizers

# Import 3rd-party libs
from salt.ext import six
Expand Down Expand Up @@ -43,6 +44,13 @@ def compile_template(template,
'''
Take the path to a template and return the high data structure
derived from the template.

Helpers:

:param mask_value:
Mask value for debugging purposes (prevent sensitive information etc)
example: "mask_value="pass*". All "passwd", "password", "pass" will
be masked (as text).
'''

# if any error occurs, we return an empty dictionary
Expand Down Expand Up @@ -107,10 +115,9 @@ def compile_template(template,
# yaml, mako, or another engine which renders to a data
# structure) we don't want to log this.
if salt.utils.stringio.is_readable(ret):
log.debug(
'Rendered data from file: %s:\n%s',
template,
salt.utils.data.decode(ret.read())) # pylint: disable=no-member
log.debug('Rendered data from file: %s:\n%s', template,
salt.utils.sanitizers.mask_args_value(salt.utils.data.decode(ret.read()),
kwargs.get('mask_value'))) # pylint: disable=no-member
ret.seek(0) # pylint: disable=no-member

# Preserve newlines from original template
Expand Down
32 changes: 32 additions & 0 deletions salt/utils/sanitizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from __future__ import absolute_import, print_function, unicode_literals
import re
import os.path
import fnmatch

# Import Salt libs
from salt.ext import six
Expand Down Expand Up @@ -62,3 +63,34 @@ def hostname(value):


clean = InputSanitizer()


def mask_args_value(data, mask):
'''
Mask a line in the data, which matches "mask".

In case you want to put to the logs rosters or other data,
but you certainly do not want to put there an actual IP address,
passwords, user names etc.

Note, this is working only when data is a single string,
ready for print or dump to the log. Also, when the data is formatted
as "key: value" in YAML syntax.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reword the above two paragraphs like this?

    This can be used for cases where keys in your roster file may contain
    sensitive data such as IP addresses, passwords, user names, etc.

    Note that this works only when ``data`` is a single string (i.e. when the
    data in the roster is formatted as ``key: value`` pairs in YAML syntax).


:param data: String data, already rendered.
:param mask: Mask that matches a single line

:return:
'''
if not mask:
return data

out = []
for line in data.split(os.linesep):
if fnmatch.fnmatch(line.strip(), mask) and ':' in line:
key, value = line.split(':', 1)
out.append('{}: {}'.format(key.strip(), '** hidden **'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a precaution, key.strip() should probably be salt.utils.stringutils.to_unicode(key.strip()), to prevent a UnicodeDecodeError if key happens to be a str type with non-ascii unicode in it.

else:
out.append(line)

return '\n'.join(out)
18 changes: 17 additions & 1 deletion tests/unit/utils/test_sanitizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from salt.ext.six import text_type as text

# Import Salt Libs
from salt.utils.sanitizers import clean
from salt.utils.sanitizers import clean, mask_args_value

# Import Salt Testing Libs
from tests.support.unit import TestCase, skipIf
Expand Down Expand Up @@ -47,3 +47,19 @@ def test_sanitized_hostname(self):
assert response == 'somedubioushostname'

test_sanitized_id = test_sanitized_hostname

def test_value_masked(self):
'''
Test if the values are masked.
:return:
'''
out = mask_args_value('quantum: fluctuations', 'quant*')
assert out == 'quantum: ** hidden **'

def test_value_not_masked(self):
'''
Test if the values are not masked.
:return:
'''
out = mask_args_value('quantum fluctuations', 'quant*')
assert out == 'quantum fluctuations'