45

Does anyone have an example of decrypting and uploading a file using ansible-vault.

I am thinking about keeping my ssl certificates encrypted in source control.

It seems something like the following should work.

---
  - name: upload ssl crt
    copy: src=../../vault/encrypted.crt dest=/usr/local/etc/ssl/domain.crt
Rico
  • 58,485
  • 12
  • 111
  • 141
Aaron Renoir
  • 4,283
  • 1
  • 39
  • 61

10 Answers10

53

The copy module now does this seamlessly as of Ansible 2.1.x. Just encrypt your file with Ansible Vault and then issue the copy task on the file.

(For reference, here's the feature that added this: https://github.com/ansible/ansible/pull/15417)

jeffmaher
  • 1,824
  • 3
  • 21
  • 20
  • 3
    I wish this answer either provided an example or linked to one. I followed the link and it's very non-obvious to me what I need to do. – Jason Swett Feb 13 '20 at 20:56
37

UPDATE: Deprecated as of 2016, Ansible 2.1

On any Ansible version prior of 2.1:

That's not going to work. What you will get is your encrypted.crt (with Ansible Vault) uploaded literally as domain.crt

What you need to do is make your playbook part of a "Vault" and add a variable that contains your certificate content. Something like this:

---
- name: My cool playbook
  hosts: all

  vars:
    mycert: |
       aasfasdfasfas
       sdafasdfasdfasdfsa
       asfasfasfddasfasdfa


  tasks:
    # Apparently this causes new lines on newer ansible versions
    # - name: Put uncrypted cert in a file
    #   shell: echo '{{ mycert }}' > mydecrypted.pem

    # You can try this as per
    # https://github.com/ansible/ansible/issues/9172
    - copy:
      content: "{{ mycert }}"
      dest: /mydecrypted.pem

    - name: Upload Cert
      copy: src=/home/ubuntu/mydecrypted.pem dest=/home/ubuntu/mydecrypteddest.pem

    - name: Delete decrypted cert
      file: path=/home/ubuntu/mydecrypted.pem state=absent

You can choose to put your mycert variable in a separate variable file using Ansible Vault too.

The copy module has been updated in Ansible 2.1. From the changelog: "copy module can now transparently use a vaulted file as source, if vault passwords were provided it will decrypt and copy on the fly." Noting it here, since some people will inevitably not look past the accepted answer. – JK Laiho

azbarcea
  • 3,323
  • 1
  • 20
  • 25
Rico
  • 58,485
  • 12
  • 111
  • 141
  • This approach results in doubled newlines for the inline content and doesn't work for anything (eg, certificates) which is sensitive to whitespace. This is acknowledged as intended behavior (https://github.com/ansible/ansible/issues/9172). – bk0 Nov 14 '14 at 01:15
  • Updated the answer, I tried this a few months ago and it was working fine. There must have been some change in the newer Ansible versions – Rico Nov 14 '14 at 01:25
  • 2
    I opened a new Github issue for this: https://github.com/ansible/ansible/issues/9556 – bk0 Nov 14 '14 at 01:41
  • 11
    The file lookup plugin now handles encrypted files just like include_vars. All you'd need `content: "{{ lookup('file', '{{item}}') }}"` – FreshPow May 04 '16 at 13:26
  • @FreshPow you should add that as a separate answer. It feels like the best polyfill for me, until the copy module is updated. – skolsuper Jun 02 '16 at 08:20
  • 11
    The copy module has been updated in Ansible 2.1. From the changelog: "copy module can now transparently use a vaulted file as source, if vault passwords were provided it will decrypt and copy on the fly." Noting it here, since some people will inevitably not look past the accepted answer. – JK Laiho Aug 02 '16 at 12:17
  • 2
    @JKLaiho thanks! Updated the answer with your quote ! – Rico Aug 02 '16 at 15:03
  • I don't want to update the answer in the case I am wrong but I believe the commented `shell echo` may possibly be fixed with a `-n` argument to echo. E.g. `echo -n '{{ mycert }}'` – Jasmine Hegman Jul 30 '17 at 03:28
22

There is a feature request to support this natively in the copy module. But until that is implemented, here is the workaround (similar to @dave1010's answer, but repeating common parts for completeness):

Create a secrets.yml file encrypted with ansible vault which contains your secrets, for example:

---
private_ssl_key: |
  -----BEGIN PRIVATE KEY-----
  abcabcabcabcabcabcabcabcabc
  -----END PRIVATE KEY-----

private_crt: |
  -----BEGIN CERTIFICATE-----
  abcabcabcabcabcabcabcabcabc
  -----END CERTIFICATE-----

In your playbook, include it:

vars_files:
  - secrets.yml

Then you can use the variables in tasks:

- name: Copy private kay
  copy: content="{{ private_ssl_key }}" dest=/some/path/ssl.key

However, this doesn't work if the file that you are trying to copy is a binary file. In that case, you need to first encode the content with base64:

cat your_secret_file | /usr/bin/base64

Then put the base64 encoded value in your secrets.yml file, e.g.:


crt_b64: |
  ndQbmFQSmxrK2IwOFZnZHNJa0sKICAxdDhFRUdmVzhMM...

Then you can create the remote file in two steps:

- name: Copy certificate (base64 encoded)
  copy: content="{{ crt_b64 }}" dest=/some/path/cert.b64

- name: Decode certificate
  shell: "base64 -d /some/path/cert.b64 > /some/path/cert.txt"
  args:
    creates: /some/path/cert.txt

Note that you could delete the temporary cert.b64 file on the remote host. But then re-running the playbook will re-create it instead of skipping this task. So, I prefer to leave it there.

UPDATE: This feature has been implemented in Ansible 2.1.

copy module can now transparently use a vaulted file as source, if vault passwords were provided it will decrypt and copy on the fly.

psiyumm
  • 6,437
  • 3
  • 29
  • 50
Yasser
  • 694
  • 10
  • 10
21

Ansible 2.5 added the parameter decrypt to the copy module.

For example, if you encrypted your file using something like:

$ ansible-vault encrypt vault/encrypted.crt

Now you can use copy + decrypt:

---
  - name: upload ssl crt
    copy:
      src: path/to/encrypted-with-vault.crt
      dest: /usr/local/etc/ssl/domain.crt
      decrypt: yes
      mode: 0600
hgdeoro
  • 1,030
  • 10
  • 7
15

I used a template and a vars_file to do it:

In your top-level playbook:

vars_files:
  - secretvars.yml

In a task:

- name: Private ssl key
  template: src=etc-ssl-private-site.key dest=/etc/ssl/private/site.key

In the template (etc-ssl-private-site.key) all you need is the variable:

{{ private_ssl_key }}

In the encrypted secretvars.yml (encrypt this with ansible-vault):

---

private_ssl_key: |
  -----BEGIN PRIVATE KEY-----
  abcabcabcabcabcabcabcabcabc
  -----END PRIVATE KEY-----
dave1010
  • 15,135
  • 7
  • 67
  • 64
14

Update: As of April 2016 my Github PR has been merged and is available in Ansible 2.1 and later. The below was an interim solution until the PR was merged.

Wanting to do the same thing I created an action plugin to implement the feature. This is available via github. The plugin is exactly the copy action plugin as shipped with ansible, but with support for vault decryption.

You can use it like this:

- name: Copy Some Secret File
  copyv: src="secret.txt" dest="/tmp/"

if secret.txt is encrypted (and the vault password is supplied) then it will be decrypted and copied.

Cam
  • 491
  • 1
  • 5
  • 9
  • 1
    Thanks for that -- this is by far the easiest solution IMO, and when the ansible folks get around to adding this themselves, just a simple playbook change is required.... This is really how vault should have worked to begin with... – Matt Billenstein Oct 07 '15 at 19:24
  • Seems like there's a TYPO: `copyv` should be `copy`, I think. I thought `copyv` was shorthand for copy-vault or something, but the module didn't exist. Just using `copy` resulted in a decrypted file on the target machine. I'm not sure enough to do the edit myself though. – A5308Y May 04 '17 at 08:31
  • @A5308Y, it was not a typo. The name of the interim module used on Ansible 1.0 and 2.0 was copyv (for copy vault). In April 2016 this functionality was merged into Ansible 2.1 as part of the copy module (no v). – Cam May 06 '17 at 04:41
13

I think, you have a simpler way to do this.

If you use certificate+key in one file in some format (like pkcs12 or just concatenated), you can use generic openssl (or gpg, or something else) encryption. It will look like this:

openssl enc -e -aes-256-ctr -in original.pem -out encrypted.aes -k <pass-vault>

After that you can just copy encrypted.aes to remote host and decrypt it in-place:

- name: copy encrypted cert and key
  copy: src=encrypted.aes dest=/root/ansible-files/ mode=0600

- name: decrypt cert and key
  command: openssl enc -aes-256-ctr -d -in /root/ansible-files/encrypted.aes -out <dest> -k {{ pass-vault }}

If you have separate key file in pem or der format, you can use

openssl rsa -in original.pem -out encrypted.pem -aes256 -passout pass:<pass-vault>
slm
  • 15,396
  • 12
  • 109
  • 124
4

Until the 'copy' module has been extended to automatically decrypt vault files, here's a simple workaround:

When stdout is not a tty, ansible-vault view <file> prints cleartext to stdout without invoking a pager.

In combination with a 'pipe' lookup, this behavior can be used with a vault password file to feed into the copy module's 'content' option:

- name: "install host key"
  copy: content="{{ lookup('pipe', 'ansible-vault view ' + src_key_file) }}"
        dest={{ dest_key_file }}
  • The problem with this workaround is that that script will prompt you for your password interactively mid-script, as opposed to upfront (presumably repeatedly so if you have multiple keys to unlock). – Declan McGrath Apr 13 '16 at 15:58
  • The output of the ansible-vault view command may also pipe into "less" and thus be paged - breaking the solution. You can workaround that problem by piping the output into the "cat" command, eg. "{{ lookup('pipe', 'ansible-vault view ' + src_key_file + '|cat') }}" – Declan McGrath Apr 13 '16 at 16:00
2

You can also use local_action to temporairly decrypt your file as part of the playbook:

- name: "temporairly decrypt the twpol.enc"
  sudo: False
  local_action: shell ansible-vault view --vault-password-file {{ lookup('env', 'ANSIBLE_VAULT_PASS_FILE') }} ./roles/copykey/files/key.enc > ./roles/copykey/files/key.txt 

- name: "copy the key to the target machine."
  copy: src=key.txt dest=/tmp

- name: "remove decrypted key.txt file"
  sudo: False
  local_action: rm ./roles/copykey/files/key.txt
  • 1
    I like the idea, but I suggest to use Ansible's `{{ role_path }}` variable instead of using hard coded paths in order to keep the role more independent from the project directory structure. Further, I would use `local_action: "file path={{ role_path }}/files/key.txt state=absent"`, since using `rm` triggers a warning (at least in Ansible 2.x). Also, I would add `changed_when: False` to the decrypt and remove tasks, since otherwise each time you run the playbook a change will be triggered, which would make idempotency tests difficult – fishi0x01 May 22 '16 at 13:58
-1

+1 for the copy: content= {{ private_ssl_key }}" method suggested by @utapyngo above.

If you are doing your key distribution as a role, rather than just in a playbook (and why not, since key distribution is something you might need again later), keep in mind the following:

  • You only get one file for your vars, so all the keys (say you have different bundles based on host machines or whatever) have to go together in <role>/vars/main.yml
  • Variables in this <role>/vars/main.yml are referenceable without any paths (that's nice!)
  • Remember that whenever you want the content of the variable, you need quotes AND curlies, i.e., "{{ your_variable_name }}"
  • If you want to copy more than one file per task, you need a with_items: loop
  • If you want to keep your sensitive data that you've gone through so much trouble to encrypt in the first place off the screen, a neat trick is to next your key variables inside a dictionary; that way, in your with_items loop you are feeding it the dictionary key rather than the contents of the variable itself.