From 1aa97c9914a4663f7478b5b6b769eb20feb587b0 Mon Sep 17 00:00:00 2001 From: Dario Ernst Date: Thu, 6 Dec 2018 16:48:26 +0100 Subject: [PATCH] Add certificate watcher for external certs to reload nginx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In case of TLS_FLAVOR=[mail,cert], the user supplies their own certificates. However, since nginx is not aware of changes to these files, it cannot reload itself e.g. when the certs get renewed. To solve this, let’s add a small daemon in the place of `letsencrypt.py`, which uses a flexible file-watching framework and reloads nginx in the case the certificates change …. --- core/nginx/Dockerfile | 2 +- core/nginx/certwatcher.py | 63 +++++++++++++++++++++++++++++++++++++++ core/nginx/start.py | 2 ++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100755 core/nginx/certwatcher.py diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index 7181487e..37b8580b 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -7,7 +7,7 @@ RUN apk add --no-cache \ RUN pip3 install jinja2 # Image specific layers under this line RUN apk add --no-cache certbot nginx nginx-mod-mail openssl curl \ - && pip3 install idna requests + && pip3 install idna requests watchdog COPY conf /conf COPY *.py / diff --git a/core/nginx/certwatcher.py b/core/nginx/certwatcher.py new file mode 100755 index 00000000..3a8682f9 --- /dev/null +++ b/core/nginx/certwatcher.py @@ -0,0 +1,63 @@ +#!/usr/bin/python3 +""" +Certificate watcher which reloads nginx or reconfigures it, depending on what +happens to externally supplied certificates. Only executed by start.py in case +of TLS_FLAVOR=[mail, cert] +""" + +from os.path import exists, split as path_split +from os import system +import time +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler, FileDeletedEvent, \ + FileCreatedEvent, FileModifiedEvent, FileMovedEvent + +class ChangeHandler(FileSystemEventHandler): + "watchdog-handler listening on any event, executing the correct configuration/reload steps" + @staticmethod + def reload_nginx(): + "merely reload nginx without re-configuring everything" + if exists("/var/run/nginx.pid"): + print("Reloading a running nginx") + system("nginx -s reload") + + @staticmethod + def reexec_config(): + "execute a reconfiguration of the system, which also reloads" + print("Reconfiguring system") + system("/config.py") + + def on_any_event(self, event): + "event-listener checking if the affected files are the cert-files we're interested in" + if event.is_directory: + return + + filename = path_split(event.src_path)[-1] + if isinstance(event, FileMovedEvent): + filename = path_split(event.dest_path)[-1] + + if filename in ['cert.pem', 'key.pem']: + # all cases except for FileModified need re-configure + if isinstance(event, (FileCreatedEvent, FileMovedEvent, FileDeletedEvent)): + ChangeHandler.reexec_config() + # file modification needs only a nginx reload without config.py + elif isinstance(event, FileModifiedEvent): + ChangeHandler.reload_nginx() + # cert files have been moved away, re-configure + elif isinstance(event, FileMovedEvent) and path_split(event.src_path)[-1] in ['cert.pem', 'key.pem']: + ChangeHandler.reexec_config() + + +if __name__ == '__main__': + observer = Observer() + handler = ChangeHandler() + observer.schedule(handler, "/certs", recursive=False) + observer.start() + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop() + + observer.join() diff --git a/core/nginx/start.py b/core/nginx/start.py index 7c5fa71e..8673f148 100755 --- a/core/nginx/start.py +++ b/core/nginx/start.py @@ -9,6 +9,8 @@ if os.path.exists("/var/run/nginx.pid"): if os.environ["TLS_FLAVOR"] in [ "letsencrypt","mail-letsencrypt" ]: subprocess.Popen(["/letsencrypt.py"]) +elif os.environ["TLS_FLAVOR"] in [ "mail", "cert" ]: + subprocess.Popen(["/certwatcher.py"]) subprocess.call(["/config.py"]) os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"])