MQTT-Kommunikation, aber sicher: TLS und Berechtigungen - Mosquitto Part II

Sichere die Kommunikation deiner IoT-Geräte mit Zertifikaten und erlaube allen Clients nur so viel wie sie können müssen mit Benutzerberechtigungen.

MQTT-Kommunikation, aber sicher: TLS und Berechtigungen - Mosquitto Part II

Sichere die Kommunikation deiner IoT-Geräte mit Zertifikaten und erlaube allen Clients nur so viel wie sie können müssen mit Benutzerberechtigungen.

Bearbeitungszeit: ~1 Stunde (Ich bin mir nicht ganz sicher. Wenn du dieser Anleitung folgst und zufällig auf die Uhr schaust, gib mir und allen Lesenden gerne Feedback, um die Schätzung zu verbessern)

Rückblick und Ziele

Im letzten Blogpost Mosquitto Part I hast du einen Mosquitto Broker installiert und in der Konfiguration Benutzer angelegt. Für diesen Post gehe ich davon aus, dass ein Benutzer mit dem Namen heizung und dem Passwort 12345 existiert.

In diesem Blogpost lernst du die nötigen TLS-Zertifikate (oft auch SSL genannt) zu erstellen, zu signieren und für eine verschlüsselte Kommunikation zwischen MQTT-Broker und -Client zu verwenden. Leider ist die Verschlüsselung nicht auf allen Endgeräten nachher verfügbar (Die Mikrocontroller haben teilweise nicht genug Rechenleistung oder Speicher dafür), weswegen beide Varianten parallel betrieben werden. Als zweites Thema zeige ich dir das Berechtigungssystem um bestimmten Benutzern bestimmte Zugriffe zu erlauben.

Ursprünglich wollte ich den Nutzer "thermometer" nennen und nicht "heizung". Tja, der Zug ist wohl abgefahren...

Zertifikate und verschlüsselte Verbindung per TLS

Was sind Zertifikate? In einer sehr verkürzten Erklärung sind Zertifikate und die zugehörigen kryptographischen Schlüssel eine Möglichkeit Kommunikation zu verschlüsseln, zu signieren und für die Authentizität anderer Zertifikate zu bürgen.

Die Authentizität von Zertifikaten wird üblicherweise von Behörden oder Unternehmen bescheinigt, die dann als sogenannte Certificate Authority (CA) auftreten und dein Zertifikat unterschreiben. Sie bürgen also im Prinzip kryptographisch dafür, dass dein Zertifikat auch zu dir als Person gehört. Für die interne Nutzung im Heimnetzwerk ist es nicht unbedingt notwendig so eine CA zu involvieren. Deswegen wirst du deine eigene CA anlegen und die Zertifikate selbst unterzeichnen.

Wichtigste Komponenten:

  • privater Schlüssel (.key)
  • öffentliches Zertifikat zu diesem Schlüssel (.crt)
  • öffentliches Zertifikat der Certificate Authority (ca.crt)

Falls du mehr Details über das Thema Zertifikate und Kryptographie suchst, schau mal auf diesem Wikipedia-Artikel vorbei.

BEVOR ES LOSGEHT: Alle openssl Befehle im Folgenden müssen einzeln ausgeführt werden, weil teilweise zusätzliche Eingaben erwartet werden. Wenn du den ganzen Block kopierst, führt der pi aber den ersten Befehl aus und versteht den zweiten Befehl als Eingabe (z.B. für die Passworteingabe)

Certificate Authority

Die CA die du im Folgenden anlegst besteht aus zwei Komponenten, einem privaten kryptographischen Schlüssel (nicht verlieren, nicht weitergeben!) und einem öffentlichen Zertifikat, das zur Validierung der unterzeichneten Nutzer-Zertifikate dient. Das CA-Zertifikat sollte idealerweise im Ordner /etc/mosquitto/ca_certificates/ liegen. Der Einfachheit halber kannst du den privaten Schlüssel auch direkt dort erzeugen.

# Als root
sudo -i

# Variablen für Pfade zu den Zertifikatsordnern, damit die Zeilen kurz bleiben.
CA_PATH=/etc/mosquitto/ca_certificates

openssl genrsa -des3 -out ${CA_PATH}/ca.key 2048

openssl req -new -x509 -days 1826 -key ${CA_PATH}/ca.key -out ${CA_PATH}/ca.crt

In Zeile 7 legst du deinen privaten Schlüssel für die CA an und gibst dem Key ein Passwort. Jeder im Besitz des Keys und des Passworts kann andere Zertifikate unterschreiben, weswegen diese beiden streng vertraulich zu behandeln sind (Im Heimkontext ist das natürlich nicht ganz so kritisch).

In Zeile 9 erzeugst du dann zu dem Key gehörig ein Zertifikat, das 1826 Tage (also 5 Jahre) gültig ist. In dem Prozess werden viele Werte abgefragt, die in das Zertifikat geschrieben werden (darunter Firmennamen und Kontakt-Email und so), für hier wichtig ist lediglich das Land auf DE Country Name (2 letter code) [AU]: DE und den Namen z.B. auf RootCA zu setzen Common Name (e.g. server FQDN or YOUR name) []:RootCA. Alles andere kannst du mit . auf leer oder mit Enter auf den in eckigen Klammern angezeigten Default-Wert setzen.

Mosquitto-Broker-Zertifikat

Als nächstes legst du ähnlich zur CA einen Key für den Mosquitto-Broker an; dieses Mal allerdings ohne Passwort.
Danach erstellst du einen certificate signing request (.csr Datei), also die Anfrage ein Zertifikat von der CA unterschreiben zu lassen.

Achtung: Ab hier sollte der Common Name der IP-Adresse des Computers entsprechen (per Aufruf von ip address show in der Konsole überprüfen, s. z.B. Abbildung mit IP 10.0.0.34). Wenn du im Heimnetzwerk Namensauflösung hast, kannst du auch den DNS-Namen verwenden, wenn du nicht weißt was das bedeutet, nimm die IP-Adresse. Mosquitto ist da sehr strikt, wenn es die Zertifikate überprüft.

Lege die Keys dieses Mal im Verzeichnis /etc/mosquitto/certs/ an.

# Als root
sudo -i

# Variablen für Pfade zu den Zertifikatsordnern, damit die Zeilen kurz bleiben.
CA_PATH=/etc/mosquitto/ca_certificates
CERT_PATH=/etc/mosquitto/certs

openssl genrsa -out ${CERT_PATH}/mosquitto.key 2048

openssl req -new -out ${CERT_PATH}/mosquitto.csr -key ${CERT_PATH}/mosquitto.key

openssl x509 -req -in ${CERT_PATH}/mosquitto.csr -CA ${CA_PATH}/ca.crt -CAkey ${CA_PATH}/ca.key -CAcreateserial -out ${CERT_PATH}/mosquitto.crt -days 360

Die letzte Zeile generiert aus dem Certificate Signing Request .csr ein unterschriebenes Zertifikat .crt für den Mosquitto Broker. Normalerweise würdest du dein .csr einschicken zur CA und die sendet dir das .crt zurück.
Die Gültigkeit von Zertifikaten ist üblicherweise 1 Jahr bzw. 360 Tage. So auch hier. Das bedeutet, dass du nach einem Jahr ein neues Zertifikat für den Broker erstellen musst.

Für Clients die auf Mikrocontrollern wohnen, empfehle ich eine längere Gültigkeit, weil es eher umständlich ist, einmal im Jahr die Firmware neu aufzuspielen.

Client-Zertifikat

Hier nochmal der Hinweis, dass die Anleitung auf den Heim-Anwendungsbereich ausgelegt ist. Normalerweise erzeugst du die privaten Schlüssel nur auf dem Gerät, das sie nutzen soll und die Schlüssel verbleiben dort. Lediglich .csr und .crt werden dann übertragen und mit der CA ausgetauscht. Für das Beispiel zeige ich dir aber wie du das Client-Zertifikat auf dem Raspberry Pi generierst.

Damit nicht nur der Broker seine Identität beweisen kann, legst du noch ein Zertifikat für deinen ersten Client an. Die Befehle sind die gleichen wie vorher, nur dass du die Namen der Dateien ersetzt.

# Als root
sudo -i

# Variablen für Pfade zu den Zertifikatsordnern, damit die Zeilen kurz bleiben.
CA_PATH=/etc/mosquitto/ca_certificates
CERT_PATH=/etc/mosquitto/certs

openssl genrsa -out ${CERT_PATH}/client_heizung.key 2048
openssl req -new -out ${CERT_PATH}/client_heizung.csr -key ${CERT_PATH}/client_heizung.key
openssl x509 -req -in ${CERT_PATH}/client_heizung.csr -CA ${CA_PATH}/ca.crt -CAkey ${CA_PATH}/ca.key -CAcreateserial -out ${CERT_PATH}/client_heizung.crt -days 360

Konfiguration

