Skip to content

MQTT Protected Broker/ ACL

Table of Contents

Description

  • This is the MQTT broker that is secured and it needs certificates for someone or another broker to communicate with it.
  • In this Documentation, I will show you how I configured the protected broker using TLS as the security protocol.
  • I will also show you how to bridge clients to the protected broker.
  • Before we can start, you need to have the below Prerequisites for you to start this project:

Prerequisites

  • Docker
  • Cloud environment (AWS OR Azure)
  • Nodejs
  • Basics of MQTT Mosquitto

Files and Folder descriptions

  • acl.js - In this file it's where the Access Control List implementation is located.
  • docker-compose.yml - In this file it's where the docker containers for MQTT are created.
  • mosquitto.conf - The configuration file for configuring the protected MQTT Broker.
  • mqtt.js - In this file it's a script use to generate the available distributors from our system and assign them passwords.
  • openmosquitto.conf - This is the configuration file for openmosquitto broker.
  • server-certs - This folder holds the server certificate and keys to be used to secure our broker.

Running the project

  • After you have the files and folders onto your server or pc which has docker installed, you will run the below command to start and create the Mosquitto containers:
docker-compose up -d
  • After starting the containers you can run the following command to see your started containers:
docker ps
  • Copy the files acl.js mqtt.js mqtt.sh and folder server-certs inside the mqtt-protected-broker. To do so, you need to run the following command to enter inside the mqtt-protected-broker container.
docker exec -it mqtt-protected-broker sh

then change the directory to the following:

cd mosquitto/config
  • Inside the config folder is where you will copy the above files.
  • After copying the files and folders you will need to run some commands inside the container to install some packages.
./mqtt.sh
  • The above command will run acl.js and mqtt.js files.

Setting up TLS in MQTT Broker

  • After you have executed the above commands, you will need to set up tls in the MQTT Broker.
  • Here I will show you how to use TLS on our Protected MQTT broker which we have just started as a Docker container.
  • I have already generated the Certificates and Keys that we will use. They are inside the folder server-certs. NB: I used openssl to generate the Certificates and Keys.
  • On your mosquitto.conf file, you will need to add the following lines of configurations for TLS to work on our broker:

```sh
  listener 8883
  allow_anonymous false
  password_file /mosquitto/config/pwd.txt
  acl_file /mosquitto/config/acl.txt
  cafile /mosquitto/config/server-certs/ca.crt
  certfile /mosquitto/config/server-certs/server.crt
  keyfile /mosquitto/config/server-certs/server.key

  tls_version tlsv1.2
- From the above snippet of code, I have done the following:
- Assigned a port number for the protected broker.
- Made the broker private by not allowing anyone to access.
- For authentication, I have linked to the password_file which has credentials for authorized users.
- For Access Control List, I have linked to the acl_file.
- For the cafile, certfile and keyfile, I have linked them to there respective files.

## ACL SETTINGS

[ACL](https://www.techtarget.com/searchnetworking/definition/access-control-list-ACL) is a list of permissions that specify which MQTT topics users are allowed to subscribe to or publish to. It is used by MQTT brokers to control access to topics and ensure that only authorized users can publish or subscribe to messages on specific topics.
The code is a Node.js script that fetches a list of distributors from an API endpoint using GraphQL. It then generates an ACL file that restricts access to MQTT topics based on the distributor's item fleet.

 **Note:**
 MQTT has two types of wildcards;
 1.  Multi-level wildcard: The multi-level wildcard symbol is "#". It is used to match multiple levels in a topic hierarchy, including zero or more levels.
        It's important to note that using multi-level wildcards can result in a large number of messages being received, which can have an impact on the performance of the MQTT broker and the subscriber. Therefore, it's recommended to use wildcards with caution and only when necessary.
 2. “+” means literally everything but only one level, so one or more may be used inside a topic.

ACL restricts both subscription and Publication

### **Here is a step-by-step explanation of the code:**

```markdown
const fetch = require("cross-fetch");
const fs = require("fs");
const AccessTokenClass = require("./accessToken.js")

The first few lines of code import the necessary modules: fetch for making API calls, fs for writing files to the file system, and AccessTokenClass for generating an access token to authenticate the API call.

require('dotenv').config(

The dotenv module is used to load environment variables from a .env file, which contains sensitive information such as the API endpoint URL and credentials.

async function acl() {
    try {
        const accessToken = new AccessTokenClass()
            // Using basic Auth for authentication
            // console.log("ACL>>>>>", await accessToken.AccessToken())
            // query endpoint from graphql
        let Token = await accessToken.AccessToken()
        const URL = process.env.API_URL
        if (Token !== null) {

The acl function is defined as an asynchronous function that uses the try catch statement to handle any errors that may occur during the execution of the script.

et res = await fetch(URL, {
                method: 'POST',
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${Token}`

                },
                body: JSON.stringify({
                    query: ` {
                  getAllDistributors(first:100){
                    page {
                      edges {
                        node {
                          _id
                          name
                          mqtt_password
                          createdAt
                          itemFleet {
                            _id
                            fleetName
                            itemList {
                              oemItemID
                            }
                          }
                        }
                      }
                    }
                  }
                }
                      `
                })
            })

The fetch function is used to make a POST request to the API endpoint with the Authorization header set to the access token generated . The request body contains a GraphQL query to retrieve the list of distributors and their item fleets.

let dist = await res.json();
            let allDist = dist.data.getAllDistributors.page.edges
                // loop through the results
            const data = []

The API response is parsed as JSON, and the allDist variable is set to an array of edges from the API response.

for (let index = 0; index < allDist.length; index++) {
                const element = allDist[index];
                if (element.node !== null) {
                    if (element.node.name !== null && element.node.itemFleet[0] !== undefined && element.node.itemFleet[0].itemList[0] !== undefined) {
                        data.push("user" + " " + element.node.name)
                        data.push("topic" + " " + "dt/V01/GPRSV2/" + element.node.itemFleet[0].itemList[0].oemItemID) 
                }
            }

A for loop is used to iterate through each distributor in the allDist array. For each distributor, the code checks if the distributor name, item fleet, and item list are not null or undefined. If they are not null or undefined, the data array is populated with a string that specifies the user and topic permissions for the distributor's item fleet.

data.push("user Super Admin")
            data.push("topic dt/#")
            data.push("user Admin")
            data.push("topic dt/#")
            data.push("user Client1")
            data.push("topic dt/#")
                // append result to a txt file
            const newFile = await fs.writeFileSync('acl.txt', data.join('\n'));
        }
    } catch (e) {
        console.log(e)
    }
}
console.log(acl())

The data array is also populated with additional user and topic permissions for Super Admin, Admin, and Client1.