Automating Let's Encrypt certificates with Gandi LiveDNS

As a Debian Developer I have a discount on using Gandi and I’ve been using it for quite a long time and have been very happy with it. I’ve been using it for registering domains. For example this blog’s domain is managed by my Gandi account.

Using publicly registered domain in private-only setup

In addition to using this DNS registrar for public stuff, like a blog, one can also use it for a domain accessible only within a private network. For example companies, large and small, use this technique – they have a set of subdomains of the domain they normally use, but those are accessible only when an employee is in the office (connected directly to the company’s network) or connected through a corporate VPN.

I have such a setup at my home network for stuff that I self-host at home and don’t want to expose to public Internet. So after registering a domain with Gandi I’ve used Let’s Encrypt to issue certificates for its subdomains so that even the services that are accessible only in my home network are encrypted and any devices using it are happy because the services I self-host are presenting an authentic Let’s Encrypt certificate. No need to fiddle with accepting self-signed or signed-by-a-custom-CA certificates.

So for example, let’s say that the domains we’re using locally only are subdomains of prezu.ca. First we’d register it with Gandi, then we’d use certbot to either provision a bunch of certificates for the subdomains (e.g. nextcloud.prezu.ca, gitea.prezu.ca, whatever-you-want-to-be-using.prezu.ca), or a single asterisk certificate for any subdomain: *.prezu.ca. I decided to take the latter approach as it’s simpler and has less moving parts.

Let’s say we want to make nextcloud.prezu.ca work only locally. To make that happen we’d use a local DNS resolver. You could use e.g. a DNS resolver built into your wireless router, if it has one. If it doesn’t, you could, for example, spin up a Pi-hole instance and modify your wireless router’s DHCP server’s settings to push your Pi-hole as DNS server to its clients. Then on the DNS resolver you could add a Local DNS Record for nextcloud.prezu.ca that would point to your local web server (e.g. 192.168.0.2 or whatever the local address of your web server is). This domain name becomes resolvable, but only within your own network. It’s never been registered anywhere else (no record for nextcloud.prezu.ca in your Gandi account). The outside world doesn’t even know, nextcloud.prezu.ca is a thing.

How to automate this?

Certbot allows you to issue certificates using various methods. Each method is centered around you proving that you own the domain for which you want to issue a certificate. Some of these methods can be fully automated and scripted. Some require you to do manual setup (e.g. set a TXT record for your domain). Certbot has a set of plugins for automating the process for various DNS registrars. Sadly, Gandi isn’t on that list. There is, however, a 3rd-party plugin that works great and makes it possible to completely automate the process.

Ansible

I like to always keep my infrastructure and configuration as code. For infra I tend to use Terraform and for configuration Ansible. Now, after creating an operating system instance for hosting my service (e.g. VM) I’m using Ansible to set it up. Because I wasn’t able to find any off-the-shelf Ansible role for provisioning the asterisk certificate hands-off using Gandi’s API, I decided to write my own Ansible role just for that. I mirrored my repo publicly on Codeberg so that anyone can grab and use it if they want.

The part of the Ansible playbook that would set Nextcloud up would contain probably a couple of important pieces:

  • Installing the Nextcloud instance itself.
  • Setting up a web server as a reverse proxy.
  • Use certbot to issue a certificate.
  • Setup automatic cron job for renewing the certificate.

The Ansible role above it taking care of tha last 2 steps. So the part of the playbook that’s dealing with Let’s Encrypt’s certificate would look something like this:

...
vars_files:
  # vars/secrets.yaml is a file encrypted using ansible-vault. Among other things it contains
  # gandi_api_token variable.
  - vars/secrets.yaml
...
roles:
  - role: certbot-gandi
    vars:
      domain: "prezu.ca"
      contact_email: contact@example.com
      web_server_reload_command: "systemctl reload nginx.service" # Assuming you're using Nginx
      # That's it. gandi_api_token will be picked up from vars/secret.yaml instead of from here.

If you’re new to Ansible and are not intimately familiar with using externally-provided Ansible role, check out the role’s README. It contains all the details.

If you have any comments and/or questions, please, hit me up over email.