Jetzt da alle Zertifikate erstellt sind, musst du Mosquitto noch mitteilen, dass es TLS nutzen soll und wo die entsprechenden Zertifikate liegen. Dazu öffne in einem Texteditor eine neue Config-Datei in /etc/mosquitto/conf.d/, z.B. sudo nano /etc/mosquitto/conf.d/020-listener-with-tls.conf und füge folgenden Inhalt ein

listener 8883

certfile /etc/mosquitto/certs/mosquitto.crt
keyfile /etc/mosquitto/certs/mosquitto.key
cafile /etc/mosquitto/ca_certificates/ca.crt

require_certificate true

Die erste Zeile definiert den Port auf dem Mosquitto lauschen soll. 8883 ist hierbei die Konvention, du kannst aber theoretisch einen anderen Port >1024 wählen.

certfile und keyfile geben die Pfade zu den Serverzertifikaten an, mit denen Mosquitto sich authentifiziert.

cafile ist das öffentliche Zertifikat der CA, mit dem sichergestellt wird, dass die Zertifikate (Server und Client) von der CA unterschrieben wurden.

require_certificate zwingt Clients sich mittels Zertifikat auszuweisen. Wenn es gute Gründe gibt, warum deine Clients keine Zertifikate brauchen oder benutzen können, kann das auf false gesetzt werden. Das verringert jedoch die Sicherheit.

Zu guter letzt muss Mosquitto einmal die Config neuladen. Ich mache das üblicherweise mit sudo systemctl restart mosquitto. Bei diesem Befehl startet Mosquitto neu und hat dadurch etwa 5-10 Sekunden Ausfallzeit. Es gibt im Internet noch eine Methode die Config im Betrieb neuzuladen per kill-Signal mit dem Befehl kill -HUP <prozess-ID>. Ich finde es umständlich mir die Prozess-ID immer herauszusuchen oder zu merken, deshalb ist ein Neustart in meinen Augen die pragmatischere Lösung.

MQTTS-Test

Fast geschafft. Für den Test empfehle ich dir wie im letzten Blogpost zwei Terminalfenster aufzumachen. Kopiere als erstes die Client-Zertifikate in das Home-Directory vom User pi (um zu simulieren, dass es eigentlich auf einem anderen Gerät liegt) und weise sie diesem User zu. Der Private-Key kann nämlich nur vom Besitzer gelesen und geschrieben werden (Zugriffsmuster rw-------). Außerdem bekommt der Pi-User eine Kopie vom CA-Zertifikat

sudo mv /etc/mosquitto/certs/client_heizung.* /home/pi/
sudo chown pi.pi /home/pi/client_heizung.*

cp /etc/mosquitto/ca_certificates/ca.crt /home/pi/ca.crt

Wenn du in den beiden Terminalfenstern jetzt mit dem normalen Pi-User eingeloggt bist, kannst du im ersten eine MQTT-Subscription starten und in dem anderen eine Nachricht publizieren. Ich gebe dir jeweils zwei äquivalente MQTT-Befehle an, nämlich einmal als URL und einmal mit Parametern, benutze den, der dir besser gefällt. Die IP-Adresse von meinem Raspberry Pi ist 10.0.0.34 falls deine anders ist, benutze bitte diese.

Terminal 1

IP=10.0.0.34

mosquitto_sub -L mqtts://${IP}/topic/temperature --cafile ~/ca.crt --cert ~/client_heizung.crt --key ~/client_heizung.key

mosquitto_sub -p 8883 -h ${IP} -t topic/temperature --cafile ~/ca.crt --cert ~/client_heizung.crt --key ~/client_heizung.key

Terminal 2

IP=10.0.0.34

mosquitto_pub -L mqtts://${IP}/topic/temperature --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m 27.6

mosquitto_pub -p 8883 -h ${IP} -t topic/temperature --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m 27.6

Wenn es klappt, prima :+1: Wenn du jetzt erst einmal selber versuchen möchtest, die Benutzer- und Passwortabfrage vom letzten Blogpost zusätzlich einzuschalten, ist das ein guter Moment. Danach geht es weiter bei Benutzerberechtigungen

Falls noch Fehler auftauchen, hier ein paar Hilfen zur Fehlersuche:

