Python Paho MQTT client autofirmato certificati websockets, howto

Usare Paho in Python con websockets e certificati autofirmati per una connessione websocket https:// ha un paio di insidie, così sto affrontando questo con un po' di codice di esempio qui:

importare ssl
tempo di importazione
importa paho.mqtt.client come mqtt

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

    # chiamato quando un messaggio che doveva essere inviato usando publish()
     # ha completato la trasmissione al broker
     # per qos=0 -> il messaggio ha lasciato il cliente
     # per qos=1 & 2 -> gli handshake sono stati completati.
     # la variabile mid corrisponde alla variabile mid che publish() restituisce
     def on_publish(self, client, userdata, mid):
         se self.verbose:
             print("pubblica callback mid: " + str(mid))

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

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

    def connect(self, test=False):
         se test:
             print("Connessione a MQTT", end=")
         self.connection_status = -1 # nessun tentativo di connessione ancora
         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() # secondo Steve meglio DOPO la connessione
         continue_flag = False
         keep_connection_flag = non test
         timeout_counter = 0
         mentre non continua_flag:
             time.sleep(1)
             timeout_counter += 1
             se test:
                 print(".", end=") # end=" sopprimerà la newline
             se self.connected_flag:
                 continue_flag = True
             se timeout_counter > 6:
                 continue_flag = True
                 keep_connection_flag = False
         codice = self.connection_status
         successo = Falso
         se codice == 0:
             successo = Vero
             messaggio = "Connessione riuscita!"
             self.connected_flag = True
             if not keep_connection_flag: # necessario essere questo, per rilevare se non siamo in grado di connetterci!
                 self.disconnect() # disconnessione pulita dal server
         elif code == -1:
             message = "Connessione scaduta - server non online? Errore del certificato (prova -selfsigned)?"
         elif code == 1:
             messaggio = "Connessione rifiutata - versione di protocollo errata"
         elif code == 2:
             messaggio = "Connessione rifiutata - identificatore client non valido"
         elif code == 3:
             messaggio = "Connessione rifiutata - server non disponibile"
         elif code == 4:
             messaggio = "Connessione rifiutata - cattivo nome utente o password"
         elif code == 5:
             messaggio = "Connessione rifiutata - non autorizzato"
         altro:
             messaggio = "Si è verificato un altro errore " + str(codice) + ", si prega di controllare la documentazione Paho".
         stampa(")
         return {"successo": successo, "codice": codice, "messaggio": messaggio}

    def publish(self, topic, message, qos=0, retain=False):
         successo = Falso
         codice = 'PCP_WRONG_INPUTFORMAT'
         error_message = 'Formato errato dei dati in ingresso (ad esempio, l'argomento non può essere Null)'
         se argomento:
             pubresult = self.mqttc.publish(
                 topic=topic,
                 payload=messaggio,
                 qos=qos,
                 retain=retain)
             # processo pubresult
             if pubresult.rc == mqtt.MQTT_ERR_SUCCESS:
                 codice = 'MQTT_ERR_SUCCESS'
                 error_message = 'Messaggio inviato con successo al broker'.
                 successo = Vero
             elif pubresult.rc == mqtt.MQTT_ERR_NO_CONN:
                 codice = 'MQTT_ERR_NO_CONN'
                 error_message = 'Nessuna connessione al server MQTT, provare a riconnettersi'
             elif pubresult.rc == mqtt.MQTT_ERR_QUEUE_SIZE:
                 codice = 'MQTT_ERR_QUEUE_SIZE'
                 error_message = 'Il messaggio non è né accodato né inviato'.

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

    def disconnect(self):
         # come per 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=Nessuno,
             protocollo=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
         se self.verbose:
             print("### abilitando la registrazione per il client MQTT ###")
             self.mqttc.on_log = self.on_log
         self.mqttc.username_pw_set(
             username=connectiondata['username'],
             password=connectiondata['password'])
         se connectiondata['transport'] == "websockets":
             self.mqttc.ws_set_options(path=self.connectiondata['path'])
         se self.verbose:
             print("### che si collega con i seguenti parametri ###")
             print("* client_id: " + connectiondata['client_id'])
             print("* clean_session: " + str(clean_sessione))
             print("* trasporto: " + connectiondata['transport'])
             print("* percorso: " + connectiondata['path'])
             print("* username: " + connectiondata['username'])
             print("* password: (non viene mostrata!)")
         # self.mqttc.tls_set_context()
         se insicuro:
             self.mqttc.tls_set(cert_reqs=ssl.CERT_NONE)
             se self.verbose:
                 print("### modalità di funzionamento insicura (-selfsigned) impostata! ###")
             self.mqttc.tls_insecure_set(True)
         altro:
             self.mqttc.tls_set()

Come potete vedere, è importante fare DUE cose per una connessione insicura:

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

La chiamata tls_insecure_set(True) vi permetterà di omettere la corrispondenza del nome del certificato. Cercherà comunque di assicurarsi che il certificato sia valido come da catena del certificato!

Quindi è un po' un termine improprio, credo.

Il codice di esempio mostrato sopra è parte di uno sviluppo su picockpit-client, per il piattaforma picockpit.com

Paho / Websockets

connectiondata['transport'] è impostato altrove nel codice. Può essere sia (una stringa) "tcp" o "websockets".

Se specificate i websockets, potete anche usare ws_set_options per impostare un percorso, per esempio "/mqtt".

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

l'host è il tuo webhost - senza prefisso http / https. Per lo sviluppo locale, per esempio, uso "picockpit.local" (proxied attraverso envoy, vedi altri miei articoli).

la porta, nel caso di una connessione TLS / SSL sulla porta https predefinita è 443

Suggerimento per un debug più facile:

Per un migliore debug passare dal codice di connessione asincrona a una connessione sincrona (sostituire connect_async):

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

Riferimenti: