Sample setuid command to enable 'MDMessageCmd renewed' to cause a graceful restart of httpd
Greatly enjoying working with mod_md. It works great. Four features I wish it also had are:
- Ability to automatically restart after issuing/renewing a certificate
- Pre-flighting ACME challenges before asking ACME server to test the challenge
- Clean up of orphaned certificate directories in
domainsandstaging - Ability to detect
domainscertificates have been updated when in a shared filesystem
This GIST aims to address (1) buy providing a setuid command+script you can install to enable httpd to automatically restart after a certificate is issued or installed.
https://gist.github.com/whereisaaron/d4e94bac59cf01ca213f50756fe1155c
Other solutions involve using external services like crond to watch or do scheduled restarts. However this solution enables a single httpd process in a single container to handle it. And it enables mod_md to load newly issued certificates without delay. It consists of a setuid command that invokes a bash script as root.
Appreciate any suggestions to improve security or robustness.
MDMessageCmd /usr/local/sbin/md_event
/*
* Execute md_event.sh
*
* 'md_event.sh' is an event handler for apache2 'mod_md' events,
* including setting up ACME challenge responses for certificates,
* and after renewing certificates.
*
* This binary simply executes 'md_event.sh' in a fixed location,
* passing any command arguments.
*
* By making the 'md_event' binary compiled from this code 'setuid'
* the 'mod_md' bash script will we run as 'root', which enables
* the script to restart apache2, enabling it to load any new
* or renewed certificiates mod_md has in the 'staging' directory.
*
* The 'md_event' binary and 'md_event.sh' script must be protected
* from tampering or else your attacker will thank you later.
*
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
const char script_path[] = "/usr/local/sbin/md_event.sh";
int main (int argc, char *argv[]) {
// Set uid to 'root'
if (setuid(0) != 0) {
perror("Failed to change user to root");
return errno;
}
// Exec the script (using its hash-bang interpretor)
execv(script_path, argv);
// execv() will only return if an error occurred
char *message;
asprintf(&message, "Failed to exec %s", script_path);
perror(message);
return errno;
}
#!/bin/bash
# Log the invocation and effective user id
echo "Running as $(id -nu $EUID): md_event.sh $*"
#
# Restart apache2 gracefully
# This enable mod_md to load newly renewed certificate
# This script needs to be run as root
#
# We only care about after a certificate has been renewed
if [[ "$1" != "renewed" ]]; then
exit 0
fi
# Debian default 'nofiles' ulimit is to set 8192 but AWS ECS Fargate hard limit is 4096
# This env var will change command run by '/usr/sbin/apachectl'
export APACHE_ULIMIT_MAX_FILES="ulimit -n 4096"
echo "Domain '$2' has been renewed, restarting apache2"
apache2ctl configtest && apache2ctl graceful
result=$?
if (( $result == 0 )); then
echo "Successful restart of apache2 after renewal of '$2'"
else
echo "Failed restart of apache2 after renewal of '$2'"
fi
# No-zero exit will mean mod_md will keeping make this same call again
# until zero is returned, which increasing back-off periods.
exit $result
# end
INSTALL
sudo gcc md_event.c -o /usr/local/sbin/md_event
sudo chmod u+s /usr/local/sbin/md_event
sudo cp md_event.sh /usr/local/sbin/
Hi,
I find this idea very interesting and will maybe use it the restart services like postfix after updating certificates using mod_md. What are the advantages of using this md_event helper instead of using something like sudo to do the restarts?
Thanks, Simon