Ziel dieses Tutorials

In diesem Tutorial installieren wir den Mosquitto MQTT-Server inklusive der Implementierung des Websockets in einem Debian 12.6 LXC-Container auf einem Proxmox-Host. Zudem werden wir den Mosquitto-Server härten mittels SSL/TLS-Zertifikaten von Let’s Encrypt via acme.sh.

Systemanforderungen und Vorbereitung

Der Server benötigt nur sehr geringe Hardware-Ressourcen. Meine Empfohlene Konfiguration:

  • 16GB Speicher
  • 512MB RAM
  • 1 vCPU

Ich empfehle zudem noch die Installation des MQTT-Explorer: MQTT-Explorer. Mit diesem können wir später den MQTT-Server sehr gut testen. Der MQTT-Eplorer ermöglicht es komfortabel alle Aktivitäten zu sehen aber auch Befehle zu schicken. Bei dem MQTT-Explorer handelt es sich um eine Desktop-App mit übersichtlicher GUI und einer gut strukturierten Übersicht der Aktivitäten auf dem MQTT-Server.

Installation

Wenn der Container erfolgreich erstellt ist, sollten wir zuerst

1
sudo apt update && sudo apt upgrade -y

ausführen. Anschließend können wir die beiden Pakete mosquitto und mosquitto-clients installieren. Da wir den Server später auch testen möchten, macht es Sinn mosquitto-clients direkt mitzu installieren. Die Version aus dem Debian repository wird immer ein wenig „hinter her hinken“ möchte man das vermeiden kann man es auch selbst compilieren: Mosquitto.org. Die nötigen Infos dazu findet man dort. Wir installieren beide Pakete nun mit den Befehlen:

1
2
3
4
sudo apt install mosquitto
sudo apt install mosquitto-clients
sudo systemctl is-enabled mosquitto
sudo systemctl status mosquitto

Der Output des Status-Befehls sollte, wenn alles geklappt hat so aussehen:

Status of mosquitto.service

Defaultkonfiguration testen

Wir können nun indem wir eine zweite Terminialsitzung öffnen einen ersten Test durchführen. Dazu führen wir im ersten Terminal den Befehl

1
sudo mosquitto_sub -h localhost -t helloworld

aus. Im zweiten Terminal führen wir anschließend den Befehl

1
sudo mosquitto_pub -h localhost -t helloworld -m "Hi there"

aus. Funktioniert alles seht ihr nun im ersten Terminal die Nachricht ‘Hi there’. Da der Mosquitto-Server nun grundlegend funktioniert kümmern wir uns nun um die einrichtung und das härten des Servers.

Benutzer-Authentifizierung einrichten

Da wir nicht möchten, dass sich Geräte ohne authentifizierung anmelden können definieren wir ein Benutzernamen und Passwort mit dem sich Geräte zukünftig anmelden müssen. Die Konfigurationsmöglchkeiten hierzu werden alle sehr ausführlich und gut in der Dokumentation unter Mosquitto Man-Pages erklärt. Um eine Benutzer/Passwort kombination zu erstellen muss der Befehl

1
sudo mosquitto_passwd -c /etc/mosquitto/.passwd USER

ausgeführt werden. Wobei du $USER durch einen Benuternamen deiner Wahl ersetzen solltest. Wenn du den Befehl ausführst wirst du anschließend um ein Passwort gebeten. Speichere das Passwort gut ab und beende den Befehl mit Enter. Unter /etc/mosquitto sollte nun eine neue, gehashte Datei Namens .passwd liegen. Damit der Mosquitto-Dienst die Datei auch nutzen kann, müssen wir unter /etc/mosquitto/conf.d/ eine neue Datei erstellen. Alle Dateien in diesem Ordner die mit .conf enden werden automatisch eingelesen. Das wird in der Datei /etc/mosquitto/mosquitto.conf so definiert.

mosquitto.conf

Nun erstellen wir die entsprechende Config-Datei:

1
sudo nano /etc/mosquitto/conf.d/auth.conf

Inhalt der Konfigurationsdatei:

1
2
3
listener 1883
allow_anonymous false
password_file /etc/mosquitto/.passwd

