Verschlüsseltes MQTT vom und zum Mosquitto-Server
Gepostet am 23. Dezember 2015  (Zuletzt geändert am 13. Dezember 2022 )
7 Minuten • 1365 Wörter • Andere Sprachen: English
Das MQTT-Protokoll eignet sich gut dazu, Serverdienste zu überwachen. Wenn die Übertragung aber über das Internet erfolgt, sollten die Informationen verschlüsselt werden. Im Folgenden werde ich die diversen Methoden dazu vorstellen.
Mosquitto installieren und Authentifizierung einrichten
Ich gehe von einer Debian/Ubuntu-Installation aus. Auf anderen Systemen sollte die Installation ähnlich verlaufen, auch wenn die Konfigurations-Dateien zum Teil an anderen Orten liegen können. Die Installation von Mosquitto und den Clients ist einfach:
#Man kann auch ein PPA-Paket installieren, wenn man immer die neueste Mosquitto-Version haben will:
# sudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa
sudo apt install mosquitto mosquitto-clients
Mosquitto beherrscht eine Reihe von Sicherungsmöglichkeiten, die auch in der Dokumentation recht ausführlich beschrieben sind. Neben der Verschlüsselung der Übertragung kann man Kanäle auch durch eine Authentifizierung absichern. Am einfachsten ist das über eine Passwort-Datei möglich:
sudo mosquitto_passwd -c /etc/mosquitto/passwords mqtt
Der obige Befehl erstellt eine Datei /etc/mosquitto/passwords
mit einem Benutzer mqtt. Das Passwort wird entsprechend
abgefragt. Zum Testen kann man z.B. „12345“ eingeben, was natürlich supersicher ist.
Was darf der Benutzer mqtt? Das beschreibt eine weitere Datei, in der die Zugangsberechtigungen gespeichert sind:
sudo bash -c "cat > /etc/mosquitto/access.acl <<EOF
user mqtt
topic readwrite #
EOF"
Die Datei /etc/mosquitto/access.acl
enthält zwei Einträge. Zunächst wird der Benutzer definiert, für den die
nachfolgenden Berechtigungen gelten. Die Zeile mit Topic bestimmt die Pfade, die erlaubt sind. In diesem Fall darf der
Benutzer mqtt in allen Pfaden (#) sowohl lesen als auch schreiben. In echten Szenarios bietet es sich natürlich an, dass
Clients entweder lesen oder schreiben dürfen, bzw. dass es überhaupt mehrere Nutzer gibt.
Danach erweitern wir die Konfiguration von Mosquitto, damit das Programm weiß, wo die Passwörter liegen:
sudo bash -c "cat > /etc/mosquitto/conf.d/auth.conf <<EOF
acl_file /etc/mosquitto/access.acl
allow_anonymous false
password_file /etc/mosquitto/passwords
EOF"
sudo chmod 600 /etc/mosquitto/passwords
Hier werden zum einen die beiden oben erstellten Dateien eingebunden und zum anderen der anonyme Zugriff auf den Server gesperrt. Danach laden wir den Server neu und testen die Einstellungen:
mosquitto_sub -t '#' &
sudo systemctl restart mosquitto.service
# sollte ausgeben: Connection Refused: not authorised.
mosquitto_pub -m "Hallo Welt" -t "test"
# sollte ausgeben: Connection Refused: not authorised.
# Das sollte funktionieren, am besten in zwei Terminals ausführen:
mosquitto_sub -u mqtt -P 12345 -t '#' &
mosquitto_pub -u mqtt -P 12345 -m "Hallo Welt" -t "test"
Die Authentifizierung bringt an sich wenig zusätzliche Sicherheit, da die Passwörter im Klartext übertragen werden. Sinnvoll ist es daher, den Übertragungskanal zu verschlüsseln.
TLS-PSK-Verschlüsselung
Eine einfache Verschlüsselung kann man mit dem sogenannten „Preshared Key“-Verfahren erreichen. Sowohl Client als auch Server müssen dazu im Grunde ein gemeinsames Passwort kennen. Das Verfahren ist daher nicht ganz so sicher wie das unten vorgestellte TLS/SSL-Verfahren mit öffentlichen und privaten Schlüsseln. Außerdem wird dieses Verfahren von den meisten Programmbibliotheken (Paho, mqtt für Nodejs u.ä.) nicht unterstützt.
Die Authentifizierung oben lassen wir so, wie sie ist. Wir erstellen lediglich eine sichere Verbindung (Port 8883):
sudo bash -c "cat > /etc/mosquitto/conf.d/ssl.conf <<EOF
port 8883
psk_hint myrandomhint
psk_file /etc/mosquitto/pskfile
EOF"
Der psk-Hint ist frei wählbar und muss dem Client auch bekannt sein, damit das Verfahren funktioniert. Die Datei /etc/mosquitto/access.acl enthält die Schlüssel aller Clients:
sudo bash -c "cat > /etc/mosquitto/pskfile <<EOF
mqtt:73656372657450534b
EOF"
sudo chmod 600 /etc/mosquitto/pskfile
Pro Zeile wird also ein Benutzername und ein hexadezimaler String angegeben. Der Benutzername muss nicht zwingend derselbe wie bei der Authentifizierung oben sein.
Die Kommunikation kann nun verschlüsselt erfolgen:
#Neustart Server
sudo systemctl restart mosquitto.service
# Empfänger
mosquitto_sub --psk-identity mqtt --psk 73656372657450534b -u mqtt -P 12345 -t '#' &
# Sender
mosquitto_pub -u mqtt -P 12345 --psk-identity mqtt --psk 73656372657450534b -m "Hallo Welt" -t "test"
TLS/SSL-Verschlüsselung
Diese Verschlüsselung funktioniert im Grunde wie oben, nur mit öffentlichem und privatem Schlüssel. Einfach ist die Handhabung, wenn man ein selbst unterzeichnetes Zertifikat erstellt. Owntracks stellt dazu ein eigenes Skript zur Verfügung:
cd /etc/mosquitto/certs
wget https://raw.githubusercontent.com/owntracks/tools/master/TLS/generate-CA.sh -O - | sudo bash
Die Erstellung eines eigenen Zertifikats ist auch in der Dokumentation grob beschrieben: http://mosquitto.org/man/mosquitto-tls-7.html .
Nun überschreiben wir die oben erstellte Datei mit den neuen Einstellungen:
host=$(hostname -f)
sudo bash -c "cat > /etc/mosquitto/conf.d/ssl.conf <<EOF
port 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/${host}.crt
keyfile /etc/mosquitto/certs/${host}.key
EOF"
Die Clients müssen nun lediglich die CA-Datei kennen. Dieser öffentliche Schlüssel ist unkritisch und kann offen an die Clients verteilt werden. Wichtig ist auch, dass der Port 8883 ist.
Zum Testen kann man folgende Skripte ausführen:
#Neustart Server
sudo systemctl restart mosquitto.service
#Empfänger
mosquitto_sub --cafile /etc/mosquitto/certs/ca.crt -u mqtt -P 12345 -t '#'
#Sender
mosquitto_pub -u mqtt -P 12345 --cafile /etc/mosquitto/certs/ca.crt -m "Hallo Welt" -t "test"
Vergleich der Übertragungsmethoden
Wie schneiden die Übertragungsmethoden ab? Um das zu vergleichen, habe ich die Größe der TCP-Streams miteinander verglichen, wie folgende Tabelle zeigt:
(in Bytes) | Quality 0 | Quality 1 | Quality 2 |
---|---|---|---|
ohne Verschlüsselung | 72 | 78 | 86 |
TLS-PSK | 1172 | 1224 | 1331 |
TLS/SSL | 3742 | 3777 | 3843 |
Zu den Größen des TCP-Inhalts kommen natürlich noch der Protokoll-Overhead von TCP/IP, wobei das nur bei der Übertragung ohne Verschlüsselung eine relevante Größe ist, da die Header in dem Fall größer sind als die übertragene Datenmenge (v.a. dank TCP-Handshake). Dennoch wird deutlich, dass die verschlüsselten Übertragungen einen deutlichen Overhead haben. Dieser kommt durch das TLS-Verfahren zustande, das ebenfalls entsprechende Handshakes und Schlüsselaustausch benötigt. Da der Schlüssel in der Regel größer ist als der übertragene Inhalt (im Fall von TLS/SSL z.B. 2048 Bytes), ist die übertragene Datenmenge pro Publish recht hoch. Weniger stark steigt die Menge der übertragenen Daten durch einen erhöhten QoS, wenn man von zusätzlich verschickten IP-Paketen einmal absieht.
Diese Größen sollten bei der Überwachung von Serverdiensten bedacht werden. Ein Rechenbeispiel: Ein Dienst meldet jede Minute den aktuellen Stand an einen zentralen Server, also 1440 Mal pro Tag oder etwa 43500 Mal im Monat. Ohne Verschlüsselung und in MQTT-Qualität 0 liegt die Menge der übertragenen Daten im Monat dann bei etwa 3 MB. Mit Netzwerk-Overhead dürfte die Menge dann real bei etwa 9 MB liegen, dennoch ist dies nicht besonders viel.
Anders schaut dies bei der verschlüsselten Übertragung aus. Bei TLS-PSK steigt die Menge der übertragenen Daten pro Monat auf etwa 48 MB an, bei TLS/SSL liegt er sogar bei 155 MB (ohne Protokolloverhead)! Will man 10 Dienste überwachen kommen also satte 1,5 GB Übertragungsvolumen pro Monat zusammen, eine Menge, die durchaus relevant sein kann.
Wichtig hierbei ist: Die Größen kommen nur dann zustande, wenn für jede Übertragung eine neue Verbindung aufgebaut wird. Das ist zum Beispiel bei den Kommandozeilenprogrammen oben der Fall. Im gerade genannten Rechenbeispiel ist es daher sinnvoll, die Verbindung aufrechtzuerhalten. Bei TLS ist Aufbau dann nur einmalig etwa 3–4 kB, die weiteren Übertragungen besitzen nur einen geringen Overhead im Vergleich zur unverschlüsselten Übertragung.1
Damit lohnt sich die verschlüsselte Übertragung für Dienste, die entweder relativ selten Updates schicken oder so viele, dass es sich lohnt die Verbindung offenzuhalten (das Beispiel oben mit der minütlichen Übertragung wäre ein guter Kandidat).
Alternativen
Will man unverschlüsselte Nachrichten sicher verschicken, gibt es weitere Möglichkeiten:
- SSH-Tunnel (autossh)
- VPN (WireGuard)
In meiner ursprünglichen Fassung des Artikels 2015 habe ich einen SSH-Tunnel vorgeschlagen. Grund dafür war seinerzeit, dass VPN-Verbindungen relativ mühselig einzurichten waren (IpSec via OpenSwan/StrongSwan o.ä. ist nicht von schlechten Eltern). SSH bietet tatsächlich gute Möglichkeiten, Daten zu tunneln. Nachteil des Tunnels ist dabei, dass er eigens erstellt und überwacht werden muss. Das lässt sich glücklicherweise einigermaßen einfach automatisieren, wenn man autossh verwendet. Zur Einrichtung von autossh gibt es im Netz einige gute Anleitungen.
Allerdings habe ich mit autossh nicht die besten Erfahrungen gemacht. Die SSH-Verbindungen brechen immer wieder mal ab und autossh konnte nicht immer sicherstellen, dass diese Verbindungen wieder hergestellt wurden. Daher rate ich von einer dauerhaften SSH-Verbindung zwischen zwei Maschinen im Live-Betrieb inzwischen ab. Falls jemand die gleichen oder andere Erfahrungen gemacht hat, bin ich für Feedback dankbar.
Eine deutlich bessere Alternative bietet VPN mittels WireGuard. Auch hier gibt es im Netz jede Menge Anleitungen. Einige Router wie die Fritz!Box 7590 bieten inzwischen WireGuard an, so dass ein Server als Publisher (und WireGuard-Client) relativ leicht einzubinden ist.
-
Dank an Janos Kutscherauer, der das 2018 für mich gemessen hat: Eine 12 Byte große MQTT-Nachricht lag bei der Übertragung via TLS bei 29 Bytes. Das mag viel klingen, ist aber gering, wenn man den gesamten Netzwerkstack vergleicht. Das übertragene Paket hat bei TLS eine Größe von 149 Byte (IP, TCP, TLS, MQTT, Payload) im Vergleich zu 120 ohne Verschlüsselung (IP, TCP, MQTT, Payload). ↩︎