MQTT is the nerve system for home-assistant and having a secure encrypted connection with your broker is critical for privacy. Imagine running Own-tracks on your phone or connecting to your broker from a hotspot that sniffs on your traffic. Even if you have an mqtt username and password set, these are sent in plain text to the server, thus they will be read by the attacker.
Installing nginx and mosquitto mqtt on linux
If you are familiar with linux system, you know that installing packages is very easy. One useful package to install is mosquitto-clients. This will install mosquitto_pub and mosquitto_sub commands that allows you to subscribe and publish to the broker. Important: these commands DO NOT WORK with websockets, so you need to keep the insecure listener enabled on port 1883.
$ sudo apt-get install nginx mosquitto mosquitto-clients certbot python-certbot-nginx [... install log...] # To test mosquitto # Open a terminal to subscribe to all topics $ mosquitto_sub -v -h localhost -t '#' # open another terminal to publish a test message $ mosquitto_pub -h localhost -t 'test' -m "This is a message" # in the first terminal you should see the message coming in
At this point you have an insecure mqtt broker listening on the default port 1883. There is no authentication and everyone can publish and listen to your topics. Let’s disable this, add user authentification and enable encryption.
To enable user authentification in moqsquitto you first need to create an username and password. Luckily, mosquitto comes with a tool for this
$ #create an user called test with the password test $ sudo mosquitto_passwd -c /etc/mosquitto/passwd test Password: test Reenter password: test
Mosquitto mqtt secure websockets configuration
To enable websocket listener in Mosquitto you need create a new configuration file e.g /etc/mosquitto/conf.d/secure.conf. Alternatively you can update /etc/mosquitto/mosquitto.conf. Don’t forget to run sudo service mosquitto restart
after.
password_file /etc/mosquitto/passwd allow_anonymous false #unsecure connection, accept connections only from localhost listener 1883 127.0.0.1 #secure websockets listener 8081 127.0.0.1 protocol websockets
If you try to connect now without an username and password you should get the following error. You can also try authentication works.
$ mosquitto_sub -v -h localhost -t '#' Connection Refused: not authorised. Connection Refused: not authorised. ^C $ mosquitto_sub -v -h localhost -t '#' -u test -P test test This is a message ^C
So now we have mosquitto listening on port 1883 for normal connections and on port 8081 for websockets. Both should be accessible ONLY from localhost.
Configuring NGINX for mosquitto mqtt secure websockets
For letsencrypt certificates to work you first need a domain. Nowadays domains are cheap, so I guess everyone has one already. Let’s imagine we want to access mqtt on mqtt.iotassistant.io. Don’t forget to create the subdomain in your DNS manager first.
Create a new configuration file named /etc/nginx/sites-enabled/mqtt with the following content:
upstream mqtt.iotassistant.io { server localhost:8081; } map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { # listen on both hosts server_name mqtt.iotassistant.io; location / { proxy_http_version 1.1; proxy_pass http://localhost:8081; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } }
Installing the certificate is done with the command letsencrypt (the first time you run the command, you’ll have to answer a few questions).
$ sudo letsencrypt Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator nginx, Installer nginx Which names would you like to activate HTTPS for? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: mqtt.iotassistant.io - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): 1 Obtaining a new certificate Performing the following challenges: http-01 challenge for mqtt.iotassistant.io Waiting for verification... Cleaning up challenges Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/mqtt Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2 Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/mqtt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations! You have successfully enabled https://mqtt.iotassistant.io You should test your configuration at: https://www.ssllabs.com/ssltest/analyze.html?d=mqtt.iotassistant.io - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/mqtt.iotassistant.io/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/mqtt.iotassistant.io/privkey.pem Your cert will expire on 2020-05-25. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
After the certificate is installed, your nginx domain config file should look like this:
upstream mqtt.iotassistant.io { server localhost:8081; } map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { # listen on both hosts server_name mqtt.iotassistant.io; location / { proxy_http_version 1.1; proxy_pass http://localhost:8081; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/mqtt.iotassistant.io/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/mqtt.iotassistant.io/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { if ($host = mqtt.iotassistant.io) { return 301 https://$host$request_uri; } # managed by Certbot server_name mqtt.iotassistant.io; listen 80; return 404; # managed by Certbot }
Normally, certbot should automatically renew your certificate, so you don’t have to worry about this.
Connecting and testing mqtt websockets with python
The first test to check that the certificate was installed correctly is to go to your mqtt subdomain ( https://mqtt.iotassistant.io in my case) from a browser. If you see 503 Bad Gatway error, that is NORMAL. It is important to have an https secure connection without errors.
Now you can use this simple python script to test your secure mosquitto mqtt broker:
#!/usr/bin/python3 import sys import paho.mqtt.client as mqtt mqtt_client = mqtt.Client(transport='websockets') mqtt_client.tls_set() #username and pass mqtt_client.username_pw_set("test", "test") mqtt_client.connect("mqtt.iotassistant.io", 443, 30) mqtt_client.loop_start() mqtt_client.publish("test", "Test message").wait_for_publish()
For a more convenient python mqtt wrapper, please check my post about installing DSMR reader for home-assistant using MQTT.
In your subscriber terminal you should see the message
$ mosquitto_sub -v -h localhost -t '#' -u test -P test test Test message