Categories
ACME Certbot HAproxy Letsencrypt

Using HAproxy with letsencrypt/certbot

While trying to do SSL off-loading/termination using haproxy for some test instances I was looking for a solution to handle certificates from letsencrypt (via certbot) with haproxy (and no separate webserver, neither the built-in one of certbot nor any apache/nginx/…).

And there really is a solution for this! A more detailed post about how to even auto-apply the generated certificates to haproxy can be found here. However for now I decided to keep things easy (even if it requires some manual editing and restarts/reloads).

But let’s start at the very beginning by preparing a letsencrypt account to use:

linux # certbot register -m  me@mydomain.de --agree-tos --no-eff-email
# Saving debug log to /var/log/letsencrypt/letsencrypt.log
# Account registered.

linux # certbot show_account
# Saving debug log to /var/log/letsencrypt/letsencrypt.log
# Account details for server https://acme-v02.api.letsencrypt.org/directory:
#   Account URL: https://acme-v02.api.letsencrypt.org/acme/acct/1234567890
#   Account Thumbprint: e_aBcDeFgHiJkLmNoPqRsTuVwaBcDeFgHiJkLmNoPqRsTuVw
#   Email contact: me@mydomain.de

One of the easiest challenges of letsencrypt to verify your possession of a certain host is a simple http-challenge. This will query an URL on your host starting with /.well-known/acme-challenge/ followed by a random challenge.

The response should contain one line: the challenge sent and your account thumbprint, separated by a “.” – the certbot command explains that (once you got the right options):

linux # certbot certonly --manual --preferred-challenges http -d myhost.mydomain.de
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for testhost.mydomain.de

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:

SfjfR1MeriHuXshmIjc5MwLoTRHGWUTzM8GBdIYjXQI.e_aBcDeFgHiJkLmNoPqRsTuVwaBcDeFgHiJkLmNoPqRsTuVw

And make it available on your web server at this URL:

http://myhost.mydomain.de/.well-known/acme-challenge/SfjfR1MeriHuXshmIjc5MwLoTRHGWUTzM8GBdIYjXQI

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

As mentioned above certbot could either answer this challenge with its built-in webserver, or any other webserver could serve files containing that data.

In practive however the required ports are often already in use (e.g. by haproxy), so it’d be nice if those applications could respond directly (without starting/stopping services etc.).

Fortunately haproxy can handle such requests. All we need to do is to make sure it’s listening on port 80 and it has a certain auto-response. To do so, we first need to set a variable containing out letsencrypt account thumbprint in the “global” section:

global
<...>
setenv ACCOUNT_THUMBPRINT 'e_aBcDeFgHiJkLmNoPqRsTuVwaBcDeFgHiJkLmNoPqRsTuVw'
<...>

And in a second step we need to add a special response to all incoming queries using the special path “/.well-known/acme-challenge/*” in the frontend section responsible for port 80 (“web” in the following example):

frontend web
<...>
http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' }
<...>

This basically takes all incoming queries starting with ‘/.well-known/acme-challenge/’, cuts the requested path into pieces (separator is ‘/’) and takes the last element of that array.

http://myhost.mydomain.de/.well-known/acme-challenge/SfjfR1MeriHuXshmIjc5MwLoTRHGWUTzM8GBdIYjXQI
-> [ '.well-known', 'acme-challenge', 'SfjfR1MeriHuXshmIjc5MwLoTRHGWUTzM8GBdIYjXQI' ]

After that it just concatenates the result with the content of our variable ”ACCOUNT_THUMBPRINT” (separator ‘.’) and returns the resulting string as “text/plain”.

So after reloading/restarting haproxy you can use curl or wget to test this:

linux # curl http://myhost.mydomain.de/.well-known/acme-challenge/test
test.e_aBcDeFgHiJkLmNoPqRsTuVwaBcDeFgHiJkLmNoPqRsTuVw

If everything looks ok, we’re ready to get our new certificate:

linux # certbot certonly --manual --preferred-challenges http -d myhost.mydomain.de --manual-auth-hook /bin/true
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for myhost.mydomain.de

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/myhost.mydomain.de/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/myhost.mydomain.de/privkey.pem
This certificate expires on 2024-12-20.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Leave a Reply

Your email address will not be published. Required fields are marked *