diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 64045e2c..ad6752ac 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,8 +1,8 @@ FROM alpine:edge -RUN apk add --no-cache nginx nginx-mod-mail python py-jinja2 +RUN apk add --no-cache nginx nginx-mod-mail python py-jinja2 certbot openssl COPY conf /conf -COPY start.py /start.py +COPY *.py / CMD /start.py diff --git a/nginx/conf/nginx.conf b/nginx/conf/nginx.conf index 46b36e1f..f1926000 100644 --- a/nginx/conf/nginx.conf +++ b/nginx/conf/nginx.conf @@ -21,7 +21,8 @@ http { server { listen 80; - {% if TLS_FLAVOR != 'notls' %} + # TLS configuration + {% if TLS and not TLS_ERROR %} listen 443 ssl; ssl_protocols TLSv1.1 TLSv1.2; @@ -29,8 +30,8 @@ http { ssl_prefer_server_ciphers on; ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - ssl_certificate /certs/cert.pem; - ssl_certificate_key /certs/key.pem; + ssl_certificate {{ TLS[0] }}; + ssl_certificate_key {{ TLS[1] }}; add_header Strict-Transport-Security max-age=15768000; @@ -39,7 +40,18 @@ http { } {% endif %} + {% if TLS_FLAVOR == 'letsencrypt' %} + location ^~ /.well-known/acme-challenge/ { + proxy_pass http://localhost:8000; + } + {% endif %} + # Actual logic + {% if TLS_ERROR %} + location / { + return 403 + } + {% else %} {% if WEBMAIL != 'none' %} location / { return 301 $scheme://$host/webmail/; @@ -52,6 +64,9 @@ http { {% endif %} {% if ADMIN == 'true' %} + location /admin { + return 301 $scheme://$host/admin/ui; + } location /admin/ui { rewrite ^/admin/(.*) /$1 break; proxy_pass http://admin; @@ -64,11 +79,12 @@ http { proxy_pass http://webdav:5232; } {% endif %} + {% endif %} } } mail { - server_name {{ HOSTNAME }}; + server_name {{ HOSTNAMES.split(",")[0] }}; auth_http http://{{ ADMIN_ADDRESS }}/internal/nginx; proxy_pass_error_message on; diff --git a/nginx/config.py b/nginx/config.py new file mode 100755 index 00000000..7408a5c6 --- /dev/null +++ b/nginx/config.py @@ -0,0 +1,27 @@ +#!/usr/bin/python + +import jinja2 +import os +import socket + +convert = lambda src, dst, args: open(dst, "w").write(jinja2.Template(open(src).read()).render(**args)) + +args = os.environ.copy() + +if "ADMIN_ADDRESS" not in os.environ: + args["ADMIN_ADDRESS"] = socket.gethostbyname("admin") + +args["TLS"] = { + "cert": ("/certs/cert.pem", "/certs/key.pem"), + "letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem", + "/certs/letsencrypt/live/mailu/privkey.pem"), + "notls": None +}[args["TLS_FLAVOR"]] + +if args["TLS"] and not all(os.path.exists(file_path) for file_path in args["TLS"]): + print("Missing cert or key file, disabling TLS") + args["TLS_ERROR"] = "yes" + + +convert("/conf/nginx.conf", "/etc/nginx/nginx.conf", args) +os.system("nginx -s reload") diff --git a/nginx/letsencrypt.py b/nginx/letsencrypt.py new file mode 100755 index 00000000..18aea292 --- /dev/null +++ b/nginx/letsencrypt.py @@ -0,0 +1,29 @@ +#!/usr/bin/python + +import os +import time +import subprocess + + +command = [ + "certbot", + "-n", "--agree-tos", # non-interactive + "-d", os.environ["HOSTNAMES"], + "-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]), + "certonly", "--standalone", + "--cert-name", "mailu", + "--preferred-challenges", "http", "--http-01-port", "8000", + "--keep-until-expiring", + "--rsa-key-size", "4096", + "--config-dir", "/certs/letsencrypt", + "--post-hook", "/config.py" +] + +# Wait for nginx to start +time.sleep(5) + +# Run certbot every hour +while True: + subprocess.call(command) + time.sleep(3600) + diff --git a/nginx/start.py b/nginx/start.py index e5e13328..daf05e1a 100755 --- a/nginx/start.py +++ b/nginx/start.py @@ -1,13 +1,15 @@ #!/usr/bin/python -import jinja2 import os -import socket - -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +import subprocess + # Actual startup script -if "ADMIN_ADDRESS" not in os.environ: - os.environ["ADMIN_ADDRESS"] = socket.gethostbyname("admin") -convert("/conf/nginx.conf", "/etc/nginx/nginx.conf") +if not os.path.exists("/certs/dhparam.pem") and os.environ["TLS_FLAVOR"] != "notls": + os.system("openssl dhparam -out /certs/dhparam.pem 4096") + +if os.environ["TLS_FLAVOR"] == "letsencrypt": + subprocess.Popen(["/letsencrypt.py"]) + +subprocess.call(["/config.py"]) os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"])