OctoPrint/OctoPrint-MQTT

Unable to use TLS encryption with Letsencrypt certificate

Lefuneste83 opened this issue · 10 comments

What were you doing?

  1. ...Create an account on Homeassistant to allow MQTT connection
  2. ...Declare the account in the Octoprint MQTT plugin. Login is OK on MQTT Broker
  3. ...Enable TLS (change port, enable TLS flag, add local path to fullchain.pem) in the Octoprint MQTT plugin. Login is KO on, MQTT Broker

What did you expect to happen?

Proper Login with TLS enabled

What happened instead?

No login

Version of OctoPrint

1.4.0

Version of the MQTT plugin

0.8.7

Used MQTT broker and its version

Mosquitto broker 5.1

Link to octoprint.log

No error in the logs

Link to contents of Javascript console in the browser

Screenshot(s)/video(s) showing the problem

+1 on this.

I'm using mosquitto-1.6.10-1.el7.x86_64 on CentOS 7 (Digital Ocean VPS) with an SSL cert signed by Letsencrypt. It works fine with the MQTT plugin using TLS on port 8883. My broker doesn't require a login though. Here's the contents of my /etc/mosquitto/mosquitto.conf file:

listener 1883 localhost

listener 8883
certfile /etc/letsencrypt/live/<MY_FQDN>/cert.pem
cafile /etc/letsencrypt/live/<MY_FQDN>/chain.pem
keyfile /etc/letsencrypt/live/<MY_FQDN>/privkey.pem

listener 8083
protocol websockets
certfile /etc/letsencrypt/live/<MY_FQDN>/cert.pem
cafile /etc/letsencrypt/live/<MY_FQDN>/chain.pem
keyfile /etc/letsencrypt/live/<MY_FQDN>/privkey.pem

and I can see the progress of my print being sent to the broker by using the subscribe command while logged into the broker:

$ mosquitto_sub -h localhost -t octoPrint2/progress/printing
{"progress": 90, "_timestamp": 1605761283, "location": "local", "path": "CE3PRO_F_Ornament_2_loop_v1.gcode"}
{"progress": 91, "_timestamp": 1605761322, "location": "local", "path": "CE3PRO_F_Ornament_2_loop_v1.gcode"}
{"progress": 92, "_timestamp": 1605761409, "location": "local", "path": "CE3PRO_F_Ornament_2_loop_v1.gcode"}
{"progress": 93, "_timestamp": 1605761495, "location": "local", "path": "CE3PRO_F_Ornament_2_loop_v1.gcode"}

I edited the HTML and Javascript files from my light tower project to pull data from the feed over websockets and it can display info on the web page as well. https://github.com/xenophod/MQTT-Light-Tower

I'm thinking it's a Homeassistant configuration that might be causing the issue, and not the MQTT plugin.

I have just moved my mosquitto server over to TLS, using a Let's Encrypt certificate. I also had some trouble getting the plugin to connect initially. I don't really understand the code here :

tls_active = False
if broker_tls:
tls_args = dict((key, value) for key, value in broker_tls.items() if value)
ca_certs = tls_args.pop("ca_certs", None)
if ca_certs: # cacerts must not be None for tls_set to work
self._mqtt.tls_set(ca_certs, **tls_args)
tls_active = True

The comment on line 309 says we have to specify ca_certs. From the docs I think this was true for earlier Python versions? The default now seems to be to use the system certificate stores.

I hacked this to work on my install by changing these lines to

    tls_active = False
    if broker_tls:
        tls_args = dict((key, value) for key, value in broker_tls.items() if value)
        import ssl
        self._mqtt.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
        tls_active = True

This then connects to my server with no problems - I'm not using client certificates though.

Obviously one would want to actually handle the provided settings instead of overriding them in a real implementation. :-)

I agree with @lexitus interpreation that starting from Python 2.7, providing a cert is no longer required by the paho-mqtt package and since Octoprint relies on Python > 2.7, we should be able to adapt this without breaking anything.

I was trying to get @lexitus solution to work but it turns out that things are a little more complicated: If you just hit "Broker requires TLS" and leave everything else untouched, @lexitus solution will not work either. In this case the dict broker_tls will be empty and thus the tls_set code will just be bypassed since if broker_tls yields false. Also in @lexitus solution the tls_args['cert_reqs'] = ssl.CERT_REQUIRED part is not required since this is the default value according to the docs (see link in his/her post). My hack looks like this now:

    tls_active = False
    if broker_tls:
        tls_args = dict((key, value) for key, value in broker_tls.items() if value)
        #ca_certs = tls_args.pop("ca_certs", None)
        #if ca_certs:  # cacerts must not be None for tls_set to work
        self._mqtt.tls_set(**tls_args)
        tls_active = True

which leaves all the other settings untouched. For this to work, you need to make sure that broker_tls is populated, e.g. by providing a cipher like ECDHE.

For a permanent fix, I would suggest adding the status of the "The broker requires TLS" checkbox to the self._settings. The code would then change to something like this:

    tls_active = self._settings.get_boolean(["broker", "tls"])['tlsActive']
    if tls_active:
        tls_args = dict((key, value) for key, value in broker_tls.items() if value)
        tls_args.pop("tlsActive")
        self._mqtt.tls_set(**tls_args)

However, I wasn't able to figure out how the self._settings attribute gets populated. Looked through all the code but could not find the link where the web form values are being fed into the Python code. Maybe someone with more plugin development experience can point me in the right direction and I'd be happy to open a pull request.

Turns out there is an awesome OctoPrint plugin development tutorial explaining what I needed to know ;-) Was able to figure it out at the cost of a minor GUI imperfection, see the linked pull request.

Any update regarding this issue/PR? I believe I'm running into the same issue. I have a broker configured with LetsEncrypt certificates, and use it with many other clients, however can't find a way to make the MQTT plug-in in OctoPrint connect to it.

Checking the logs, I see this:

2021-12-02 01:04:09,855 - octoprint.plugin - ERROR - Error while calling plugin mqtt
Traceback (most recent call last):
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint/plugin/__init__.py", line 271, in call_plugin
    result = getattr(plugin, method)(*args, **kwargs)
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint/util/__init__.py", line 1737, in wrapper
    return f(*args, **kwargs)
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint_mqtt/__init__.py", line 83, in on_startup
    self.mqtt_connect()
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint_mqtt/__init__.py", line 310, in mqtt_connect
    self._mqtt.tls_set(ca_certs, **tls_args)
  File "/home/pi/oprint/lib/python3.7/site-packages/paho/mqtt/client.py", line 804, in tls_set
    context.load_verify_locations(ca_certs)

@edgauthier If you want, you can try my fix. You can download the zip from the forked repo and install via zip (Settings --> Plugin manager --> More --> Upload file). Uninstall the official plugin first. Let me know how it works for you (you should be able to comment in the pull request)

Thanks! This version fixed the issue for me.

Now that I have confirmation this PR works I'll handle bumping the version and merging later tonight. Thanks @edgauthier

PR has been merged and version 0.8.11 has been released.