Zur digitalen Selbstbestimmung mit einem virtuellen privaten Server (VPS)

Stand : 04.02.2023
Autor : Gerd Raudenbusch

Inhalt

Die Intension der digitalen Selbstbestimmung ist es, jederzeit die absolute Kontrolle über die eigenen persönlichen und sensiblen Daten zu haben : Emails, Messenger-Chats, Kontakte, Kalender. Und dies von überall und mit jedem Gerät. Dadurch ist man unabhängiger von Big Tech (Google, Apple, Microsoft) und von deren Datenschutzbestimmungen, die mitunter haarsträubend sind. Es besteht außerdem die Möglichkeit eines öffentlichen Internet-Auftritts (Webseite, Blog, Webshop, Wiki, o. ä.). Und dies für die ganze Familie und Freunde, und für unter 50€ im Jahr !

Den Server mieten

Bei IONOS kann man für bereits 1€/Monat den VPS Linux XS mit 1GB RAM und 10GB SSD mieten. Um realistisch zu bleiben, empfehle ich den Tarif VPS Linux S mit 2GB RAM und 80GB SSD für 2€/Monat plus eine Domain, die i. d. R. 2€/Monat kostet. Im Jahr sind das 48€, davon wird man nicht arm, und es reicht aus, um sich digital komplett auf EIGENE Füße zu stellen.

Erste Inbetriebnahme

Auf die eine oder andere Weise bekommt man nach Abschluß des Vertrags über ein Webportal Zugriff auf Domain und den Linux-Server. Dies beinhaltet auch die IP-Adresse, den Benutzername und das Passwort. Damit kann man sich per SSH auf seinem virtuellen Rechner im Netz anmelden.

Benutzer administrieren

SSH-Zugang absichern

Da dieser ein beliebtes Angriffsziel von Hackern auf virtueller Server ist, sollte man den SSH-Zugang nachhaltig mit den folgenden Maßnahmen absichern :

SSH Port ändern

Der reguläre SSH-Port ist TCP/22, den man durch eine beliebige andere Nummer zwischen 1024-65535 ersetzen kann. In der Datei /etc/ssh/sshd_config ändert man mit dem Texteditor seiner Wahl die Zeile Port 22 z.B. in Port 5678. Danach startet man den SSH-Dienst neu mit systemctl restart sshd. Achtung : Portänderungen auf virtuellen Servern müssen normalerweise über das VPS-Portal in den Netzwerkeinstellungen (Firewall/Portfreigaben) mitgezogen, sprich, dort EBENSO geändert werden.

Zertifikatsbasierte Authentifizierung einrichten

Weiterhin sollte man die Passwort-basierte Anmeldung abschalten, nachdem man die zertifikatsbasierte Anmeldung für jeden Benutzer aktiviert hat.

Für jeden Benutzer :

Passwort-basierte Authentifizierung abschalten

Dazu setzt man in der Datei /etc/ssh/sshd_config die Option PasswordAuthentication auf no. Dies gilt ebenso für alle weiteren Dateien unter /etc/ssh/sshd_config.d.

Zertifikate für Domain erstellen

Da heutzutage HTTPS zum Standard geworden ist, werden gültige TLS-Zertifikate gebraucht, die an einen Domainnamen gebunden sind. Das Ausstellen von TLS-Zertifikaten kostet normalerweise immer Geld. Glücklicherweise hat es sich letsencrypt.org schon vor einiger Zeit zur Aufgabe gemacht, für die massive Verbreitung von HTTPS zu sorgen, indem sie TLS-Zertifikate für Webserver kostenlos ausstellen.

In Ubuntu ist das ein Einzeiler : apt install -y letsencrypt && letsencrypt certonly --standalone -d meine-domain.de && letsencrypt certonly --standalone -d meine-zweite-domain.de

Der Mozilla SSL Konfigurator bietet hilfreiche Konfigurations-Vorlagen für eine Vielzahl von Anwendungen. Mit Qualys SSL Labs oder den Online SSL-Tools gibt es die Möglichkeit, HTTPS-Zertifikate zu testen.

Primäre DNS-Einträge konfigurieren

Wer eine Domain mietet, sollte dabei auf die Möglichkeit bestehen, die von der IANA definierten DNS-Records vollständig selbst kontrollieren zu können.

Dies ist notwendig, damit die verschiedenen Dienste des VPS auch über die Domain erreichbar sind. Zur Kontrolle können wir z. B. den öffentlichen DNS-Resolver von Cloudflare (1.1.1.1) bemühen, ob wir auch wirklich gefunden werden.

Feld Name Wert Überprüfung
A @ Ipv4-Adresse des VPS (z. B. 1.2.3.4) dig @1.1.1.1 +short A meine-domain.de liefert die eigene öffentliche IPv4, z. B. 1.2.3.4
AAAA @ IPv6-Adresse des VPS dig @1.1.1.1 +short AAAA meine-domain.de liefert z. B. fe80::1:dead:beef:1234

Anmerkungen :

Docker installieren

Docker-Images kann man sich als Archive vorstellen, die darüberhinaus lauffähig sind. Sie ermöglichen :

Registrieren muss man sich im offiziellen öffentlichen Docker-Repository hub.docker.com nur dann, wenn man selbst eigene Images dort hinterlegen will, aber nicht, wenn man sie nur runterlädt (pullt). Wenn dort eine Anwendung als Image mal nicht zu finden ist, kann man dieses auch auf der Grundlage eines blanken Betriebssystem-Images selbst erstellen, um sich die mitunter mühsame Installationsarbeit nur ein einziges einmal machen zu müssen.

Laufende Images nennt man Container. Die Laufzeit-Plattform, auf der Images verwaltet werden und die Container laufen, könnte man zwar eigentlich mit : sudo apt update && sudo apt -y upgrade && sudo apt -y install docker installieren; aber in diesem Fall lohnt sich, die apt-Quellen von Docker zu verwenden :

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo apt-key add - && sudo add-apt-repository
"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \
sudo apt -y install docker docker-ce docker-ce-cli containerd.io

Docker Cheatsheet

Befehl Funktion
docker pull <image-name>[:tag] Zieht ein externes Docker Image ins lokale Docker-Repository.
docker images Zeigt alle Images des lokalen Docker-Repos.
docker image rm -f <image-name>[:tag] Löscht ein Image aus dem lokalen Repo. Alle laufenden Container des Images werden dabei beendet.
docker run <optionen> <image-name>[:tag] Startet ein Image aus dem lokalen Repo als neuen Container. Container können vielfach aus ein und demselben Image gestartet werden. Folgende Optionen werden besonders häufig benötigt :
  • --name <container-name> Hilft dabei, den Container automatisiert zu identifizieren. Wird ansonsten zufällig vergeben.
  • -d startet den Container im Hintergrund ("detached")
  • -p <host-port>:<container-port> ordnet einen Netzwerkport des Containers dem Host zu
  • -v <host-dir>:<container-dir> führt ein Verzeichnis des Containers nach außen bzw. blendet ein äußeres Verzeichnis in den Container ein. Merke : Nur die symbolischen Links innerhalb der Freigabe sind aktiv !
  • -e <Variable>=<Wert> setzt eine Umgebungsvariable (Environment) im Container.
docker pull ubuntu:latest && docker run --name <container-name> -d -t ubuntu:latest sleep infinity Dieser Befehl instanziert z. B. einen blanken Ubuntu-Container als Grundlage für eigene Anwendungen; es sind aber auch andere Linux-Derivate als Grundlage möglich. Das sleep infinity am Ende ist nötig, weil sich Container beenden, sobald nichts mehr im Vordergrund läuft. Es kann später ersetzt werden.
docker ps Zeigt die aktuell laufenden Container
docker rm -f <container-name> Stoppt einen Container. Merke : Alle Änderungen innerhalb des Containers, die nicht im Image sind, gehen verloren !
docker logs <container-name> Zeigt die Ausgaben im Container
docker exec <container-name> <Kommando> Führt einen Befehl in einem Container aus.
docker exec -it <container-name> /bin/bash Öffnet eine interaktive Shell in einen Container.
docker commit <container-name> <image-name>[:tag] Überführt Änderungen, die im Container gemacht wurden, in ein Image und macht sie somit persistent. Wenn der gewählte Image-Name nicht existiert, wird ein neues Image erstellt.
docker save <image-name> [:tag] | gzip > <archiv.tar.gz> Speichert ein Image des lokalen Repos als tar.gz-Archiv
docker load < <archiv.tar> Lädt ein Image aus einem tar.gz-Archiv ins lokale Docker-Repo

Webserver

Einen Webserver zu haben, ist nicht für jeden notwendig, aber immer wieder ein adäquates Mittel, im eigene Informationen im Netz bereitzustellen. nginx ist ein leichtgewichtiger Webserver, der gut funktioniert.

Docker Image für Webserver installieren

Das Docker-Image "nginx" installiert man mit docker pull nginx

Webserver administrieren

Diese Docker-Optionen beim Start sind entscheidend :

Die an HTTPS angepasste Konfiguration des Webservers im Verzeichnis /etc/containers/webserver sieht so aus :

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	types_hash_max_size 2048;
	client_body_buffer_size 1K; 
	client_header_buffer_size 1k;
	client_max_body_size 1k; 
	large_client_header_buffers 2 1k;
	server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;


	#
	# HTTP listener redirects
	#

	server {
    		listen		80;
        	server_name	meine-domain.de;
		index		meine-domain.html;
		root		/usr/share/nginx/html/meine-domain.de;
		return		301 https://$host$request_uri;	
	}


	server {
		listen 		80;
		server_name 	meine-zweite-domain.de;
		index		meine-zweite-domain.html;
		root    	/usr/share/nginx/html/meine-zweite-domain.de;
		return 		301 https://$host$request_uri;
	}

	server {
		listen 		80;
		server_name 	zweiteseite.meine-zweite-domain.de;
		return 		301 https://meine-zweite-domain.de/zweiteseite/index.html;
	}

	#
	# HTTPS listeners
	#

	server {
		listen			443 ssl;
		server_name		meine-domain.de;
		ssl_certificate		/etc/certs/live/meine-domain.de/fullchain.pem;
		ssl_certificate_key	/etc/certs/live/meine-domain.de/privkey.pem;
		ssl_protocols		TLSv1.2 TLSv1.3;
		ssl_ciphers		HIGH:!aNULL:!MD5;
		index			meine-domain.html;
		root			/usr/share/nginx/html/meine-domain.de;
    }
        server { 
		listen			443 ssl default_server;
		server_name		meine-zweite-domain.de;
		ssl_certificate		/etc/certs/live/meine-zweite-domain.de/fullchain.pem;
		ssl_certificate_key	/etc/certs/live/meine-zweite-domain.de/privkey.pem;
		ssl_protocols		TLSv1.2 TLSv1.3;
		ssl_ciphers		HIGH:!aNULL:!MD5; 
		index			meine-zweite-domain.html;
		root			/usr/share/nginx/html/meine-zweite-domain.de;
	}

	 

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}

