1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Re-allow templating of complex_args, but count params to prevent injection

Fixes #8810
This commit is contained in:
James Cammarata 2014-09-04 16:00:02 -05:00
parent 8bafc646cb
commit 8cc3543918
4 changed files with 45 additions and 21 deletions

View file

@ -757,11 +757,6 @@ class Play(object):
# ************************************************* # *************************************************
def _has_vars_in(self, msg):
return "$" in msg or "{{" in msg
# *************************************************
def _update_vars_files_for_host(self, host, vault_password=None): def _update_vars_files_for_host(self, host, vault_password=None):
def generate_filenames(host, inject, filename): def generate_filenames(host, inject, filename):
@ -782,7 +777,7 @@ class Play(object):
# filename4 is the dwim'd path, but may also be mixed-scope, so we use # filename4 is the dwim'd path, but may also be mixed-scope, so we use
# both play scoped vars and host scoped vars to template the filepath # both play scoped vars and host scoped vars to template the filepath
if self._has_vars_in(filename3) and host is not None: if utils.contains_vars(filename3) and host is not None:
inject.update(self.vars) inject.update(self.vars)
filename4 = template(self.basedir, filename3, inject) filename4 = template(self.basedir, filename3, inject)
filename4 = utils.path_dwim(self.basedir, filename4) filename4 = utils.path_dwim(self.basedir, filename4)
@ -810,8 +805,8 @@ class Play(object):
raise errors.AnsibleError("%s must be stored as a dictionary/hash" % filename4) raise errors.AnsibleError("%s must be stored as a dictionary/hash" % filename4)
if host is not None: if host is not None:
target_filename = None target_filename = None
if self._has_vars_in(filename2): if utils.contains_vars(filename2):
if not self._has_vars_in(filename3): if not utils.contains_vars(filename3):
target_filename = filename3 target_filename = filename3
else: else:
target_filename = filename4 target_filename = filename4
@ -860,7 +855,7 @@ class Play(object):
else: else:
# just one filename supplied, load it! # just one filename supplied, load it!
filename2, filename3, filename4 = generate_filenames(host, inject, filename) filename2, filename3, filename4 = generate_filenames(host, inject, filename)
if self._has_vars_in(filename4): if utils.contains_vars(filename4):
continue continue
if process_files(filename, filename2, filename3, filename4, host=host): if process_files(filename, filename2, filename3, filename4, host=host):
processed.append(filename) processed.append(filename)

View file

@ -226,10 +226,6 @@ class Runner(object):
# changed later via options like accelerate # changed later via options like accelerate
self.original_transport = self.transport self.original_transport = self.transport
# enforce complex_args as a dict
if type(self.complex_args) != dict:
raise errors.AnsibleError("args must be a dictionary, received %s (%s)" % (self.complex_args, type(self.complex_args)))
# misc housekeeping # misc housekeeping
if subset and self.inventory._subset is None: if subset and self.inventory._subset is None:
# don't override subset when passed from playbook # don't override subset when passed from playbook
@ -677,11 +673,37 @@ class Runner(object):
inject['item'] = ",".join(use_these_items) inject['item'] = ",".join(use_these_items)
items = None items = None
# logic to replace complex args if possible def _safe_template_complex_args(args, inject):
complex_args = self.complex_args # Ensure the complex args here are a dictionary, but
# first template them if they contain a variable
returned_args = args
if isinstance(args, basestring):
# If the complex_args were evaluated to a dictionary and there are
# more keys in the templated version than the evaled version, some
# param inserted additional keys (the template() call also runs
# safe_eval on the var if it looks like it's a datastructure). If the
# evaled_args are not a dict, it's most likely a whole variable (ie.
# args: {{var}}), in which case there's no way to detect the proper
# count of params in the dictionary.
templated_args = template.template(self.basedir, args, inject, convert_bare=True)
evaled_args = utils.safe_eval(args)
if isinstance(evaled_args, dict) and len(evaled_args) > 0 and len(evaled_args) != len(templated_args):
raise errors.AnsibleError("a variable tried to insert extra parameters into the args for this task")
# set the returned_args to the templated_args
returned_args = templated_args
# and a final check to make sure the complex args are a dict
if not isinstance(returned_args, dict):
raise errors.AnsibleError("args must be a dictionary, received %s" % returned_args)
return returned_args
# logic to decide how to run things depends on whether with_items is used # logic to decide how to run things depends on whether with_items is used
if items is None: if items is None:
complex_args = _safe_template_complex_args(self.complex_args, inject)
return self._executor_internal_inner(host, self.module_name, self.module_args, inject, port, complex_args=complex_args) return self._executor_internal_inner(host, self.module_name, self.module_args, inject, port, complex_args=complex_args)
elif len(items) > 0: elif len(items) > 0:
@ -700,12 +722,8 @@ class Runner(object):
this_inject = inject.copy() this_inject = inject.copy()
this_inject['item'] = x this_inject['item'] = x
# TODO: this idiom should be replaced with an up-conversion to a Jinja2 template evaluation complex_args = _safe_template_complex_args(self.complex_args, this_inject)
if isinstance(self.complex_args, basestring):
complex_args = template.template(self.basedir, self.complex_args, this_inject, convert_bare=True)
complex_args = utils.safe_eval(complex_args)
if type(complex_args) != dict:
raise errors.AnsibleError("args must be a dictionary, received %s" % complex_args)
result = self._executor_internal_inner( result = self._executor_internal_inner(
host, host,
self.module_name, self.module_name,

View file

@ -1268,6 +1268,12 @@ def list_difference(a, b):
result.append(x) result.append(x)
return result return result
def contains_vars(data):
'''
returns True if the data contains a variable pattern
'''
return "$" in data or "{{" in data
def safe_eval(expr, locals={}, include_exceptions=False): def safe_eval(expr, locals={}, include_exceptions=False):
''' '''
This is intended for allowing things like: This is intended for allowing things like:

View file

@ -518,6 +518,11 @@ class TestUtils(unittest.TestCase):
self.assertEqual(ansible.utils.is_list_of_strings(['foo', 'bar', True]), False) self.assertEqual(ansible.utils.is_list_of_strings(['foo', 'bar', True]), False)
self.assertEqual(ansible.utils.is_list_of_strings(['one', 2, 'three']), False) self.assertEqual(ansible.utils.is_list_of_strings(['one', 2, 'three']), False)
def test_contains_vars(self):
self.assertTrue(ansible.utils.contains_vars('{{foo}}'))
self.assertTrue(ansible.utils.contains_vars('$foo'))
self.assertFalse(ansible.utils.contains_vars('foo'))
def test_safe_eval(self): def test_safe_eval(self):
# Not basestring # Not basestring
self.assertEqual(ansible.utils.safe_eval(len), len) self.assertEqual(ansible.utils.safe_eval(len), len)