Search code examples
encryptionansibleansible-vault

How do I properly encrypt a file from inside an Ansible Playbook?


I'm currently using an Ansible playbook to extract and then transfer a configuration backup from some network devices (a basic text file) to an external storage.

I'd like to encrypt the configuration backups before sending them to their final storage. What would be the most adequate way to encrypt a file from inside an Ansible playbook task? To me, the obvious way would be to use the shell module to either call an external encryption tool (openssl) or an ansible-vault command to encrypt the backup in a format that ansible itself can read later in some other context; i.e. one of the two tasks below (simplified):

  - name: Encrypt stuff with OpenSSL using a password read from vault file
    shell:
      cmd: openssl {{ openssl_parameters }} -k {{ vaulted_encryption_password }} -in {{ file_to_encrypt }} -out {{ encrypted_file }}

  - name: Encrypt stuff with Ansible-Vault
    shell:
      cmd: ansible-vault encrypt {{ file_to_encrypt }} --vault-password-file {{ vault_password_file }}

However, none of these solutions seem completely secure, given they require passing the encryption password to an external tool via a shell (which can expose the password to anyone monitoring the processes on the host this runs in, for example) or require writing the plain-text password on a file for ansible-vault to use.

Is there a better way of doing file encryption inside an Ansible task that I'm missing here? (a dedicated module, or some other solution?).


Solution

  • Updated answer valid since ansible 2.12

    The original answer below was one solution until the availability of ansible-core v2.12. Since then, there is a new ansible.builtin.vault filter which make this much easier.

    Here is a complete test (which needs to be hardened for complete security of course...)

    First, we create a secret.txt file we later want to encrypt:

    echo "I'm a file that needs to be encrypted" > secret.txt
    
    

    Then the playbook encrypt.yml:

    ---
    - hosts: localhost
      gather_facts: false
    
      vars_prompt:
        name: vault_secret
        prompt: Please enter the password to encrypt the file
        default: v3rys3cr3t
        private: true
    
      vars:
        vault_file: secret.txt
    
      tasks:
        - name: In-place (re)encrypt file {{ vault_file }}
          copy:
            content: "{{ lookup('ansible.builtin.file', vault_file) | ansible.builtin.vault(vault_secret) }}"
            dest: "{{ vault_file }}"
            decrypt: false
    

    Gives:

    $ ansible-playbook encrypt.yml 
    Please enter the password to encrypt the file [v3rys3cr3t]: 
    
    PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
    
    TASK [In-place (re)encrypt file secret.txt] ********************************************************************************************************************************************************************************************
    changed: [localhost]
    
    PLAY RECAP *****************************************************************************************************************************************************************************************************************************
    localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    

    And we can now check the file was effectively encrypted and still contains the original data

    $ ansible-vault view --ask-vault-pass secret.txt 
    Vault password: 
    I'm a file that needs to be encrypted
    (END)
    

    Note that the above playbook is not idempotent. If you replay the tasks again:

    1. you will have to provide the current vault password to ansible (with --ask-vault-pass or any other supported mechanism) so that the file lookup can read the content.
    2. the file will be changed even if the decrypt and encrypt passwords are identical.

    Previous answer kept for history and still valid for ansible < 2.12

    There are no modules I know to use ansible-vault from playbooks directly (besides the obvious intended use which is to decrypt variables and file contents on the fly).

    One possible way to improve security (as far as listing processes is concerned) with your ansible-vault example through a command would be to use the interactive prompt mode and fill the password with the expect module. An other security layer can be added by adding the no_log: true parameter to the task so it does not print content of the variables.

    Here is a simple example (you will need to pip install pexpect on the target host):

     ---
     - hosts: localhost
       gather_facts: false
     
       tasks:
         - name: Vault encrypt a given file
           vars:
             vault_pass: v3rys3cur3
           expect:
             command: ansible-vault encrypt --ask-vault-pass toto.txt
             responses:
               New Vault password: "{{ vault_pass }}"
               Confirm New Vault password: "{{ vault_pass }}"
    

    Which gives (using the verbose mode to illustrate the no_log feature and provided the given file exist and is not yet encrypted...):

    $ ansible-playbook -v test.yml 
    No config file found; using defaults
    
    PLAY [localhost] **************************************************************************************************************************************************************************************************
    
    TASK [Vault encrypt a given file] *********************************************************************************************************************************************************************************
    changed: [localhost] => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result", "changed": true}
    
    PLAY RECAP ********************************************************************************************************************************************************************************************************
    localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0