Folgendes rudimentäre Shellskript webserver.sh hilft dabei, die nginx-Webserver als Docker-Container zu kontrollieren. Sein Name (hier webserver) fordert gleichnamige Unterverzeichnisse in /etc/containers (Konfiguration), /var/log/containers (Logging) und /var/www (Webinhalte). Dies ermöglicht es, mehrere Webserver-Container gleichzeitig laufen zu lassen. Lediglich die äußeren Ports (vor dem Doppelpunkt) in den Optionen müssen dazu in den Kopien dieses Skripts angepasst werden.

#!/bin/bash
tag="latest"
if [ "${2}" != "" ]; then
	tag="${2}"
fi
name=$(basename ${0} |cut -d "." -f1)
img="nginx"
options="\
-v /etc/containers/${name}/${img}.conf:/etc/${img}/${img}.conf \
-v /var/www/${name}:/usr/share/nginx/html:ro \
-v /etc/letsencrypt:/etc/certs \
-v /var/log/containers/${name}:/var/log/${img} \
-d -p 80:80 -p 443:443"

mkdir -p /etc/containers/${name}
mkdir -p /var/www/${name}
mkdir -p /var/log/containers/${name}
#########################
echo "Container: ${name}"
if [ "${1}" == "start" ]; then
	echo "Starting"
	docker run --name ${name} ${options} ${img}:${tag}
elif [ "${1}" == "stop" ]; then 
	echo "Stopping"
	docker rm -f ${name}
elif [ "${1}" == "restart" ]; then
	docker rm -f ${name} && docker run --name ${name} ${options} ${img}:${tag}
elif [ "${1}" == "login" ]; then
	docker exec -it ${name} /bin/bash
elif [ "${1}" == "commit" ]; then
    echo "Committing"
	docker commit ${name} ${img}:${tag}
elif [ "${1}" == "load" ]; then
	echo "Loading"
	docker load < ${name}.tar.gz
elif [ "${1}" == "save" ]; then
	echo "Saving"
	docker save ${img}:${tag} | gzip > ${name}.tar.gz
elif [ "${1}" == "stats" ]; then  
	docker exec -it ${name} curl http://127.0.0.1/webstats
else
	echo "Unknown : ${1}"
	exit 1
fi

Das Skript erzeugt man mit einem favorisierten Texteditor in /usr/local/bin und macht es mit chmod 775 /usr/local/bin/webserver.sh ausführbar. Das Skript unterstützt folgende Optionen :

Option Funktion
start Start des Containers
stop Stop des Containers
login Shell im Container öffnen
commit Manuell getätigte Änderungen im laufenden Container ins Image übernehmen
save Image von Docker in Archiv speichern
load Image aus Archiv in Docker laden

Kalender und Kontakte

Docker Image für Nextcloud installieren

Das Docker-Image "Nextcloud" installiert man mit docker pull nextcloud.

Auch hier hilft wieder ein Steuerskript nextcloud.sh :

#!/bin/bash
tag="latest"
if [ "${2}" != "" ]; then
	tag="${2}"
fi
name=$(basename ${0} |cut -d "." -f1)
img="nextcloud"
options="-d -p 8899:443 -v /var/www/${img}:/var/www/html -v /etc/letsencrypt:/etc/certs --log-driver syslog"
#########################

mkdir -p /var/www/${name}
mkdir -p /var/log/containers/${name}

echo "Container: ${name}"
if [ "${1}" == "start" ]; then
	echo "Starting"
	docker run --name ${name} ${options} ${img}:${tag}
	docker logs -f ${name} > /var/log/containers/${name}/${name}.log 2>&1 &
elif [ "${1}" == "stop" ]; then 
	echo "Stopping"
	docker rm -f ${name}
	#kill $(ps axn | grep "docker logs -f nextcloud" | grep -v grep | cut -d " " -f1)
elif [ "${1}" == "restart" ]; then
	#kill $(ps axn | grep "docker logs -f nextcloud" | grep -v grep | cut -d " " -f1)
	docker rm -f ${name} && docker run --name ${name} ${options} ${img}:${tag}
	docker logs -f ${name} > /var/log/containers/${name}/${name}.log 2>&1 &
elif [ "${1}" == "login" ]; then
	docker exec -it ${name} /bin/bash
elif [ "${1}" == "commit" ]; then  
        echo "Committing"
	docker commit ${name} ${2}:${tag}
elif [ "${1}" == "load" ]; then
	echo "Loading"
	docker load < ${name}.tar.gz
elif [ "${1}" == "save" ]; then
	echo "Saving"
	docker save ${img}:${tag} | gzip > ${name}.tar.gz
else
	echo "Unknown : ${1}"
	exit 1
fi

Das Skript unterstützt folgende Optionen :

Option Funktion
start Start des Containers
stop Stop des Containers
login Shell im Container öffnen
commit Manuell getätigte Änderungen im laufenden Container ins Image übernehmen
save Image von Docker in Archiv speichern
load Image aus Archiv in Docker laden

Nextcloud für HTTPS anpassen

Damit auch Nextcloud die TLS-Zertifikate für HTTPS benutzt, muss der verwendete Apache-Webserver umkonfiguriert werden. Im folgenden Skript passt man meine-domain.de an die eigenen Bedürfnisse an und legt es zunächst auf dem Host ab :

#!/bin/bash
# setssl.sh

echo 'SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
SSLProtocol All -SSLv2 -SSLv3
SSLHonorCipherOrder On
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains"
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
SSLCompression off
SSLSessionTickets Off' > /etc/apache2/conf-available/ssl-params.conf
echo "<IfModule mod_ssl.c>
        <VirtualHost _default_:443>
                ServerAdmin root@meine-domain.de
                ServerName meine-domain.de
" > /etc/apache2/sites-available/default-ssl.conf
echo '
                DocumentRoot /var/www/html

                ErrorLog ${APACHE_LOG_DIR}/error.log
                CustomLog ${APACHE_LOG_DIR}/access.log combined

                SSLEngine on

                SSLCertificateFile    /etc/certs/live/meine-domain.de/fullchain.pem
                SSLCertificateKeyFile /etc/certs/live/meine-domain.de/privkey.pem

                <FilesMatch "\.(cgi|shtml|phtml|php)$">
                                SSLOptions +StdEnvVars
                </FilesMatch>
                <Directory /usr/lib/cgi-bin>
                                SSLOptions +StdEnvVars
                </Directory>
        </VirtualHost>
</IfModule>' >> /etc/apache2/sites-available/default-ssl.conf
a2enmod ssl >/dev/null
a2ensite default-ssl >/dev/null
a2enconf ssl-params >/dev/null
apachectl restart

Nachdem man den Nextcloud-Container mit dem Steuerskript gestartet hat, führt man anschließend das Anpassungsskript im Container aus :

docker exec -i nextcloud bash < ./setssl.sh (man beachte die zwei Leerzeichen nach 'bash')

Nun sollte Nextcloud über https://meine-domain.de:7575 erreichbar sein. Ein kurzer abschließender Blick, ob Nextcloud widersandsgähig genug ist, schadet nicht. Ist dies der Fall, kann man die erfolgreichen Anpassungen des laufenden Nextcloud-Containers im Docker-Image persistieren, damit sie den Neustart des Containers überleben : docker commit nextcloud nextcloud:latest (oder mit Steuerskript : nextcloud.sh commit)

Das Nextcloud-Portal ist nun auf https://meine-domsin.de:8899 erreichbar.

iOS Kalender und Erinnerungen auf Nextcloud umstellen

Das Nextcloud-Benutzerhandbuch beschreibt :

Hinweis : Bei Verwendung des TOTP-Moduls braucht iOS ein App-Passwort, anstelle des Konto-Passworts. App-Passwörter können in den Benutzereinstellungen von Nextcloud vergeben werden.

iOS Adressbuch auf Nextcloud umstellen

Das Nextcloud-Benutzerhandbuch beschreibt :

Abschließend kann man unter Einstellungen ➔ Apple ID, iCloud, Medien und Käufe ➔ iCloud ➔ Alle anzeigen die Reiter "Kontakte", "Kalender" und "Erinnerungen" ausschalten.

Hinweis : Bei Verwendung des TOTP-Moduls braucht iOS ein App-Passwort, anstelle des Konto-Passworts. App-Passwörter können in den Benutzereinstellungen von Nextcloud vergeben werden.

Email-Server

Der Docker-Mailserver ist sehr gut dokumentiert.

Ports für Mailserver konfigurieren

TCP-Port Protokoll Beschreibung
465 SMTP (StartTLS) Von Email-Client zu Email-Server
587 SMTP (TLS) Von Email-Client zu Email-Server
25 SMTP Von Email-Server zu Email-Server
143 IMAP (StartTLS) Von Email-Server zu Email-Client
993 IMAP (TLS) Von Email-Server zu Email-Client

Die Ports : Neben den Ports 143, 465, 587 und 993 ist vor allem Port 25 zwingend notwendig, auf dem die Email-Server explizit unter sich kommunizieren. Manche VPS-Provider öffnen ihn erst gar nicht. IONOS bezeichnet es als eine "vertrauensbildende Maßnahme", dies nur nach kurzem, persönlichen Gespräch zu tun, damit ausgeschlossen wird, daß irgendwelche Spam-Bots im Spiel sind.

DNS-Records für Mailserver konfigurieren

Für den Mailserver müssen einige DNS-Records konfiguriert werden, bei deren Konfiguration das Online-Tool mxtoolbox.com sehr hilfreich sein kann. Diese DNS-Records müssen konfiguriert werden :

Feld Name Wert Überprüfung
A mail Ipv4-Adresse des VPS dig @1.1.1.1 +short A mail.meine-domain.de liefert die eigene öffentliche IPv4, z. B. 1.2.3.4
AAAA mail IPv6-Adresse des VPS dig @1.1.1.1 +short AAAA mail.meine-domain.de liefert z. B. fe80::1:dead:beef:1234
MX @ mail.meine-domain.de dig @1.1.1.1 +short MX liefert mail.meine-domain.de
PTR meine-domain.de 4.3.2.1.in-addr.arpa dig @1.1.1.1 +short -x 1.2.3.4 liefert meine-domain.de, bzw. nslookup 1.2.3.4 liefert 4.3.2.1.in-addr.arpa name = meine-domain.de

Neben A müssen insbesondere PTR und MX korrekt sitzen. Die SPF-, DMARC- und DKIM-Records sind zwar optional, aber so wenig aufwändig, daß es nicht lohnt, sie zu vernachlässigen.

SPF, DMARC und DKIM

Die Dokumentation zitiert hierzu die Beschreibung von Cloudflare :

»DMARC, DKIM und SPF sind drei Methoden zur E-Mail-Authentifizierung. Zusammen eingesetzt verhindern sie, dass Spammer, Phisher und andere Unbefugte E-Mails im Namen einer fremden Domain* versenden.

DKIM und SPF sind vergleichbar mit einer Geschäftslizenz oder dem Doktortitel eines Arztes, der in der Praxis hängt, sie demonstrieren Legitimität.

