1

I am writing up a playbook that takes a user input to find out if the file exists in the directory.

This is what I have so far

- name: Encrypt file
  hosts: localhost
  connection: local

  vars:
    working_directory: "{{ playbook_dir }}"
    enc_files: []
    my_file: shared_config

  tasks:

    - name: Get all Decrypted .yaml files
      find:
        paths: "{{ working_directory }}"
        patterns: '*.yaml'
        recurse: yes
        excludes: "*.enc.yaml,decrypt.yaml,encrypt_all.yaml,encrypt_file.yaml"
      register: files

    - name: Add Decrypted files to Array
      set_fact:
        enc_files: "{{ enc_files + [item.path | basename] }}"
      loop: "{{ files.files }}"
      no_log: true

    - debug:
        msg: "{{ enc_files }}"
      when: '"{{ my_file | lower }}" in "{{ enc_files | lower }}"'

What I can't seem to get to work is that it it finds if the file by name, not extension exists. If it does, I want to return the file with extension to do things with it.

Here is my current tree:

├── README.md
├── database
│   └── postgres_config.enc.yaml
├── decrypt.yaml
├── encrypt_all.yaml
├── encrypt_file.yaml
├── infra
│   ├── infra_config.enc.yaml
│   └── infra_config.yaml
├── middleware
│   ├── middleware_config.enc.yaml
│   └── middleware_config.yaml
├── services
│   ├── log_service_config.enc.yaml
│   ├── log_service_config.yaml
│
├── shared
│   ├── shared_config.enc.yaml
│   └── shared_config.yaml

What I want to do is have the user input either shared_config or shared_config.yaml and return shared_config.yaml so I can encrypt that file. I am also trying to figure out a way they can pass shared config in their input (as well as any of the other possible inputs, but I can try to figure that out on my own later).

1 Answer 1

1

I think I wouldn't approach it this way (e.g. list all files and then find if the user input matches a files).

I would probably rather approach it the other way around: get the input of the user and then assert that the corresponding file does exists.

Then you are just left with playing with Jinja filter and Python string manipulation to transform the user input in the expected path.


Below is a proposed playbook, with upside and pitfalls.

Upside:

  • It will work with multiple chunk of path separated by spaces some path will give some/some_path.yaml
  • It should be robust enough to correct erroneous multiple spaces: some path will give some/some_path.yaml
  • It also accepts underscores some_path will give some/some_path.yaml
  • It should be robust enough to correct erroneous multiple underscore: some___path will give some/some_path.yaml
  • As well as a mix of both underscore and spaces some path_here multispace___multiunderscore will give some/some_path_here_multispace_multiunderscore.yaml
  • It works with or without extension specified in the user input
  • It should give a bit of resilience against a transversal path attack

Pitfall:

  • But it won't cope with a space between the last word and the extension, as it will bring an underscore too much: some path .yaml will give some/some_path_.yaml

And, so, here is the said playbook:

- hosts: all
  gather_facts: no

  vars_prompt:
    - name: file_name
      prompt: Which decrypted file do you need?
      private: no

  pre_tasks:
    - set_fact:
        fqn: "{{ chunk.0 ~ '/' ~ chunk | join('_') if chunk | length > 1 else chunk | join('_') }}"
      vars:
        chunk: "{{ ((file if file.endswith('.yaml') else file ~ '.yaml')  | replace('_',' ')).split() }}"
        file: "{{ file_name | trim }}"

    - assert:
        that:
          - fqn is file  
          - not fqn.startswith('.') # I am just trying to limit transversal path attack here
          - not fqn.startswith('/') # I am just trying to limit transversal path attack here
          - "'enc' != fqn.split('.')[-2]" # this one is a protection against accessing the encrypted files
        msg: "{{ fqn }} is not a file"          

  tasks:
    - debug:
        msg: "Now, do whatever you like best with the file `{{ fqn | basename }}` at `{{ fqn }}` because I am sure it exists"

Here is two examples of running it:

  • With a correct user input:
    Which decrypted file do you need?: middleware config
    
    PLAY [all] *********************************************************************
    
    TASK [set_fact] ****************************************************************
    ok: [localhost]
    
    TASK [assert] ******************************************************************
    ok: [localhost] => changed=false 
      msg: All assertions passed
    
    TASK [debug] *******************************************************************
    ok: [localhost] => 
      msg: Now, do whatever you like best with the file `middleware_config.yaml` at `middleware/middleware_config.yaml` because I am sure it exists
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    
  • With an erroneous user input:
    Which decrypted file do you need?: middleware config
    
    PLAY [all] *********************************************************************
    
    TASK [set_fact] ****************************************************************
    ok: [localhost]
    
    TASK [assert] ******************************************************************
    fatal: [localhost]: FAILED! => changed=false 
      assertion: fqn is file
      evaluated_to: false
      msg: fake/fake_config.yaml is not a file
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
    
Sign up to request clarification or add additional context in comments.

8 Comments

This helps signficantly, I really appreciate the help here. I have some research to do on some of the things you did here. How can I update it to return the file name though instead of dir/filename.yaml Also is there a way we can make it match any file type except for .enc.extension? Example: match .txt match .yaml do not match enc.txt do not match .enc.yaml
The path/path_to_file is reconstructed there: fqn. If you don't like/want it, you could just go: fqn: "{{ chunk | join('_') }}"
For the extension, build that in the assertions list e.g. - "'.enc.' not in fqn" or - "fqn.split('.')[-2] != 'enc'"
Also a neat trick to go back to a file with a /path/to/file in Ansible is the basename filter. e.g. {{ 'middleware/middleware_config.yaml | basename }}` would give you middleware_config.yaml
^--- all this is now in the edited version
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.