I want to protect my mosquitto container with certificates preferably not using self-signed certificates. Therefore i have the question if it would be possible with Traefik to extract the MQTT certificates from the acme.json (cafile/certfile/keyfile) and save it to a different docker volume upon changes (probably using a bash script on the docker host)
For MQTT i have the following settings i need to set:
cafile
certfile
keyfile
As ca-file i probably need this one: https://letsencrypt.org/certs/trustid-x3-root.pem.txt
and certfile & keyfile map to the string contents of the corresponding certificate and keyfile sections in acme.json if i am correct.
The copy step is needed because i think mosquitto needs to load the certificates in order to ensure WSS and MQTTS protocols. I prefer all external connections (mqtt.mydomain.com) to be TLS encrypted. Locally/internally i don't need certificates.
Or is there a better/alternative way to do this?
I've fixed it, here is the info for others who are trying to do this; It consists of a few parts:
- Working Docker container with traefik v2.02
- Working Docker container with Eclipse-mosquitto
- Some bash scripting knowledge
- 2 port forwards in your firewall.
(note: below solution does not enforce user/password connection yet; i first wanted to get SSL/TLS working)
1 - Working Docker container with traefik v2.02
Obviously out of scope for this reply.
2 - Working Docker container with Eclipse-mosquitto
This is the part of the docker-compose file containing Eclipse-mosquitto:
network-mosquitto:
image: eclipse-mosquitto
container_name: network-mosquitto
restart: unless-stopped
environment:
- PUID=1000
- PGID=999
networks:
f43lan:
ipv4_address: 192.168.1.39
volumes:
- /etc/localtime:/etc/localtime:ro
- network-mosquitto-config:/mosquitto/config
- network-mosquitto-data:/mosquitto/data
- network-mosquitto-log:/mosquitto/log
labels:
# Enable traefik
- "traefik.enable=true"
- "traefik.docker.network=traefik"
# Router: Plex-specific
- "traefik.http.routers.network-mosquitto.rule=Host(`mqtt.at.domain.com`)"
- "traefik.http.routers.network-mosquitto.entrypoints=websecure"
- "traefik.http.routers.network-mosquitto.tls=true"
- "traefik.http.routers.network-mosquitto.tls.certresolver=leresolver"
The volume network-mosquitto-config
is a named volume which maps to /var/lib/docker/volumes/network-mosquitto-config/
.
My mosquitto configuration:
# Plain MQTT protocol
listener 1883
# MQTT over TLS/SSL
listener 8883
cafile /mosquitto/config/certs/trustid-x3-root.pem
certfile /mosquitto/config/certs/certificate.crt
keyfile /mosquitto/config/certs/privatekey.key
# Plain WebSockets configuration
listener 9001
protocol websockets
# WebSockets over TLS/SSL
listener 9883
protocol websockets
cafile /mosquitto/config/certs/trustid-x3-root.pem
certfile /mosquitto/config/certs/certificate.crt
keyfile /mosquitto/config/certs/privatekey.key
3 - Some bash scripting knowledge
With the addition of the following self-written script, i've managed to get Lets encrypt working. This script runs daily (at night) as a cronjob.
#!/bin/bash
#----------------------------------------------------------------------------------------------------
# Variables
#----------------------------------------------------------------------------------------------------
# Location of the 'traefik-certs-dumper' script, written by LDEZ <https://github.com/ldez/traefik-certs-dumper>
CERT_DUMP_SCRIPT="/mnt/nas/docker/scripts/traefik-certs-dumper/traefik-certs-dumper"
# Temp directory to write all exported certificates to (unfortunately the script from ldez doesn't support to export 'just one' domain.) - will be removed at the end of the script.
CERT_DUMP_DIR="/mnt/nas/docker/scripts/traefik-certs-dumper/tmp"
# Location of the Traefik generated acme.json file
TRAEFIK_ACME_FILE="/var/lib/docker/volumes/core-traefik-acme/_data/acme.json"
# File format version of Traefik to use (i am running v2.0.2 when i wrote this script, hence i need to pass 'v2')
TRAEFIK_ACME_VERSION="v2"
# My docker container and hostname to look for
MQTT_DOCKER_CONTAINER="network-mosquitto"
MQTT_HOSTNAME="mqtt.at.domain.com"
# The location of where to save the certificates
MQTT_CERT_DIR="/var/lib/docker/volumes/network-mosquitto-config/_data/certs"
# Ensure the certificates eventually have this file ownership
MQTT_CERT_OWNERSHIP="docker:users"
#----------------------------------------------------------------------------------------------------
# Start script (no changed needed below)
#----------------------------------------------------------------------------------------------------
# Check if certificate dump script exists
if [ ! -f "$CERT_DUMP_SCRIPT" ]; then
echo "ERROR: The 'traefik-certs-dumper' script doesn't exist: $CERT_DUMP_SCRIPT."
exit 1
fi
# Check if the Traefik ACME file exists
if [ ! -f "$TRAEFIK_ACME_FILE" ]; then
echo "ERROR: The Traefik acme file doesn't exist: $TRAEFIK_ACME_FILE."
exit 1
fi
# Check if container exists
if [[ $(docker ps --filter "name=^/$MQTT_DOCKER_CONTAINER$" --format '{{.Names}}') != $MQTT_DOCKER_CONTAINER ]]; then
echo "ERROR: The docker container doesn't seem to exist: $MQTT_DOCKER_CONTAINER."
exit 1
fi
# Check if certificate directory exists
if [ ! -d "$MQTT_CERT_DIR" ]; then
echo "ERROR: The location of the certificates doesn't seem to exist: $MQTT_CERT_DIR."
exit 1
fi
# Run the certificate dump script
$CERT_DUMP_SCRIPT file --source $TRAEFIK_ACME_FILE --domain-subdir=true --version $TRAEFIK_ACME_VERSION --dest $CERT_DUMP_DIR >> /dev/null 2>&1
ERROR=$?
if [ $ERROR -eq 0 ]; then
# Verify the certificate and the key
CERT_EXPORTED=$CERT_DUMP_DIR/$MQTT_HOSTNAME/certificate.crt
if [ ! -f "$CERT_EXPORTED" ]; then
echo "ERROR: Unable to find the configured certificate in the export: $CERT_EXPORTED."
ERROR=1
fi
KEY_EXPORTED=$CERT_DUMP_DIR/$MQTT_HOSTNAME/privatekey.key
if [ ! -f "$KEY_EXPORTED" ]; then
echo "ERROR: Unable to find the configured privatekey in the export: $KEY_EXPORTED."
ERROR=1
fi
# Can we still continue?
if [ $ERROR -eq 0 ]; then
CERT_EXISTING=$MQTT_CERT_DIR/certificate.crt
CERT_COPY=0
if [ ! -f "$CERT_EXISTING" ]; then
# Copy the exported certificate to the MQTT certificate directory if it doesn't exist yet.
echo "Notice: Copying exported certificate since certificate doesn't exist yet..."
CERT_COPY=1
else
# Verify if the exported certificate needs to be copied to the MQTT certificate directory
diff --binary --brief $CERT_EXPORTED $CERT_EXISTING > /dev/null 2>&1
if [ $? -eq 1 ]; then
echo "Notice: Updating existing certificate since exported certificate is different..."
CERT_COPY=1
fi
fi
if [ $CERT_COPY -eq 1 ]; then
echo "- Copying $CERT_EXPORTED to $CERT_EXISTING ..."
cp $CERT_EXPORTED $CERT_EXISTING
echo "- Updating file ownership of $CERT_EXISTING ..."
chown $MQTT_CERT_OWNERSHIP $CERT_EXISTING
fi
KEY_EXISTING=$MQTT_CERT_DIR/privatekey.key
KEY_COPY=0
if [ ! -f "$KEY_EXISTING" ]; then
# Copy the exported privatekey to the MQTT certificate directory if it doesn't exist yet.
echo "Notice: Copying exported privatekey since privatekey doesn't exist yet..."
KEY_COPY=1
else
# Verify if the exported privatekey needs to be copied to the MQTT certificate directory
diff --binary --brief $KEY_EXPORTED $KEY_EXISTING > /dev/null 2>&1
if [ $? -eq 1 ]; then
echo "Notice: Updating existing privatekey since exported privatekey is different..."
KEY_COPY=1
fi
fi
if [ $KEY_COPY -eq 1 ]; then
echo "- Copying $KEY_EXPORTED to $KEY_EXISTING ..."
cp $KEY_EXPORTED $KEY_EXISTING
echo "- Updating file ownership of $KEY_EXISTING ..."
chown $MQTT_CERT_OWNERSHIP $KEY_EXISTING
fi
# If we did something, then...
if [[ $CERT_COPY -eq 1 || $KEY_COPY -eq 1 ]]; then
# Check if container is running (to determine automatic restart)
if [[ $(docker inspect --format '{{.State.Running}}' $MQTT_DOCKER_CONTAINER) == "true" ]]; then
echo "Sending RESTART to $MQTT_DOCKER_CONTAINER ..."
RET=$(docker container restart $MQTT_DOCKER_CONTAINER)
# Note: sending a SIGHUP would be better, but this causes the logfile to show: "Reloading config." followed by "Error: Unable to open config file /mosquitto/config/mosquitto.conf." in my eclipse-docker container.
#echo "- Sending SIGHUP to $MQTT_DOCKER_CONTAINER ..."
#RET=$(docker kill --signal=HUP $MQTT_DOCKER_CONTAINER)
fi
fi
fi
# Remove the exported certificate directory
[ -d $CERT_DUMP_DIR ] && rm -rf $CERT_DUMP_DIR
fi
exit $ERROR
4 - Two port forwards in your firewall
Then all you need to do is forward port 8883 and 9883 in your UniFi security gateway or firewall / router towards the IP running your broker (in my case 192.168.1.39) and you should be able to connect via MQTTS and WSS protocols using a validated certificate.
1 Like
I am on the same journey: docker, traefik, grafana and google auth is the goal. I am glad, I found your topic
Although there are still some obstacles, I thing I will use the docker image of the cert dumper with watch-option. Look here: traefik-certs-dumper/docker-compose-traefik-v2.yml at master · ldez/traefik-certs-dumper · GitHub
Well 3 uses the cert-dumper and restarts the docker service for which the certificate has been updated if needed.