Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent losing unsafe from lookups #77609

Merged
merged 3 commits into from Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/77535-prevent-losing-unsafe-lookups.yml
@@ -0,0 +1,2 @@
bugfixes:
- Prevent losing unsafe on results returned from lookups (https://github.com/ansible/ansible/issues/77535)
12 changes: 9 additions & 3 deletions lib/ansible/template/__init__.py
Expand Up @@ -1080,16 +1080,20 @@ def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=

jvars = AnsibleJ2Vars(self, t.globals)

self.cur_context = new_context = t.new_context(jvars, shared=True)
rf = t.root_render_func(new_context)
# In case this is a recursive call to do_template we need to
# save/restore cur_context to prevent overriding __UNSAFE__.
cached_context = self.cur_context

self.cur_context = t.new_context(jvars, shared=True)
rf = t.root_render_func(self.cur_context)

try:
if not self.jinja2_native and not convert_data:
res = ansible_concat(rf)
else:
res = self.environment.concat(rf)

unsafe = getattr(new_context, 'unsafe', False)
unsafe = getattr(self.cur_context, 'unsafe', False)
if unsafe:
res = wrap_var(res)
except TypeError as te:
Expand All @@ -1100,6 +1104,8 @@ def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=
else:
display.debug("failing because of a type error, template data is: %s" % to_text(data))
raise AnsibleError("Unexpected templating type error occurred on (%s): %s" % (to_native(data), to_native(te)))
finally:
self.cur_context = cached_context

if isinstance(res, string_types) and preserve_trailing_newlines:
# The low level calls above do not preserve the newline
Expand Down
45 changes: 45 additions & 0 deletions test/integration/targets/template/unsafe.yml
Expand Up @@ -17,3 +17,48 @@
that:
- this_always_safe == imunsafe
- imunsafe == this_was_unsafe.strip()


- hosts: localhost
gather_facts: false
vars:
output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}"
tasks:
- set_fact:
unsafe_foo: "{{ lookup('list', var0) }}"
vars:
var0: "{{ var1 }}"
var1:
- unsafe

- assert:
that:
- "{{ unsafe_foo[0] | type_debug == 'AnsibleUnsafeText' }}"

- block:
- copy:
dest: "{{ file_name }}"
content: !unsafe "{{ i_should_not_be_templated }}"

- set_fact:
file_content: "{{ lookup('file', file_name) }}"

- assert:
that:
- not file_content is contains('unsafe')

- set_fact:
file_content: "{{ lookup('file', file_name_tmpl) }}"
vars:
file_name_tmpl: "{{ file_name }}"

- assert:
that:
- not file_content is contains('unsafe')
vars:
file_name: "{{ output_dir }}/unsafe_file"
i_should_not_be_templated: unsafe
always:
- file:
dest: "{{ file_name }}"
state: absent
25 changes: 25 additions & 0 deletions test/units/template/test_templar.py
Expand Up @@ -443,3 +443,28 @@ def test_resolve_none(self):
def test_is_unsafe(self):
context = self._context()
self.assertFalse(context._is_unsafe(AnsibleUndefined()))


def test_unsafe_lookup():
res = Templar(
None,
variables={
'var0': '{{ var1 }}',
'var1': ['unsafe'],
}
).template('{{ lookup("list", var0) }}')
assert getattr(res[0], '__UNSAFE__', False)


def test_unsafe_lookup_no_conversion():
res = Templar(
None,
variables={
'var0': '{{ var1 }}',
'var1': ['unsafe'],
}
).template(
'{{ lookup("list", var0) }}',
convert_data=False,
)
assert getattr(res, '__UNSAFE__', False)