In der ersten Zeile definieren wir den Port. Hier Port 1883 welches der Standardport für ungesichterte Verbindungen zu dem MQTT Server ist. Wir können Port 1883 mit User-Authentification also immer als Fallback nutzen, Falls ein Gerät einen höheren Standard nicht beherrscht. In der zweiten Zeile untersagen wir Verbindungen ohne die Korrekte User/Passwort kombination und in der letzen Zeile sagen wir Mosquitto noch wo die Passwort-Datei, die wir zuvor erstellt haben für den Abgleich liegt. Jetzt können wir den Server neu starten mit dem Befehl

1
sudo systemctl restart mosquitto.service 

damit die Änderungen gültig werden. Um die neuen Einstellungen zu testen haben wir zwei Möglichkeiten: wir können wieder mit zwei Termialsessions arbeiten oder den MQTT-Explorer nehmen. Ich zeige euch nun letzteres.

MQTT-Explorer

Wir müssen dort das Protocoll, in unserem Fall mqtt://, die IP des Servers und den Port angeben. Für den Port benutzen wir 1883. Wenn wir uns nun anmelden wird es nicht funktionieren. Ihr könnt dazu paralel im Terminalfenster den Befehl

1
sudo tail -f /var/log/mosquitto/mosquitto.log 

ausführen. Dieser ermöglicht euch eine Liveansicht des Logs. Dort seht ihr auch den fehlgeschlagen Loginversuch. Gebt ihr aber die richtige User/Passwort Kombination ein, könnt ihr euch erfolgreich anmelden. Auch das zeigt euch der Log.

Mosquitto Log

SSL und TLS-Zertifikate einrichten

Um den Server mit SSL/TLS-Zertifikaten abzusichern müssen wir eine eigene TLD haben. Das erstellen auf .local oder .home ist nicht möglich. Ich rate auch davon ab diese Endungen zu nehmen. Die Endung .local ist laut RFC6762 für mDNS Wikipedia: mDNS vorgesehen und führt früher oder später zu Problemen. Ich empfehle, wo immer möglich, immer auf eine gesicherte Verbindung mittels SSL/TLS zu setzen. Nur so kann eine verschlüsselte Datenübertragung zwischen Client und MQTT-Broker erzielt werden. Wir brauchen also eine eigene Domain auf die wir Zugriff haben. Die Zertifikate werden wir mittels acme.sh und Let’s Encrypt generieren. Ich stelle euch dazu mein Script für die DNS-01 Challange vor. Provider mit Support für acme.sh findet ihr in einer sehr übersichtlichen Liste hier: Let’s Encrypt Forum Ich benutze deSEC und die zur Verfügung gestellte API. Der Service ist kostenlos, es besteht aber die Möglichkeit zu Spenden sofern ihr mit dem Dienstleister zufrieden seid und ihn nutzen wollt. Ich gehe hier nicht auf die generelle API-Nutzung und das einrichten ein. Dafür gibt es eine gute Dokumentation. Ihr könnt aber in meinem Script die Schritte sehen die Nötig sind um ein Let’s Encrypt Zertifikat zu generieren.


Achtung! Generiert die Zertifikate immer erst dann wenn ihr einen Dry-run gemacht habt und dieser erfolgreich war. Anonsten gerät man sehr schnell in das Rate-Limit von Let’s Encrypt. Das würde bedeuten, dass ihr lange Wartezeiten habt bis ihr einen neuen Versuch starten könnt.


Die folgenden Schritte sind auch notwendig wenn ihr einen anderen Weg nutzen wollt und weichen erst gegen Ende ab. Wir werden im Verlauf den Befehl curl benutzen. Also schauen wir als erstes ob dieser Vorhanden ist. Wir machen das mit dem Befehl

1
which curl

Ist curl verfügbar sollte das so aussehen:

which curl

Hier seht ihr das curl bereits erfolgreich installiert ist. Ist das nicht der Fall holen wir das mit sudo apt install curl -y nach.

Nun erstellen wir einen neunen Benutzer der für die Generierung der Zertifikate zuständig ist Wir machen das mit dem Befehl

1
adduser acmeuser --gecos "" --disabled-password

Das Flag --gecos "" verhindert, dass wir nach Credentials gefragt werden und da wir für den technisches User keine Benötigen spart uns das ein paar Schritte. Anschließed fügen wir den neuen Benutzer zu der Gruppe mosquitto hinzu

