Docker (Mosquitto / mqtt) Container mittels Traefik SSL/TLS absichern

Chris81T

Member
Hi zusammen,

vor einiger Zeit hab ich dank eurer Hilfe hier im Forum mein Heimnetzwerk mit meinen betriebenen Services via SSL / https abgesichert. Dank dem certbotgodaddy Image kann ich mir darüber vie DNS Challenge über meine eigene Domain gültige SSL Zertifikate beziehen und im Heimnetz nutzen.

Da ich etwas Smarthome betreibe (Openhab als Abstraktion zur Nutzung verschiedener Gerätschaften und Node-RED zur Automatisierung), möchte ich gerne Eclipse Mosquitto (MQTT Broker) nutzen, um von mancher Hardware wie Funkschaltern, welche per MQTT kommunizieren können, entsprechend Nachrichten zu empfangen und weiter zu verteilen.

Nun hab ich hier die Herausforderung, bzw. den Wunsch, den Datenverkehr über MQTT ebenfalls abzusichern. Teils habe ich dazu ein paar Snippets im Netz gefunden, aber irgendwie hab ich es noch nicht richtig lauffähig bekommen. Daher hab ich die Hoffnung, dass sich hier jemand damit schon einmal erfolgreich beschäftigt hat, oder berichten kann, wie man mqtt alternativ absichern kann.

Ich nutze, wie oben beschrieben, Traefik nicht dafür SSL Zertifikate zu beziehen, sondern diese werden über ein Volume Mount bereitgestellt.

Optimal wäre es, wenn ich zu meinen Adressen wie openhab.mydomain.de eine mqtt.mydomain.de nutzen kann, so dass über den Proxy die Anfragen entsprechend weitergeleitet werden.

Hier meine Portainer Stacks (docker compose):

Traefik
Code:
version: '3.9'

services:
  traefik:
    image: traefik:v2.9.6
    container_name: traefik
    restart: always
    
    # Enables the web UI and tells Traefik to listen to docker
    command:
      - "--providers.docker=true"
      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.docker.exposedbydefault=false"

      # Entrypoints
      - "--entrypoints.web.address=:80"
      - "--entryPoints.websecure.address=:443"
      - "--entrypoints.mqtt.address=:8883"

      # Redirections
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
      
      # Common
      - "--log=true"
      - "--log.level=INFO"
      - "--accesslog=false"
      - "--accesslog.format=json"
    
    ports:
      # The HTTP port
      - 80:80
      # The HTTPS / TLS port
      - 443:443 
      # The MQTT Secured port
      - 8883:8883
      # The Web UI (enabled by --api.insecure=true)
      - 8080:8080
    
    labels:
      # Wenn dieses Label nicht hinterlegt wird, kommt es zu einem 504 Gateway I/O Timeout
      - "--providers.docker.network=jarvis-proxy-network"
    
    networks:
     - jarvis-proxy-network
    
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-volume:/etc/traefik
      - traefik-cert-config-volume:/etc/traefik/dynamic
      - certbot-letsencrypt-volume:/etc/certs:ro
      

volumes:
  traefik-volume:
    external: true
  traefik-cert-config-volume:
    external: true
  certbot-letsencrypt-volume:
    external: true

networks:
  jarvis-proxy-network:
      name: jarvis-proxy-network

Mosquitto

Code:
version: '3.9'
services:
  mosquitto:
    image: eclipse-mosquitto:latest
    container_name: mosquitto   
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mqtt.rule=Host(`mqtt.mydomain.de`)"
      - "traefik.http.routers.mqtt.entrypoints=websecure"
      - "traefik.http.services.mqtt.loadbalancer.server.port=9001"     
      - "traefik.tcp.routers.mqtt.rule=HostSNI(`*`)"
      - "traefik.tcp.services.mqtt.loadbalancer.server.port=8883"
      - "traefik.tcp.routers.mqtt.entrypoints=mqtt"
    volumes:
      - mosquitto-data-volume:/mosquitto/data
      - mosquitto-config-volume:/mosquitto/config
      - mosquitto-log-volume:/mosquitto/log
    networks:
      - jarvis-proxy-network

