Ende Januar habe ich den Quelltext von PostTLS veröffentlicht und sehr positives Feedback bekommen. In den letzten Tagen habe ich PostTLS auf einem weiteren Produktivserver installiert. Da es noch keine umfassende Dokumentation der für die Einrichtung des Systems notwendigen Schritte gibt, möchte ich hier dokumentieren, welche Schritte in meinem konkreten Fall erforderlich waren.
Es handelte sich um ein ganz einfaches Postfix Relay, welches vor eine Groupware geschaltet ist. Es dient sowohl als MX host als auch als ausgehendes Mail Relay. Als Betriebssystem kommt Debian „Jessie“ zum Einsatz.
Auf geht's.
Zur Einrichtung der zweiten Postfix-Instanz wird auf die
postmulti
-Funktionalität zurück gegriffen. Sofern man damit nicht
vertraut ist, rate ich zur Lektüre der entsprechenden Dokumentation
(Managing multiple Postfix instances on a single host).
Wir aktivieren Multi Instance Support, sofern nicht bereits geschehen,
und erstellen eine neue Instanz, die wir postfix-out-notls
nennen.
$ postmulti -e init
$ postmulti -I postfix-out-notls -e create
Ich wurde dabei mit folgender Fehlermeldung konfrontiert:
postfix: warning: dict_open_dlinfo: cannot open /etc/postfix-out-notls/dynamicmaps.cf. No dynamic maps will be allowed.
Da die Datei dynamicmaps.cf
keine spezifischen Inhalte aufweist,
löse ich das Problem wie folgt.
$ cp /etc/postfix/dynamicmaps.cf /etc/postfix-out-notls/
Damit haben wir die neue Instanz eingerichtet, aber noch nicht
aktiviert (multi_instance_enable = no
). Wir können also in Ruhe
weiter konfigurieren.
<pre><code class="html">$ postmulti -l
Es sollte folgendes automatisch angelegt worden sein:
/etc/postfix-out-notls/
und/var/spool/postfix-out-notls/
.Bei der Konfiguration der neuen Postfix-Instanz gibt es lediglich zwei Besonderheiten:
Erstens soll der smtpd
nicht auf Port 25 lauschen, sondern auf einem
anderen Port. Ich habe 10026 verwendet. Dazu muss in der master.cf
so etwas stehen:
127.0.0.1:10026 inet n - n - - smtpd
Nun wollen wir, dass diese Instanz Mails auch dann zustellt, wenn der
Zielserver kein TLS unterstützt. Wir setzen daher in der main.cf
:
smtp_tls_security_level = may
Mit dieser Konfiguration kann die Postfix-Instanz nun aktiviert und gestartet werden:
$ postmulti -i postfix-out-notls -e enable
$ postmulti -i postfix-out-notls -p start
Man wird feststellen, dass der smtpd
der zweiten Instanz noch nicht
auf Port 10026 erreichbar ist. Das liegt daran, dass postmulti
bei
der Einrichtung der Instanz folgende Option in der main.cf
der
zweiten Instanz gesetzt hat:
master_service_disable = inet
Dieser Eintrag ist auszukommentieren. Nach einem Neustart von Postfix
sollte der zweite smtpd
erreichbar sein:
$ netstat -tulpen | grep 10026
tcp 0 0 127.0.0.1:10026 0.0.0.0:* LISTEN 0 35899 7093/master
Die primäre Postfix-Instanz soll nun so konfiguriert werden, dass
Mails zurück gehalten werden, wenn der Zielserver kein TLS
unterstützt. Dazu setzen wir in der Datei main.cf
folgende Option:
smtp_tls_security_level = encrypt
Ich betreibe PostTLS mit Nutzerrechten und gebe dem Benutzer nur für
die benötigten Kommandos die entsprechenden Rechte, diese per
sudo
aufzurufen.
Benutzer muss Befehle via sudo
ausführen können:
$ apt-get install sudo
$ adduser hendrik sudo
Dann über visudo
folgendes ergänzen:
# User hendrik can use apps needed for PostTLS
hendrik ALL = NOPASSWD: /usr/bin/mailq
hendrik ALL = NOPASSWD: /usr/sbin/postcat
hendrik ALL = NOPASSWD: /usr/sbin/postsuper
Also: Als Benutzer zunächst das Repository klonen:
$ mkdir ~/apps
$ cd ~/apps
$ git clone https://github.com/suenkler/PostTLS.git
Folgende Pakete installieren:
$ apt-get install python3-virtualenv python3-pip
$ apt-get install virtualenvwrapper
Nun eine virtuelle Python-Umgebung erstellen:
$ mkvirtualenv --python=/usr/bin/python3 posttls
Und die Abhängigkeiten installieren:
(posttls) $ pip install -r requirements.txt
Nun eine Datei env.sh
erstellen und die Einträge entsprechend der
eigenen Umgebung anpassen:
# Django configuration
export POSTTLS_SECRET_KEY="verysecretkey"
export POSTTLS_STATIC_ROOT_DIR="/home/hendrik/apps/posttls/static/"
export POSTTLS_MEDIA_ROOT_DIR="/home/hendrik/apps/posttls/media/"
# Set this to 'production' in production environment (see Django settings file)
export POSTTLS_ENVIRONMENT_TYPE="development"
# PostTLS settings
export POSTTLS_NOTIFICATION_SENDER="postmaster@domain.com (Postmaster)"
export POSTTLS_NOTIFICATION_SMTP_HOST="localhost"
# Needed to generate the links in the notification mail:
export POSTTLS_TLS_HOST="server.domain.com"
export POSTTLS_NOTIFICATION_SYSADMIN_MAIL_ADDRESS="sysadmin@domain.de"
Es sollte sicher gestellt sein, dass die oben genannten Verzeichnisse bestehen!
(posttls) $ source env.sh
(posttls) $ ./manage.py migrate
Nun sollte der folgende Befehl ohne Fehlermeldungen ausgeführt werden können:
(posttls) $ ./manage.py process_queue
Nun ist es an der Zeit, dass wir testen, ob PostTLS auch tut, was es soll. Dafür benötigen wir eine Mail, die wegen fehlender TLS-Unterstützung der Gegenseite in der Queue hängen bleibt. Das sieht dann so ähnlich aus:
$ mailq
-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
5D7FB1FC64 28198 Tue Mar 8 12:41:59 user@internal-domain.de
(TLS is required, but was not offered by host mail.domain.de[192.168.10.7])
user@external-domain.de
-- 28 Kbytes in 1 Request.
Wenn man nun erneut ./manage.py process_queue
aufruft, sollte
PostTLS dem Absender, hier user@internal-domain.de
eine
entsprechende Nachricht zusenden.
Der Entwicklungsserver von Django kann mit folgendem Befehl gestartet werden:
(posttls) $ ./manage.py runserver 0.0.0.0:8080
Bitte beachten Sie, dass die Links in der Benachrichtigungsmail auf „https“ lauten, der Entwicklungsserver aber nur die unverschlüsselte Variante unterstützt. Ändern Sie für diesen Test einfach die URL entsprechend.
Wenn alles funktioniert, kann es weiter gehen.
Bisher haben wir den Entwicklungsserver von Django verwendet. Dieser sollte im Produktivbetrieb natürlich nicht zum Einsatz kommen. Ich verwende immer gerne Gunicorn hinter einem Nginx Reverse Proxy.
(posttls) $ pip install gunicorn
Das folgende Script liegt in
/home/hendrik/apps/posttls/gunicorn_start
:
#!/bin/bash
NAME="posttls"
DJANGODIR=/home/hendrik/apps/posttls/PostTLS/posttls
SOCKFILE=/home/hendrik/apps/posttls/run/gunicorn.sock
USER=hendrik
GROUP=hendrik
NUM_WORKERS=3
DJANGO_SETTINGS_MODULE=config.settings.base
DJANGO_WSGI_MODULE=config.wsgi
echo "Starting $NAME"
cd $DJANGODIR
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR
# set environment variables
source /home/hendrik/apps/posttls/env.sh
# start application
exec /home/hendrik/.virtualenvs/posttls/bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
--user=$USER --group=$GROUP \
--log-level=debug \
--bind=unix:$SOCKFILE
Debian Jessie verwendet systemd
. Wir erstellen also die Datei
/etc/systemd/system/django.service
mit folgendem Inhalt:
[Unit]
Description=PostTLS
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/home/hendrik/apps/posttls/PostTLS
ExecStart=/home/hendrik/apps/posttls/gunicorn_start
[Install]
WantedBy=multi-user.target
Anschließend können wir den neuen Service aktivieren und starten:
$ systemctl enable django.service
$ systemctl start django.service
Nun fehlt noch Nginx. Zuerst wird Nginx installiert:
$ apt-get install nginx
Dann ergänzen wir folgenden Eintrag...
upstream posttls_server {
server unix:/home/hendrik/apps/posttls/run/gunicorn.sock fail_timeout=0;
}
server {
listen 8080 ssl;
server_name posttls.domain.de;
ssl_certificate /etc/letsencrypt/live/posttls.domain.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/posttls.domain.de/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/dhparams.pem;
client_max_body_size 4G;
access_log /home/hendrik/apps/posttls/logs/nginx-access.log;
error_log /home/hendrik/apps/posttls/logs/nginx-error.log;
location /static/ {
alias /home/hendrik/apps/posttls/static/;
}
location /media/ {
alias /home/hendrik/apps/posttls/media/;
}
location / {
# an HTTP header important enough to have its own Wikipedia entry:
# http://en.wikipedia.org/wiki/X-Forwarded-For
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# enable this if and only if you use HTTPS, this helps Rack
# set the proper protocol for doing redirects:
proxy_set_header X-Forwarded-Proto https;
# pass the Host: header from the client right along so redirects
# can be set properly
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
# Deny all except my ip adress
#allow XXX.XXX.XXX.XXX;
#deny all;
# Serve static files from nginx
if (!-f $request_filename) {
proxy_pass http://posttls_server;
break;
}
}
}
Falls Nginx aus dem Internet erreichbar sein muss, macht es ggf. Sinn, den Zugriff per IP-Adresse zu beschränken (oben auskommentiert):
allow XXX.XXX.XXX.XXX;
deny all;
Der Django-Entwicklungsserver liefert die Static Files direkt mit aus. Bei der Verwendung von Nginx müssen wir die Static Files aber noch in einem gesonderten Verzeichnis für Nginx verfügbar machen; vgl. dazu die Einstellungen von Nginx oben und die Django Settings. Sind diese Werte korrekt gesetzt, kann mit folgendem Befehl der Kopiervorgang gestartet werden:
(posttls) $ ./manage.py collectstatic
Dies sollte es nun sein. Wir testen das Ganze nochmals, indem wir eine Mail an einen Empfänger senden, dessen Mailserver kein TLS unterstützt:
mailq
./manage.py process_queue
.Wenn alles funktioniert, können wir den Vorgang automatisieren.
Zu guter Letzt können wir einen Cron Job einrichten, der PostTLS jede Minute aufruft und damit in der Postfix Queue nach „hängengebliebenen“ Mails sucht und bei Erfolg die Absender per Mail darüber benachrichtigt. Wir verwenden dabei Flock, um sicher zu stellen, dass sich keine PostTLS-Prozesse überschneiden.
*/1 * * * * . /home/hendrik/apps/posttls/env.sh && /usr/bin/flock -w 0 /home/hendrik/apps/posttls/cron.lock /home/hendrik/.virtualenvs/posttls/bin/python3 /home/hendrik/apps/posttls/posttls/posttls/manage.py process_queue >/dev/null 2>&1
Es sind derzeit zugegebenermaßen eine Reihe von Schritten erforderlich, um PostTLS auf einem Server zu installieren. Soweit sinnvoll, werde ich diese Schritte in Zukunft automatisieren.
PostTLS setzt in der Regel jedoch an einer bestehenden, möglicherweise sehr komplexen Postfix-Installation an. Es wäre risikoreich und auch schlechter Stil, in diese bestehende Konfiguration automatisiert einzugreifen. Insofern werde ich mit der Automatisierung zurückhaltend sein.