1
usermod -aG mosquitto acmeuser. 

Damit der User acmeuser der jetzt Teil der Gruppe mosquitto ist auch schreiben darf, müssen wir noch die Ordnerrecht anpassen und der Gruppe mosquitto Schreibrechte geben. Das machen wir mit

1
2
sudo chmod 775 /etc/mosquitto/certs
sudo chmod 775 /etc/mosquitto/ca_certificates

Nun erlauben wir dem Benutzer ebenfalls den Mosquitto Server neuzustarten und die Rechte der Zertifikate anzupassen. Das machen wir mit

1
touch /etc/sudoers.d/acmeuser

Die soeben erstellte Datei öffnen wir nun mit

1
sudo visudo -f /etc/sudoers.d/acmeuser

Das -f Flag prüft die Datei direkt beim Schließen auf eine gültige Syntax. Das ist wichtig, damit der User acmeuser später alle nötigen Berechtigungen, ohne das Passwort einzugeben, besitzt. In die Datei tragen wir die folgenden Zeilen ein:

1
acmeuser ALL=(ALL) NOPASSWD:/bin/systemctl restart mosquitto.service,/bin/systemctl stop mosquitto.service,/bin/chown -R mosquitto\:mosquitto /etc/mosquitto/certs,/bin/chown -R mosquitto\:mosquitto /etc/mosquitto/ca_certificates,/bin/chown -R acmeuser\:acmeuser /etc/mosquitto/certs,/bin/chown -R acmeuser\:acmeuser /etc/mosquitto/ca_certificates,/bin/chmod 640 /etc/mosquitto/certs/key.pem,/bin/chmod 644 /etc/mosquitto/certs/cert.pem,/bin/chmod 644 /etc/mosquitto/ca_certificates/ca.pem,/bin/chmod 775 /etc/mosquitto/certs,/bin/chmod 775 /etc/mosquitto/ca_certificates

Die Datei müssen wir noch mit den richtigen Rechten versehen, so dass nur root sie lesen kann und andere nutzer sie nicht ändern können. Das machen wir mit

1
sudo chmod 0440 /etc/sudoers.d/acmeuser

Der Output von ls -l /etc/sudoers.d/acmeuser sollte in etwa so aussehen: -r--r-----. Nachdem der User der später unsere Zertifikate verwaltet nun erstellt ist, installieren wir die benötige Software für die Zertifikatserstellung mit dem Befehl

1
curl https://get.acme.sh | sudo -u acmeuser sh

Als letzten Schritt vor der generierung der Zertifikate setzen wir noch Let’s Encrypt als unseren Standard-Issuer mit dem Befehl:

1
su - acmeuser -c ".acme.sh/acme.sh --set-default-ca --server letsencrypt"

Vor dem generieren der Zertifikate müssen die Pfade vorhanden sein und Berechtigungen gesetzt werden. Jetzt, nachdem alle Vorbindungen erfüllt sind kommen wir zur Zertifikatserstellung. Ich benutze dafür wie schon geschrieben die API von deSEC und die DNS-01 Challange. Dazu erstellen wir ein neues Script mit dem Befehl

1
sudo nano /home/acmeuser/issue_certificates.sh

und fügen den follgeden Inhalt ein:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/bin/bash

# Path to acme.sh for the letsencrypt user
ACME_SH="/home/acmeuser/.acme.sh/acme.sh"

# DeSEC API token
DESEC_TOKEN="YOUR_TOKEN"

# Export DEDYN_TOKEN for acme.sh
export DEDYN_TOKEN="$DESEC_TOKEN"

# Path to store the certificates
CERT_PATH="/etc/mosquitto/certs"
CA_PATH="/etc/mosquitto/ca_certificates"
SUBDOMAIN="my.sub.domain.tld"

# Function to prepare the environment
prepare_environment() {
    # Stop Mosquitto service
    sudo /bin/systemctl stop mosquitto.service

    # Change ownership of CERT_PATH and CA_PATH to acmeuser:acmeuser
    sudo chown -R acmeuser:acmeuser "$CERT_PATH"
    sudo chown -R acmeuser:acmeuser "$CA_PATH"

    # Set permissions to 775
    sudo chmod 775 "$CERT_PATH"
    sudo chmod 775 "$CA_PATH"
}


# Function to issue/renew RSA certificate
issue_rsa_cert() {
"$ACME_SH" --issue \
--dns dns_desec \
-d "$SUBDOMAIN" \
--server letsencrypt \
--keylength 4096 \
--key-file "$CERT_PATH/key.pem" \
--ca-file "$CA_PATH/ca.pem" \
--cert-file "$CERT_PATH/cert.pem" \
--fullchain-file "$CERT_PATH/fullchain.pem" \
--dnssleep 120 \
--force
}

# Function to set ownership and permissions
set_permissions() {
    # Set ownership to mosquitto user
    sudo chown -R mosquitto:mosquitto "$CERT_PATH"
    sudo chown -R mosquitto:mosquitto "$CA_PATH"

    # Set permissions for security and accessibility
    sudo chmod 640 "$CERT_PATH/key.pem"
    sudo chmod 644 "$CERT_PATH/cert.pem"
    sudo chmod 644 "$CA_PATH/ca.pem"
}

# Function to restart Mosquitto
restart_mosquitto() {
    sudo /bin/systemctl restart mosquitto.service
}

# Run the functions
prepare_environment
issue_rsa_cert
set_permissions
restart_mosquitto

Wir müssen das Script noch ausführbar machen und dem User acmeuser zuweisen. Das machen wir mit den beiden Befehlen

1
2
sudo chmod +x /home/acmeuser/issue_certificates.sh
sudo chown -R acmeuser:acmeuser /home/acmeuser/issue_certificates.sh

Das Script bezieht ein Zertifikat für eine definierte Domain und setzt anschließend ebenfalls die korrekten Rechte, den richtigen Eigentümer und startet anschließend den Dienst mosquitto.service neu.

Automatische Erneuerung der Zertifikate mittels SystemD-Timer

Die DNS-01-Challange erfordert eine manuelle Erneuerung der Zertifikate in regelmäßigen Abstnden. Damit wir uns darum nicht selsbt kümmern müssen, können wir das mittels eines SystemD-Timers und dazugehörigen SystemD-Dientes machen. Wir erstellen als erstes den Service:

1
sudo nano /etc/systemd/system/renew_cert.service

In diesen geben wir folgenden Inhalt:

1
2
3
4
5
6
7
[Unit]
Description=Renew SSL certificates via Let's Encrypt

[Service]
Type=oneshot
User=acmeuser
ExecStart=/home/acmeuser/issue_certificates.sh

Anschließend erstellen wir den Timer:

1
sudo nano /etc/systemd/system/renew_cert.timer

In diesen geben wir den folgenden Inhalt:

1
2
3
4
5
6
7
8
9
[Unit]
Description=Timer to renew SSL certificates every 60 days

[Timer]
OnCalendar=monthly
Unit=renew_cert.service

[Install]
WantedBy=timers.target

Da wir die beiden Abhängigkeiten nun implementiert haben können wir die Dienste noch starten. Das machen wir mit

1
2
3
4
sudo systemctl daemon-reload
sudo systemctl enable renew_cert.service && sudo systemctl enable renew_cert.timer
sudo systemctl start renew_cert.service
sudo systemctl start renew_cert.timer

Ob alles problemlos funktioniert können wir uns mit

1
2
sudo systemctl status renew_cert.timer
sudo systemctl timer renew_cert.service

Anzeigen lassen.


Alternative Zertifikatserstellung ohne DNS-01-Challange

Wir können die Zertifikate auch ohne die DNS-01-Challange beziehen. Das wird deutlich breiter Unterstüzt und bietet einen deutlich geringeren Aufwand. Da es sich hier nich um meinen favorisierten Weg handelt möchte ich die Alternative nur grob aufzeigen und nicht tief in die Details einsteigen. Wir wechseln dazu zuerst in den entsprechenden User

1
su - acmeuser

Anschließend können wir mittels diesen Befehls die Zertifikate beantragen:

1
acme.sh --issue -d my.domain.tld --server letsencrypt --keylength 4096 -w /path/to/your/certs --key-file /path/to/your/certs/key.pem --ca-file /path/to/your/certs/ca.pem --cert-file /path/to/your/certs/cert.pem --fullchain-file /path/to/your/certs/fullchain.pem

Es ist unbeding darauf zu achten, dass der Pfad vorher korrekt erstellt wurde und der User acmeuser die entsprechenden Rechte besitzt. Anschließend müssen auch hier die Zertifikate in die entsprechenden Pfade verteilt werden und mit den richtigen Rechten versehen werden. Die automatische erneuerung der Zertifikate kann man hier bequem mittels crontab einrichten mit dem Befehl

1
crontab -u acmeuser -e

Diffie-Hellman-Schlüsseltausch

Bevor wir das Script das erste mal ausführen sind noch weitere Schritte nötig. Für zusätzliche Sicherheit ist es empfohlen noch den Diffie-Hellman-Schlüsselaustausch zu implementieren, dass erhöht die Sicherheit der Kommunikation zwischen Client und Server. Wir machen das mit dem Befehel:

1
sudo openssl dhparam -out /etc/mosquitto/certs/dhparam.pem 4096

Je nach System kann das etwas dauern. Ist die Generierung abgeschlossen setzten wir noch den richtigen Eigentümer mit:

1
sudo chown -R mosquitto:mosquitto /etc/mosquitto/certs

Da wir nun alle nötigen Zertifikate und Schlüssel beisammen haben, müssen wir dem Mosquitto-Server noch den Umgang hiermit zeigen. Dazu erstellen wir eine weitere Configdatei mit

1
sudo nano /etc/mosquitto/conf.d/ssl.conf

Die Configuration füllen wir mit folgendem Inhalt:

1
2
3
4
5
listener 8883
certfile /etc/mosquitto/certs/cert.pem
cafile /etc/mosquitto/ca_certificates/ca.pem
keyfile /etc/mosquitto/certs/key.pem
dhparamfile /etc/mosquitto/certs/dhparam.pem

Um den Mosquitto-Server mit aktiviertem TLS zu betreiben, speichern wir die Datei und starten den Dienst neu:

1
sudo systemctl restart mosquitto.service

Mit dem MQTT-Explorer oder den beiden Tools mosquitto_sub und mosquitto_pub können wir das soeben eingerichtet TLS testen. Hierzu stellen wir den Port auf 8883. Zudem müssen wir uns nun statt mit einer IP mit der Domain anmelden für die wir die Zertifikate ausgestellt haben. Einwählen mit der IP und aktiviertem TLS ist nicht möglich. Der MQTT Hostname muss unbedingt mit dem CN im Zertifikat übereinstimmen.

MQTT mit SSL/TLS

Unterstützung für Websockets aktivieren

Als letztes wollen wir noch die Verbindungsmöglichkeit über Websocket ermöglichen. Die Verbindung über Websocket kann in bestimmten Fällen von Vorteil sein, da sie eine bidirektionale Verbindung über TCP ermöglicht, Daten direkt senden kann und schneller ist als eine Verbindung über http da ein erneuer Handshake nach dem Openening entfällt. Wir haben bereits alle Vorbindungen geschaffen und können direkt die entsprechende Config anlegen:

1
sudo nano /etc/mosquitto/conf.d/websockets.conf

Inhalt der Websocket-Konfigurationsdatei:

1
2
3
4
5
listener 8083
protocol websockets
certfile /etc/mosquitto/certs/cert.pem
cafile /etc/mosquitto/ca_certificates/ca.pem
keyfile /etc/mosquitto/certs/key.pem

Nach dem Speichern müssen wir den Mosquitto-Dienst noch neustarten:

1
sudo systemctl restart mosquitto.service

Mosquitto-MQTT mit aktiviertem Websocket

Um Websocket zu nutzen müssen wir den Port auf 8083 einstellen und das Protokoll auf ws:// wechseln. Im Log von Mosquitto sollten wir nun eine neue, erfolgreiche Verbindung sehen.

Fazit

Die Einrichtung eines Mosquitto MQTT-Servers in einem LXC-Container auf Proxmox bietet umfangreiche Konfigurationsmöglichekiten für MQTT-Verbindungen im Netzwerk. Mit Benutzer-Authentifizierung, SSL/TLS-Verschlüsselung und Websocket-Unterstützung ist der Server gut gerüstet für den Einsatz in verschiedenen Szenarien.

Fandest du diesen Artikel hilfreich? Dann freue ich mich über einen Kaffee! ❤️