networks:
  jarvis-proxy-network:
      name: jarvis-proxy-network
      
volumes:
  mosquitto-data-volume:
    external: true     
  mosquitto-config-volume:
    external: true     
  mosquitto-log-volume:
    external: true

Zum ersten Test nutze ich einen MQTT Client, um überhaupt eine Verbindung zum Broker aufzubauen. Dabei habe ich die Adresse mqtt.mydomain.de mit dem Port 8883 genutzt. Die Verbindung kann nicht aufgebaut werden.

Im Traefik Log hab ich lediglich:
time="2023-04-17T09:00:41Z" level=error msg="Error while connecting to backend: dial tcp 172.29.4.3:8883: connect: connection refused"
time="2023-04-17T09:00:42Z" level=error msg="Error while connecting to backend: dial tcp 172.29.4.3:8883: connect: connection refused"
time="2023-04-17T09:00:43Z" level=error msg="Error while connecting to backend: dial tcp 172.29.4.3:8883: connect: connection refused"
time="2023-04-17T09:00:44Z" level=error msg="Error while connecting to backend: dial tcp 172.29.4.3:8883: connect: connection refused"
entdeckt.

Grundlegend müsste Traefik passend reagiert haben, aber der Broker (wird wohl hier mit backend gemeint sein?) hat die Anfrage dann verweigert, oder?

Auf jeden Fall schon mal vielen Dank für eure Unterstützung! :)
 
Bisher habe ich die mosquitto.conf nicht beachtet und testweise folgende Config hinzugefügt:
Code:
listener 8883 0.0.0.0
protocol mqtt
allow_anonymous true

Damit liefert mir der Mosquitto Container folgendes Log, wenn im Client als host mqtt.mydomain.de Port 8883 und die Option "Ecryption (tls)" aktiv ist:
1681765249: mosquitto version 2.0.15 starting
1681765249: Config loaded from /mosquitto/config/mosquitto.conf.
1681765249: Opening ipv4 listen socket on port 8883.
1681765249: mosquitto version 2.0.15 running
1681765261: New connection from 172.29.4.2:42670 on port 8883.
1681765261: Client <unknown> disconnected due to protocol error.
Der MQTT Client meldet:
Client Network socket disconnected before secure TLS connection was established

Deaktiviere ich die Option "Ecryption (tls)", dann wird eine Verbindung aufgebaut:
1681765542: New connection from 172.29.4.2:44296 on port 8883.
1681765542: New client connected from 172.29.4.2:44296 as mqtt-explorer-a47dadf1 (p2, c1, k60).

Aber ich habe keine Ahnung, ob ich nun verschlüsselt unterwegs bin und hab keine Idee, wie ich es prüfen kann. Der Port 8883 sollte eigentlich der abgesicherte Port sein, der 1883er Port der ungesicherte.

Wenn ich in meinem Mosquitto Stack das label

Code:
- "traefik.tcp.routers.mqtt.tls=true"

hinzufüge, bekomme ich bei aktivierter Option "Ecryption (tls)" im Server Log:

1681764894: New connection from 172.29.4.2:39964 on port 8883.
1681764894: Client <unknown> closed its connection.

Der MQTT Client meldet lediglich, dass das Zertifikat abgelaufen sei.

Im Client habe ich die Option, dass das Certificat validiert werden kann. Wenn ich diese Option deaktiviere, aber die Option "Ecryption (tls)" aktiv lasse, bekomme ich eine Verbindung zustande.

Auch hier das gleiche wie oben: Ich habe keine Idee, wie ich überprüfen kann, ob ich nun wirklich eine verschlüsselte Verbindung habe oder nicht.
 
Ah ich ahne, durch das Volume. Sprich dort hast Du dann auch eine certs-traefik.yml die ähnlich wie das hier aussieht:
Code:
tls:
  certificates:
    - certFile: /etc/certs/examplecert.crt
      keyFile: /etc/certs/examplecert.key

Kannst ja mal versuchen was passiert, wenn du TLS über die Labels am mqtt Service erzwingst
Code:
      - "traefik.http.routers.mqtt.tls=true"
      - "traefik.tcp.routers.mqtt.tls=true"
 
Scheinbar kann man das konkret zu verwendende Zertifikat bei "bring your own certificate" nicht festlegen.
Nur wenn man über den CertificatResolver die Zertifikate bei LE erzeugen lässt, kann man den jeweiligen CertificateRsolver zuordnen.

Mann kann mit openssl s_client -connect mqtt.mydomain.de:8883 das Zertifikat anzeigen lassen, dann siehste ja welches er verwendet und dieses abgelaufen ist.
 
Ich habe eben dies mit dem hier bereitgestellten Befehl getestet:
Code:
openssl s_client -connect mqtt.mydomain.de:8883

und habe damit Folgendes erhalten:

CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = *.mydomain.de
verify return:1
---
Certificate chain
0 s:CN = *.mydomain.de
i:C = US, O = Let's Encrypt, CN = R3
1 s:C = US, O = Let's Encrypt, CN = R3
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
i:O = Digital Signature Trust Co., CN = DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFLDCCBBSgAwIBAgISBMJ3jDXlhhm1dochtv5y2fyjMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
....
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_128_GCM_SHA256
....

Daher sieht dies doch schon einmal ganz gut aus, würd ich sagen. :)
 
Zuletzt bearbeitet:
Zumindest wird schon mal ein Zertifikat zur Verfügung gestellt, dass auch zur Domain passt.

Was der Befehl leider nicht anzeigt, sind Details zur Validität des Zertifikats. Kann ja durchaus sein, dass es nicht mehr aktuell ist.

Mir fällt gerade kein schlauer Einzeiler ein, aber man kann aus dem Output von openssl s_client -connect mqtt.mydomain.de:8883 den Text zwischen "-----BEGIN CERTIFICATE-----" und "-----END CERTIFICATE-----" (einschließlich der beiden Zeilen) in eine Datei mit crt-Endung packen und dann sich die Validität dann mit openssl x509 -in {wie immer deine Datei heisst}.crt -text anschauen.

Wenn die Zertifikats-Validierung fehlschlägt, kann es eigentlich nur daran liegen, dass das Zertifikat nicht zur aufgerufenen URL passt (nur https, bei tcp in tls verpackt benötigt es im Zweifel Hilfe durch SNI), das Zertifikat nicht mehr oder noch nicht gültig ist, oder die Zertifikatskette teilweise oder vollständig unbekannt ist (was bei LE Zertifikaten eher unwahrscheinlich ist).

Je nachdem, ob der Client die SNI Informationen beim Request mitgibt oder nicht, kann es auch sein, dass hier da Problem liegt. Andererseits hat openssl s_client es offensichtlich auch ohne zusätzlichen Schubser hinbekommen.
 
Zuletzt bearbeitet:
Was der Befehl leider nicht anzeigt, sind Details zur Validität des Zertifikats. Kann ja durchaus sein, dass es nicht mehr aktuell ist.
Huh?
Bei mir zeigt der Befehl die komplette Zertifikats-Chain, sowie die Gültigkeitsdaten der Zertifikate an.
Du musst halt nur ein wenig im Output "rumlesen" und der ist ein paar Bildschirmseiten lang.

Wenn Du das Datum rausfiltern willst kannst Du das so machen:
echo | openssl s_client -connect mqtt.mydomain.de:8883 | openssl x509 -noout -dates
 
Interessant. Bei mir werden die Gültigkeitsdaten mit s_client nicht angezeigt, die Chain natürlich schon.

Danke für den Einzeiler, @Burangar!

Er ist deutlich simpler als ich es erwartet hatte. :oops: Ich hätte eigentlich wissen müssen das alles außerhalb von BEGIN/END CERTIFICATE als Kommentar angesehen und deswegen ignoriert wird beim chainen. Zumindest hab ich jetzt Dank dir erstmal wieder eine Zeitlang im vorderen Stirnlappen.
 
Zuletzt bearbeitet:

Zurzeit aktive Besucher

Letzte Anleitungen

Statistik des Forums

Themen
4.639
Beiträge
47.439
Mitglieder
4.288
Neuestes Mitglied
framp
Zurück
Oben