Python Paho MQTT client zelf-ondertekende certificaten websockets, howto
Het gebruik van Paho in Python met websockets en zelf-ondertekende certificaten voor een https:// websocket connectie heeft een paar valkuilen, dus die pak ik hier aan met wat voorbeeld code:
ssl importeren
importtijd
importeer paho.mqtt.client als mqttklasse PCPMQTTClient:
def on_connect(self, client, userdata, flags, rc):
self.connection_status = rc
als rc == 0:
self.connected_flag = True
self.connection_error_flag = False
anders:
self.connected_flag = False
self.connection_error_flag = True# opgeroepen wanneer een bericht dat via publish() moest worden verzonden
# heeft verzending naar de makelaar voltooid
# voor qos=0 -> bericht heeft de client verlaten
# voor qos=1 & 2 -> handshakes zijn voltooid.
# mid variabele komt overeen met mid variabele die publish() teruggeeft
def on_publish(self, client, userdata, mid):
als self.verbose:
print("callback midden publiceren: " + str(mid))def on_log(client, userdata, level, buf):
print(buf)def testconnection(self):
return self.connect(test=True)def connect(self, test=False):
als test:
print("Verbinding maken met MQTT", end=")
self.connection_status = -1 # nog geen verbindingspogingen
self.connection_error_flag = False
self.mqttc.connect_async(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_adres="")self.mqttc.loop_start() # volgens Steve beter AFTER connect
doorgaan_vlag = Onwaar
keep_connection_flag = geen test
timeout_counter = 0
terwijl niet doorgaan_vlag:
time.sleep(1)
timeout_counter += 1
als test:
print(".", end=") # end=" onderdrukt newline
als self.connected_flag:
doorgaan_vlag = Waar
als timeout_counter > 6:
doorgaan_vlag = Waar
keep_connection_flag = False
code = self.connection_status
succes = Onwaar
als code == 0:
succes = Waar
message = "Verbinding geslaagd!"
self.connected_flag = True
if not keep_connection_flag: # nodig om dit te zijn, om te detecteren of we niet in staat zijn om verbinding te maken!
self.disconnect() # verbreek de verbinding met de server
elif code == -1:
message = "Connection timed out - server niet online? Certificaatfout (probeer -selfsigned)?"
elif code == 1:
message = "Verbinding geweigerd - onjuiste protocolversie"
elif code == 2:
message = "Verbinding geweigerd - ongeldige client-identifier"
elif code == 3:
message = "Verbinding geweigerd - server niet beschikbaar"
elif code == 4:
message = "Verbinding geweigerd - slechte gebruikersnaam of wachtwoord"
elif code == 5:
message = "Verbinding geweigerd - niet gemachtigd"
anders:
message = "Nog een fout " + str(code) + " opgetreden, gelieve de Paho-documentatie te raadplegen"
print(")
keer terug {"succes": succes, "code": code, "bericht": bericht}def publish(self, topic, message, qos=0, retain=False):
succes = Onwaar
code = "PCP_WRONG_INPUTFORMAT
error_message = "Verkeerd invoerformaat van gegevens (bv. onderwerp kan niet Null zijn)
als onderwerp:
pubresult = self.mqttc.publish(
onderwerp=onderwerp,
payload=bericht,
qos=qos,
behouden=behouden)
# proces pubresult
als pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
code = "MQTT_ERR_SUCCESS
error_message = "Succesvol bericht naar makelaar verzonden.
succes = Waar
elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
code = "MQTT_ERR_NO_CONN
error_message = "Geen verbinding met MQTT-server, probeer opnieuw te verbinden
elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
code = "MQTT_ERR_QUEUE_SIZE
error_message = "Bericht is noch in de wachtrij geplaatst noch verzonden.return {"succes": succes, "code": code, "message": error_message, "mid": pubresult.mid}
def disconnect(self):
# zoals per 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 = verbindingsgegevens
self.connected_flag = False
self.mqttc.on_connect = self.on_connect
self.mqttc.on_publish = self.on_publish
als self.verbose:
print("### logging inschakelen voor MQTT-client ###")
self.mqttc.on_log = self.on_log
self.mqttc.username_pw_set(
gebruikersnaam=connectiegegevens['gebruikersnaam'],
password=connectiondata['password'])
als connectiondata['transport'] == "websockets":
self.mqttc.ws_set_options(path=self.connectiondata['path'])
als self.verbose:
print("### verbinding maken met de volgende parameters ###")
print("* client_id: " + connectiondata['client_id'])
print("* clean_session: " + str(clean_session))
print("* transport: " + connectiondata['transport'])
print("* pad: " + connectiondata['pad'])
print("*gebruikersnaam: " + connectiondata['gebruikersnaam'])
print("* wachtwoord: (wordt niet getoond!)")
# self.mqttc.tls_set_context()
als het niet veilig is:
self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
als self.verbose:
print("### onveilige bedieningsmodus (-selfsigned) ingesteld! ###")
self.mqttc.tls_insecure_set(True)
anders:
self.mqttc.tls_set()
Zoals u ziet, is het belangrijk om TWEE dingen te doen voor een onveilige verbinding:
- tls_set(cert_reqs=ssl.CERT_NONE)
- tls_insecure_set(True)
Met de aanroep tls_insecure_set(True) kunt u het matchen van certificaatnamen achterwege laten. Het zal nog steeds proberen om ervoor te zorgen dat het certificaat geldig volgens de certificaatketen!
Dus het is een beetje een verkeerde benaming, denk ik.
De voorbeeldcode hierboven is onderdeel van een ontwikkeling aan picockpit-client, voor de picockpit.com platform
Paho / Websockets
connectiondata['transport'] wordt elders in de code ingesteld. Het kan ofwel (een string) "tcp" of "websockets" zijn.
Als je websockets specificeert, kun je ook ws_set_options gebruiken om een pad in te stellen, bijvoorbeeld "/mqtt".
host=self.connectiondata['host'],
port=self.connectiondata['port'],
de host is je webhost - zonder http / https voorvoegsel. Voor lokale ontwikkeling, bijvoorbeeld, gebruik ik "picockpit.local" (geproxied via envoy, zie mijn andere artikelen).
de poort, in het geval van een TLS / SSL verbinding over de standaard https poort is 443
Suggestie voor makkelijker debuggen:
Voor betere debugging schakel over van de async verbindingscode naar een synchrone verbinding (vervang connect_async):
self.mqttc.connect(
host=self.connectiondata['host'],
port=self.connectiondata['port'],
keepalive=60,
bind_adres="")
Referenties:
- 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/ - de officiële Paho Python documentatie