Python Paho MQTT-Client selbstsignierte Zertifikate Websockets, howto
Die Verwendung von Paho in Python mit Websockets und selbstsignierten Zertifikaten für eine https:// Websocket-Verbindung birgt ein paar Fallstricke, die ich hier mit einem Beispielcode anspreche:
ssl importieren
Einfuhrzeit
paho.mqtt.client als mqtt importierenKlasse PCPMQTTClient:
def on_connect(self, client, userdata, flags, rc):
self.connection_status = rc
wenn rc == 0:
self.connected_flag = True
self.connection_error_flag = False
sonst:
self.connected_flag = False
self.connection_error_flag = True# wird aufgerufen, wenn eine Nachricht, die mit publish() gesendet werden sollte
# hat die Übermittlung an den Makler abgeschlossen
# für qos=0 -> Nachricht hat den Client verlassen
# für qos=1 & 2 -> Handshakes wurden abgeschlossen.
Die mittlere Variable # entspricht der mittleren Variable, die von publish() zurückgegeben wird.
def on_publish(self, client, userdata, mid):
if self.verbose:
print("Rückruf Mitte veröffentlichen: " + str(mid))def on_log(client, userdata, level, buf):
print(buf)def testconnection(self):
return self.connect(test=True)def connect(self, test=False):
wenn test:
print("Verbindung zu MQTT herstellen", end=")
self.connection_status = -1 # noch keine Verbindungsversuche
self.connection_error_flag = False
self.mqttc.connect_async(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_address="")self.mqttc.loop_start() # laut Steve besser AFTER connect
weiter_flag = False
keep_connection_flag = not test
timeout_counter = 0
solange nicht continue_flag:
time.sleep(1)
timeout_counter += 1
wenn test:
print(".", end=") # end=" unterdrückt Zeilenumbruch
if self.connected_flag:
weiter_flag = True
wenn timeout_counter > 6:
weiter_flag = True
keep_connection_flag = False
code = self.connection_status
Erfolg = Falsch
wenn Code == 0:
Erfolg = Wahr
message = "Verbindung erfolgreich!"
self.connected_flag = True
if not keep_connection_flag: # muss dies sein, um zu erkennen, wenn wir nicht in der Lage sind, eine Verbindung herzustellen!
self.disconnect() # saubere Trennung der Verbindung vom Server
elif code == -1:
message = "Zeitüberschreitung der Verbindung - Server nicht online? Zertifikatsfehler (versuchen Sie -selfsigned)?"
elif code == 1:
message = "Verbindung abgelehnt - falsche Protokollversion"
elif code == 2:
message = "Verbindung verweigert - ungültiger Client-Bezeichner"
elif code == 3:
message = "Verbindung abgelehnt - Server nicht verfügbar"
elif code == 4:
message = "Verbindung abgelehnt - falscher Benutzername oder Passwort"
elif code == 5:
message = "Verbindung abgelehnt - nicht autorisiert"
sonst:
message = "Ein weiterer Fehler " + str(code) + " ist aufgetreten, bitte lesen Sie die Paho-Dokumentation"
print(")
return {"Erfolg": Erfolg, "Code": Code, "Nachricht": Nachricht}def publish(self, topic, message, qos=0, retain=False):
Erfolg = Falsch
code = 'PCP_WRONG_INPUTFORMAT'
error_message = 'Falsches Eingabeformat der Daten (z.B. Thema kann nicht Null sein)'
wenn Thema:
pubresult = self.mqttc.publish(
topic=topic,
Nutzlast=Nachricht,
qos=qos,
beibehalten=beibehalten)
# Prozess-Ergebnis
if pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
code = 'MQTT_ERR_SUCCESS'
error_message = 'Nachricht erfolgreich an den Makler gesendet.'
Erfolg = Wahr
elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
code = 'MQTT_ERR_NO_CONN'
error_message = 'Keine Verbindung zum MQTT-Server, versuchen Sie, sich erneut zu verbinden'.
elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
code = 'MQTT_ERR_QUEUE_SIZE'
error_message = 'Die Nachricht steht weder in der Warteschlange noch wurde sie gesendet.return {"success": success, "code": code, "message": error_message, "mid": pubresult.mid}
def disconnect(self):
# wie von Steve http://www.steves-internet-guide.com/client-connections-python-mqtt/
self.mqttc.loop_stop()
self.mqttc.disconnect()
self.connected_flag = Falsedef __init__(self, connectiondata, verbose=False, insecure=False):
self.verbose = verbose
clean_session = True
self.mqttc = mqtt.Client(
client_id=connectiondata['client_id'],
clean_session=clean_session,
userdata=None,
protocol=mqtt.MQTTv311,
transport=connectiondata['transport'])
self.connectiondata = connectiondata
self.connected_flag = False
self.mqttc.on_connect = self.on_connect
self.mqttc.on_publish = self.on_publish
if self.verbose:
print("### aktiviert Protokollierung für MQTT-Client ###")
self.mqttc.on_log = self.on_log
self.mqttc.username_pw_set(
username=connectiondata['username'],
passwort=verbindungsdaten['passwort'])
if connectiondata['transport'] == "websockets":
self.mqttc.ws_set_options(path=self.connectiondata['path'])
if self.verbose:
print("### verbindet sich mit den folgenden Parametern ###")
print("* client_id: " + connectiondata['client_id'])
print("* clean_session: " + str(clean_session))
print("* Transport: " + connectiondata['transport'])
print("* Pfad: " + connectiondata['path'])
print("* Nutzername: " + Verbindungsdaten['Nutzername'])
print("* Passwort: (wird nicht angezeigt!)")
# self.mqttc.tls_set_context()
wenn sie unsicher sind:
self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
if self.verbose:
print("### unsicherer Betriebsmodus (-selfsigned) eingestellt! ###")
self.mqttc.tls_insecure_set(True)
sonst:
self.mqttc.tls_set()
Wie Sie sehen können, ist es wichtig, ZWEI Dinge für eine unsichere Verbindung zu tun:
- tls_set(cert_reqs=ssl.CERT_NONE)
- tls_insecure_set(True)
Mit dem Aufruf tls_insecure_set(True) können Sie den Abgleich der Zertifikatsnamen auslassen. Es wird immer noch versucht, sicherzustellen, dass das Zertifikat gültig gemäß der Zertifikatskette!
Daher ist der Begriff wohl ein wenig irreführend.
Der oben gezeigte Beispielcode ist Teil einer Entwicklung am picockpit-client, für den Plattform picockpit.com
Paho / Websockets
connectiondata['transport'] wird an anderer Stelle im Code festgelegt. Es kann entweder (ein String) "tcp" oder "websockets" sein.
Wenn Sie Websockets angeben, können Sie zusätzlich ws_set_options verwenden, um einen Pfad festzulegen, zum Beispiel "/mqtt".
host=self.connectiondata['host'],
port=self.connectiondata['port'],
der Host ist Ihr Webhost - ohne http / https Präfix. Für die lokale Entwicklung verwende ich z. B. "picockpit.local" (Proxy durch envoy, siehe meine anderen Artikel).
den Port, im Falle einer TLS/SSL-Verbindung über den Standard-HTTPS-Port 443
Vorschlag zur leichteren Fehlersuche:
Zur besseren Fehlersuche wechseln Sie vom asynchronen Verbindungscode zu einer synchronen Verbindung (ersetzen Sie connect_async):
self.mqttc.connect(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_address="")
Referenzen:
- https://github.com/eclipse/paho.mqtt.python/issues/85
- https://github.com/eclipse/paho.mqtt.python/issues/148
- https://aws.amazon.com/blogs/iot/how-to-implement-mqtt-with-tls-client-authentication-on-port-443-from-client-devices-python/
- https://www.eclipse.org/paho/clients/python/docs/ - die offizielle Paho-Python-Dokumentation