Skip to content

Instantly share code, notes, and snippets.

@whereisaaron
Last active September 12, 2023 14:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whereisaaron/d4e94bac59cf01ca213f50756fe1155c to your computer and use it in GitHub Desktop.
Save whereisaaron/d4e94bac59cf01ca213f50756fe1155c to your computer and use it in GitHub Desktop.
Support for automatic restarting for Apache after mod_md renews a certificate and invokes `MDMessageCmd renewed` command
# ... more config ...
MDMessageCmd /usr/local/sbin/md_event
# ... more config ...
#--------------------------------------
# Build md_event command
#--------------------------------------
FROM gcc:bullseye as build
COPY ./md_event.c .
RUN gcc md_event.c -o md_event
#--------------------------------------
# Apache container
#--------------------------------------
FROM ubuntu/apache2:2.4-22.04_beta
# ...Configure apache2...
COPY --from=build md_event /usr/local/sbin/md_event
RUN chmod u+s /usr/local/sbin/md_event
COPY ./md_event.sh /usr/local/sbin/
/*
* 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
@whereisaaron
Copy link
Author

whereisaaron commented Sep 6, 2022

This utility works with the excellent mod_md module for Apache httpd that issues and renews TLS certificates using the ACME protocol:

https://github.com/icing/mod_md

After a certificate is renewed mod_md involved any configured MDMessageCmd command with the argument renewed. This code here provides a md_event binary that can be installed as a setuid command and will execute a md_event.sh script that will invoke a graceful restart of httpd, enabling it to load the new certificate.

The Dockerfile snippet illustrates a multistage container build to build and install this tool.

FROM gcc:bullseye as build
COPY ./md_event.c .
RUN gcc md_event.c -o md_event

FROM ubuntu/apache2:2.4-22.04_beta
# ...Configure apache2...
COPY --from=build md_event /usr/local/sbin/md_event
RUN chmod u+s /usr/local/sbin/md_event
COPY ./md_event.sh /usr/local/sbin/

You must also configure mod_md to invoke the md_event command:

MDMessageCmd /usr/local/sbin/md_event

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment