Introduction
Sometimes you would like to fetch a file from one server, then put it onto another. There are many ways to solve this. One way that I have gotten very used to is to use the Ansible module slurp
to fetch the content of the file into a variable, then putting it onto the destination server using the module copy
. This has worked really well for me for quite some time, as I unutil now only have dealt with text (i.e letsencrypt certificates in PEM format).
The other day, we ran into a situation where we needed to fetch a PKCS12 formatted certificate and send it to a Windows server. It took us quite some time before we figured out that:
- The normal way of doing this with the
copy
module and the filterb64decode
is NOT binary safe
There are long rants about this around the internet, claiming that doing it this way sends the content through Ansible’s templating system. By doing so, it is apparently impossible to force the output to be binary. In our case, the binary content is converted to UTF8, as if it was a text string. The result is a file that is about twice the size of the original certificate file on the source system.
#--- this does not work as planned, outputs UTF8
- win_copy:
content: "{{ remote.content | b64decode }}"
dest: "{{ certificate_destination }}"
This is how we solved it in the end:
- We run the playbook for the windows host tst01-web-w01a
- The PKCS12 certificate is stored in our linux host tst-certificatemanager-l01
- We use
delegate_to
to tell Ansible to fetch the certificate from a different server - The magic sauce =>
[System.Convert]::FromBase64String("{{ remote.content }}") | Set-Content {{ certificate_destination }} -encoding byte
This example assumes that you have already converted your PEM formatted certificate to PKCS12 format on your server, and put it next to your other letsencrypt certificate.
- hosts: tst01-web-w01a
vars:
letsencrypt_host: tst-certificatemanager-l01
letsencrypt_path: /etc/nginx/ssl
letsencrypt_site: api.tst01.corp.example.com
certificate_destination: C:\ansible\mycert.test.p12
- name: Slurp certificate
slurp:
src: "{{letsencrypt_path}}/{{letsencrypt_site}}/fullcert.p12"
register: remote
delegate_to: "{{ letsencryp_host }}"
become: true
become_method: sudo
remote_user: "{{ ansible_user }}"
- name: Place the certificate on the remote host
win_shell: |
[System.Convert]::FromBase64String("{{ remote.content }}") | Set-Content {{ certificate_destination }} -encoding byte
(Note: I am deliberately breaking the “whitespace” rule of PEP8 in my examples above. Makes it easier for me to read the code.)