DMARC sagt den Mailservern, was sie bei einem erfolglosen DKIM oder SPF tun sollen; die E-Mail als Spam markieren, die E-Mail trotzdem zustellen oder die E-Mail ganz löschen.

Domains, die SPF, DKIM und DMARC nicht korrekt eingerichtet haben, riskieren, dass ihre E-Mails als Spam unter Quarantäne gestellt werden oder nicht beim Empfänger ankommen. Außerdem besteht für sie die Gefahr, von Spammern imitiert zu werden.«

SPF-Record setzen

Das Sender Policy Framework (SPF) erlaubt einer Domain die Auflistung aller Server, von denen sie E-Mails sendet. Es ist wie ein öffentlich zugängliches Mitarbeiterverzeichnis, in dem man prüfen kann, ob ein Mitarbeiter für ein Unternehmen arbeitet. Mailserver, die eine E-Mail-Nachricht empfangen, gleichen die Nachricht mit dem SPF-Eintrag ab, bevor sie an den Posteingang des Empfängers weitergeleitet wird.

Für unseren Mailserver sieht dieser Eintrag so aus :

Feld Name Wert Überprüfung
TXT @ v=spf1 mx -all dig +short TXT meine-domain.de

DMARC-Record setzen

Domain-based Message Authentification Reporting and Conformance (DMARC) teilt einem empfangenden E-Mail-Server mit, was er mit den Ergebnissen der SPF- und DKIM-Prüfung tun soll. Die DMARC-Richtlinie einer Domain lässt sich auf verschiedene Weise festlegen: Sie kann Mailserver anweisen, alle E-Mails, die SPF oder DKIM (oder beides) nicht bestehen, unter Quarantäne zu stellen, und sie kann sie anweisen, solche E-Mails abzulehnen oder sie zuzustellen.

Für unseren Mailserver sieht dieser Eintrag so aus :

Feld Name Wert Überprüfung
TXT _dmarc v=DMARC1; p=none; sp=none; fo=0; adkim=r; pct=100; rf=afrf; ri=86400; rua=mailto:dmarc.report@meine-domain.de; ruf=mailto:dmarc.report@meine-domain.de dig +short TXT _dmarc.meine-domain.de

DKIM aktivieren

Mit DomainKeys Identified Mail (DKIM) können Domainbesitzer E-Mails von ihrer Domain automatisch „signieren“, ähnlich wie Sie mit einer Unterschrift auf einem Scheck bestätigen, wer den Scheck ausgestellt hat. Die DKIM-„Signatur“ ist eine digitale Unterschrift, mithilfe von Kryptographie verifiziert sie mathematisch, dass die E-Mail von der Domain stammt.

Der Befehl docker exec -it mailserver setup config dkim erzeugt das entsprechende Zertifikatsmatetial im Verzeichnis /etc/containers/mail/config/opendkim/keys des VPS. Für jede eigene Domain der registrierten Benutzer wird dadurch ein Verzeichnis angelegt. Wenn man einen Blick in die Datei mail.txt des Unterverzeichnisses meine-domain.de wirft, kann man damit den DNS-Record Eintrag erstellen. Den DKIM-Key könnte man daraus z. B. so extrahieren : cat mail.txt | tr '\n' ' ' | sed -En "s/.*(p=.*)\".*/\1/p" | sed "s/\"//g; s/ //g"

Dann kann man damit den DNS-Record setzen :

Feld Name Wert Überprüfung
TXT mail._domainkey v=DKIM1; h=sha256; k=rsa; p=<dkim-key> dig +short TXT mail._domainkey.meine-domain.de

Installation der Erweiterung docker-compose

Es empfiehlt sich für diesen Mailserver, die Installation über docker-compose zu machen. Das ist eine kleine Erweiterung, welche die Arbeit mit Docker sehr erleichtert.

Dieser Befehl installiert und überprüft die Erweiterung :

mkdir -p ~/.docker/cli-plugins/ && curl -SL https://github.com/docker/compose/releases/download/v2.3.3/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose && chmod +x ~/.docker/cli-plugins/docker-compose && docker compose version

Am Ende sollte die Version ausgegeben werden : Docker Compose version v2.3.3.

Konfigurationsdateien vorbereiten

Die Vorlage für eine Konfiguration, sowie die yaml-Datei zum Bau des Containers könnte man beziehen mit dem Befehl : DMS_GITHUB_URL="https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master" wget "${DMS_GITHUB_URL}/compose.yaml" wget "${DMS_GITHUB_URL}/mailserver.env"

Die beiden Dateien müssen den eigenen Bedürfnissen angepasst werden, sodaß die vollständige Datei compose.yaml so aussieht :

services:
  mailserver:
    image: ghcr.io/docker-mailserver/docker-mailserver:latest
    container_name: mailserver
    # Provide the FQDN of your mail server here (Your DNS MX record should point to this value)
    hostname: mail.meine-domain.de
    env_file: mailserver.env
    # More information about the mail-server ports:
    # https://docker-mailserver.github.io/docker-mailserver/latest/config/security/understanding-the-ports/
    # To avoid conflicts with yaml base-60 float, DO NOT remove the quotation marks.
    ports:
      - "25:25"    # SMTP  (explicit TLS => STARTTLS)
      - "143:143"  # IMAP4 (explicit TLS => STARTTLS)
      - "465:465"  # ESMTP (implicit TLS)
      - "587:587"  # ESMTP (explicit TLS => STARTTLS)
      - "993:993"  # IMAP4 (implicit TLS)
    volumes:
      - /var/mail:/var/mail/
      - /var/mail-state:/var/mail-state/
      - /var/log/containers/mail:/var/log/mail/
      - /etc/containers/mail/config:/tmp/docker-mailserver/
      - /etc/localtime:/etc/localtime:ro
      - /etc/letsencrypt:/etc/certs/ 
    restart: always
    stop_grace_period: 1m
    # Uncomment if using `ENABLE_FAIL2BAN=1`:
    cap_add:
       - NET_ADMIN
    healthcheck:
      test: "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"
      timeout: 3s
      retries: 0

Außerdem muss die Datei mailserver.env so aussehen :

# -----------------------------------------------
# --- Mailserver Environment Variables ----------
# -----------------------------------------------

# DOCUMENTATION FOR THESE VARIABLES IS FOUND UNDER
# https://docker-mailserver.github.io/docker-mailserver/latest/config/environment/

# -----------------------------------------------
# --- General Section ---------------------------
# -----------------------------------------------

# empty => uses the `hostname` command to get the mail server's canonical hostname
# => Specify a fully-qualified domainname to serve mail for.  This is used for many of the config features so if you can't set your hostname (e.g. you're in a container platform that doesn't let you) specify it in this environment variable.
OVERRIDE_HOSTNAME=mail.meine-domain.de

# REMOVED in version v11.0.0! Use LOG_LEVEL instead.
DMS_DEBUG=0

# Set the log level for DMS.
# This is mostly relevant for container startup scripts and change detection event feedback.
#
# Valid values (in order of increasing verbosity) are: `error`, `warn`, `info`, `debug` and `trace`.
# The default log level is `info`.
LOG_LEVEL=info

# critical => Only show critical messages
# error => Only show erroneous output
# **warn** => Show warnings
# info => Normal informational output
# debug => Also show debug messages
SUPERVISOR_LOGLEVEL=

# 0 => mail state in default directories
# 1 => consolidate all states into a single directory (`/var/mail-state`) to allow persistence using docker volumes
ONE_DIR=1

# **empty** => use FILE
# LDAP => use LDAP authentication
# OIDC => use OIDC authentication (not yet implemented)
# FILE => use local files (this is used as the default)
ACCOUNT_PROVISIONER=

# empty => postmaster@domain.com
# => Specify the postmaster address
POSTMASTER_ADDRESS=postmaster@meine-domain.de

# Check for updates on container start and then once a day
# If an update is available, a mail is sent to POSTMASTER_ADDRESS
# 0 => Update check disabled
# 1 => Update check enabled
ENABLE_UPDATE_CHECK=1

# Customize the update check interval.
# Number + Suffix. Suffix must be 's' for seconds, 'm' for minutes, 'h' for hours or 'd' for days.
UPDATE_CHECK_INTERVAL=1d

# Set different options for mynetworks option (can be overwrite in postfix-main.cf)
# **WARNING**: Adding the docker network's gateway to the list of trusted hosts, e.g. using the `network` or
# `connected-networks` option, can create an open relay
# https://github.com/docker-mailserver/docker-mailserver/issues/1405#issuecomment-590106498
# The same can happen for rootless podman. To prevent this, set the value to "none" or configure slirp4netns
# https://github.com/docker-mailserver/docker-mailserver/issues/2377
#
# none => Explicitly force authentication
# container => Container IP address only
# host => Add docker container network (ipv4 only)
# network => Add all docker container networks (ipv4 only)
# connected-networks => Add all connected docker networks (ipv4 only)
PERMIT_DOCKER=none

# Set the timezone. If this variable is unset, the container runtime will try to detect the time using
# `/etc/localtime`, which you can alternatively mount into the container. The value of this variable
# must follow the pattern `AREA/ZONE`, i.e. of you want to use Germany's time zone, use `Europe/Berlin`.
# You can lookup all available timezones here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
TZ=

# In case you network interface differs from 'eth0', e.g. when you are using HostNetworking in Kubernetes,
# you can set NETWORK_INTERFACE to whatever interface you want. This interface will then be used.
#  - **empty** => eth0
NETWORK_INTERFACE=

# empty => modern
# modern => Enables TLSv1.2 and modern ciphers only. (default)
# intermediate => Enables TLSv1, TLSv1.1 and TLSv1.2 and broad compatibility ciphers.
TLS_LEVEL=

# Configures the handling of creating mails with forged sender addresses.
#
# **0** => (not recommended) Mail address spoofing allowed. Any logged in user may create email messages with a forged sender address (see also https://en.wikipedia.org/wiki/Email_spoofing).
# 1 => Mail spoofing denied. Each user may only send with his own or his alias addresses. Addresses with extension delimiters(http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
SPOOF_PROTECTION=

# Enables the Sender Rewriting Scheme. SRS is needed if your mail server acts as forwarder. See [postsrsd](https://github.com/roehling/postsrsd/blob/master/README.md#sender-rewriting-scheme-crash-course) for further explanation.
#  - **0** => Disabled
#  - 1 => Enabled
ENABLE_SRS=0

# Enables the OpenDKIM service.
# **1** => Enabled
#   0   => Disabled
ENABLE_OPENDKIM=1

# Enables the OpenDMARC service.
# **1** => Enabled
#   0   => Disabled
ENABLE_OPENDMARC=1


# Enabled `policyd-spf` in Postfix's configuration. You will likely want to set this
# to `0` in case you're using Rspamd (`ENABLE_RSPAMD=1`).
#
# - 0     => Disabled
# - **1** => Enabled
ENABLE_POLICYD_SPF=1

# 1 => Enables POP3 service
# empty => disables POP3
ENABLE_POP3=

# Enables ClamAV, and anti-virus scanner.
#   1   => Enabled
# **0** => Disabled
ENABLE_CLAMAV=0

