Configuring mosquitto
Overview
mosquitto
is an MQTT broker. When used with the pyDE1 suite, it receives
requests to publish MQTT notifications from the pyDE1 core, as well as the
Visualizer uploader. Clients can subscribe to one or more of these and the
broker will send them to the client. These notifications are what can be used
to update a client in effectively real time.
MQTT needs care in configuration as there is no distinction between a publisher and a subscriber. Any client, if allowed by the broker, can publish. This includes connections over WebSockets.
The approach taken with this example configuration is to at least reduce the exposure within reasonable bounds. For the coffee connoisseur, this is “Starbucks-quality” security. Not completely unpalatable, accepted unknowingly by millions, yet completely lacking in so many things. Using Unix domain sockets for same-host connections would be preferable, but the “standard” MQTT library, paho, doesn’t support them. So, localhost is has to be. For simplicity, as these are same-host connections, TLS is not used.
Warning
Security should not be taken lightly. Even when “only” exposed on the loopback interface, any service poses a vector for attack. If on an accessible network segment, and even if not, use of TLS is highly recommended, along with more secure authorization than the username/password supplied by MQTT.
Installing mosquitto
apt install mosquitto mosquitto-clients
mosquitto-clients
is optional. It provides the mosquitto_pub
and
mosquitto_sub
utilities that are useful for debugging and monitoring.
Example mosquitto Configuration
The default /etc/mosquitto/mosquitto.conf
includes a directive to read
configuration from /etc/mosquitto/conf.d/
.
As discussed in the overview, this example configuration does not use TLS and, as such, is not suitable for use on anything but the loopback interface (“localhost”) and then only on hosts where you are confident that the loopback interface can’t be monitored.
Warning
It appears that a WebSocket listener can’t be restricted to the loopback interface, exposing it on the network.
The (mis-)behavior seen at https://www.eclipse.org/lists/mosquitto-dev/msg00799.html still appears to be the current behavior at least as of 2.0.11
Listeners Without TLS
Without TLS, the configuration of listeners is straightforward.
The ports here are not not “set in stone” the way that HTTP and HTTP-S are specified.
conf.d/listeners.conf
listener 1883 localhost
listener 1884
protocol websockets
Listeners Adding TLS
Here, port 8883 was selected for MQTT over TLS. The WebSocket listener is
reverse-proxied by nginx
, so it is not enabled here.
conf.d/listeners.conf
listener 1883 localhost
listener 8883
cafile /etc/ssl/certs/ca-certificates.crt
# For verifiable certs (e.g, Let's Encrypt)
# certfile /etc/mosquitto/certs/fullchain.pem
# For stand-alone, self-signed certs
certfile /etc/mosquitto/certs/cert.pem
# For all types of certs
keyfile /etc/mosquitto/certs/privkey.pem
tls_version tlsv1.3
listener 1884
protocol websockets
As previously noted, mosquitto
needs to be able to read the private key
as the mosquitto
user, not root
. This is somewhat ugly, as reading
as root
then dropping privelege helps protect the key from compromise.
For now, we note and live with the risks. At least until I can find a better
approach, privkey.pem
needs to be (only) mosquitto
-readable.
$ sudo chown mosquitto:root /etc/mosquitto/certs/privkey.pem
$ sudo chmod 600 /etc/mosquitto/certs/privkey.pem
$ ls -l /etc/mosquitto/certs/privkey.pem
-r-------- 1 mosquitto root 3272 Jan 4 15:19 /etc/mosquitto/certs/privkey.pem
Current versions of pyDE1
allow configuration of TLS for MQTT
through the config files. For details of the parameters,
see paho’s Client.set_tls()
. With a verifiable certificate,
setting mqtt.TLS: true
should be sufficient. With self-signed certificates,
mqtt.TLS_CA_CERTS
likely would also need to be set to the path to
the corresponding CA or public certificate in use.
Blocking Off-Host Access to WebSockets
As the listener for WebSockets is on all interfaces, it presents enough of a
security risk to block the port from off-host access. There are several
firewall tools for Linux, many of which are very outdated and now deprecated.
Here are some simple rules using nftables
that should block access to
port 1884 from other hosts. For more information on nftables
,
see, for example,
https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management
Warning
This is not a complete firewall. It will need to be integrated with your existing firewall.
nft add inet filter input iifname != 'lo' tcp dport 1884 drop
On a “fresh” Debian Bullseye system, the resulting ruleset may look something like the following:
$ sudo nft -a list ruleset
table inet filter { # handle 1
chain input { # handle 1
type filter hook input priority filter; policy accept;
iifname != "lo" tcp dport 1884 drop # handle 4
}
chain forward { # handle 2
type filter hook forward priority filter; policy accept;
}
chain output { # handle 3
type filter hook output priority filter; policy accept;
}
}
You may need to enable and start nftables.service
if it is not yet running.
systemctl status nftables.service
will show if it is enabled and/or running.
The enable
, start
, … actions require root privilege (sudo
).
The default configuration file is /etc/nftables.conf
.
Logging
/etc/mosquitto/conf.d/logging.conf
The change here is to use human-readable timestamps in the log files, rather than Unix timestamps.
log_timestamp_format %Y-%m-%dT%H:%M:%S
Note
Remember to sudo systemctl restart mosquitto.service
to have the changes take effect.