{{ :undefined:mosquitto-logo-only.png?100 |Mosquitto Logo}} ====== Mosquitto MQTT message broker ====== Das [[wpde>MQTT]] Nachrichtenprotokoll ermöglicht den Austausch von Nachrichten resp. Telemetriedaten zwischen Geräten. Im Open Source Bereich ist MQTT mittlerweile synonym mit der [[https://mosquitto.org/|Mosquitto MQTT Software Suite]]. Über einen oder mehrere zentrale MQTT Broker (Server) können Clients Nachrichten senden (publish) und empfangen (subscribe). Zu sendende Nachrichten werden mit einem Thema (Topic) versehen, andere Clients erhalten diese Nachricht nur, wenn sie das entsprechende Topic abonniert (subscribed) haben und über die nötigen Berechtigungen verfügen. ===== Broker (Server) ===== Als Message Broker (Server) wird bisher fast ausschliesslich Mosquitto eingesetzt. Zum momentanten Zeitpunkt in der Version 1.4.10 erhältlich. Da die Software zurzeit noch erhebliche Entwicklungsschritte erfährt, ist es auch für Linux User sinnvoll sich nicht auf die Pakete der Distribution zu verlassen, sondern die aktuelle Version herunterzuladen und zu kompilieren. ==== Mosquitto aus Quellen installieren ==== Installieren von Abhängigkeiten (ev. noch mehr): sudo apt-get install build-essential libc-ares-dev uuid-dev Herunterladen der aktuellen Quellen von https://mosquitto.org/download/: cd /tmp; wget http://mosquitto.org/files/source/mosquitto-1.4.10.tar.gz Auspacken des Archivs: tar xzvf mosquitto-1.4.10.tar.gz Kompilieren: cd mosquitto-1.4.10/ make Installieren direkt: make install oder als deinstallierbares Paket mit checkinstall: sudo checkinstall -D --install=no make install ==== Mosquitto konfigurieren (Broker/Server) ==== Damit wir den Mosquitto Server als broker starten können, müsssen wir noch ein paar Kleinigkeiten konfigurieren: Einen User für Mosquitto anlegen (soll ja nicht als root laufen..): adduser --disabled-password mosquitto Erstellen einer leeren, aber dokumentierten Konfigurationsdatei die später mit Optionen und Direktiven erweitert werden kann: sudo cp /etc/mosquitto/mosquitto.conf.example /etc/mosquitto/mosquitto.conf Nun können wir den Broker für einen ersten Testlauf starten: sudo mosquitto -c /etc/mosquitto/mosquitto.conf Die darauffolgende Ausgabe sollte in etwa so aussehen: 1485093598: mosquitto version 1.4.10 (build date 2017-01-22 13:37:36+0100) starting 1485093598: Using default config. 1485093598: Opening ipv4 listen socket on port 1883. 1485093598: Opening ipv6 listen socket on port 1883. ===== Clients (Subscriber/Publisher) ===== Mit der Installation von mosquitto wie unter [[#mosquitto_aus_quellen_installieren|Mosquitto aus Quellen installieren]] beschrieben erhalten wir auch die beiden Client Programme //mosquitto_sub// und //mosquitto_pub//. ==== Einfacher Sub/Pub Test ==== Nach dem der Server auf dem gleichen Rechner wie unter [[#mosquitto_konfigurieren_brokerserver|Mosquitto konfigurieren]] beschrieben läuft, können wir mit einem Client lauschen und mit einem anderen eine Nachricht senden. Am besten geht das mit zwei Terminalfenstern nebeneinander. Im ersten Fenster subscriben wir auf alles (#): mosquitto_sub -v -t "#" Das -v sorgt dafür, dass uns auch das Topic ausgegeben wird. Im zweiten Fenster senden wir eine Nachricht: mosquitto_pub -t "public/test1" -m "Hello World" Im ersten Fenster sollte nun sowas ähnliches auftauchen wie: public/test1 Hello World Bravo, der Server läuft und die Clients können verbinden, senden und empfangen. ===== Weitere Schritte (things to try) ===== ==== Verbindung über Netzwerk ==== Unser Demo Mosquitto Server kann natürlich auch über das Netzwerk erreicht werden, dazu ist einfach beim Aufruf von mosquitto_sub resp. mosquitto_pub der Aufruf um ''-h hostname/ip'' zu erweitern: mosquitto_sub -h 192.0.2.25 -v -t "#" resp. mosquitto_pub -h 192.0.2.25 -t "public/test1" -m "Hello World" Wenn die IP Adresse des Mosquitto server 192.0.2.25((https://tools.ietf.org/html/rfc5737)) ist. ==== Verwenden mit anderen Programmen (piping) ==== Eine der Stärken von Unix ist unter anderem das Piping. Das bedeutet, dass wir die Ausgabe eines Programmes als Eingabe für ein anderes Programm verwenden könen und umgekehrt. Im Zusammenhang mit Mosquitto ergeben sich daraus viele interessante Möglichkeiten, hier nur mal ein paar exemplarische Anregungen: === Logfile überwachen === So können wir z.B. ein Logfile auf einem anderen Rechner überwachen: sudo tail -f /var/log/auth.log | mosquitto_pub -h mosquitto-host.sample.tld -t "public/test1" -l === Statusmeldungen als Bildschirm Overlay ausgeben === Mit aosd_cat((Paket **aosd-cat** in Debian-basierenden Distributionen)) kann man einfach Texte als Bildschirm Overlay ausgeben: mosquitto_sub -t "#" | \ /usr/bin/aosd_cat -p 0 -x 360 -y 150 -B blue -b 100 -f 6000 -o 6000 -n "Cantarell 20" -w 1200 Das sieht dann in etwa so aus (vorausgesetzt, irgendwo publiziert ein anderer Client die entsprechende Nachricht): {{ :mqtt:aosd_cat1.png?400 |aosd_cat Ausgabe}} ==== Weitere Clients / Libraries ==== Eine Liste zurzeit verfügbarer APIs findet sich unter https://github.com/mqtt/mqtt.github.io/wiki/apis_and_examples. === Python === * [[https://eclipse.org/paho/clients/python/docs/|paho-mqtt]]\\ Installation mit pip install paho-mqtt === Arduino (C++) === * [[https://github.com/knolleary/pubsubclient|Arduino Client for MQTT]] * [[https://github.com/mqtt/mqtt.github.io/wiki/Arduino%20MQTT%20Client|Arduino Pub/Sub Client]] === Node.js === * [[https://www.npmjs.com/package/mqtt|A (Node.js) library for the MQTT protocol]] * [[https://blog.risingstack.com/getting-started-with-nodejs-and-mqtt/|Getting Started with Node.js and MQTT]] ===== Erweiterte Konfiguration ===== ==== User Authentisierung ==== Mosquitto verfügt über integriertes einfaches Usermanagement zur Zugriffskontrolle. Um dieses zu nutzen muss die Serverkonfiguration ///etc/mosquitto/mosquitto.conf//((https://mosquitto.org/man/mosquitto-conf-5.html)) wie folgt erweitert werden: .. password_file /etc/mosquitto/mqtt_passwd .. Nun kann dass password_file mit folgendem Befehl angelegt und ein erster User //testuser// erzeugt werden: sudo mosquitto_passwd -c /etc/mosquitto/mqtt_passwd testuser Diese User können nun zur Zugriffsteuerung mit ACLs verwendet werden. ==== ACL ==== Mosquitto bietet eingeschränkte Möglichkeiten zur Zugriffsbeschränkung mit der Konfiguration einer externen ACL((Access Control Lists)) Datei in der Mosquitto Konfiguration, z.B.: acl_file /etc/mosquitto/mqtt_acl In dieser Datei können dann rudimentäre Zugriffsberechtigungen vergeben werden, nachfolgend die ACL Datei die für unseren Testserver mqtt.boxtec.ch verwendet wird: # Zugriffsberechtigungen für anonyme User, falls mit allow_anonymous true eingeschaltet topic read # # Bridge Status Meldungen pattern write $SYS/broker/connection/%c/state # Authentisierte User können auf user/meinusername/ und alles darunter lesen und schreiben pattern readwrite user/%u/# # Public Bereich pub/playground und alles darunter kann von jedem authentisierten User geschrieben und gelesen werden pattern readwrite pub/playground/# Zuoberst in der Datei werden die Berechtigungen für anonyme Zugriffe gesetzt, ist //allow_anonymous false// haben diese keine Wirkung. Jede Zeile beginnt entweder mit //topic//, //user// oder //pattern//, folgt eine Zeile mit //user//, sind nachfolgende Zeilen für diesen User gültig. Eine ausführliche Beschreibung findet sich in der man-page zu [[https://mosquitto.org/man/mosquitto-conf-5.html|mosquitto-conf]]. ==== Bridging ==== ~TBD~ ==== SSL/TLS ==== SSL/TLS kann für die Verschlüsselung der Kommunikation, aber auch für die Authentisierung der Clients auf PKI Basis dienen. In einem ersten Schritt möchten wir dafür sorgen, dass die Kommunikation mit dem Server über SSL gesichert wird. == Zertifikate und Schlüssel erstellen == Zuerst müssen wir für den Server ein Zertifikat erstellen und dies (in unserem Fall von einer eigenen) Certificat Authority signieren lassen. In einem ersten Schritt benötigen wir das openssl Paket falls es noch nicht installiert ist: sudo apt-get install openssl sudo mkdir /etc/mosquitto/certs cd /etc/mosquitto/certs Erzeugen eines eigenen CA Schlüssels und Zertifikat (PEM Passphrase gut merken!): sudo openssl req -new -x509 -days 1999 -extensions v3_ca -keyout ca.key -out ca.crt Erzeugen eines Server Schlüssels: sudo openssl genrsa -out server.key 2048 Certificate Signing Request erzeugen: sudo openssl req -out server.csr -key server.key -new Selber signieren: sudo openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 == Serverkonfiguration == Damit haben wir alle nötigen Dateien beinander und können nun die Serverkonfiguration ///etc/mosquitto/mosquitto.conf//((https://mosquitto.org/man/mosquitto-conf-5.html)) um folgende Zeilen erweitern: listener 1883 listener 1884 cafile /etc/mosquitto/certs/ca.crt keyfile /etc/mosquitto/certs/server.key certfile /etc/mosquitto/certs/server.crt Damit haben wir nun zwei listener, einer auf Port 1883 der auf unverschlüsselte Anfragen reagiert und einer auf 1884 der nur mit SSL Anfragen arbeitet. == Client Verbindungen mit SSL == Damit Clients nun verschlüsselt mit dem Server kommunizieren können, muss die weiter oben erstellte Datei //ca.crt// alle Clients zugänglich gemacht werden. Da wir den SSL Port auf den nicht-standard Port 1884 gelegt haben, muss auch dieser mitgegeben werden: mosquitto_sub -v -t "#" --cafile /etc/mosquitto/certs/ca.crt -p 1884 zum Subscriben, resp. zum Publizieren: mosquitto_pub -t "public/test1" -m "Hello World" --cafile /etc/mosquitto/certs/ca.crt -p 1884 == Geht ned, und nu? (common pitfalls) == Der Name, den Du beim Erstellen des Certificate Signing Request als //Common Name / FQDN// angegeben hast, muss mit dem übereinstmmen den Du beim Client als Argument für //-h// verwendest. Wenn Du also z.B. als FQDN //meincooler-mosquitto.local// verwendet hast und danach mit //-h 127.0.0.1// die Verbindung testest wird das scheitern. Du musst dann ebenfalls die Option --insecure mitgeben, also z.B.: mosquitto_sub -v -t "#" --cafile /etc/mosquitto/certs/ca.crt -p 1884 --insecure ==== Nachrichten signieren mit ECDSA ==== Nachrichten die über Mosquitto nach dem MQTT Standard versendet werden, können unterwegs durch einen Angreifer recht problemlos verändert werden. Man kann sicher damit leben, wenn der experimentelle Temperatursensor auf dem Tisch 0.5° höhere Werte meldet als er eigentlich hat. Wenn man aber z.B. über eine solche Nachricht z.B. Lasten schaltet, sagen wir doch mal eine Sprinkler-Anlage, einen externen Alarm etc., dann gewinnt die Frage ob das Signal auch wirklich von dort kommt wo es vorgibt herzukommen und man dem Inhalt der Nachricht trauen kann an erheblichem Gewicht. Für unser Beispiel haben wir uns für eine digitale Signatur mit einem digitalen Signatur Algortithmus((DSA)) auf Basis der [[wpde>Elliptic_Curve_Cryptography|Elliptische-Kurven-Kryptographie]] entschieden. Elliptische Kurven bieten im Gegensatz zu anderer Public Key Kryptographie bei viel geringerer Schlüsselgrösse viel höhere Sicherheit. Als Parametrisierungs-Standard haben wir uns für //secp256k1((https://en.bitcoin.it/wiki/Secp256k1))// entschieden. Der Standard //secp256k1// bietet eine schnelle Berechnung bei angemessener Sicherheit und er wird unter anderem auch für Bitcoin und die meisten alternativen Kryptowährungen eingesetzt (damit sind um 15 Mia USD gesichert, was man als recht wirkungsvolle Bug-Bounty ansehen kann). Unser Ziel in diesem kleinen Beispiel soll sein, dass wir uns beim Empfang einer signierten Meldung darauf verlassen können, dass diese vom Absender den wir erwarten kommt und auch nicht in Transit verändert wurde. Wir haben also Daten-Integrität, Authentisierung des Absenders und Nachweisbarkeit. === Beispiel: ECDSA Nachrichten mit Python versenden und empfangen === == Voraussetzungen == Eine Python Installation (2.7x oder 3.x) mit dem Modul [[https://github.com/warner/python-ecdsa|python-ecdsa]] installiert: sudo apt-get install python-ecdsa oder auf nicht Debian-basierenden Systemen: pip install python-ecdsa == Schlüssel für ECDSA erzeugen == Für unser Beispiel benötigen wir nur einen privaten Schlüssel und den dazugehörigen öffentlichen Schlüssel (//Public Key//). Wir nutzen hier dafür [[https://www.openssl.org/|OpenSSL]], alternativ können die Schlüssel aber auch mit //python-ecdsa// erstellt werden, wenn auf Deiner Plattform openssl nicht vorhanden ist. Zuerst wird der private Schlüssel erzeugt, dieser sollte wie der Name schon suggeriert auch wirklich privat bleiben, die Zuverlässigkeit des ganzen Verfahrens steht und fällt mit der Geheimhaltung dieses Schlüssels: openssl ecparam -genkey -name secp256k1 -noout -out private.pem Aus diesem privaten und geheimen Schlüssel wird wiederum der öffentliche Schlüssel, der nicht geheim ist, erzeugt: openssl ec -in private1.pem -pubout -out public.pem Der oder die Empfänger von signierten Nachrichten benötigen nun den Public Key um Signaturen von Nachrichten verifizieren zu können. Der Sender benötigt den Private Key um eine Signatur zu einer Nachricht erstellen zu können. == Python Code Sender == import paho.mqtt.client as mqtt import time, random, base64 from ecdsa import SigningKey from os import urandom def publish_signed(client, topic, message): sk = SigningKey.from_pem(open("private.pem").read()) message = str(int(time.time())) + message signed_message = sk.sign(message) out_message = message + ":" + base64.b64encode(signed_message) client.publish(topic, out_message) if __name__ == '__main__': client = mqtt.Client() client.username_pw_set("meinuser", "meinpw") client.connect("mqtt.boxtec.ch") client.subscribe("user/boxtec/#", qos=1) random.seed(urandom(32)) my_message = "Testmessage" + "_" + str(random.randrange(0,100)) publish_signed(client, "user/meinuser/dsa-test", my_message) print "message sent, exiting." Die Nachricht selber wird mit einem : vom Timestamp getrennt, um die Sache etwas spannender zu machen hängen wir dem String jeweils noch eine Zufallszahl von 0-99 an. == Python Code Empfänger == import paho.mqtt.client as mqtt import time, base64 from ecdsa import VerifyingKey, BadSignatureError def on_subscribe(client, userdata, mid, granted_qos): print "Subscribed: " + str(mid) + " " + str(granted_qos) def on_message(client, userdata, msg): global last_timestamp try: if int(msg.payload[0:10]) <= last_timestamp: print "Replay attack detected, aborting." return except: return cleartext, b64sig = msg.payload.split(":") signature = base64.b64decode(b64sig) print "Timestamp:", cleartext[0:10] print "Cleartext:", cleartext[10:] print "Signature:", signature vk = VerifyingKey.from_pem(open("public.pem").read()) try: vk.verify(signature, cleartext) print "Signature verified!" last_timestamp = int(cleartext[0:10]) except BadSignatureError: print "Signature invalid!" if __name__ == '__main__': last_timestamp = int(time.time()) time.sleep(1) client = mqtt.Client() client.on_subscribe = on_subscribe client.on_message = on_message client.username_pw_set("meinuser", "meinpw") client.connect("mqtt.boxtec.ch") client.subscribe("user/meinuser/#", qos=1) while True: client.loop(timeout=1.0, max_packets=1) Der Sender sendet mit jeder Sendung eine Timestamp. Um Replay-Angriffe zu erschwerden, wird vom Empfänger jeweils nur ein nächstes Paket mit einer grösseren Timestamp akzeptiert. Pakete mit gültiger Signatur und gleichhohem oder kleineren Timestamp werden verworfen. Der Demo Code obige ist jenseits einer brauchbaren Implementation, zeigt aber auf wie einfach übertragene Nachrichten sicher signiert werden können. ===== Fragen, Anregungen, Kommentare ===== Für Anmerkungen, Vorschläge und Korrekturen sind wir sehr dankbar, Fragen beantworten wir im Rahmen unserer Möglichkeiten gerne, bitte postet dazu in das entsprechende Forum Thema unter: http://forum.boxtec.ch/index.php/topic,3216.0.html