From 0eb6220672d16b009870d8296c3afaa836d876cb Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 23 Jul 2025 09:55:53 -0500 Subject: [PATCH] r/mod_md: Configure Apache for ACME certificates Apache supports fetching server certificates via ACME (e.g. from Let's Encrypt) using a new module called _mod_md_. Configuring the module is fairly straightforward, mostly consisting of `MDomain` directives that indicate what certificates to request. Unfortunately, there is one rather annoying quirk: the certificates it obtains are not immediately available to use, and the server must be reloaded in order to start using them. Fortunately, the module provides a notification mechanism via the `MDNotifyCmd` directive, which will run the specified command after obtaining a certificate. The command is executed with the privileges of the web server, which does not have permission to reload itself, so we have to build in some indirection in order to trigger the reload: the notification runs a script that creates an empty file in the server's state directory; systemd is watching for that file to be created, then starts another service unit to trigger the actual reload, then removes trigger file. Website roles, etc. that want to switch to using _mod_md_ to manage their certificates should depend on this role and add an `MDomain` directive to their Apache configuration file fragments. --- group_vars/public-web/mod_md.yml | 5 +++ roles/mod_md/defaults/main.yml | 1 + roles/mod_md/files/httpd-reload.path | 8 ++++ roles/mod_md/files/httpd-reload.service | 9 +++++ roles/mod_md/files/md-notify.sh | 5 +++ roles/mod_md/meta/main.yml | 2 + roles/mod_md/tasks/main.yml | 54 +++++++++++++++++++++++++ roles/mod_md/templates/mod_md.conf.j2 | 16 ++++++++ websites.yml | 3 ++ 9 files changed, 103 insertions(+) create mode 100644 group_vars/public-web/mod_md.yml create mode 100644 roles/mod_md/defaults/main.yml create mode 100644 roles/mod_md/files/httpd-reload.path create mode 100644 roles/mod_md/files/httpd-reload.service create mode 100755 roles/mod_md/files/md-notify.sh create mode 100644 roles/mod_md/meta/main.yml create mode 100644 roles/mod_md/tasks/main.yml create mode 100644 roles/mod_md/templates/mod_md.conf.j2 diff --git a/group_vars/public-web/mod_md.yml b/group_vars/public-web/mod_md.yml new file mode 100644 index 0000000..914fe00 --- /dev/null +++ b/group_vars/public-web/mod_md.yml @@ -0,0 +1,5 @@ +mod_md_contact_email: dustin@hatch.name +mod_md_private_keys: secp256r1 +mod_md_status_enabled: true +mod_md_status_config: |- + Allow from 172.30.0.0/16 diff --git a/roles/mod_md/defaults/main.yml b/roles/mod_md/defaults/main.yml new file mode 100644 index 0000000..67c1c0f --- /dev/null +++ b/roles/mod_md/defaults/main.yml @@ -0,0 +1 @@ +mod_md_status_enabled: false diff --git a/roles/mod_md/files/httpd-reload.path b/roles/mod_md/files/httpd-reload.path new file mode 100644 index 0000000..c49939c --- /dev/null +++ b/roles/mod_md/files/httpd-reload.path @@ -0,0 +1,8 @@ +[Unit] +Description=Apache auto-reload watch + +[Path] +PathExists=/var/lib/httpd/reload + +[Install] +WantedBy=paths.target diff --git a/roles/mod_md/files/httpd-reload.service b/roles/mod_md/files/httpd-reload.service new file mode 100644 index 0000000..ff673ee --- /dev/null +++ b/roles/mod_md/files/httpd-reload.service @@ -0,0 +1,9 @@ +[Unit] +Description=Reload Apache HTTPD on demand +After=httpd.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/systemctl reload httpd +ExecStopPost=+-/bin/rm -f /var/lib/httpd/reload +CapabilityBoundingSet= diff --git a/roles/mod_md/files/md-notify.sh b/roles/mod_md/files/md-notify.sh new file mode 100755 index 0000000..f4023d4 --- /dev/null +++ b/roles/mod_md/files/md-notify.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +logger -p local7.debug "$0 $*" +logger -p local7.info 'Signaling systemd to reload httpd' +touch /var/lib/httpd/reload diff --git a/roles/mod_md/meta/main.yml b/roles/mod_md/meta/main.yml new file mode 100644 index 0000000..e2827fe --- /dev/null +++ b/roles/mod_md/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: +- systemd-base diff --git a/roles/mod_md/tasks/main.yml b/roles/mod_md/tasks/main.yml new file mode 100644 index 0000000..7a50444 --- /dev/null +++ b/roles/mod_md/tasks/main.yml @@ -0,0 +1,54 @@ +- name: ensure mod_md is installed + package: + name: mod_md + state: present + tags: + - install + +- name: ensure mod_md is configured + template: + src: mod_md.conf.j2 + dest: /etc/httpd/conf.d/01-mod_md.conf + owner: root + group: root + mode: u=rw,go=r + notify: + - reload httpd + tags: + - config + +- name: ensure mod_md notify script is installed + copy: + src: md-notify.sh + dest: /usr/local/libexec/md-notify + owner: root + group: root + mode: u=rwx,go=rx + tags: + - notify-script + +- name: ensure httpd-reload systemd units are installed + copy: + src: '{{ item }}' + dest: /etc/systemd/system/{{ item }} + owner: root + group: root + mode: u=rw,go=r + loop: + - httpd-reload.path + - httpd-reload.service + notify: reload systemd + tags: + - systemd +- name: ensure httpd-reload.path unit is enabled + systemd: + name: httpd-reload.path + enabled: true + tags: + - service +- name: ensure httpd-reload.path unit is active + systemd: + name: httpd-reload.path + state: started + tags: + - service diff --git a/roles/mod_md/templates/mod_md.conf.j2 b/roles/mod_md/templates/mod_md.conf.j2 new file mode 100644 index 0000000..7a58d95 --- /dev/null +++ b/roles/mod_md/templates/mod_md.conf.j2 @@ -0,0 +1,16 @@ +MDCertificateAgreement accepted +MDContactEmail {{ mod_md_contact_email }} + +MDNotifyCmd /usr/local/libexec/md-notify +{% if mod_md_private_keys is defined %} +MDPrivateKeys {{ mod_md_private_keys }} +{% endif %} +{% if mod_md_status_enabled %} + + + SetHandler md-status +{% if mod_md_status_config %} + {{ mod_md_status_config | indent(2) }} +{% endif %} + +{% endif %} diff --git a/websites.yml b/websites.yml index 9840def..c2de61e 100644 --- a/websites.yml +++ b/websites.yml @@ -3,6 +3,9 @@ apache_default_ssl_vhost: false roles: - apache + - role: mod_md + tags: + - mod_md - role: formsubmit tags: formsubmit - role: websites/pyrocufflink.net