# Enables Rspamd
# **0** => Disabled
#   1   => Enabled
ENABLE_RSPAMD=0

# When `ENABLE_RSPAMD=1`, an internal Redis instance is enabled implicitly.
# This setting provides an opt-out to allow using an external instance instead.
# 0 => Disabled
# 1 => Enabled
ENABLE_RSPAMD_REDIS=

# When enabled,
#
# 1. the "[autolearning][rspamd-autolearn]" feature is turned on;
# 2. the Bayes classifier will be trained when moving mails from or to the Junk folder (with the help of Sieve scripts).
#
# **0** => disabled
# 1     => enabled
RSPAMD_LEARN=0

# Controls whether the Rspamd Greylisting module is enabled.
# This module can further assist in avoiding spam emails by greylisting
# e-mails with a certain spam score.
#
# **0** => disabled
# 1     => enabled
RSPAMD_GREYLISTING=0

# Can be used to enable or disable the Hfilter group module.
#
# - 0     => Disabled
# - **1** => Enabled
RSPAMD_HFILTER=1

# Can be used to control the score when the HFILTER_HOSTNAME_UNKNOWN symbol applies. A higher score is more punishing. Setting it to 15 is equivalent to rejecting the email when the check fails.
#
# Default: 6
RSPAMD_HFILTER_HOSTNAME_UNKNOWN_SCORE=6

# Amavis content filter (used for ClamAV & SpamAssassin)
# 0 => Disabled
# 1 => Enabled
ENABLE_AMAVIS=1

# -1/-2/-3 => Only show errors
# **0**    => Show warnings
# 1/2      => Show default informational output
# 3/4/5    => log debug information (very verbose)
AMAVIS_LOGLEVEL=0

# This enables DNS block lists in Postscreen.
# Note: Emails will be rejected, if they don't pass the block list checks!
# **0** => DNS block lists are disabled
# 1     => DNS block lists are enabled
ENABLE_DNSBL=0

# If you enable Fail2Ban, don't forget to add the following lines to your `compose.yaml`:
#    cap_add:
#      - NET_ADMIN
# Otherwise, `nftables` won't be able to ban IPs.
ENABLE_FAIL2BAN=1

# Fail2Ban blocktype
# drop   => drop packet (send NO reply)
# reject => reject packet (send ICMP unreachable)
FAIL2BAN_BLOCKTYPE=drop

# 1 => Enables Managesieve on port 4190
# empty => disables Managesieve
ENABLE_MANAGESIEVE=

# **enforce** => Allow other tests to complete. Reject attempts to deliver mail with a 550 SMTP reply, and log the helo/sender/recipient information. Repeat this test the next time the client connects.
# drop => Drop the connection immediately with a 521 SMTP reply. Repeat this test the next time the client connects.
# ignore => Ignore the failure of this test. Allow other tests to complete. Repeat this test the next time the client connects. This option is useful for testing and collecting statistics without blocking mail.
POSTSCREEN_ACTION=enforce

# empty => all daemons start
# 1 => only launch postfix smtp
SMTP_ONLY=

# Please read [the SSL page in the documentation](https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl) for more information.
#
# empty => SSL disabled
# letsencrypt => Enables Let's Encrypt certificates
# custom => Enables custom certificates
# manual => Let's you manually specify locations of your SSL certificates for non-standard cases
# self-signed => Enables self-signed certificates
SSL_TYPE=manual

# These are only supported with `SSL_TYPE=manual`.
# Provide the path to your cert and key files that you've mounted access to within the container.
SSL_CERT_PATH=/etc/certs/live/mail.meine-domain.de/fullchain.pem
SSL_KEY_PATH=/etc/certs/live/mail.meine-domain.de/privkey.pem
# Optional: A 2nd certificate can be supported as fallback (dual cert support), eg ECDSA with an RSA fallback.
# Useful for additional compatibility with older MTA and MUA (eg pre-2015).
# Hat mir Probleme gemacht :
# SSL_ALT_CERT_PATH=/etc/certs/live/meine-domain.de/fullchain.pem
# SSL_ALT_KEY_PATH=/etc/certs/live/meine-domain.de/privkey.pem

# Set how many days a virusmail will stay on the server before being deleted
# empty => 7 days
VIRUSMAILS_DELETE_DELAY=

# Configure Postfix `virtual_transport` to deliver mail to a different LMTP client (default is a dovecot socket).
# Provide any valid URI. Examples:
#
# empty => `lmtp:unix:/var/run/dovecot/lmtp` (default, configured in Postfix main.cf)
# `lmtp:unix:private/dovecot-lmtp` (use socket)
# `lmtps:inet:<host>:<port>` (secure lmtp with starttls)
# `lmtp:<kopano-host>:2003` (use kopano as mailstore)
POSTFIX_DAGENT=

# Set the mailbox size limit for all users. If set to zero, the size will be unlimited (default).
#
# empty => 0
POSTFIX_MAILBOX_SIZE_LIMIT=

# See https://docker-mailserver.github.io/docker-mailserver/edge/config/user-management/accounts/#notes
# 0 => Dovecot quota is disabled
# 1 => Dovecot quota is enabled
ENABLE_QUOTAS=1

# Set the message size limit for all users. If set to zero, the size will be unlimited (not recommended!)
#
# empty => 10240000 (~10 MB)
POSTFIX_MESSAGE_SIZE_LIMIT=

# Mails larger than this limit won't be scanned.
# ClamAV must be enabled (ENABLE_CLAMAV=1) for this.
#
# empty => 25M (25 MB)
CLAMAV_MESSAGE_SIZE_LIMIT=

# Enables regular pflogsumm mail reports.
# This is a new option. The old REPORT options are still supported for backwards compatibility. If this is not set and reports are enabled with the old options, logrotate will be used.
#
# not set => No report
# daily_cron => Daily report for the previous day
# logrotate => Full report based on the mail log when it is rotated
PFLOGSUMM_TRIGGER=

# Recipient address for pflogsumm reports.
#
# not set => Use REPORT_RECIPIENT or POSTMASTER_ADDRESS
# => Specify the recipient address(es)
PFLOGSUMM_RECIPIENT=

# Sender address (`FROM`) for pflogsumm reports if pflogsumm reports are enabled.
#
# not set => Use REPORT_SENDER
# => Specify the sender address
PFLOGSUMM_SENDER=

# Interval for logwatch report.
#
# none => No report is generated
# daily => Send a daily report
# weekly => Send a report every week
LOGWATCH_INTERVAL=

# Recipient address for logwatch reports if they are enabled.
#
# not set => Use REPORT_RECIPIENT or POSTMASTER_ADDRESS
# => Specify the recipient address(es)
LOGWATCH_RECIPIENT=

# Sender address (`FROM`) for logwatch reports if logwatch reports are enabled.
#
# not set => Use REPORT_SENDER
# => Specify the sender address
LOGWATCH_SENDER=

# Defines who receives reports if they are enabled.
# **empty** => ${POSTMASTER_ADDRESS}
# => Specify the recipient address
REPORT_RECIPIENT=

# Defines who sends reports if they are enabled.
# **empty** => mailserver-report@${DOMAINNAME}
# => Specify the sender address
REPORT_SENDER=

# Changes the interval in which log files are rotated
# **weekly** => Rotate log files weekly
# daily => Rotate log files daily
# monthly => Rotate log files monthly
#
# Note: This Variable actually controls logrotate inside the container
# and rotates the log files depending on this setting. The main log output is
# still available in its entirety via `docker logs mail` (Or your
# respective container name). If you want to control logrotation for
# the Docker-generated logfile see:
# https://docs.docker.com/config/containers/logging/configure/
#
# Note: This variable can also determine the interval for Postfix's log summary reports, see [`PFLOGSUMM_TRIGGER`](#pflogsumm_trigger).
LOGROTATE_INTERVAL=weekly


# If enabled, employs `reject_unknown_client_hostname` to sender restrictions in Postfix's configuration.
#
# - **0** => Disabled
# - 1 => Enabled
POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME=0

# Choose TCP/IP protocols for postfix to use
# **all** => All possible protocols.
# ipv4 => Use only IPv4 traffic. Most likely you want this behind Docker.
# ipv6 => Use only IPv6 traffic.
#
# Note: More details at http://www.postfix.org/postconf.5.html#inet_protocols
POSTFIX_INET_PROTOCOLS=all

# Choose TCP/IP protocols for dovecot to use
# **all** => Listen on all interfaces
# ipv4 => Listen only on IPv4 interfaces. Most likely you want this behind Docker.
# ipv6 => Listen only on IPv6 interfaces.
#
# Note: More information at https://dovecot.org/doc/dovecot-example.conf
DOVECOT_INET_PROTOCOLS=all

# -----------------------------------------------
# --- SpamAssassin Section ----------------------
# -----------------------------------------------

ENABLE_SPAMASSASSIN=0

# deliver spam messages in the inbox (eventually tagged using SA_SPAM_SUBJECT)
SPAMASSASSIN_SPAM_TO_INBOX=1

# KAM is a 3rd party SpamAssassin ruleset, provided by the McGrail Foundation.
# If SpamAssassin is enabled, KAM can be used in addition to the default ruleset.
# - **0** => KAM disabled
# - 1 => KAM enabled
#
# Note: only has an effect if `ENABLE_SPAMASSASSIN=1`
ENABLE_SPAMASSASSIN_KAM=0

# spam messages will be moved in the Junk folder (SPAMASSASSIN_SPAM_TO_INBOX=1 required)
MOVE_SPAM_TO_JUNK=1

# add spam info headers if at, or above that level:
SA_TAG=2.0

# add 'spam detected' headers at that level
SA_TAG2=6.31

# triggers spam evasive actions
SA_KILL=10.0

# add tag to subject if spam detected
SA_SPAM_SUBJECT=***SPAM*****

# -----------------------------------------------
# --- Fetchmail Section -------------------------
# -----------------------------------------------

ENABLE_FETCHMAIL=0

# The interval to fetch mail in seconds
FETCHMAIL_POLL=300

# Enable or disable `getmail`.
#
# - **0** => Disabled
# - 1 => Enabled
ENABLE_GETMAIL=0

# The number of minutes for the interval. Min: 1; Max: 30.
GETMAIL_POLL=5

# -----------------------------------------------
# --- LDAP Section ------------------------------
# -----------------------------------------------

# A second container for the ldap service is necessary (i.e. https://github.com/osixia/docker-openldap)

# with the :edge tag, use ACCOUNT_PROVISIONER=LDAP
# empty => LDAP authentication is disabled
# 1 => LDAP authentication is enabled
ENABLE_LDAP=

# empty => no
# yes => LDAP over TLS enabled for Postfix
LDAP_START_TLS=

# If you going to use the mailserver in combination with Docker Compose you can set the service name here
# empty => mail.domain.com
# Specify the dns-name/ip-address where the ldap-server
LDAP_SERVER_HOST=

# empty => ou=people,dc=domain,dc=com
# => e.g. LDAP_SEARCH_BASE=dc=mydomain,dc=local
LDAP_SEARCH_BASE=

