After taking a big step into HTTPS Only, I have gotten used to pairing with HTTPS for best practices, regardless of the environment. When I set up HSTS for my domain, I never considered going back to HTTP actually. However, some people say that certificate management can be somewhat difficult or mentally burdensome. For example, non-commercial certificates have a relatively short validity period, and whether renewing them manually or configuring automatic renewal, it is an extra workload. Moreover, obtaining a wildcard certificate for non-commercial use requires understanding DNS01. Even if you manage to get one, there are still various issues such as how to distribute it securely. Although I mostly use Cloudflare’s automatically configured certificates, but there are still so many situations like purely internal networks or somewhere I don’t wanna use a CDN (here I mean Cloudflare), that require me to request and configure the certificates by myself. So, I have also experienced the problems above. To solve this problem, I established a workflow for automatic certificate request using GitLab CI/CD, and completed storage, authentication, and securely serve through Cloudflare Workers and KV.
Design
Let’s start by briefly designing the logical structure:
First, certificates are requested and issued through CI/CD on GitLab. Then, using Cloudflare’s API, the issued certificates are pushed to KV for storage. Cloudflare Workers are used to create an API interface with authentication, which authenticates the requesting client device and retrieves the certificate from KV to respond. This means that our certificates only need to be updated once on GitLab, and each device can flexibly call the API for certificates according to the scenario requirements, making it elegant and convenient.
GitLab CI/CD
This part has already been open-sourced at fernvenue/certificates-updater. Here we use DNS01 to request for wildcard certificates for the domain, if you interested in DNS01, you can refer to Let’s Encrypt: Challenge Types. Before starting, we need to fill in some parameters that will be used, in Settings - CI/CD - Variables:
- CERTBOT_EMAIL
- CLOUDFLARE_ACCOUNT_ID
- CLOUDFLARE_API_TOKEN
- CLOUDFLARE_NAMESPACE_ID
- DNS_CLOUDFLARE_API_TOKEN
- DOMAIN
Here, I won’t introduce these values one by one. The CLOUDFLARE_NAMESPACE_ID parameter might be a bit confusing, but it is simply the ID of the KV. Additionally, there are two token values here. The DNS_CLOUDFLARE_API_TOKEN is used for updating DNS records to complete domain ownership verification. When creating it, just select the Edit zone DNS template in the Cloudflare dashboard. The CLOUDFLARE_API_TOKEN is used to push new certificates to the KV, so the permissions should be set to Account - Workers KV Storage - Edit, without assigning any additional permissions.
For the repository’s scheduled tasks, we need to set the trigger cycle in Build - Pipeline schedules. For example, I use 0 0 1 * * here, which means it runs once at 00:00 on the 1st of each month. By the way, although the certificate validity period is generally three months, for conservative reasons, I configure it to run once a month here.
Cloudflare Workers
After completing the configuration of the GitLab repository, we can see the certificates in Workers KV that have been updated and base64 encoded through CI/CD. Next, we need to call KV in Workers. First, in the Settings - Variables - KV Namespace Bindings option of the corresponding Worker, add the KV where the certificates are located so that the Worker can read the certificates. For example, I use KV_SSL_CERTIFICATES as the parameter name and add a parameter like X_CERTIFICATE_KEY in Environment Variables for authentication. Now we can build a simple API interface with authentication part:
For reference, you can use this part directly as a single Worker, or you can extract the content of the try block and place it into your existing Worker as an extension of the current interface.
Request
Suppose we have a device currently providing HTTPS service on Nginx. We can write a simple script like this:
Now create a systemd service for it:
Then we need a systemd timer:
Enable it:
Of course, this is just a simple example to get you started. There are many more ways to play around with this and various application scenarios to freely explore :)