From 890b34bcc1a6b4073d1e512b1386634f7bc5ea52 Mon Sep 17 00:00:00 2001
From: "Adam T. Carpenter" <atc@53hor.net>
Date: Wed, 21 Apr 2021 22:57:39 -0400
Subject: unified posts dir, until I can figure out makefile sub-subdirs.
 makefile auto-generates index

---
 ...w-to-automate-certbot-renewal-with-haproxy.html | 256 +++++++++++++++++++++
 1 file changed, 256 insertions(+)
 create mode 100644 posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html

(limited to 'posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html')

diff --git a/posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html b/posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html
new file mode 100644
index 0000000..634530b
--- /dev/null
+++ b/posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html
@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="stylesheet" href="/includes/stylesheet.css" />
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta
+      property="og:description"
+      content="The World Wide Web pages of Adam Carpenter"
+    />
+    <meta
+      property="og:image"
+      content="https://nextcloud.53hor.net/index.php/s/Nx9e7iHbw4t99wo/preview"
+    />
+    <meta property="og:site_name" content="53hor.net" />
+    <meta
+      property="og:title"
+      content="How to Automate Certbot Renewal with HAProxy"
+    />
+    <meta property="og:type" content="website" />
+    <meta property="og:url" content="https://www.53hor.net" />
+    <title>53hornet ➙ How to Automate Certbot Renewal with HAProxy</title>
+  </head>
+
+  <body>
+    <nav>
+      <ul>
+        <li>
+          <a href="/">
+            <img src="/includes/icons/home-roof.svg" />
+            Home
+          </a>
+        </li>
+        <li>
+          <a href="/info.html">
+            <img src="/includes/icons/information-variant.svg" />
+            Info
+          </a>
+        </li>
+        <li>
+          <a href="https://git.53hor.net">
+            <img src="/includes/icons/git.svg" />
+            Repos
+          </a>
+        </li>
+        <li>
+          <a href="/hosted.html">
+            <img src="/includes/icons/desktop-tower.svg" />
+            Hosted
+          </a>
+        </li>
+        <li>
+          <a type="application/rss+xml" href="/rss.xml">
+            <img src="/includes/icons/rss.svg" />
+            RSS
+          </a>
+        </li>
+      </ul>
+    </nav>
+
+    <article>
+      <h1>How to Automate Certbot Renewal with HAProxy</h1>
+
+      <p>
+        So this is specifically for HAProxy on FreeBSD, but it should apply to
+        other *nix systems as well. Basically, I use HAProxy as a reverse proxy
+        to a bunch of servers I administer. I use Let's Encrypt for a
+        certificate and I used <code>certbot</code> to generate that
+        certificate. Generating the certificate for the first time is easy and
+        has lots of documentation, but it wasn't initially clear on how I could
+        easily set up auto-renewal. Here's how I did it.
+      </p>
+
+      <p>
+        If you've already set up TLS termination with HAProxy and
+        <code>certbot</code>, you know you need to combine your Let's Encrypt
+        fullchain and private key to get a combined certificate that HAProxy can
+        use. You can do this by <code>cat</code>-ing the chain and key together
+        like so:
+      </p>
+
+      <pre>
+<code>
+cat /usr/local/etc/letsencrypt/live/$SITE/fullchain.pem /usr/local/etc/letsencrypt/live/$SITE/privkey.pem > /usr/local/etc/ssl/haproxy.pem
+</code>
+	  </pre>
+
+      <p>
+        In this example, <code>$SITE</code> is your domain name that you fed
+        HAProxy when you created the certificate and <code>haproxy.pem</code> is
+        wherever you're storing HAProxy's combined certificate. Your HAProxy
+        config then points to that certificate like this:
+      </p>
+
+      <pre>
+<code>
+macon% grep crt /usr/local/etc/haproxy.conf
+        bind *:443 ssl crt /usr/local/etc/ssl/haproxy.pem
+</code>
+	  </pre>
+
+      <p>
+        And that was the end of the first-time setup. Then a few months later
+        you probably had to do it again because Let's Encrypt certs are only
+        good for 90 days in between renewals. To renew the certificate, you
+        usually run <code>certbot renew</code>, it detects which certificates
+        are present, and uses either the webroot or standlone server renewal
+        process. Then you have to <code>cat</code> the fullchain and privkey
+        together and restart HAProxy so it starts using the new certificate.
+      </p>
+
+      <p>
+        To automate those steps, newer versions of
+        <code>certbot</code> will run any post renewal hooks (read: scripts)
+        that you want. You can also configure HAProxy and
+        <code>certbot</code> to perform the ACME challenge dance for renewal so
+        that you don't have to use it interactively.
+      </p>
+
+      <p>
+        First, if you haven't already done it, change your HAProxy config so
+        there's a frontend+backend for responding to ACME challenges. In a
+        frontend listening for requests, create an access control list for any
+        request looking for <code>/.well-known/acme-challenge/</code>. Send
+        those requests to a backend server with an unused local port.
+      </p>
+
+      <pre>
+<code>
+frontend http-in
+		acl letsencrypt-acl path_beg /.well-known/acme-challenge/
+        use_backend letsencrypt-backend if letsencrypt-acl
+		...
+backend letsencrypt-backend
+		server letsencrypt 127.0.0.1:54321
+</code>
+	  </pre>
+
+      <p>
+        What this will do is allow <code>certbot</code> and Let's Encrypt to
+        renew your server in standalone mode via your reverse proxy. As an added
+        bonus it prevents you from having to open up an additional port on your
+        firewall.
+      </p>
+
+      <p>
+        Now you've gotta configure <code>certbot</code> to do just that. A
+        config file was created in <code>certbot</code>'s
+        <code>renew</code> directory for your site. All you need to do in that
+        file is add a line to the <code>[renewalparams]</code> section
+        specifying the port you're using in your HAProxy config.
+      </p>
+
+      <pre>
+<code>
+macon% echo "http01_port = 54321" >> /usr/local/etc/letsencrypt/renewal/$SITE.conf
+</code>
+	  </pre>
+
+      <p>
+        Now you need the post-renewal hooks. I dropped two separate scripts into
+        the <code>renewal-hooks</code> directory: one does the job of combining
+        the certificate chain and private key and the other just restarts
+        HAProxy.
+      </p>
+
+      <pre>
+<code>
+macon% cat /usr/local/etc/letsencrypt/renewal-hooks/post/001-catcerts.sh
+#!/bin/sh
+
+SITE=(your site of course)
+
+cd /usr/local/etc/letsencrypt/live/$SITE
+cat fullchain.pem privkey.pem > /usr/local/etc/ssl/haproxy.pem
+macon% cat /usr/local/etc/letsencrypt/renewal-hooks/post/002-haproxy.sh
+#!/bin/sh
+service haproxy restart
+</code>
+	  </pre>
+
+      <p>
+        When <code>certbot renew</code> is run, <code>certbot</code> checks the
+        <code>renewal-hooks/post</code> directory and runs any executable things
+        in it after it's renewed the certificate(s). As a side note,
+        <em>make sure you hit those scripts with <code>chmod +x</code></em> or
+        they probably won't run.
+      </p>
+
+      <p>
+        Now all that's left is dropping a job into <code>cron</code> or
+        <code>periodic</code> to run <code>certbot renew</code> at least once or
+        twice within the renewal period.
+      </p>
+
+      <pre>
+<code>
+macon% doas crontab -l|grep certbot
+# certbot renewal
+@monthly certbot renew
+</code>
+	  </pre>
+
+      <p>
+        You can always test that your scripts are working with
+        <code>certbot renew --dry-run</code> just to be safe.
+      </p>
+
+      <pre>
+<code>
+macon% doas certbot renew --dry-run
+Saving debug log to /var/log/letsencrypt/letsencrypt.log
+
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Processing /usr/local/etc/letsencrypt/renewal/53hor.net.conf
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Cert not due for renewal, but simulating renewal for dry run
+Plugins selected: Authenticator standalone, Installer None
+Simulating renewal of an existing certificate for 53hor.net and 7 more domains
+Performing the following challenges:
+http-01 challenge for 53hor.net
+http-01 challenge for carpentertutoring.com
+http-01 challenge for git.53hor.net
+http-01 challenge for nextcloud.53hor.net
+http-01 challenge for pkg.53hor.net
+http-01 challenge for plex.53hor.net
+http-01 challenge for theglassyladies.com
+http-01 challenge for www.53hor.net
+Waiting for verification...
+Cleaning up challenges
+
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+new certificate deployed without reload, fullchain is
+/usr/local/etc/letsencrypt/live/53hor.net/fullchain.pem
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Congratulations, all simulated renewals succeeded:
+  /usr/local/etc/letsencrypt/live/53hor.net/fullchain.pem (success)
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Running post-hook command: /usr/local/etc/letsencrypt/renewal-hooks/post/001-catcerts.sh
+Running post-hook command: /usr/local/etc/letsencrypt/renewal-hooks/post/002-haproxy.sh
+Output from post-hook command 002-haproxy.sh:
+Waiting for PIDS: 15191.
+Starting haproxy.
+
+</code>
+		</pre>
+
+      <p>
+        And there it is. Automated Let's Encrypt certificate renewal on FreeBSD
+        with HAProxy.
+      </p>
+    </article>
+  </body>
+</html>
-- 
cgit v1.2.3