# empty => cn=admin,dc=domain,dc=com
# => take a look at examples of SASL_LDAP_BIND_DN
LDAP_BIND_DN=

# empty** => admin
# => Specify the password to bind against ldap
LDAP_BIND_PW=

# e.g. `"(&(mail=%s)(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for users
LDAP_QUERY_FILTER_USER=

# e.g. `"(&(mailGroupMember=%s)(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for groups
LDAP_QUERY_FILTER_GROUP=

# e.g. `"(&(mailAlias=%s)(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for aliases
LDAP_QUERY_FILTER_ALIAS=

# e.g. `"(&(|(mail=*@%s)(mailalias=*@%s)(mailGroupMember=*@%s))(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for domains
LDAP_QUERY_FILTER_DOMAIN=

# -----------------------------------------------
# --- Dovecot Section ---------------------------
# -----------------------------------------------

# empty => no
# yes => LDAP over TLS enabled for Dovecot
DOVECOT_TLS=

# e.g. `"(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))"`
DOVECOT_USER_FILTER=

# e.g. `"(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))"`
DOVECOT_PASS_FILTER=

# Define the mailbox format to be used
# default is maildir, supported values are: sdbox, mdbox, maildir
DOVECOT_MAILBOX_FORMAT=maildir

# empty => no
# yes => Allow bind authentication for LDAP
# https://wiki.dovecot.org/AuthDatabase/LDAP/AuthBinds
DOVECOT_AUTH_BIND=

# -----------------------------------------------
# --- Postgrey Section --------------------------
# -----------------------------------------------

ENABLE_POSTGREY=0
# greylist for N seconds
POSTGREY_DELAY=300
# delete entries older than N days since the last time that they have been seen
POSTGREY_MAX_AGE=35
# response when a mail is greylisted
POSTGREY_TEXT="Delayed by Postgrey"
# whitelist host after N successful deliveries (N=0 to disable whitelisting)
POSTGREY_AUTO_WHITELIST_CLIENTS=5

# -----------------------------------------------
# --- SASL Section ------------------------------
# -----------------------------------------------

ENABLE_SASLAUTHD=0

# empty => pam
# `ldap` => authenticate against ldap server
# `shadow` => authenticate against local user db
# `mysql` => authenticate against mysql db
# `rimap` => authenticate against imap server
# Note: can be a list of mechanisms like pam ldap shadow
SASLAUTHD_MECHANISMS=

# empty => None
# e.g. with SASLAUTHD_MECHANISMS rimap you need to specify the ip-address/servername of the imap server  ==> xxx.xxx.xxx.xxx
SASLAUTHD_MECH_OPTIONS=

# empty => Use value of LDAP_SERVER_HOST
# Note: since version 10.0.0, you can specify a protocol here (like ldaps://); this deprecates SASLAUTHD_LDAP_SSL.
SASLAUTHD_LDAP_SERVER=

# empty => Use value of LDAP_BIND_DN
# specify an object with privileges to search the directory tree
# e.g. active directory: SASLAUTHD_LDAP_BIND_DN=cn=Administrator,cn=Users,dc=mydomain,dc=net
# e.g. openldap: SASLAUTHD_LDAP_BIND_DN=cn=admin,dc=mydomain,dc=net
SASLAUTHD_LDAP_BIND_DN=

# empty => Use value of LDAP_BIND_PW
SASLAUTHD_LDAP_PASSWORD=

# empty => Use value of LDAP_SEARCH_BASE
# specify the search base
SASLAUTHD_LDAP_SEARCH_BASE=

# empty => default filter `(&(uniqueIdentifier=%u)(mailEnabled=TRUE))`
# e.g. for active directory: `(&(sAMAccountName=%U)(objectClass=person))`
# e.g. for openldap: `(&(uid=%U)(objectClass=person))`
SASLAUTHD_LDAP_FILTER=

# empty => no
# yes => LDAP over TLS enabled for SASL
# If set to yes, the protocol in SASLAUTHD_LDAP_SERVER must be ldap:// or missing.
SASLAUTHD_LDAP_START_TLS=

# empty => no
# yes => Require and verify server certificate
# If yes you must/could specify SASLAUTHD_LDAP_TLS_CACERT_FILE or SASLAUTHD_LDAP_TLS_CACERT_DIR.
SASLAUTHD_LDAP_TLS_CHECK_PEER=

# File containing CA (Certificate Authority) certificate(s).
# empty => Nothing is added to the configuration
# Any value => Fills the `ldap_tls_cacert_file` option
SASLAUTHD_LDAP_TLS_CACERT_FILE=

# Path to directory with CA (Certificate Authority) certificates.
# empty => Nothing is added to the configuration
# Any value => Fills the `ldap_tls_cacert_dir` option
SASLAUTHD_LDAP_TLS_CACERT_DIR=

# Specify what password attribute to use for password verification.
# empty => Nothing is added to the configuration but the documentation says it is `userPassword` by default.
# Any value => Fills the `ldap_password_attr` option
SASLAUTHD_LDAP_PASSWORD_ATTR=

# empty => `bind` will be used as a default value
# `fastbind` => The fastbind method is used
# `custom` => The custom method uses userPassword attribute to verify the password
SASLAUTHD_LDAP_AUTH_METHOD=

# Specify the authentication mechanism for SASL bind
# empty => Nothing is added to the configuration
# Any value => Fills the `ldap_mech` option
SASLAUTHD_LDAP_MECH=

# -----------------------------------------------
# --- SRS Section -------------------------------
# -----------------------------------------------

# envelope_sender => Rewrite only envelope sender address (default)
# header_sender => Rewrite only header sender (not recommended)
# envelope_sender,header_sender => Rewrite both senders
# An email has an "envelope" sender (indicating the sending server) and a
# "header" sender (indicating who sent it). More strict SPF policies may require
# you to replace both instead of just the envelope sender.
SRS_SENDER_CLASSES=envelope_sender

# empty => Envelope sender will be rewritten for all domains
# provide comma separated list of domains to exclude from rewriting
SRS_EXCLUDE_DOMAINS=

# empty => generated when the image is built
# provide a secret to use in base64
# you may specify multiple keys, comma separated. the first one is used for
# signing and the remaining will be used for verification. this is how you
# rotate and expire keys
SRS_SECRET=

# -----------------------------------------------
# --- Default Relay Host Section ----------------
# -----------------------------------------------

# Setup relaying all mail through a default relay host
#
# empty => don't configure default relay host
# default host and optional port to relay all mail through
DEFAULT_RELAY_HOST=

# -----------------------------------------------
# --- Multi-Domain Relay Section ----------------
# -----------------------------------------------

# Setup relaying for multiple domains based on the domain name of the sender
# optionally uses usernames and passwords in postfix-sasl-password.cf and relay host mappings in postfix-relaymap.cf
#
# empty => don't configure relay host
# default host to relay mail through
RELAY_HOST=

# empty => 25
# default port to relay mail
RELAY_PORT=25

# empty => no default
# default relay username (if no specific entry exists in postfix-sasl-password.cf)
RELAY_USER=

# empty => no default
# password for default relay user
RELAY_PASSWORD=

Bauen und Starten des Docker-Containers für den Mailserver

Der Befehl docker mkdir -p /var/mail && mkdir -p /var/mail-state && mkdir -p /var/log/containers/mail && mkdir -p /etc/containers/mail/config && docker compose up erzeugt man die notwendigen Verzeichnisse, und baut und startet den Container. In den Bildschirmausgaben sieht man, daß der Server innerhalb von fünf Minuten wieder runterfahren wird, wenn kein Email-Konto vorhanden ist.

Anlegen von Benutzern

In einer zweiten Konsole kann man nun das erste Emailkonto, sowie ein Alias für den postmaster anlegen : docker exec -it mailserver setup email add ich@meine-domain.de && docker exec -it mailserver setup alias add postmaster@meine-domain.de ich@meine-domain.de

Nun kann man in der anderen Konsole den Container mit Strg-C stoppen und ihn zukünftig mit docker compose up -d im Hintergrund laufen lassen.

Dieses Steuerskript hilft bei der Verwendung :

#!/bin/bash
tag="latest"
if [ "${2}" != "" ]; then
	tag="${2}"
fi
name=$(basename ${0} |cut -d "." -f1)
img="mailserver"
options="-d \
	-p 587:587 \
	-p 465:465 \
	-p 143:143 \
	-p 993:993 \
	-v /var/mail:/var/mail \
	-v /var/mail-state:/var/mail-state \
	-v /var/log/containers/mail:/var/log/mail \
	-v /etc/containers/mail/config:/tmp/docker-mailserver \
	-v /etc/localtime:/etc/localtime:ro \
	-v /etc/letsencrypt:/etc/certs \
	-e HOSTNAME=mail.meine-domain.de \
	-e OVERRIDE_HOSTNAME=mail.meine-domain.de \
	-e SSL_TYPE=manual \
	-e SSL_CERT_PATH=/etc/certs/live/mail.meine-domain.de/fullchain.pem \
	-e SSL_KEY_PATH=/etc/certs/live/mail.meine-domain.de/privkey.pem \
	-e POSTMASTER_ADDRESS=\"postmaster@meine-domain.de\""

mkdir -p /var/mail
mkdir -p /var/mail-state
mkdir -p /var/log/containers/mail
mkdir -p /etc/containers/mail/config

#########################
echo "Container: ${name}"
if [ "${1}" == "start" ]; then
	echo "Starting"
	( cd /opt/mailserver && docker compose up -d )
elif [ "${1}" == "stop" ]; then 
	echo "Stopping"
	docker stop ${name} && docker rm -f ${name}
elif [ "${1}" == "restart" ]; then
	( cd /opt/mailserver && docker stop ${name} && docker rm -f ${name} && docker compose up -d )
elif [ "${1}" == "login" ]; then
	docker exec -it ${name} /bin/bash
elif [ "${1}" == "commit" ]; then
	echo "Committing"
	docker commit ${name} ${img}:${tag}
elif [ "${1}" == "load" ]; then
	echo "Loading"
	docker load < ${name}.tar.gz
elif [ "${1}" == "save" ]; then
	echo "Saving"
	docker save ${img}:${tag} | gzip > ${name}.tar.gz
else
	echo "Unknown : ${1}"
	exit 1
fi

Weitere Domains mit dem Mailserver verwenden

Wenn man eine weitere Domain, z. B. meine-zweite-domain.de mit dem Mailserver verwenden will, muss man auf dem Mailserver zunächst den neuen Benutzer anlegen :

docker exec -it mailserver setup email add anderer@meine-zweite-domain.de

Dann kann man den MX-Record der Domain auf die Domain oder IP-Adresse des Mailservers und den SPF- und DMARC-Record setzen :

Feld Name Wert Überprüfung
MX @ mail.meine-domain.de dig @1.1.1.1 +short MX liefert mail.meine-domain.de

Der Befehl docker exec -it mailserver setup config dkim lässt die bestehenden Schlüssel in Ruhe und erzeugt nur die neuen, noch fehlenden. Analog kann man den SPF-, DMARC- und DKIM-Record setzen.

