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:
1 2 3 4 |
#Man kann auch ein PPA-Paket installieren, wenn man immer die neueste Mosquitto-Version haben will. Auf Ubuntu 14.04 es auf jeden Fall installiert werden: #sudo apt-get install software-properties-common #sudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa sudo apt-get 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:
1 |
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 darf der Benutzer mqtt? Das beschreibt eine weitere Datei, in der die Zugangsberechtigungen gespeichert sind:
1 2 3 4 |
sudo bash -c "cat > /etc/mosquitto/access.acl <<EOF user mqtt pattern 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 Pattern bestimmt das Muster, das erlaubt ist. 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:
1 2 3 4 5 6 |
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:
1 2 3 4 5 6 7 8 9 10 |
mosquitto_sub -t '#' sudo service mosquitto restart # 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:
1 2 3 4 |
sudo bash -c "cat > /etc/mosquitto/conf.d/ssl.conf <<EOF 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:
1 2 3 4 |
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 der selbe wie bei der Authentifizierung oben sein.
Die Kommunikation kann nun verschlüsselt erfolgen:
1 2 3 4 5 6 7 |
#Neustart Server sudo service mosquitto restart # 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:
1 2 |
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:
1 2 3 4 5 6 |
host=$(hostname -f) sudo bash -c "cat > /etc/mosquitto/conf.d/ssl.conf <<EOF 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.
Zum Testen kann man folgende Skripte ausführen:
1 2 3 4 5 6 7 |
#Server neu starten sudo service mosquitto restart #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.
Das macht die Verfahren für die Überwachung von Serverdiensten etwas problematisch. 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 relavant sein kann.
Damit lohnt sich die verschlüsselte Übertragung eher für Dienste, die entweder relativ selten Updates schicken oder gleich sowieso so viele, dass der Unterschied zur unverschlüsselten Übertragung irrelevant wird.
Alternative: SSH-Tunnel
Will man auf Sicherheit trotzdem nicht verzichten, bietet sich ein SSH-Tunnel an (oder alternativ eine VPN-Verbindung, auf die ich hier nicht eingehe). Dabei wird ein verschlüsselter Kanal zwischen zwei Maschinen aufgebaut. Weder Broker noch MQTT-Dienste müssen eine Verschlüsselung unterstützen, für Publisher und Subscriber scheint es so, als liefe der Broker auf dem lokalen System. Der SSH-Tunnel besitzt ebenfalls einen gewissen Overhead: Am Anfang werden Schlüssel ausgetauscht u.ä. Die Übertragung der Daten selbst ist jedoch deutlich leichtgewichtiger als bei den TLS-Übertragungen, die ja pro Übertragung die Verschlüsselungsinformationen austauschen müssen. Im Test kam der oben beispielhaft verwendete Publish auf eine Datenmenge von 636 Bytes. Das liegt vor allem daran, fass SSH ein TCP over TCP-Tunnelprotokoll ist und der im Tunnel verschlüsselte TCP-Verkehr zum Übertragungsvolumen angerechnet werden muss. Die Datenmenge ist damit immer noch hoch, aber deutlich geringer als bei den TLS-Verfahren. Rechnerisch kommt man pro Monat und Dienst dann auf eine Datenmenge von 26 MB + Protokolloverhead – akzeptabel.
Ach ja, um SSH-Tunneling zu testen, kann man auf der lokalen Maschine folgendes eingeben:
1 2 3 4 |
# Server zunächst stoppen sudo service mosquitto stop ssh -p 22 -L 1883:localhost:1883 mein-server.de -N |
22 gibt dabei die Portnummer des SSH-Dienstes an (ändern, falls man einen anderen Port verwendet), mein-server.de muss natürlich der Name oder die IP einer entfernten Maschine sein. Nun kann man die mosquitto_sub und -pub-Befehle auf der lokalen Maschine ausführen als würde auf ihr ein lokaler Broker laufen.
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.
Nachtrag zu autossh: Ich habe das in verschiedenen größeren Text-Situationen zwischen Servern getestet und war nicht ganz zufrieden. 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 eher ab. Falls jemand die gleichen oder andere Erfahrungen gemacht hat, bin ich für Feedback dankbar.
cprior
Prima Artikel!
Janos Kutscherauer
Hallo und vielen herzlichen Dank für diesen guten Beitrag!
Allein mit dem „Vergleich der Übertragungsmethoden“ bin ich nicht einverstanden. Bei der Berechnung wird für jede MQTT-Kommunikation ein TSL-Verbindungsaufbau mit berechnet. Dieser ist in der Tat sehr teuer, nach meiner Messung ca. 3 kB, das Internet schreibt von ca. 6 kB. Die Verbindung kann und sollte bei MQTT aber einfach offen gehalten werden, und schon wird der Overhead in der normalen Datenübertragung untergehen.
Den TLS-Overhead einer 12-byte MQTT Nachricht (topic + message) habe ich mit 29 Bytes gemessen. Inklusive IP und TCP.
Also ohne TLS: 120 Bytes (inkl. IP, TCP, MQTT, Payload)
Mit TLS: 149 Bytes (inkl. IP, TCP, TLS, MQTT, Payload)
Also einfach Verbindung offen halten, dann ist der TLS Overhead deutlich kleiner.
mkalus Autor
Hallo Janos,
danke für das Feedback. Richtig, wenn die Verbindung offen bleibt, dann ist der TLS Overhead natürlich viel, viel geringer. In der Regel wird die Verbindung ja offen gehalten, ähnlich wie bei SSH-Tunnel, der ja auch initial einen Overhead hat und danach nicht mehr.
Ich hatte seinerzeit (2015) mit den Kommandozeilentools getestet, die in der Tat immer wieder eine neue Verbindung aufbauen und ich kenne ich Clients, die das ebenfalls ähnlich machen. Das betrifft vor allem Sender, die nur sehr selten Updates schicken, da ihr Zustand sich sehr selten ändert.
Deshalb der Hinweis: Entweder den Overhead einkalkulieren oder die Verbindungen offen halten, auch bei den Sendern.
test1b2
Schöner Artikel!
Ein kleiner Fehler hat sich bei:
sudo bash -c „cat > /etc/mosquitto/access.acl <<EOF
user mqtt
pattern readwrite #
EOF"
eingeschlichen statt pattern muss es topic heißen
Grüße
mkalus Autor
Danke für das Feedback und guter Punkt. Meines Wissens müsste an dieser Stelle auch
pattern
funktionieren. Müsste ich jetzt glatt mal testen, was ich da vor drei Jahren behauptet habe 😉mkalus Autor
Wer mehr auf Videos steht, hier eine gute Anleitung und Beispiele für den Arduino: https://www.youtube.com/watch?v=gU5Vp0zCzak