Python Paho MQTT client websockets certificats auto-signés, howto

L'utilisation de Paho en Python avec des websockets et des certificats auto-signés pour une connexion websocket https:// présente quelques pièges, c'est pourquoi j'y remédie avec quelques exemples de code ici :

Importer ssl
temps d'importation
import paho.mqtt.client as mqtt

classe PCPMQTTClient :
     def on_connect(self, client, userdata, flags, rc) :
         self.connection_status = rc
         si rc == 0 :
             self.connected_flag = True
             self.connection_error_flag = False
         autre :
             self.connected_flag = False
             self.connection_error_flag = True

    # appelé lorsqu'un message qui devait être envoyé en utilisant publish()
     # a terminé la transmission au courtier.
     # pour qos=0 -> le message a quitté le client
     # pour qos=1 & 2 -> la poignée de main est terminée.
     La variable médiane # correspond à la variable médiane que renvoie publish()
     def on_publish(self, client, userdata, mid) :
         si self.verbose :
             print("publier le milieu du rappel : " + str(mid))

    def on_log(client, userdata, level, buf) :
         print(buf)

    def testconnection(self) :
         return self.connect(test=True)

    def connect(self, test=False) :
         si le test :
             print("Connexion à MQTT", end=")
         self.connection_status = -1 # pas encore de tentative de connexion
         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() # selon Steve mieux APRÈS la connexion
         continue_flag = False
         keep_connection_flag = not test
         compteur_d'attente = 0
         tant que le drapeau ne continue pas :
             temps.sleep(1)
             compteur de temps d'attente += 1
             si le test :
                 print(".", end=") # end=" supprimera la nouvelle ligne
             si self.connected_flag :
                 continue_flag = True
             si le compteur de délai d'attente > 6 :
                 continue_flag = True
                 keep_connection_flag = False
         code = self.connection_status
         succès = Faux
         si le code == 0 :
             succès = Vrai
             message = "Connexion réussie !"
             self.connected_flag = True
             if not keep_connection_flag : # nécessaire pour être ceci, pour détecter si nous ne sommes pas capables de nous connecter !
                 self.disconnect() # déconnexion propre du serveur
         elif code == -1 :
             message = "Connection timed out - server not online ? Erreur de certificat (essayez -selfsigned) ?"
         elif code == 1 :
             message = "Connexion refusée - version de protocole incorrecte".
         elif code == 2 :
             message = "Connexion refusée - identifiant client non valide".
         elif code == 3 :
             Message = "Connexion refusée - serveur indisponible".
         elif code == 4 :
             Message = "Connexion refusée - mauvais nom d'utilisateur ou mot de passe".
         elif code == 5 :
             message = "Connexion refusée - non autorisée
         autre :
             message = "Une autre erreur " + str(code) + " s'est produite, veuillez consulter la documentation de Paho".
         print(")
         return {"success" : success, "code" : code, "message" : message}

    def publish(self, topic, message, qos=0, retain=False) :
         succès = Faux
         Code = 'PCP_WRONG_INPUTFORMAT'.
         error_message = 'Mauvais format d'entrée des données (par exemple, le sujet ne peut pas être Null)'.
         si le sujet :
             pubresult = self.mqttc.publish(
                 topic=topic,
                 payload=message,
                 qos=qos,
                 retain=retain)
             Publication du processus #
             si pubresult.rc == mqtt.MQTT_ERR_SUCCESS :
                 code = "MQTT_ERR_SUCCESS".
                 error_message = 'Successfully sent message to broker'.
                 succès = Vrai
             elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN :
                 code = "MQTT_ERR_NO_CONN
                 error_message = 'Pas de connexion au serveur MQTT, essayez de vous reconnecter'.
             elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE :
                 code = "MQTT_ERR_QUEUE_SIZE
                 error_message = 'Le message n'est ni en file d'attente ni envoyé'.

        return {"success" : success, "code" : code, "message" : error_message, "mid" : pubresult.mid}

    def disconnect(self) :
         # selon Steve http://www.steves-internet-guide.com/client-connections-python-mqtt/
         self.mqttc.loop_stop()
         self.mqttc.disconnect()
         self.connected_flag = False

    def __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,
             protocole=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
         si self.verbose :
             print("### activant la journalisation pour le client MQTT ###")
             self.mqttc.on_log = self.on_log
         self.mqttc.username_pw_set(
             username=connectiondata['username'],
             password=connectiondata['password'])
         si connectiondata['transport'] == "websockets" :
             self.mqttc.ws_set_options(path=self.connectiondata['path'])
         si self.verbose :
             print("### se connectant avec les paramètres suivants ###")
             print("* client_id : " + connectiondata['client_id'])
             print("* clean_session : " + str(clean_session))
             print("* transport : " + connectiondata['transport'])
             print("* chemin : " + connectiondata['chemin'])
             print("* nom d'utilisateur : " + connectiondata['username'])
             print("* password : (not being shown !)")
         # self.mqttc.tls_set_context()
         en cas d'insécurité :
             self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
             si self.verbose :
                 print("### insecure operation mode (-selfsigned) set ! ###")
             self.mqttc.tls_insecure_set(True)
         autre :
             self.mqttc.tls_set()

Comme vous pouvez le constater, il est important de faire DEUX choses pour une connexion non sécurisée :

  • tls_set(cert_reqs=ssl.CERT_NONE)
  • tls_insecure_set(True)

L'appel tls_insecure_set(True) vous permettra d'omettre la correspondance du nom du certificat. Il essaiera tout de même de s'assurer que le certificat est valide selon la chaîne de certificats !

Il s'agit donc d'un terme un peu mal choisi, je suppose.

L'exemple de code présenté ci-dessus fait partie d'un développement sur picockpit-client, pour l'application plate-forme picockpit.com

Paho / Websockets

connectiondata['transport'] est défini ailleurs dans le code. Il peut être soit (une chaîne) "tcp" ou "websockets".

Si vous spécifiez des websockets, vous pouvez utiliser en plus ws_set_options pour définir un chemin, par exemple "/mqtt".

            host=self.connectiondata['host'],
             port=self.connectiondata['port'],

l'hôte est votre hébergeur - sans préfixe http / https. Pour le développement local, par exemple, j'utilise "picockpit.local" (proxié par envoy, voir mes autres articles).

le port, dans le cas d'une connexion TLS / SSL sur le port https par défaut est 443

Suggestion pour faciliter le débogage :

Pour un meilleur débogage, passer du code de connexion asynchrone à une connexion synchrone (remplacer connect_async) :

        self.mqttc.connect(
            host=self.connectiondata['host'],
            port=self.connectiondata['port'],
            keepalive=60,
            bind_address="")

Références :