no_log mask suboption fallback values and defaults CVE-2021-20228 (#73487)
* no_log mask suboption fallback values and defaults * Added changelog * Remove lambda expression
This commit is contained in:
parent
4315e18807
commit
0cdc410dce
6 changed files with 119 additions and 11 deletions
2
changelogs/fragments/no_log-fallback.yml
Normal file
2
changelogs/fragments/no_log-fallback.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
security_fixes:
|
||||||
|
- '**security issue** - Mask default and fallback values for ``no_log`` module options (CVE-2021-20228)'
|
|
@ -714,6 +714,9 @@ class AnsibleModule(object):
|
||||||
if k not in self.argument_spec:
|
if k not in self.argument_spec:
|
||||||
self.argument_spec[k] = v
|
self.argument_spec[k] = v
|
||||||
|
|
||||||
|
# Save parameter values that should never be logged
|
||||||
|
self.no_log_values = set()
|
||||||
|
|
||||||
self._load_params()
|
self._load_params()
|
||||||
self._set_fallbacks()
|
self._set_fallbacks()
|
||||||
|
|
||||||
|
@ -725,8 +728,6 @@ class AnsibleModule(object):
|
||||||
print('\n{"failed": true, "msg": "Module alias error: %s"}' % to_native(e))
|
print('\n{"failed": true, "msg": "Module alias error: %s"}' % to_native(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Save parameter values that should never be logged
|
|
||||||
self.no_log_values = set()
|
|
||||||
self._handle_no_log_values()
|
self._handle_no_log_values()
|
||||||
|
|
||||||
# check the locale as set by the current environment, and reset to
|
# check the locale as set by the current environment, and reset to
|
||||||
|
@ -1931,13 +1932,14 @@ class AnsibleModule(object):
|
||||||
param = self.params
|
param = self.params
|
||||||
for (k, v) in spec.items():
|
for (k, v) in spec.items():
|
||||||
default = v.get('default', None)
|
default = v.get('default', None)
|
||||||
if pre is True:
|
|
||||||
# this prevents setting defaults on required items
|
# This prevents setting defaults on required items on the 1st run,
|
||||||
if default is not None and k not in param:
|
# otherwise will set things without a default to None on the 2nd.
|
||||||
param[k] = default
|
if k not in param and (default is not None or not pre):
|
||||||
else:
|
# Make sure any default value for no_log fields are masked.
|
||||||
# make sure things without a default still get set None
|
if v.get('no_log', False) and default:
|
||||||
if k not in param:
|
self.no_log_values.add(default)
|
||||||
|
|
||||||
param[k] = default
|
param[k] = default
|
||||||
|
|
||||||
def _set_fallbacks(self, spec=None, param=None):
|
def _set_fallbacks(self, spec=None, param=None):
|
||||||
|
@ -1958,9 +1960,13 @@ class AnsibleModule(object):
|
||||||
else:
|
else:
|
||||||
fallback_args = item
|
fallback_args = item
|
||||||
try:
|
try:
|
||||||
param[k] = fallback_strategy(*fallback_args, **fallback_kwargs)
|
fallback_value = fallback_strategy(*fallback_args, **fallback_kwargs)
|
||||||
except AnsibleFallbackNotFound:
|
except AnsibleFallbackNotFound:
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
if v.get('no_log', False) and fallback_value:
|
||||||
|
self.no_log_values.add(fallback_value)
|
||||||
|
param[k] = fallback_value
|
||||||
|
|
||||||
def _load_params(self):
|
def _load_params(self):
|
||||||
''' read the input and set the params attribute.
|
''' read the input and set the params attribute.
|
||||||
|
|
31
test/integration/targets/module_utils/callback/pure_json.py
Normal file
31
test/integration/targets/module_utils/callback/pure_json.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# (c) 2021 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
name: pure_json
|
||||||
|
type: stdout
|
||||||
|
short_description: only outputs the module results as json
|
||||||
|
'''
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackModule(CallbackBase):
|
||||||
|
|
||||||
|
CALLBACK_VERSION = 2.0
|
||||||
|
CALLBACK_TYPE = 'stdout'
|
||||||
|
CALLBACK_NAME = 'pure_json'
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||||
|
self._display.display(json.dumps(result._result))
|
||||||
|
|
||||||
|
def v2_runner_on_ok(self, result):
|
||||||
|
self._display.display(json.dumps(result._result))
|
||||||
|
|
||||||
|
def v2_runner_on_skipped(self, result):
|
||||||
|
self._display.display(json.dumps(result._result))
|
35
test/integration/targets/module_utils/library/test_no_log.py
Normal file
35
test/integration/targets/module_utils/library/test_no_log.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# (c) 2021 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
explicit_pass=dict(type='str', no_log=True),
|
||||||
|
fallback_pass=dict(type='str', no_log=True, fallback=(env_fallback, ['SECRET_ENV'])),
|
||||||
|
default_pass=dict(type='str', no_log=True, default='zyx'),
|
||||||
|
normal=dict(type='str', default='plaintext'),
|
||||||
|
suboption=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
explicit_sub_pass=dict(type='str', no_log=True),
|
||||||
|
fallback_sub_pass=dict(type='str', no_log=True, fallback=(env_fallback, ['SECRET_SUB_ENV'])),
|
||||||
|
default_sub_pass=dict(type='str', no_log=True, default='xvu'),
|
||||||
|
normal=dict(type='str', default='plaintext'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exit_json(changed=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,9 @@
|
||||||
|
# This is called by module_utils_vvvvv.yml with a custom callback
|
||||||
|
- hosts: testhost
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Check no_log invocation results
|
||||||
|
test_no_log:
|
||||||
|
explicit_pass: abc
|
||||||
|
suboption:
|
||||||
|
explicit_sub_pass: def
|
|
@ -3,3 +3,28 @@
|
||||||
tasks:
|
tasks:
|
||||||
- name: Use a specially crafted module to see if things were imported correctly
|
- name: Use a specially crafted module to see if things were imported correctly
|
||||||
test:
|
test:
|
||||||
|
|
||||||
|
# Invocation usually is output with 3vs or more, our callback plugin displays it anyway
|
||||||
|
- name: Check no_log invocation results
|
||||||
|
command: ansible-playbook -i {{ inventory_file }} module_utils_test_no_log.yml
|
||||||
|
environment:
|
||||||
|
ANSIBLE_CALLBACK_PLUGINS: callback
|
||||||
|
ANSIBLE_STDOUT_CALLBACK: pure_json
|
||||||
|
SECRET_ENV: ghi
|
||||||
|
SECRET_SUB_ENV: jkl
|
||||||
|
register: no_log_invocation
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
no_log_invocation: '{{ no_log_invocation.stdout | trim | from_json }}'
|
||||||
|
|
||||||
|
- name: check no log values from fallback or default are masked
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- no_log_invocation.invocation.module_args.default_pass == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||||
|
- no_log_invocation.invocation.module_args.explicit_pass == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||||
|
- no_log_invocation.invocation.module_args.fallback_pass == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||||
|
- no_log_invocation.invocation.module_args.normal == 'plaintext'
|
||||||
|
- no_log_invocation.invocation.module_args.suboption.default_sub_pass == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||||
|
- no_log_invocation.invocation.module_args.suboption.explicit_sub_pass == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||||
|
- no_log_invocation.invocation.module_args.suboption.fallback_sub_pass == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||||
|
- no_log_invocation.invocation.module_args.suboption.normal == 'plaintext'
|
||||||
|
|
Loading…
Reference in a new issue