Problem/Fehlermeldung Ursache Lösung
A TLS error occured Das Broker-Zertifikat hat den falschen Common Name Aufruf mit mosquitto_sub ... --insecure sollte funktionieren. Langfristig, Zertifikat neu anlegen.
A TLS error occured Das Client-Zertifikat ist nicht von dieser CA unterschrieben Client-Zertifikat neu anlegen.
A TLS error occured ... Aufruf mit mosquitto_sub ... -d gibt mehr Informationen. Das Log auf dem Pi sudo tail /var/log/mosquitto/mosquitto.log bietet vielleicht antworten
Message kommt nicht an Falsches Topic versuche als Topic bei mosquitto_sub "#" anzugeben, das lauscht auf alle Nachrichten. Klappt es dann, war das topic nicht exakt gleich geschrieben
Andere Fehlermeldung ... Schreib mir auf Twitter oder per Mail am besten mit Ausgabe von mosquitto_sub ... -d und sudo tail /var/log/mosquitto/mosquitto.log. Ich suche dann die Lösung und füge es der Tabelle hinzu.

Benutzerberechtigungen

Benutzerkonten

Beim letzten Mal hast du eine Datei /etc/mosquitto/conf.d/010-access-list angelegt. Mir ist aufgefallen, dass das die Tab-Completion stört, deshalb nennen wir die Datei jetzt /etc/mosquitto/conf.d/accounts.txt

Datei mit

  • einem Account heizung und dem Passwort 12345
  • einem Account plant mit dem Passwort programmer
    /etc/mosquitto/conf.d/accounts.txt
heizung:$6$guNAJNVikT6GYIO7$npbKCOxCX6/LpuCkhTrMsG5JpMbC2ye917RjnGh9uUHjufdOZcoBunvcfICEphOv2y/JqPNi23q3Jj5/1AYUhQ==
plant:$6$DeqKyKQUwH3y6tcz$g72IMq5FRtJaaCZhw9SNYHfcruQ24xWgtdMBpnG+s7dlFSLLHrCeyiuDu6gn9Lw4AIUNipsgVqbqU1idH7FqcQ==

Der Hash kann bei dir anders aussehen, und trotzdem das gleiche Passwort verwenden (Internetsuche "Kryptographie Salt" für mehr Info). Du kannst auch gerne diesen hier verwenden.

/etc/mosquitto/conf.d/020-listener-with-tls.conf

...
require_certificate = true

password_file /etc/mosquitto/conf.d/accounts.txt
allow_anonymous true

Damit sagst du dem Mosquitto-Broker, dass er Passwortlogin unterstützen soll. Außerdem erlaubst du jetzt anonymen Zugriff (ohne Login), weil du ohnehin gleich die Berechtigungen anpasst.

ANMERKUNG eines Lesers (Danke Joachim): Wenn allow_anonymous in Teil 1 gesetzt wurde und hier nicht, dann gilt es implizit trotzdem. Denn die Settings werden teilweise global verstanden. Damit das nicht passiert, musst du an den Anfang eines deiner Config-Files die Zeile per_listener_settings true einfügen. Dadurch werden auch wirklich nur die Konfigurationen berücksichtigt, die explizit beim jeweiligen Listener definiert sind.

Berechtigungen

Als nächstes brauchst du also eine Konfiguration die unterscheidet, welcher User worauf Zugriff hat. Theoretisch kann das direkt ins .conf File geschrieben werden, ich mag es aber übersichtlich und empfehle daher eine separate Datei

/etc/mosquitto/conf.d/access-control-list.txt

# Regeln für alle Accounts, die nicht explizit genannt sind
# Erlaube nur Lesezugriff (bzw. verbiete Schreibzugriff) auf $SYS-topics
topic read $SYS/#

# Regeln für spezifische Accounts
user heizung
topic readwrite topic/test
topic write house/temperature

user plant
topic readwrite #

# Regeln für alles andere (Achtung: hier kann nur noch pattern verwendet werden)
pattern write $SYS/broker/connection/%c/state
pattern read topic/test
pattern readwrite quote_of_the_day

Hast du die Regeln in die Config geschrieben, kannst du direkt weitermachen. Falls du wie ich ein separates File angelegt hast, füge deiner Konfiguration noch die Zeile acl_file /etc/mosquitto/conf.d/access-control-list.txt hinzu.

Berechtigungs-Test

Terminal 1 (Lausche auf alles)

IP=10.0.0.34
mosquitto_sub -L mqtts://plant:programmer@${IP}/# --cafile ca.crt --cert client_heizung.crt --key client_heizung.key

Fall 1 Alle dürfen Topic quote_of_the_day schreiben

Terminal 2

IP=10.0.0.34
# user heizung schreibt
mosquitto_pub -L mqtts://heizung:12345@${IP}/quote_of_the_day --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m "Hallo."
# user plant schreibt
mosquitto_pub -L mqtts://plant:programmer@${IP}/quote_of_the_day --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m "Guten Tag."
# anonymer user schreibt
mosquitto_pub -L mqtts://${IP}/quote_of_the_day --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m "Sei gegrüßt!"

