From cdc6c5208ec018ad48bc9281bb2621deec596113 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Fri, 25 Sep 2015 14:54:20 -0400 Subject: [PATCH] Clean string data run through the template engine Also strip UnsafeProxy off of low level srings and objects to ensure they don't cause issues later down the road Fixes #12513 --- lib/ansible/plugins/strategy/__init__.py | 30 ++---------- lib/ansible/template/__init__.py | 59 ++++++++++++++++++++++-- lib/ansible/vars/unsafe_proxy.py | 24 ++++++++++ 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 77caba8152..5c8d790095 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -37,7 +37,7 @@ from ansible.playbook.included_file import IncludedFile from ansible.playbook.role import hash_params from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader from ansible.template import Templar -from ansible.vars.unsafe_proxy import UnsafeProxy +from ansible.vars.unsafe_proxy import wrap_var try: from __main__ import display @@ -244,31 +244,8 @@ class StrategyBase: # the variable goes in the fact_cache host = result[1] var_name = result[2] - var_value = result[3] + var_value = wrap_var(result[3]) - def _wrap_var(v): - if isinstance(v, dict): - v = _wrap_dict(v) - elif isinstance(v, list): - v = _wrap_list(v) - else: - if v is not None and not isinstance(v, UnsafeProxy): - v = UnsafeProxy(v) - return v - - def _wrap_dict(v): - for k in v.keys(): - if v[k] is not None and not isinstance(v[k], UnsafeProxy): - v[k] = _wrap_var(v[k]) - return v - - def _wrap_list(v): - for idx, item in enumerate(v): - if item is not None and not isinstance(item, UnsafeProxy): - v[idx] = _wrap_var(item) - return v - - var_value = _wrap_var(var_value) self._variable_manager.set_nonpersistent_facts(host, {var_name: var_value}) elif result[0] in ('set_host_var', 'set_host_facts'): @@ -291,7 +268,8 @@ class StrategyBase: if result[0] == 'set_host_var': var_name = result[4] - var_value = result[5] + var_value = wrap_var(result[5]) + self._variable_manager.set_host_variable(target_host, var_name, var_value) elif result[0] == 'set_host_facts': facts = result[4] diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index 20287e078c..ca0e72ede4 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -19,7 +19,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import StringIO import ast +import contextlib +import os import re from six import string_types, text_type, binary_type @@ -49,6 +52,7 @@ NON_TEMPLATED_TYPES = ( bool, Number ) JINJA2_OVERRIDE = '#jinja2:' + def _escape_backslashes(data, jinja_env): """Double backslashes within jinja2 expressions @@ -108,6 +112,7 @@ def _count_newlines_from_end(in_str): # Uncommon cases: zero length string and string containing only newlines return i + class Templar: ''' The main class for templating, with the main entry-point of template(). @@ -148,6 +153,12 @@ class Templar: self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string)) + self.block_start = self.environment.block_start_string + self.block_end = self.environment.block_end_string + self.variable_start = self.environment.variable_start_string + self.variable_end = self.environment.variable_end_string + self._clean_regex = re.compile(r'(?:%s[%s%s]|[%s%s]%s)' % (self.variable_start[0], self.variable_start[1], self.block_start[1], self.block_end[0], self.variable_end[0], self.variable_end[1])) + def _get_filters(self): ''' Returns filter plugins, after loading and caching them if need be @@ -197,6 +208,47 @@ class Templar: return jinja_exts + def _clean_data(self, orig_data): + ''' remove jinja2 template tags from a string ''' + + if not isinstance(orig_data, string_types): + return orig_data + + with contextlib.closing(StringIO.StringIO(orig_data)) as data: + # these variables keep track of opening block locations, as we only + # want to replace matched pairs of print/block tags + print_openings = [] + block_openings = [] + for mo in self._clean_regex.finditer(orig_data): + token = mo.group(0) + token_start = mo.start(0) + + if token[0] == self.variable_start[0]: + if token == self.block_start: + block_openings.append(token_start) + elif token == self.variable_start: + print_openings.append(token_start) + + elif token[1] == self.variable_end[1]: + prev_idx = None + if token == '%}' and block_openings: + prev_idx = block_openings.pop() + elif token == '}}' and print_openings: + prev_idx = print_openings.pop() + + if prev_idx is not None: + # replace the opening + data.seek(prev_idx, os.SEEK_SET) + data.write('{#') + # replace the closing + data.seek(token_start, os.SEEK_SET) + data.write('#}') + + else: + raise AnsibleError("Error while cleaning data for safety: unhandled regex match") + + return data.getvalue() + def set_available_variables(self, variables): ''' Sets the list of template variables this Templar instance will use @@ -218,11 +270,11 @@ class Templar: # their constituent type. if hasattr(variable, '__UNSAFE__'): if isinstance(variable, text_type): - return text_type(variable) + return self._clean_data(text_type(variable)) elif isinstance(variable, binary_type): - return bytes(variable) + return self._clean_data(bytes(variable)) else: - return variable + return self._clean_data(variable._obj) try: if convert_bare: @@ -258,6 +310,7 @@ class Templar: # FIXME: if the safe_eval raised an error, should we do something with it? pass + #return self._clean_data(result) return result elif isinstance(variable, (list, tuple)): diff --git a/lib/ansible/vars/unsafe_proxy.py b/lib/ansible/vars/unsafe_proxy.py index a69bfb262d..e3e4754c03 100644 --- a/lib/ansible/vars/unsafe_proxy.py +++ b/lib/ansible/vars/unsafe_proxy.py @@ -50,6 +50,8 @@ # http://code.activestate.com/recipes/496741-object-proxying/ # Author: Tomer Filiba +__all__ = ['UnsafeProxy', 'wrap_var'] + class UnsafeProxy(object): __slots__ = ["_obj", "__weakref__"] def __init__(self, obj): @@ -149,3 +151,25 @@ class UnsafeProxy(object): ins = object.__new__(theclass) return ins +def _wrap_dict(v): + for k in v.keys(): + if v[k] is not None and not isinstance(v[k], UnsafeProxy): + v[k] = _wrap_var(v[k]) + return v + +def _wrap_list(v): + for idx, item in enumerate(v): + if item is not None and not isinstance(item, UnsafeProxy): + v[idx] = _wrap_var(item) + return v + +def wrap_var(v): + if isinstance(v, dict): + v = _wrap_dict(v) + elif isinstance(v, list): + v = _wrap_list(v) + else: + if v is not None and not isinstance(v, UnsafeProxy): + v = UnsafeProxy(v) + return v +