fix a callback bug that causes a task_args leak between job events

see: #5802
This commit is contained in:
Ryan Petrello
2017-03-17 16:55:34 -04:00
parent 7a8d27bd60
commit 6a7743b274
2 changed files with 42 additions and 35 deletions

View File

@@ -166,6 +166,37 @@ def test_callback_plugin_no_log_filters(executor, cache, playbook):
assert 'SENSITIVE' not in json.dumps(cache.items()) assert 'SENSITIVE' not in json.dumps(cache.items())
@pytest.mark.parametrize('playbook', [
{'no_log_on_ok.yml': '''
- name: args should not be logged when task-level no_log is set
connection: local
hosts: all
gather_facts: no
tasks:
- shell: echo "SENSITIVE"
- shell: echo "PRIVATE"
no_log: true
'''}, # noqa
])
def test_callback_plugin_task_args_leak(executor, cache, playbook):
executor.run()
events = cache.values()
assert events[0]['event'] == 'playbook_on_start'
assert events[1]['event'] == 'playbook_on_play_start'
# task 1
assert events[2]['event'] == 'playbook_on_task_start'
assert 'SENSITIVE' in events[2]['event_data']['task_args']
assert events[3]['event'] == 'runner_on_ok'
assert 'SENSITIVE' in events[3]['event_data']['task_args']
# task 2 no_log=True
assert events[4]['event'] == 'playbook_on_task_start'
assert events[4]['event_data']['task_args'] == "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa
assert events[5]['event'] == 'runner_on_ok'
assert events[5]['event_data']['task_args'] == "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa
@pytest.mark.parametrize('playbook', [ @pytest.mark.parametrize('playbook', [
{'strip_env_vars.yml': ''' {'strip_env_vars.yml': '''
- name: sensitive environment variables should be stripped from events - name: sensitive environment variables should be stripped from events

View File

@@ -55,31 +55,10 @@ class BaseCallbackModule(CallbackBase):
'playbook_on_no_hosts_remaining', 'playbook_on_no_hosts_remaining',
] ]
CENSOR_FIELD_WHITELIST = [
'msg',
'failed',
'changed',
'results',
'start',
'end',
'delta',
'cmd',
'_ansible_no_log',
'rc',
'failed_when_result',
'skipped',
'skip_reason',
]
def __init__(self): def __init__(self):
super(BaseCallbackModule, self).__init__() super(BaseCallbackModule, self).__init__()
self.task_uuids = set() self.task_uuids = set()
def censor_result(self, res):
if res.get('_ansible_no_log', False):
return {'censored': "the output has been hidden due to the fact that 'no_log: true' was specified for this result"} # noqa
return res
@contextlib.contextmanager @contextlib.contextmanager
def capture_event_data(self, event, **event_data): def capture_event_data(self, event, **event_data):
@@ -90,6 +69,9 @@ class BaseCallbackModule(CallbackBase):
else: else:
task = None task = None
if event_data.get('res') and event_data['res'].get('_ansible_no_log', False):
event_data['res'] = {'censored': "the output has been hidden due to the fact that 'no_log: true' was specified for this result"} # noqa
with event_context.display_lock: with event_context.display_lock:
try: try:
event_context.add_local(event=event, **event_data) event_context.add_local(event=event, **event_data)
@@ -137,7 +119,9 @@ class BaseCallbackModule(CallbackBase):
task_ctx['task_path'] = task.get_path() task_ctx['task_path'] = task.get_path()
except AttributeError: except AttributeError:
pass pass
if not task.no_log: 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' % a for a in task.args.items()))
task_ctx['task_args'] = task_args task_ctx['task_args'] = task_args
if getattr(task, '_role', None): if getattr(task, '_role', None):
@@ -315,13 +299,11 @@ class BaseCallbackModule(CallbackBase):
if result._task.get_name() == 'setup': if result._task.get_name() == 'setup':
result._result.get('ansible_facts', {}).pop('ansible_env', None) result._result.get('ansible_facts', {}).pop('ansible_env', None)
res = self.censor_result(result._result)
event_data = dict( event_data = dict(
host=result._host.get_name(), host=result._host.get_name(),
remote_addr=result._host.address, remote_addr=result._host.address,
task=result._task, task=result._task,
res=res, res=result._result,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None, event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
) )
with self.capture_event_data('runner_on_ok', **event_data): with self.capture_event_data('runner_on_ok', **event_data):
@@ -329,13 +311,10 @@ class BaseCallbackModule(CallbackBase):
def v2_runner_on_failed(self, result, ignore_errors=False): def v2_runner_on_failed(self, result, ignore_errors=False):
# FIXME: Add verbosity for exception/results output. # FIXME: Add verbosity for exception/results output.
res = self.censor_result(result._result)
event_data = dict( event_data = dict(
host=result._host.get_name(), host=result._host.get_name(),
remote_addr=result._host.address, remote_addr=result._host.address,
res=res, res=result._result,
task=result._task, task=result._task,
ignore_errors=ignore_errors, ignore_errors=ignore_errors,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None, event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
@@ -425,31 +404,28 @@ class BaseCallbackModule(CallbackBase):
super(BaseCallbackModule, self).v2_on_file_diff(result) super(BaseCallbackModule, self).v2_on_file_diff(result)
def v2_runner_item_on_ok(self, result): def v2_runner_item_on_ok(self, result):
res = self.censor_result(result._result)
event_data = dict( event_data = dict(
host=result._host.get_name(), host=result._host.get_name(),
task=result._task, task=result._task,
res=res, res=result._result,
) )
with self.capture_event_data('runner_item_on_ok', **event_data): with self.capture_event_data('runner_item_on_ok', **event_data):
super(BaseCallbackModule, self).v2_runner_item_on_ok(result) super(BaseCallbackModule, self).v2_runner_item_on_ok(result)
def v2_runner_item_on_failed(self, result): def v2_runner_item_on_failed(self, result):
res = self.censor_result(result._result)
event_data = dict( event_data = dict(
host=result._host.get_name(), host=result._host.get_name(),
task=result._task, task=result._task,
res=res, res=result._result,
) )
with self.capture_event_data('runner_item_on_failed', **event_data): with self.capture_event_data('runner_item_on_failed', **event_data):
super(BaseCallbackModule, self).v2_runner_item_on_failed(result) super(BaseCallbackModule, self).v2_runner_item_on_failed(result)
def v2_runner_item_on_skipped(self, result): def v2_runner_item_on_skipped(self, result):
res = self.censor_result(result._result)
event_data = dict( event_data = dict(
host=result._host.get_name(), host=result._host.get_name(),
task=result._task, task=result._task,
res=res, res=result._result,
) )
with self.capture_event_data('runner_item_on_skipped', **event_data): with self.capture_event_data('runner_item_on_skipped', **event_data):
super(BaseCallbackModule, self).v2_runner_item_on_skipped(result) super(BaseCallbackModule, self).v2_runner_item_on_skipped(result)