Ausgabe T1

Hallo.
Guten Tag.
Sei gegrüßt!

Fall 2 schreiben auf Topic house/temperature dürfen nicht alle

Terminal 2 (house/temperature-Topic schreiben)

IP=10.0.0.34
# user plant schreibt (Regel readwrite #)
mosquitto_pub -L mqtts://plant:programmer@${IP}/topic/test --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m 27.7

# user heizung schreibt (Regel write house/temperature)
mosquitto_pub -L mqtts://heizung:12345@${IP}/topic/test --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m 27.8

# anonymer user schreibt (erfolglos)
mosquitto_pub -L mqtts://${IP}/topic/test --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m 27.9

Ausgabe T1

27.7
27.8

Fall 3 User heizung darf auf Topic house/temperature NUR schreiben

Terminal 1 (heizung lauscht auf alles)

IP=10.0.0.34
mosquitto_sub -L mqtts://heizung:12345@${IP}/# --cafile ca.crt --cert client_heizung.crt --key client_heizung.key

Terminal 2 (house/temperature-Topic schreiben)

IP=10.0.0.34
# Schreiben auf house/temperature macht keine Ausgabe in T1 (weil heizung in T1 keine Leserechte hat)
mosquitto_pub -L mqtts://heizung:12345@${IP}/house/temperature --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m 28.0

mosquitto_pub -L mqtts://heizung:12345@${IP}/quote_of_the_day --cafile ca.crt --cert client_heizung.crt --key client_heizung.key -m "Das geht wie gehabt"

Ausgabe T1

Das geht wie gehabt

Zusammenfassung

Du hast in diesem Blogpost gelernt, wie du eine CA anlegst, private Schlüssel und Zertifikatanfragen erstellst, diese mit der CA unterschreibst, um ein valides Zertifikat zu erhalten und wie du Mosquitto konfigurierst, diese Zertifikate zu nutzen. Das Thema Verschlüsselung wird im Bereich MQTT oft zu sehr vernachlässigt, es besteht also die Chance, dass du jetzt besser aufgestellt bist, als alle in deinem MQTT-Umfeld :smile:

Im zweiten Teil hast du gelernt, wie du Regeln etablieren kannst um den Zugriff für bestimmte User (und auch anonyme User) zu beschränken. Deinem sicheren MQTT-Heimnetzwerk steht also nichts mehr im Weg.

Was fehlt? Ich habe die Konfiguration von Mosquitto für Websockets weggelassen. Sie ist sehr ähnlich zu dem, was du bereits gemacht hast und ist, soweit ich weiß, notwendig, wenn du MQTT über Javascript (zum Beispiel aus dem Browser) verschlüsselt ansprechen willst.

Aber was ist mit dem listener auf Port 1883?

Hervorragende Frage. Ich habe den unverschlüsselten MQTT listener laufen lassen, weil nicht alle Hardware leistungsfähig genug ist, um TLS zu bewältigen. Hier bleibt leider keine andere Möglichkeit, als unverschlüsselt zu kommunizieren. Dafür werden dann die Berechtigungen umso wichtiger. Im Heimnetzwerk spielt das keine riesige Rolle, spätestens für einen öffentlichen Broker oder Client würde ich mehr Geld in die Hardware investieren um TLS zu garantieren. Auf die genauen Hardware-spezifischen Einschränkungen gehe ich in den folgenden Blogposts ein.

Separate Benutzerregeln für den Listener auf Port 1883 (ohne TLS) anlegen und einbinden schaffst du vermutlich nach diesem Blogpost alleine. Wenn du trotzdem Fragen hast, kontaktiere mich jederzeit auf Twitter oder per Email an info@ und der Domain dieses Blogs.

Ausblick

Für die nächsten Blogposts aus der Reihe, habe ich überlegt, einen Temperatursensor (vermutlich DHT11) und einen WS2812B-LED-Streifen (siehe Abbildung) anzubinden. Das heißt du wirst in den folgenden Posts noch mit Arduino-C++ (ggfs. Python) und verschiedenster Hardware in Berührung kommen (keine Angst, wir machen alles weiterhin Schritt für Schritt). Wenn du darüberhinaus noch Wünsche hast, schreib mir gerne auf Twitter oder per Email an info@ (+Domain dieses Blogs).

Abbildung 2

Den nächsten Teil findest du jetzt unter: https://plantprogrammer.de/mqtt-auf-dem-raspberry-pi-mosquitto-part-iii/