Feld Name Wert Überprüfung
TXT @ v=spf1 mx -all dig +short TXT meine-zweite-domain.de
TXT _dmarc v=DMARC1; p=none; sp=none; fo=0; adkim=r; pct=100; rf=afrf; ri=86400; rua=mailto:dmarc.report@meine-zweite-domain.de; ruf=mailto:dmarc.report@meine-zweite-domain.de dig +short TXT _dmarc.meine-zweite-domain.de
TXT mail._domainkey v=DKIM1; h=sha256; k=rsa; p=<dkim-key> dig +short TXT mail._domainkey.meine-zweite-domain.de

Im Mailclient wird für das Senden und empfangen mit IMAP und SMTP dann die Original-Domain mail.meine-domain.de des Mailservers zusammen mit dem Benutzernamen anderer@meine-zweite-domain.de verwendet, der den alternativen Domainnamen enthält.

Weboberfläche für Mailserver einrichten

Roundcube ist eine Weboberfläche für einen Email-Server, die auch auf Mobilgeräten gut bedienbar ist. Mit dem Befehl docker pull roundcube/roundcubemail wird das Image installiert.

Dies ist das Steuerskript :

#!/bin/bash
tag="latest"
if [ "${2}" != "" ]; then
	tag="${2}"
fi
name=$(basename ${0} |cut -d "." -f1)
img="roundcube/roundcubemail"
options="-d -e ROUNDCUBEMAIL_DEFAULT_HOST=tls://mail.meine-domain.de \
	-e ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.meine-domain.de \
	-e ROUNDCUBEMAIL_SMTP_PORT=587 \
	-p 4642:443 -v /etc/letsencrypt:/var/letsencrypt"

#########################
echo "Container: ${name}"
if [ "${1}" == "start" ]; then
	echo "Starting"
	docker run --name ${name} ${options} ${img}:${tag}
elif [ "${1}" == "stop" ]; then 
	echo "Stopping"
	docker rm -f ${name}
elif [ "${1}" == "restart" ]; then
	docker rm -f ${name} && docker run --name ${name} ${options} ${img}:${tag}
elif [ "${1}" == "login" ]; then
	docker exec -it ${name} /bin/bash
elif [ "${1}" == "commit" ]; then
	docker commit ${name} ${img}:${tag}
elif [ "${1}" == "load" ]; then
	echo "Loading"
	docker load < ${name}.tar.gz
elif [ "${1}" == "save" ]; then
	echo "Saving"
	docker save ${img}:${tag} | gzip > ${name}.tar.gz
else
	echo "Unknown : ${1}"
	exit 1
fi

Wir blenden die https-Zertifikate unter /var/letsencrypt ein und passen die Datei /etc/apache2/sites-available/default-ssl.conf im Container an :

<VirtualHost *:443>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html
        ErrorLog ${APACHE_LOG_DIR}/error.log                
        CustomLog ${APACHE_LOG_DIR}/access.log combined
        SSLCertificateFile      /var/letsencrypt/live/meine-domain.de/fullchain.pem
        SSLCertificateKeyFile   /var/letsencrypt/live/meine-domain.de/privkey.pem
        <FilesMatch "\.(?:cgi|shtml|phtml|php)$">
            SSLOptions +StdEnvVars
        </FilesMatch>
        <Directory /usr/lib/cgi-bin>
            SSLOptions +StdEnvVars
        </Directory>
</VirtualHost>

Dann verlinken wir sie noch :

cd /etc/apache2/sites-enabled && \
ln -s ../sites-available/default-ssl.conf .

Schließlich können wir im Container das Apache-SSL-Modul mit a2enmod ssl aktivieren und aktualisieren den Container mit roundcube.sh commit.

Die Weboberfläche ist nach dem Neustart des Containers unter https://meine-domain.de:4642 erreichbar.

Matrix-Messenger-Server

Matrix ist neben XMPP das bekannteste etablierte, freie Protokoll, welches ein freies Messaging ermöglicht. Wie bei der Email kommunizieren die Benutzer durch ihre Clients über Knotenserver miteinander, sodaß jeder Benutzer von jedem anderen Knoten aus erreichbar ist.

