PostgreSQL-based MQTT access control
MQTT’s lightweight, pub-sub model is a natural choice for IoT systems, but takes some fiddling to secure. There is less documentation than for popular protocols like HTTP, especially for specific brokers ([Mosquitto(http://mosquitto.org) vs Apache, anyone?). Having already chosen Mosquitto due to its broad hardware support, I needed scalable per-device authentication beyond the built-in Access Control List, which worked fine in testing, but required a service restart to update access. A PostgreSQL installation was already configured, and was the natural place for device-related data storage.
mosquitto-auth-plug to the rescue. This Mosquitto plugin extends authentication to include a variety of databses. As can be seen from a three-year-old guide for Mosquitto 1.3.5 and MySQL, the setup is slightly involved. This post builds on that for PostgreSQL (fully supported by the plugin) and Mosquitto 1.5. It assumes that the database, git, and compilation libraries (make, gcc, g++) are already installed. Debian-style commands are used.
Initial installation and build
Since the plugin must be built with a path to the source, I had to uninstall Mosquitto first (you may want to back up any complex configurations from /etc/mosquitto first, and fully purge the installation to avoid problems on re-install). The initial steps to build the fresh Mosquitto consisted of:
The setup for the plugin remained mercifully unchanged:
Editing config.mk, to account for the different database and folder,
([path] will vary depending on the earlier tar)
Making mosquitto-auth-plug consisted of:
Configuration and database
It was now time to configure Mosquitto. My prior mosquitto.conf had some persistence and log settings, so I left those in place instead o overwriting everything with the example. I appended a modified version of the guide’s text, fortunately shorter for PostgreSQL:
The last three are default PostgreSQL calls (on principle, I recommend using your own schema). Per the documentation,
- userquery: mandatory query returning a 1x1 result with the PBKDF2 password hash for a given user
- superquery: query for superusers who are exempt from access control restrictions, returning 1x1 entry with 0/1 indicating whether th user is a superuser (useful since I needed a global user to read from all the channels)
- aclquery: query returning a single column with any number of rows, each containing an MQTT topic string.
It was time to test the np password generation utility, and set up the database.
Testing
After a few false starts (failing silently with the -c argument, yet running normally without it), the logs revealed the error described by Rex Xia, with the same solution:
From here, Mosquitto and the plugin ran perfectly. Note that any flaw in the PostgreSQL authentication for the user configured in mosquitto.conf, or in that user’s access to the newly created tables, reveals itself here, bringing authentication/authorization checks to a premature halt.
Success looked like
on subscribe with, e.g., MQTT.fx (any credentials besides testuser/password fail to connect), and:
when publishing to testuser/test. Oddly enough, though an rw value of 3 should grant both read and write, changing this value was not enough for testuser. Since the following appeared whenever I attempted a subscription to the same topic,
I set a permission of 4 and was able to both publish a message and receive it with the same connection. Attempting to subscribe to any unpermitted topic still caused the connection to terminate.
Next steps
- Consider the schema - does the default table structure make sense? If you will only ever need one topic string per user, you may wish to combine the account and acl tables.
- Personalize the schema - rename tables/columns to make your database a bit less obvious.
- Set up tools for managing the new permission tables. For me, the main stumbling block was reproducing the np script’s encryption in Node-RED.