Merge pull request #1104 from ryanpetrello/fix-1101

properly sanitize module arguments with no_log (like uri:password)
This commit is contained in:
Ryan Petrello 2018-03-26 11:57:28 -04:00 committed by GitHub
commit 7842b67bea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 43 additions and 3 deletions

View File

@ -36,6 +36,8 @@ from .events import event_context
from .minimal import CallbackModule as MinimalCallbackModule
CENSORED = "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa
OMIT = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
class BaseCallbackModule(CallbackBase):
@ -68,6 +70,7 @@ class BaseCallbackModule(CallbackBase):
@contextlib.contextmanager
def capture_event_data(self, event, **event_data):
censored_task_args = []
event_data.setdefault('uuid', str(uuid.uuid4()))
if event not in self.EVENTS_WITHOUT_TASK:
@ -76,6 +79,14 @@ class BaseCallbackModule(CallbackBase):
task = None
if event_data.get('res'):
invocation = event_data['res'].get('invocation')
if invocation:
module_args = invocation.get('module_args')
sensitive_module_args = set([
k for k, v in module_args.items()
if v == OMIT
])
censored_task_args = sensitive_module_args.intersection(task.args.keys())
if event_data['res'].get('_ansible_no_log', False):
event_data['res'] = {'censored': CENSORED}
if event_data['res'].get('results', []):
@ -88,7 +99,7 @@ class BaseCallbackModule(CallbackBase):
try:
event_context.add_local(event=event, **event_data)
if task:
self.set_task(task, local=True)
self.set_task(task, local=True, censored_task_args=censored_task_args)
event_context.dump_begin(sys.stdout)
yield
finally:
@ -120,7 +131,8 @@ class BaseCallbackModule(CallbackBase):
event_context.remove_global(play=None, play_uuid=None, play_pattern=None)
self.clear_task()
def set_task(self, task, local=False):
def set_task(self, task, local=False, censored_task_args=None):
censored_task_args = censored_task_args or []
# FIXME: Task is "global" unless using free strategy!
task_ctx = dict(
task=(task.name or task.action),
@ -134,7 +146,10 @@ class BaseCallbackModule(CallbackBase):
if task.no_log:
task_ctx['task_args'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
else:
task_args = ', '.join(('%s=%s' % a for a in task.args.items()))
task_args = ', '.join((
'%s=%s' % (k, CENSORED if k in censored_task_args else v)
for k, v in task.args.items()
))
task_ctx['task_args'] = task_args
if getattr(task, '_role', None):
task_role = task._role._role_name

View File

@ -279,3 +279,28 @@ def test_callback_plugin_saves_custom_stats(executor, cache, playbook):
assert json.load(f) == {'foo': 'bar'}
finally:
shutil.rmtree(os.path.join(private_data_dir))
@pytest.mark.parametrize('playbook', [
{'no_log_module_with_var.yml': '''
- name: ensure that module-level secrets are redacted
connection: local
hosts: all
vars:
- pw: SENSITIVE
tasks:
- uri:
url: https://example.org
user: john-jacob-jingleheimer-schmidt
password: "{{ pw }}"
'''}, # noqa
])
def test_module_level_no_log(executor, cache, playbook):
# https://github.com/ansible/tower/issues/1101
# It's possible for `no_log=True` to be defined at the _module_ level,
# e.g., for the URI module password parameter
# This test ensures that we properly redact those
executor.run()
assert len(cache)
assert 'john-jacob-jingleheimer-schmidt' in json.dumps(cache.items())
assert 'SENSITIVE' not in json.dumps(cache.items())