Die Server-Implementierung, die wir für Matrix betreiben, heißt "Synapse". Die Benutzer finden sich weltweit über sogenannte Identitätsserver (z. B. https://matrix.org/ oder https://vector.im/. Diese beantworten die Anfragen der Matrix-Clients (z. B. Element) nach Matrix-Adressen der Form @benutzername:matrixserver.tld eines Matrix-Benutzers.

Konfiguration der DNS-Einträge für Matrix

Diese DNS-Records müssen gesetzt sein :

Feld Name Wert Überprüfung
A matrix Ipv4-Adresse des VPS dig @1.1.1.1 +short A matrix.meine-domain.de liefert die eigene öffentliche IPv4, z. B. 1.2.3.4
SRV @ _matrix dig +short TXT meine-domain.de

Installation des Docker-Containers für Matrix

Das Docker-Image des Matrix-Servers "Synapse" installiert man mit docker pull matrixdotorg/synapse.

Konfiguration

Im Verzeichnis /etc/containers/synapse/synapse-data/_data muss die Synapse-Konfiguration homeserver.yaml mit folgendem Inhalt liegen :

# Configuration file for Synapse.
#
# This is a YAML file: see [1] for a quick introduction. Note in particular
# that *indentation is important*: all the elements of a list or dictionary
# should have the same indentation.
#
# [1] https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
#
# For more information on how to configure Synapse, including a complete accounting of
# each option, go to docs/usage/configuration/config_documentation.md or
# https://matrix-org.github.io/synapse/latest/usage/configuration/config_documentation.html

server_name: "matrix.meine-domain.de"
public_baseurl: "https://matrix.meine-domain.de"
tls_certificate_path: "/etc/certs/fullchain.pem"
tls_private_key_path: "/etc/certs/privkey.pem"
pid_file: /data/homeserver.pid
listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    resources:
      - names: [client, federation]
        compress: false

  - port: 8448
    tls: true
    type: http
    x_forwarded: true
    resources:
      - names: [client, federation]
        compress: false

database:
  name: sqlite3
  args:
    database: /data/homeserver.db

log_config: "/data/matrix.meine-domain.de.log.config"
media_store_path: /etc/media
uploads_path: "/etc/media/uploads"

max_image_pixels: 108M
media_retention:
    local_media_lifetime: 4d
    remote_media_lifetime: 1d

max_upload_size: 100M

max_avatar_size: 10M
retention:
    enabled: true
    default_policy:
        min_lifetime: 90d
        max_lifetime: 180d
        allowed_lifetime_min: 90d
        allowed_lifetime_max: 180d


registration_shared_secret: "(automatisch-generiert)"
report_stats: true
macaroon_secret_key: "(automatisch-generiert)"
form_secret: "(automatisch-generiert)"
signing_key_path: "/data/matrix.meine-domain.de.signing.key"
trusted_key_servers:
  - server_name: "matrix.org"
  - server_name: "vector.im"

max_upload_size: 50M
url_preview_enabled: true
url_preview_ip_range_blacklist:
 - '127.0.0.0/8'
 - '10.0.0.0/8'
 - '172.16.0.0/12'
 - '192.168.0.0/16'
 - '100.64.0.0/10'
 - '192.0.0.0/24'
 - '169.254.0.0/16'
 - '::1/128'
 - 'fe80::/64'
 - 'fc00::/7'

enable_metrics: false
report_stats: false

enable_registration: true
registrations_require_3pid:
  - email

allowed_local_3pids:
  - medium: email
    pattern: '^[^@]+@meine-domain\.de$'
  - medium: email
    pattern: '^[^@]+@tutanota\.com$'

email:
  smtp_host: "mail.meine-domain.de"
  smtp_port: 587
  smtp_user: "matrix-bot@meine-domain.de"
  smtp_pass: "(smtp-passwort)"
  require_transport_security: true
  enable_tls: true
  force_tls: false
  notif_from: "Your Friendly %(app)s homeserver <matrix-bot@meine-domain.de>"
  app_name: "matrix_server"
  enable_notifs: true
  notif_for_new_users: true
  client_base_url: "https://matrix.meine-domain.de"
  validation_token_lifetime: 15m
  invite_client_location: "https://app.element.io"

  subjects:
    password_reset: "[%(server_name)s] Password reset"
    email_validation: "[%(server_name)s] Validate your email"

# vim:ft=yaml

Die Dateiberechtigungen der Verzeichnisstruktur von Synapse passen wir entweder im Container mit chown -R 991:991 <verzeichnis> an, oder wir verwenden Kopien des Zertifikatsmatetials dafür.

Außerdem scheinen sich fast alle Clients an der Portnummer am Ende der URL zu stören. Und so muss der bereits laufende Webserver ein paar zusätzliche Aufgaben übernehmen, für die URI matrix.meine-domain.de. Die nginx-Konfiguration erweitert sich um folgenden Block :

	server {
		listen			443 ssl;
		server_name		matrix.meine-domain.de;
		ssl_certificate		/etc/certs/live/matrix.meine-domain.de/fullchain.pem;
		ssl_certificate_key	/etc/certs/live/matrix.meine-domain.de/privkey.pem;
		ssl_protocols		TLSv1.2 TLSv1.3;
		ssl_ciphers		HIGH:!aNULL:!MD5;

		location ~ ^(/_matrix|/_synapse/client) { 
			proxy_pass https://meine-domain.de:8448;
			proxy_set_header X-Forwarded-For $remote_addr;
			proxy_set_header X-Forwarded-Proto $scheme;
			proxy_set_header Host $host;#$request_uri;
			proxy_http_version 1.1;
			client_max_body_size 50M;
		}

     		location /.well-known/matrix/server {
			add_header Content-Type application/json;
     			return 200 '{"m.server": "matrix.meine-domain.de"}';
     		}

    		location /.well-known/matrix/client {
			add_header Content-Type application/json;
			add_header "Access-Control-Allow-Origin" *;
      			return 200 '{"m.homeserver": {"base_url": "https://matrix.meine-domain.de"}, "m.identity_server": {"base_url": "https://vector.im/"}}';
     		}
	}

Inbetriebnahme

Auch hier gibt es wieder ein adaptiertes Steuerskript; es braucht aber sqlite3 (einmalig zu installieren mit apt install sqlite3) :

#!/bin/bash
tag="latest"
if [ "${2}" != "" ]; then
	tag="${2}"
fi
name=$(basename ${0} |cut -d "." -f1)
img="matrixdotorg/synapse"
options="-d -v /etc/containers/synapse/synapse-data/_data:/data \
	-v /etc/containers/synapse/certs:/etc/certs \
	-v /etc/containers/synapse/media:/etc/media
	-p 8448:8448 -p 8008:8008"


#########################
echo "Container: ${name}"
if [ "${1}" == "start" ]; then
	echo "Starting"
	docker run --name ${name} ${options} ${img}:${tag}
elif [ "${1}" == "generate" ]; then
	rm -rf /var/lib/docker/volumes/synapse-data/_data/*
	docker run -it --rm --name ${name} ${options} -e SYNAPSE_SERVER_NAME=matrix.meine-domain.de -e SYNAPSE_REPORT_STATS=yes ${img}:${tag} generate
elif [ "${1}" == "stop" ]; then 
	echo "Stopping"
	docker rm -f ${name}
elif [ "${1}" == "restart" ]; then
	docker rm -f ${name} && docker run --name ${name} ${options} ${img}:${tag}
elif [ "${1}" == "login" ]; then
	docker exec -it ${name} /bin/bash
elif [ "${1}" == "commit" ]; then  
        echo "Committing"
	docker commit ${name} ${img}:${tag}
elif [ "${1}" == "load" ]; then
	echo "Loading"
	docker load < ${name}.tar.gz
elif [ "${1}" == "save" ]; then
	echo "Saving"
	docker save ${img}:${tag} > ${name}.tar.gz
elif [ "${1}" == "adduser" ]; then
	docker exec -it synapse register_new_matrix_user https://matrix.meine-domain.de:8448 -c /data/homeserver.yaml
elif [ "${1}" == "listuser" ]; then 
	sqlite3 /etc/containers/synapse/synapse-data/_data/homeserver.db "select name, user_type, admin from users;"
elif [ "${1}" == "deluser" ]; then
	sqlite3 /etc/containers/synapse/synapse-data/_data/homeserver.db "delete from users where name='${2}';"
elif [ "${1}" == "setpass" ]; then
	username="${2}"
	newpass="${3}"
	passhash=$(docker exec ${name} hash_password -c /data/homeserver.yaml -p "${newpass}") && sqlite3 /etc/containers/synapse/synapse-data/_data/homeserver.db "UPDATE users SET password_hash='${passhash}' WHERE name='${username}';"
elif [ "${1}" == "log" ]; then
	f=$(docker inspect synapse | grep log | sed -En "s,.*\"(/.*)\".*,\1,p")
	echo "Tailing ${f}"
	tail -f ${f}
else
	echo "Unknown : ${1}"
	exit 1
fi

Es unterstützt folgende Optionen :

Option Funktion
generate Initialisiert den Matrix-Server für den ersten Start
start Start des Containers
stop Stop des Containers
login Shell im Container öffnen
commit Manuell getätigte Änderungen im laufenden Container ins Image übernehmen
save Image von Docker in Archiv speichern
load Image aus Archiv in Docker laden
adduser Benutzer zufügen (interaktiv)
setpass "<benutzername>" "<passwort>" Passwort ändern
deluser "<benutzername>" Benutzer löschen
listuser Alle Benutzer anzeigen

Nach dem initialen Start mit synapse.sh generate kann der öffentliche Matrix Federation Tester sehr helfen, um noch etwaige Probleme zu lösen. Auch die Matrix-Community ist sehr hilfsbereit, wenn man sie im Matrix-Raum #synapse:matrix.org um Hilfe bittet.

Die Registrierung für Fremde ist in dieser Konfiguration abgeschaltet, da dies einige zusätzliche Sicherheitsmaßnahmen erfordern würde, um unerwünschte Registrierungen zu verhindern. Stattdessen können Freunde und Familie mit synapse.sh adduser zugefügt werden.

Administration

Die Admin-API von Synapse kann man mit JSON ansprechen. Das dazu nötige Access-Token zeigen manche Clients; man kann aber auch im Docker mit curl eine Anmeldung machen : curl -k -XPOST -d '{"type":"m.login.password", "user":"meinBenutzer", "password":"meinPasswort"}' "https://127.0.0.1:8448/_matrix/client/r0/login". In der erfolgreichen Antwort ist das Token zu sehen. Auch alle weiteren Anfragen können dann mit curl getätigt werden. Es gibt auch GUIs, die auf der Admin-API aufsetzen.

Login mit Matrix-Clients

Die Server-URL für unsere Benutzer ist matrix.meine-domain.de, ihre Matrix-Adresse ist @benutzer:matrix.meine-domain.de.

Es gibt zahlreiche Matrix-Clients, wie z. B. FluffyChat oder oder Element. Letzterer kann auch vom Webbrowser aus verwendet werden. In solch einem Matrix-Client können sich die Benutzer an unserem Matrix-Server anmelden und mit dem Rest der Welt chatten.

Sollte der Identitätsserver nicht konfiguriert sein, muss man dort nachtragen (z. B. matrix.org oder vector.im).

fail2ban

Sobald man Dienste ins öffentliche Netz stellt, ist man der Verantwortung ausgesetzt, diese gegen Angriffe zu verteidigen. Ein Blick in die Logs der Server offenbart, daß diese Angriffe täglich mehrere Male versucht werden.

fail2ban ist ein Dienst, der beliebige Logdateien durch Filter beobachtet und Aktionen ausführt, wenn diese Filter in einer bestimmten Zeit zu oft angeschlagen haben. Die Aktion besteht für Server normalerweise darin, die erkannte IP-Adresse durch das Erstellen einer Firewall-Regel (z. B. iptables oder ufw) für eine bestimmte Zeit zu blockieren.

Installation von fail2ban

Die Installation geschieht mit apt update && apt -y upgrade && apt install fail2ban.

fail2ban Filter erstellen

Für nginx gibt es im Verzeichnis /etc/fail2ban/filter.d bereits mehrere vordefinierte, von denen wir zwei verwenden wollen und ein eigener dazukommt, der etwas strenger ist :

[Definition]
failregex = ^ \[error\] \d+#\d+: \*\d+ user "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(?:, referrer: "\S+")?\s*$
ignoreregex = 
datepattern = {^LN-BEG}
[Definition]
ngx_limit_req_zones = [^"]+
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
ignoreregex = 
datepattern = {^LN-BEG}

# Filter für ungültige Anfragen

[INCLUDES]
before = botsearch-common.conf

[Definition]
failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/.+ \S+\" (301|400|403|404|405|413) .+$
            ^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$

ignoreregex = 
datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
              ^[^\[]*\[({DATE})
              {^LN-BEG}

fail2ban Aktionen erstellen

Da wir Docker verwenden, können wir die vordefinierten Aktionen unter /etc/fail2ban/action.d nicht verwenden und müssen dort eigene zufügen. Wir verwenden dazu Regeln für iptables.

[Definition]
actionstart = iptables -N webserver
              iptables -A webserver -j RETURN
              iptables -I FORWARD -p tcp -m multiport --dports 80,443 -j webserver
 
actionstop  = iptables -D FORWARD -p tcp -m multiport --dports 80,443 -j webserver
             iptables -F webserver
             iptables -X webserver
actioncheck = iptables -n -L FORWARD | grep -q 'webserver[ \t]'
actionban   = iptables -I webserver 1 -s <ip> -j DROP
actionunban = iptables -D webserver -s <ip> -j DROP
[Definition]
actionstart = iptables -N nextcloud
              iptables -A nextcloud -j RETURN
              iptables -I FORWARD -p tcp -m multiport --dports 7575 -j nextcloud
actionstop  = iptables -D FORWARD -p tcp -m multiport --dports 7575 -j nextcloud
             iptables -F nextcloud
             iptables -X nextcloud
actioncheck = iptables -n -L FORWARD | grep -q 'nextcloud[ \t]'
actionban   = iptables -I nextcloud 1 -s <ip> -j DROP
actionunban = iptables -D nextcloud -s <ip> -j DROP

fail2ban Filter mit Aktionen verbinden

Folgende Zeilen sind der Datei /etc/fail2ban/paths-common.conf zuzufügen :

webserver_error_log = /var/log/containers/webserver/*error.log
webserver_access_log = /var/log/containers/webserver/*access.log
nextcloud_log = /var/log/containers/nextcloud/nextcloud.log

Im Verzeichnis /etc/fail2ban/jail.d muss man nun für jeden Dienst eine Datei anlegen, die den jeweiligen Filter mit der gewünschten Aktion verbindet und die Randbedingungen festlegt : Wird innerhalb von 120 Sekunden zweimal ein Filter getroffen, tritt die zugehörige Aktion (das Blockieren der IP-Adresse) für einen Tag inkraft.

[webserver-http-auth]
filter  = nginx-http-auth
port    = http,https
logpath = %(webserver_error_log)s
          %(webserver_access_log)s
enabled = true
maxretry = 2
findtime = 120
bantime = 86400
banaction = docker-webserver

[webserver-limit-req]
filter  = nginx-limit-req
port    = http,https
logpath = %(webserver_error_log)s
          %(webserver_access_log)s
enabled = true
maxretry = 2
findtime = 120
bantime = 86400
banaction = docker-webserver

[webserver-botsearch]
filter   = nginx-only-200
port     = http,https
logpath  = %(webserver_error_log)s
           %(webserver_access_log)s
enabled = true
maxretry = 2
findtime = 120
bantime = 86400
banaction = docker-webserver
[nextcloud-http-auth]
filter  = nginx-http-auth
port    = 7575
logpath = %(nextcloud_log)s
enabled = true
maxretry = 2
findtime = 120
bantime = 86400
banaction = docker-nextcloud

[nextcloud-limit-req]
filter  = nginx-limit-req
port    = 7575
logpath = %(nextcloud_log)s
enabled = true
maxretry = 2
findtime = 120
bantime = 86400
banaction = docker-nextcloud

[nextcloud-botsearch]
filter   = nginx-only-200
port     = 7575
logpath  = %(nextcloud_log)s
enabled = true
maxretry = 2
findtime = 120
bantime = 86400
banaction = docker-nextcloud

fail2ban starten und testen

Man sollte sich nun selbst davon überzeugen, daß der Schutz auch richtig funktioniert :

fail2ban neustarten

Mit systemctl restart fail2ban startet man den Dienst neu, wodurch die veränderte Konfiguration eingelesen wird. Mit systemctl status fail2ban prüft man, ob der Dienst die Konfiguration auch wirklich lesen und sich starten konnte.

Filter auslösen

Anschließend kann man mit einem Browser seiner Wahl mehrmals versuchen, eine ungültige Seite aufzurufen, z. B. mit der URL https://meine-domain.de/abc123.html, was erwartungsgemäß zu einem HTTP Error 404 führen sollte. Nach erneutem Öffnen des Browsers sollte der Webserver unter dieser URL nicht mehr erreichbar sein, auch nicht für gültige Webseiten.

Sperren einsehen und lösen

Mit dem fail2ban-Befehl fail2ban-client banned werden alle aktuellen Blockaden angezeigt. Zur vorzeitigen Freigabe der eigenen blockierten IP-Adresse dient der fail2ban-Befehl : fail2ban-client set webserver-botsearch unbanip <ip-adresse>

Monitoring

Es ist hilfreich, zu erfahren, wenn es dem VPS schlecht geht. Die primitivste Variante ist eine Skriptsammlung, die per cron zeitgesteuert aufgerufen wird und prüft, ob alles in Ordnung ist. Dies könnte z. B. der verbleibende Festplattenspeicher, das Laufen der Docker-Container oder auch die Gültigkeit der Zertifikate sein.

Automatische Emails mit sSMTP

Das Tool sSMTPerlaubt es, ohne zusätzlichen MTA Emails von der Kondole aus zu schicken. Die Installation geschieht mit dem Befehl : sudo apt install ssmtp mailutils.

Nachdem man auf dem Email-Server einen neuen Benutzer, z. B. health@meine-domain.de mit dem Passwort MeinPasswort angelegt hat, kann man die Konfiguration von sSMTP vornehmen.

Die Datei /etc/ssmtp/ssmtp.conf sollte so aussehen :

#
# Config file for sSMTP sendmail
#
# The person who gets all mail for userids < 1000
# Make this empty to disable rewriting.
root=health@meine-domain.de

# The place where the mail goes. The actual machine name is required no 
# MX records are consulted. Commonly mailhosts are named mail.domain.com
mailhub=mail.meine-domain.de:587

# Where will the mail seem to come from?
rewriteDomain=meine-domain.de

# The full hostname
hostname=meine-domain.de

# Are users allowed to set their own From: address?
# YES - Allow the user to specify their own From: address
# NO - Use the system generated From: address
FromLineOverride=YES

AuthMethod=LOGIN
AuthUser=health@meine-domain.de
AuthPass=MeinPasswort
UseTLS=YES

Die Datei /etc/ssmtp/revaliases sollte so aussehen :

# sSMTP aliases
# 
# Format:	local_account:outgoing_address:mailhub
#
# Example: root:your_login@your.domain:mailhub.your.domain[:port]
# where [:port] is an optional port number that defaults to 25.

root:root@meine-domain.de:mail.meine-domain.de:465

Shell-Bibliothek

Shell-Funktionen, die immer wieder benötigt werden, kann man in Funktionen, und diese in Funktions-Bibliotheken kapseln. Man legt solch eine Shell-Datei z. B. unter /usr/local/bin/vps-extension.sh an.

Später kann man die Bibliothek inkludieren mit source /usr/local/bin/vps-extension.sh oder . /usr/local/bin/vps-extension.sh in den Shell-Skripten inkludieten, in denen man ihre Funktionen verwenden will. Auch als Erweiterung für die Kommandozeile ist sie geeignet, indem man in die Datei /root/.bash_aliases folgendes schreibt :

#!/bin/bash
. /usr/local/bin/vps-extension.sh`

Damit stehen beim nächsten Login alle Funktionen der Bibliothek als Befehl zur Verfügung.

Das Skript vps-extension.sh könnte anfangs z. B. so aussehen ;


#!/bin/bash

#
# Colored condole support
#
colors () {
	BLACK=""
	RED=""
	GREEN=""
	YELLOW=""
	BLUE=""
	MAGENTA=""
	CYAN=""
	WHITE=""
	BOLD=""
	RESET=""
	if [ "${1}" == "off" ]; then
		return
	fi
	if test -t 1; then
        	ncolors=$(tput colors)
	        if test -n "$ncolors" && test $ncolors -ge 8; then
        	        BLACK=`tput setaf 0`
			RED=`tput setaf 1`
			GREEN=`tput setaf 2`
			YELLOW=`tput setaf 3`
			BLUE=`tput setaf 4`
			MAGENTA=`tput setaf 5`
			CYAN=`tput setaf 6`
			WHITE=`tput setaf 7`

			BOLD=`tput bold`
			RESET=`tput sgr0`
		fi
	fi
}
colors on

#
# Backup-Funktion (sehr einfach)
# TODO: Durch incrementelle Varianten ersetzen
# 
backup () {
	rm -f /opt/vps-backup.tgz && tar czf /opt/vps-backup.tgz /etc/containers/* /etc/letsencrypt/* /var/www/* /usr/local/bin/* /etc/fail2ban /opt/mailserver /var/mail /var/mail-state
}

#
# Bestimmte IP per Firewall blockem
#
fw-block () {
	if [ "${1}" != "" ]; then
		iptables -A INPUT -s ${1} -j DROP
	else
		echo "Give IP address to block"
	fi
}

#
# Firewall-Blockierung für bestimmte IP aufheben
#
fw-unblock () {
	if [ "${1}" != "" ]; then
		iptables -D INPUT -s ${1} -j DROP
		fail2ban-client unban ${1}
	else
		echo "Give IP address to unblock"

	fi
}

#
# Aktuell per fail2ban geblockte IP-Adressen anzeigen mit Aufruf-Zähler 
#
fw-blocked () {
	blocked=$(iptables --list-rules | grep DROP | sed -En "s,.*-s (.*)/32 -j.*,\1,p" | tr '\n' ' ')
	logs=$(find /var/log/containers -iname "access.log" | tr '\n' ' ')
	echo "IP-Address|TIMES BLOCKED|FILE"
	for ip in ${blocked}; do
        	for f in ${logs}; do
			count=$(cat ${f} | grep "${ip}" | grep -v '" 200' | grep -v '" 301' | wc -l)
                	if [ "${count}" -gt "0" ]; then
                        	echo "${ip}|${count}|${f}"
                	fi
                	if [ "${1}" != "" ]; then
                        	cat ${f} | grep "${ip}" | grep -v '" 200' | grep -v '" 301'
				echo "--------------------------------------------------------------------"
                	fi
        	done
	done
}

#
# Alle Blockierungen anzeigen
#
fw-list () {
	echo "${BOLD}Blocked hard :${RESET}"
	iptables -n -L INPUT
	echo ""
	echo "${BOLD}Banned :${RESET}"
	if [ "${1}" == "" ]; then
		column="| column -ts \"|\""
	fi
	eval "fw-blocked | grep -vE \"IP-Address.*\" | sort -u -t \"|\" -k 2nr ${column}"
}

#
# Aktuell laufende Docker-Container anzeigen
#

docker-list () {
	echo "${BOLD}Docker containers :${RESET}"
	echo "-------------------"
	docker ps | sed -E "s/ [ ]+/|/g" | cut -d "|" -f5,7 | column -ts "|" 
}

#
# Prüfen, ob alle Docker-Container laufen. Fehlende werden dreimal versucht, neu zu starten
#
docker-watch () {
        tries="1"
        ok="0"
        while [ "${tries}" -le "3" ]; do
                echo "Docker container health check ${tries}/3"
                # Check containers
                _containers () {
                        echo "${BOLD}Container|Status${RESET}"
                                       
                        containers="webserver mailserverserver roundcube synapse nextcloud"
                        ok="0"
                        for container in ${containers}; do
                                res=$(docker-list | grep ${container} | grep -c "Up")
                                if [ "${res}" == "1" ] ; then
                                        echo "${container}|${GREEN}HEALTHY${RESET}"
                                else
                                        echo "${container}|${RED}DEAD${RESET}, trying to restart"
                                        eval "/usr/local/bin/${container}.sh restart"
                                        ok="1"
                                fi
                        done
                }
                _containers | column -ts "|"
                if [ "${ok}" == "0" ]; then
                        break
                fi
                tries=$((tries + 1))
        done
	return ${ok}
}

#
# Status der HTTPS-Zertifikate prüfen
#
certdates () {
	local ok="0"
	_certdates () {
		files=$(find /etc/letsencrypt -iname "cert.pem" | tr '\n' ' ');
		echo "${BOLD}Certificate|Status${RESET}"
		for f in $files; do 
			certname="$(dirname ${f} | sed "s,.*/,,g")"
			expirestr="$(openssl x509 -inform pem -text -in ${f} | grep -i "not after" | sed -E "s/ [ ]+/ /g;s/Not After : //g")"
			expiredate="$(date -d "${expirestr}" +%s)"
			myformat="$(date -d @"${expiredate}" "+%Y-%m-%d %H:%M:%S")"
			currentdate="$(date +%s)"
			expiresoon=$((currentdate - 604800)) # one week
			stat=""
			if [ "${expiredate}" -lt "${expiresoon}" ]; then
				stat="${YELLOW}SOON${RESET}"
			elif [ "${expiredate}" -lt "${currentdate}" ]; then
				stat="${RED}EXPIRED${RESET}"
				ok="255"
			else
				stat="${GREEN}HEALTHY${RESET}"
			fi
			echo "${certname} | ${stat} (${myformat})"
		done
	}
	_certdates | column -ts "|"
	return ${ok}
}

