Blog |Follow Nick on Mastodon| About
 

There's quite a few options/roles in ansible-galaxy or github to generate a Let's Encrypt Certificate, before running a random role it's good practice to review the tasks to get an idea of what it's doing. Jeff Geerling is a very reputable source for ansible roles however his certbot role by default is for Internet facing systems, that is a server that can directly respond to a either a standalone challenge or apache/nginx with .well-known setup correctly.

A DNS challenge allows Certbot to issue a cert from behind a firewall, like at home, without creating any DMZ or port-forwarding; after reviewing a few roles on offer to do this with ansible I realized it's actually quite straightforward!

To start with, use ansible-galaxy to install geerlingguy.certbot:

$ ansible-galaxy install geerlingguy.certbot

Now the plan here is to make your system / playbook require the role, but not issue the certificate as part of the role, instead issue the cert as a follow up task. Here's the initial playbook:

---
- hosts: internal_servers
  become: true
  #check_mode: yes

  vars:
    certbot_auto_renew: true
    certbot_auto_renew_user: root
    certbot_email: "[email protected]"
    certbot_cloudflare_api_token: 'xxxxx'

  roles:
  - geerlingguy.certbot

  tasks:
  - name: Install Certbot Cloudflare
    package: 
        name: python3-certbot-dns-cloudflare
        state: present

  - name: Create Certbot folder - /etc/letsencrypt
    file:
      path: /etc/letsencrypt
      state: directory
      owner: root
      group: root
      mode: 0700

  - name: Certbot Template
    template:
      src: "{{ item.src }}"
      dest: "{{ item.dest }}"
      owner: root
      group: root
      mode: 0600
    with_items:
      - { src: 'dnscloudflare.ini.j2', dest: '/etc/letsencrypt/dnscloudflare.ini' }

  - name: Certbot | Generate Certificate
    command: certbot certonly --non-interactive --agree-tos --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/dnscloudflare.ini -m {{certbot_email}} -d {{ansible_host}}
    args:
      creates: /etc/letsencrypt/renewal/{{ansible_host}}.conf

What's gonna happen here is geerlingguy.certbot will install the certbot package and setup the renewal cron task... and that's it. Once the role is done, it'll move into our tasks, so let's step thru those:

1. Install Certbot Cloudflare

Cloudflare support in Certbot is an optional add0on that you need to install. I'm running this on Redhat Enterprise Linux 8, for me the package for certbot-dns-cloudflare is called python3-certbot-dns-cloudflare, so if you're running this on Ubuntu/Alpine etc you will need to change that.

2/3. Create Certbot folder & Template

The certbot-dns-cloudflare plug-in needs credentials, since we haven't issued any certs the files & folders are not in place. So first ensure the folder is there and then you need a template file: dnscloudflare.ini.j2

# Cloudflare API credentials used by Certbot
dns_cloudflare_api_token = {{certbot_cloudflare_api_token}}

As above it's actually just one line, so probably could do it with line-in-file task but I like the template approach when the file is being created by me and I'm not just editing an existing file 😉

As you can see, the template simply writes your API Token in a format that the plug-in expects.

4. Generate Certificate

Finally we run the command manually in a way that calls the DNS challenge with all the information it needs. In my example, the certificate domain matches the ansible hostname but you can change that to what ever you need!

...Final thoughts & Renewal

As you can see, it was simple no need to install multiple roles/etc to achieve our goal. A quick point will be as we've issued the cert manually we might need to manage a renewal hook, on option is to drop a script in /etc/letsencrypt/renewal-hooks/post that will be run each time certbot runs, the other is a task to put a line in the file...

- name: Check Certbot File - post_hook
  lineinfile:
    dest: /etc/letsencrypt/renewal/{{ansible_host}}.conf
    line: "post_hook = /bin/systemctl restart nginx.service"
    regexp: "^post_hook ="

Enjoy!

 

 
Nick Bettison ©