DIY Dry box — Part 5

Everything is now in place… except there’s nothing to monitor the humidity in the boxes anymore. As I said before, I’m not fond of the battery powered hygrometers.

The best solution here is to have desiccant that change color when wet and design the desiccant box in a way that allows to see the color from the outside.

But that’s not what I chose. I wanted to experiment with something else. Being a software engineer, this project was lacking software so far 😉.

The idea is to put a hygrometer chip in each box, link them to a microcontroller through an I²C bus. Then develop a program for the microcontroller to fetch the humidity level of each sensor and send it to an MQTT server.

First the MQTT server. MQTT is a message queuing system, so the microcontroller will send messages to it, and they will be consumed by something else (a bit more on that later). Mosquitto is probably the most well known MQTT server implementation, and there are docker images for it.

A well known thing about IoT, is that the “S” in “IoT” stands for security… so there are a few things I want to put in place:

  • Encrypted channel, by default MQTT messages are sent over the network in plain text.
  • Authentication, you may prevent eavesdropping by encrypting the communication, but if anyone can log into the system and read the content, that’s pointless. Both work together: having authentication but not encrypting the communication leaves eavesdroppers the possibility to steal the authentication credentials.
  • Add ACL: the microcontroller should be allowed to only post in its topics, not others, while the visualization client should be able to read all the topics it is allowed to.

MQTT encryption

As it could be expected, securing an MQTT channel simply consists in tunneling it through TLS. TLS works better with a valid certificate, we can generate one with Let’s encrypt.

There are plenty of ways to do so. Here I’ll use Traefik proxy, which is a reverse proxy that can generate Let’s encrypt certificates automatically. All incoming connections will go through Traefik, Traefik will be in charge of the TLS part, but then the connections between Traefik and the other Docker containers will not be encrypted.

Example of docker compose file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
version: '3'

services:

  proxy:
    image: traefik:latest
    ports:
      - 80:80
      - 443:443
      - 8883:8883
    networks:
      - internal
    command:
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entrypoints.mqtts.address=:8883
      - --providers.docker=true
      - --providers.file.directory=/configuration/
      - --providers.file.watch=true
      - --certificatesresolvers.le.acme.email=your-email@address
      - --certificatesresolvers.le.acme.storage=/configuration/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /path/to/traefik/configuration/:/configuration/

  mqtt:
    image: eclipse-mosquitto:latest
    depends_on:
      - proxy
    volumes:
      - /path/to/mosquitto/configuration:/mosquitto/config
      - /path/to/mosquitto/data:/mosquitto/data
      - /path/to/mosquitto/log:/mosquitto/log
    networks:
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.tcp.routers.mqtt.rule=HostSNI(`mqtt.example.com`)"
      - "traefik.tcp.routers.mqtt.tls=true"
      - "traefik.tcp.routers.mqtt.tls.certresolver=le"
      - "traefik.tcp.routers.mqtt.entrypoints=mqtts"
      - "traefik.tcp.services.mqtt.loadbalancer.server.port=1883"

networks:
  internal

Some explanations:

  • Lines 7 to 10: ports opened on the container and exposed to the network. 80 and 443 for HTTP and HTTPS, used by Let’s encrypt to validate certificates. Port 8883 is the standard MQTT over TLS port.
  • Lines 14 to 16: definition of endpoints in Traefik, basically the ports Traefik will listen to.
  • Lines 20 to 22: Let’s encrypt configuration. Your Let’s encrypt account email address, where the certificates will be stored (they need to be on a permanent storage otherwise they will need to be generated each tie the container starts, then probably be blocked by Let’s encrypt quota…).
  • Line 39: tell Traefik to route TCP connections to the mqtt container when the SNI matches the specified domain. Obviously the domain should be declared in DNS and point to the server.
  • Lines 40 and 42: make the connection to the entrypoint called “mqtts” over TLS and use Let’s encrypt.
  • Line 43: make the connection from the entrypoint to the mqtt container on port 1883 (standard non-encrypted MQTT port).

Authentication

In mosquitto.conf file, make sure anonymous connections are not allowed and specify the path to a password file. Example of configuration file:

listener 1883
persistence true
persistence_location /mosquitto/data
log_dest file /mosquitto/log/mosquitto.log
allow_anonymous false
password_file /mosquitto/config/mosquitto.passwd

Now, create the password file, give mosquitto user (1883) ownership of the file, make sure its readable only by that user and call the mosquitto_passwd command (living inside the docker container) in order to add credentials to the file:

sudo touch mosquitto.passwd
sudo chown 1883:1883 mosquitto.passwd
sudo chmod 600 mosquitto.passwd
docker exec <mqtt-container-name> mosquitto_passwd -b /mosquitto/config/mosquitto.passwd <username> <password>

ACL

In mosquitto.conf file, specify a file for ACL:

acl_file /mosquitto/config/mosquitto.acl

Then create the matching file on the server. Here is an example of a user home_automation that can do everything and a user drybox that can only write to a specific topic:

user home_automation
topic readwrite #

user drybox
topic write devices/drybox/#

More about security

This is a basic example, a few more things to consider:

  • IoT devices should be in their own network with limited/no internet access;
  • depending on your setup, the example here may expose some services to the internet, tweaking the configurations (firewall, router’s port forwarding…) is required;
  • if access from outside the internal network is required, if possible, don’t expose the services to the internet but connect to the internal network through a VPN;
  • keep everything up-to-date.

Test

Make sure mosquitto has been restarted to take into account the modifications made in the configuration file.

Install an MQTT client, for instance on debian:

sudo apt install mosquitto-clients

In a terminal, subscribe to all topics:

mosquitto_sub -h mqtt.example.com -p 8883 -u home_automation -P <password> -t '#' -v

In another terminal, send messages like:

mosquitto_pub -h mqtt.exmaple.com -p 8883 -u home_automation -P <password> -t devices/drybox/humidity -m 36

On the first terminal, the messages and their topic should be displayed.

Any messages sent to a topic the user is not allowed to write should not be received by the subscribed user. A wrong login and/or password should throw an error message, likewise when not providing any credentials.

More on this:

Comments Add one by sending me an email.