#
# Den VPS-Gedundheitscheck durchführen und bei Fehlern eine Email senden
#
healthcheck () {

	#
	# Docker containers
	#
	docker-watch
	if [ "$?" != "0" ]; then
                send-healthreport "[VPS] Check docker containers"
                return 1
	fi

	#
	# Certificates
	#
	certdates
	if [ "$?" != "0" ]; then
		send-healthreport "[VPS] Check certificates"
		return 1
	fi
	return 0
}

#
# Stellt einen Statusbericht zusammen
#
healthreport () {
	echo "${BOLD}VPS is healthy${RESET}"
	echo "========== "
	echo ""
	date
	echo ""

	echo "${BOLD}Users :${RESET} $(users)"
	echo ""
	echo "${BOLD}Docker-Containers${RESET}"
	echo "================="
	docker-watch
	echo ""

	echo "${BOLD}Certificates :${RESET}"
	echo "=============="
	certdates
	echo ""

	echo "${BOLD}Blocked IPs${RESET}"
	echo "==========="
	fw-list
	echo ""

	echo "${BOLD}Active Connections${RESET}"
        echo "=================="
	(netstat -tanp | grep "ESTABLISHED" && netstat -tulpn | grep "ESTABLISHED") | sed -E "s/[ ]+/|/g" | cut -d "|" -f1,4,7 | column -ts "|"
	echo ""

	echo ""${BOLD}Active services"${RESET}"
	echo "==============="
	(netstat -tanp | grep "LISTEN" && netstat -tulpn | grep "LISTEN") | sed -E "s/[ ]+/|/g" | cut -d "|" -f1,4,7 | column -ts "|"
	echo ""

	echo "${BOLD}Journal${RESET}"
	echo "======="
	journalctl --since "$(date -d '30 minutes ago' "+%Y-%m-%d %H:%M:%S")"
	netstat -tanp
	echo "====== [DONE] ======"
}

#
# Sendet einen Status-Bericht per Email
#
send-healthreport () 
{
	subject="[VPS] Manual report"
	if [ "${1}" != "" ]; then 
		subject="${1}"
	fi
	colors off
	healthreport | mail -s "${subject}" meine-email@meine-domain.de
	colors on
}

Cronjobs

Die zeitgesteuerte Ausführung per cron kann z. B. folgende Aufgaben erledigen :

Dazu öffnet man die Datei mit crontab -e und fügt folgende Zeilen ein :

0 * * * * ". /usr/local/bin/vps-extension.sh && backup"                  
*/5 * * * * ". /usr/local/bin/vps-extension.sh && healthcheck"          
0 7,13 * * * ". /usr/local/bin/vps-extension.sh && send-healthreport"

Ausblick

Alle Daten, die mit den Containern mit der Docker-Option -v verbunden sind, könnten in ein Veracrypt-Volume gesteckt werden, damit sämtliche Benutzerdaten der Container-Anwendungen vollverschlüsselt sind. Dies wäre ein erster Schritt in Richtung Zero Knowledge.


Zurück zur Hauptseite