how do I use an ansible string variable that contains jinja delimiters?

Viewed 302

this

- name: ugly
  hosts: localhost
  vars:
    badstr: "asdf{%jkl"
    mydir: "."
    mydict:
      filea:
        Value:  "blue!42!"
      fileb:
        Value:  "a{%isbad"
  tasks:
    - copy:
        dest: "{{ item.key }}"
        content: "{{ item.value.Value }}"
      loop: "{{ mydict|default({})|dict2items }}"

gives me this error:

fatal: [localhost]: FAILED! => {"msg": "An unhandled exception occurred while templating 'asdf{%jkl'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: Encountered unknown tag 'jkl'.. String: asdf{%jkl"}

The 'mydict' structure is returned from a plugin and I do not get to define the members. One of the 'Value's contains a "{%". Any reference to it will cause an error, whether as a variable, file content or in a template.

I have tried all kinds of quoting and combinations of unsafe, {{, %raw, etc. It either gives me the error or puts the name of the variable in the file.

How can I write the value to a file? Or just use it as a variable?

Ansible 2.8.4 on MacOS 11.3, also ansible 2.9 on RHEL 7.

2 Answers

You can use !unsafe for the variables expected to have these chars. Check this documentation. when !unsafe is used, the string/variable will never get templated.

- name: ugly
  hosts: localhost
  vars:
    badstr: !unsafe "asdf{%jkl"
    mydir: "."
    mydict:
      filea:
        Value:  !unsafe "blue!42!"
      fileb:
        Value: !unsafe  "a{%isbad"
  tasks:
    - copy:
        dest: "{{ item.key }}"
        content: "{{ item.value.Value }}"
      loop: "{{ mydict|default({})|dict2items }}"

When handling values returned by lookup plugins, Ansible uses a data type called unsafe to block templating. Marking data as unsafe prevents malicious users from abusing Jinja2 templates to execute arbitrary code on target machines. The Ansible implementation ensures that unsafe values are never templated. It is more comprehensive than escaping Jinja2 with {% raw %} ... {% endraw %} tags.

You can use the same unsafe data type in variables you define, to prevent templating errors and information disclosure. You can mark values supplied by vars_prompts as unsafe. You can also use unsafe in playbooks. The most common use cases include passwords that allow special characters like { or %, and JSON arguments that look like templates but should not be templated. For example:

---
mypassword: !unsafe 234%234{435lkj{{lkjsdf

The problem here is not in the copy task where the values are evaluated; the problem is how they are being set. For example, if I create a simple ansible module named example.sh that looks like this:

#!/bin/sh

cat <<EOF
{
    "files": {
    "filea": {
      "Value": "blue!42!"
    },
    "fileb": {
      "Value": "a{%isbad"
    }
    }
}
EOF

I can write a playbook like this:

- name: ugly
  hosts: localhost
  tasks:
    - example:
      register: mydict

    - copy:
        dest: "{{ item.key }}"
        content: "{{ item.value.Value }}"
      loop: "{{ mydict.files|dict2items }}"

And this runs as expected, creating without any errors a file fileb with the content:

a{%isbad

Similarly, if I read the data from a JSON file and pass it through from_json, it also works fine:

- name: ugly
  hosts: localhost
  tasks:
    - set_fact:
        mydict: "{{ lookup('file', 'data.json')|from_json }}"

    - copy:
        dest: "{{ item.key }}"
        content: "{{ item.value.Value }}"
      loop: "{{ mydict.files|dict2items }}"

The problem only happens if you define the variables in a context in which Ansible is looking for Jinja templating -- so, as the values of variables in a playbook, a vars file, the arguments to set_fact, etc.

You can potentially work around the problem by changing how you are consuming these values.

Related