Browse code

[stable-2.7] Properly mask no_log values is sub parameters during failure (#63405) (cherry picked from commit 156330b485)

Co-authored-by: Sam Doran <sdoran@redhat.com>

Sam Doran authored on 2019/10/03 07:04:08
Showing 7 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+bugfixes:
1
+  - '**security issue** - properly hide parameters marked with ``no_log`` in suboptions when invalid parameters are passed to the module (CVE-2019-14858)'
... ...
@@ -1665,6 +1665,34 @@ class AnsibleModule(object):
1665 1665
                 if no_log_object:
1666 1666
                     self.no_log_values.update(return_values(no_log_object))
1667 1667
 
1668
+            # Get no_log values from suboptions
1669
+            sub_argument_spec = arg_opts.get('options')
1670
+            if sub_argument_spec is not None:
1671
+                wanted_type = arg_opts.get('type')
1672
+                sub_parameters = param.get(arg_name)
1673
+
1674
+                if sub_parameters is not None:
1675
+                    if wanted_type == 'dict' or (wanted_type == 'list' and arg_opts.get('elements', '') == 'dict'):
1676
+                        # Sub parameters can be a dict or list of dicts. Ensure parameters are always a list.
1677
+                        if not isinstance(sub_parameters, list):
1678
+                            sub_parameters = [sub_parameters]
1679
+
1680
+                        for sub_param in sub_parameters:
1681
+                            # Validate dict fields in case they came in as strings
1682
+
1683
+                            if isinstance(sub_param, string_types):
1684
+                                sub_param = self._check_type_dict(sub_param)
1685
+
1686
+                            try:
1687
+                                if not isinstance(sub_param, Mapping):
1688
+                                    raise TypeError("Value '{1}' in the sub parameter field '{0}' must by a {2}, "
1689
+                                                    "not '{1.__class__.__name__}'".format(arg_name, sub_param, wanted_type))
1690
+                            except TypeError as te:
1691
+                                self.fail_json(msg="Failure when processing no_log parameters. Module invocation will be hidden. "
1692
+                                                   "%s" % to_native(te), invocation={'module_args': 'HIDDEN DUE TO FAILURE'})
1693
+
1694
+                            self._handle_no_log_values(sub_argument_spec, sub_param)
1695
+
1668 1696
             if arg_opts.get('removed_in_version') is not None and arg_name in param:
1669 1697
                 self._deprecations.append({
1670 1698
                     'msg': "Param '%s' is deprecated. See the module docs for more information" % arg_name,
... ...
@@ -2032,7 +2060,6 @@ class AnsibleModule(object):
2032 2032
                     self._set_fallbacks(spec, param)
2033 2033
                     options_aliases = self._handle_aliases(spec, param)
2034 2034
 
2035
-                    self._handle_no_log_values(spec, param)
2036 2035
                     options_legal_inputs = list(spec.keys()) + list(options_aliases.keys())
2037 2036
 
2038 2037
                     self._check_arguments(self.check_invalid_arguments, spec, param, options_legal_inputs)
2039 2038
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+#!/usr/bin/python
1
+# -*- coding: utf-8 -*-
2
+# Copyright (c) 2019 Ansible Project
3
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
+
5
+from __future__ import absolute_import, division, print_function
6
+__metaclass__ = type
7
+
8
+from ansible.module_utils.basic import AnsibleModule
9
+
10
+
11
+def main():
12
+    module = AnsibleModule(
13
+        argument_spec={
14
+            'state': {},
15
+            'secret': {'no_log': True},
16
+            'subopt_dict': {
17
+                'type': 'dict',
18
+                'options': {
19
+                    'str_sub_opt1': {'no_log': True},
20
+                    'str_sub_opt2': {},
21
+                    'nested_subopt': {
22
+                        'type': 'dict',
23
+                        'options': {
24
+                            'n_subopt1': {'no_log': True},
25
+                        }
26
+                    }
27
+                }
28
+            },
29
+            'subopt_list': {
30
+                'type': 'list',
31
+                'elements': 'dict',
32
+                'options': {
33
+                    'subopt1': {'no_log': True},
34
+                    'subopt2': {},
35
+                }
36
+            }
37
+
38
+        }
39
+    )
40
+    module.exit_json(msg='done')
41
+
42
+
43
+if __name__ == '__main__':
44
+    main()
... ...
@@ -47,6 +47,7 @@
47 47
       poll: 1
48 48
       shell: echo "DO_NOT_LOG_ASYNC_TASK_SUCCEEDED"
49 49
       no_log: true
50
+      ignore_errors: yes
50 51
 
51 52
 - name: play-level no_log set
52 53
   hosts: testhost
53 54
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+- name: test no log with suboptions
1
+  hosts: testhost
2
+  gather_facts: no
3
+
4
+  tasks:
5
+    - name: Task with suboptions
6
+      module:
7
+        secret: GLAMOROUS
8
+        subopt_dict:
9
+          str_sub_opt1: AFTERMATH
10
+          str_sub_opt2: otherstring
11
+          nested_subopt:
12
+            n_subopt1: MANPOWER
13
+
14
+        subopt_list:
15
+          - subopt1: UNTAPPED
16
+            subopt2: thridstring
17
+
18
+          - subopt1: CONCERNED
19
+
20
+    - name: Task with suboptions as string
21
+      module:
22
+        secret: MARLIN
23
+        subopt_dict: str_sub_opt1=FLICK
0 24
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+- name: test no log with suboptions
1
+  hosts: testhost
2
+  gather_facts: no
3
+  ignore_errors: yes
4
+
5
+  tasks:
6
+    - name: Task with suboptions and invalid parameter
7
+      module:
8
+        secret: SUPREME
9
+        invalid: param
10
+        subopt_dict:
11
+          str_sub_opt1: IDIOM
12
+          str_sub_opt2: otherstring
13
+          nested_subopt:
14
+            n_subopt1: MOCKUP
15
+
16
+        subopt_list:
17
+          - subopt1: EDUCATED
18
+            subopt2: thridstring
19
+          - subopt1: FOOTREST
20
+
21
+    - name: Task with suboptions as string with invalid parameter
22
+      module:
23
+        secret: FOOTREST
24
+        invalid: param
25
+        subopt_dict: str_sub_opt1=CRAFTY
26
+
27
+    - name: Task with suboptions with dict instead of list
28
+      module:
29
+        secret: FELINE
30
+        subopt_dict:
31
+          str_sub_opt1: CRYSTAL
32
+          str_sub_opt2: otherstring
33
+          nested_subopt:
34
+            n_subopt1: EXPECTANT
35
+        subopt_list:
36
+          foo: bar
37
+
38
+    - name: Task with suboptions with incorrect data type
39
+      module:
40
+        secret: AGROUND
41
+        subopt_dict: 9068.21361
42
+        subopt_list:
43
+          - subopt1: GOLIATH
44
+          - subopt1: FREEFALL
... ...
@@ -13,3 +13,9 @@ set -eux
13 13
 
14 14
 # no log disabled, should produce 0 censored
15 15
 [ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=yes|grep -c 'output has been hidden')" = "0" ]
16
+
17
+# test no log for sub options
18
+[ "$(ansible-playbook no_log_suboptions.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(MANPOWER|UNTAPPED|CONCERNED|MARLIN|FLICK)')" = "0" ]
19
+
20
+# test invalid data passed to a suboption
21
+[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "0" ]