Importing variables from remote files

I needed to import different sets of vars (hosted remotely) depending on the playbook at runtime. slurp is too limited and no other in-house solution is flexible enough.

So I wrote a plugin (GitHub - charlesrocket/essential-collection):

- name: Import user variables
  charlesrocket.essential.remote_vars:
    url: https://www.example.com/user_vars.yml

The plugin pulls YAML files from remote locations and injects its content into ansible_facts. It works flawlessly and allows me to do interesting things with this profiling system later. But it feels very hacky and I wonder if theres a better way to achieve this.

1 Like

My initial thought was “why an action plugin and not just a module which returns facts”, and then I saw in the code that you expland templates in the variables, which is a neat idea. I like it.

1 Like

Allowing to evaluate templates provided by a remote system can be dangerous, you just need one lookup (or filter or test) plugin installed on the controller that can be tricked into executing arbitrary commands to give you an remote execution exploit that can be triggered from the remotes on your Ansible controller.

(This actually did happen in ansible-core in the past and go a CVSS 3.x rating of 7.1 High: NVD - CVE-2021-3583, Joshua Makinen | Ansible Template Injection (CVE-2021-3583) Writeup)

4 Likes

Indeed, but my (possibly incorrect) assumption would be that such files are under the control of the playbook “owners”.

Good you point that out explicitly.

@charlesrocket possibly a parameter explicitly permitting templating would be called for so that people have to think about doing so.

- name: Import user variables
  charlesrocket.essential.remote_vars:
    url: https://www.example.com/user_vars.yml
    render: true  # default false

Thanks! I hope I can make it work

O yes, it could cause some trouble, so I tried to limit the usage:

pre_tasks:
    - name: "Activating profile | {{ profile }}"
      charlesrocket.essential.remote_vars:
        url:
          "https://raw.githubusercontent.com/charlesrocket/\
          freebsd-collection/trunk/profiles/{{ profile }}/\
          {{ profile_version | default('station') }}.yml"
      when: profile is defined
      tags: always

What would you recommend in this case? I was thinking profile validator with listst/etc. Storing profiles in the collection would probably improve the case but still, sloppy and does not feel right.

How is this different than using the uri lookup and from_yaml filter?

I don’t remember right now, but probably due jinja2 in profiles not being evaluated at the right time. I remember looking for built-in solutions/roles and jinja2 was a big barrier in all cases.

Evaluation time here would be the same, at task rurntime

Would it work with jinja tho? I could not get any solutions to work with template vars in profiles, not sure why. I’ll try to figure out why when get a chance but I guess its done on purpose (also why anybody would need Slurp if theres built-in module already).

Jinja IS the tempalting, there is no ‘too’, its ONLY Jinja

uri is a lookup plugin whereas slurp is a module. The former are run on the controller, the latter on a node (unless delegated to the controller).

What you might have an issue is that remote data is ‘unsafe’ aka untrusted by default, otherwise you open yourself to having the ‘manager’ aka controller be injected with code from any possibly compromised target, making 1 host compromised into ALL hosts compromised. So if you are bypassing this protection you are creating a CVE we already patched.

Should I move profiles into the collection? But then somebody might slip a PR with a bad profile just as well — not sure how to approach this one.

The more I look at this the more this looks like it should be part of inventory or a vars plugin

You mean rewriting the plugin as vars plugin and adding profiles to it? Is there a way to keep the same workflow or one would have to first turn on vars plugin? Currently I just install ansible with the collection and start the playbook with the profile env var.

I am trying to keep this functional in profiles as well:

doas_config:
  - permit nopass keepenv {{ ansible_user | default(ansible_user_id) }}

Seems like I could rewrite it as vars plugin utilizing wrap_var()

Turned out I can’t call vars plugin the same way so converting is not an option (I should try to activate it in cfg tho but this breaks the original workflow so outcome is completely irrelevant). Docs around plugins are very weak, can’t even find anything about testing. Guess I need to research the inventory approach, but I have a feeling templating will not roll there. And where to store profiles with the inventory approach? Bloating user’s deployment with foreign profiles seems lazy and mean.

Trying to use builtins and jinja2 vars are ignored, as expected

---

- name: Fetch remote variables
  ansible.builtin.uri:
    url: "{{ vars_url }}"
    return_content: true
  register: remote_vars

- name: Include remote variables
  ansible.builtin.set_fact: "{{ item.key }}={{ item.value }}"
  with_dict: "{{ remote_vars.content | from_yaml }}”
m

Tho maybe I could work around this one.