Serie
- Gestión de certificados (PKI) – easy-rsa
- MQTT Broker (Mosquitto) con certificado servidor (self-signed)
- MQTT Broker (Mosquitto) con certificado servidor (self-signed) y certificado en los clientes
- MQTT Broker (Mosquitto) con certificado servidor (Let’s Encrypt)
- Formatos de Certificados: PEM, DER y PKCS#12 y Manipulación de Claves Privadas
Descripción del escenario
Se describe cómo montar un bróker de MQTT usando Mosquitto, uno de los servidores más rápidos y sencillos de desplegar. Se va a mostrar el fichero de configuración más sencillo posible para cargar los certificados generados tanto para conexión TCP como Websockets.
Seguidamente, se va a demostrar con todo detalle cómo se negocia la conexión cifrada a través del certificado; esto lo haremos usando OpenSSL como cliente TCP+SSL/TLS; después se usarán los clientes de Mosquitto para suscribirse y publicar mensajes en un topic de demo. Seguidamente, también se mostrará cómo consumir el servicio con un simple código hecho en Python. Para finalizar, el servicio se consumirá con el cliente EMQ MQTTX vía consola que nos va a permitir consumir el servicio vía TCP y también vía Websockets.
Asumciones
- Tenemos los conceptos básicos descritos descritos en el post Gestión de certificados (PKI) – easy-rsa.
- Se asume que tenemos una PKI desplegada tal y como se explica en Gestión de certificados (PKI) – easy-rsa. Además se asume que la configuración de la misma está en el fichero
vars
.
- También asumiremos que tenemos un servidor DNS que resuelve, por lo menos, en nuestra red privada. Este paso también podría sustituirse con las modificaciones pertinentes del,
/etc/hosts
pero considero que es una mala práctica si se puede evitar.
- También se asume que tenemos instalado Mosquitto y sus tools/clients en el sistema operativo que vamos a usar. Todas nuestras demostraciones serán en Linux Ubuntu 22.04
Demostración en vídeo
Obtención de los certificados
Creamos petición de certificado
El objetivo es rellenar los campos que va a tener nuestro certificado con la información que identifica a nuestro servicio. El campo más importante es el X.509 DN, porque debe coincidir con al menos uno de los nombres canónicos de la máquina (FQDN). Si no tendremos problemas cuando se conecten los clientes, ya que estos se quejarán de que no somos quienes decimos ser. El principal objetivo de un certificado es asegurar que somos quienes decimos ser.
El Common Name (CN), también conocido como Fully Qualified Domain Name (FQDN), es el valor característico que se almacena dentro del campo DN (Distinguished Name), también conocido como X.509 DN.
# desde la máquina que queramos con easy-rsa instalado
./easyrsa gen-req mqtt.example.tld
# se genera fichero mqtt.example.tld.req
Firma de la petición de certificado
Si no se lanzó la petición de certificado (certificate request) desde el mismo PKI, hay que copiarlo a la máquina del PKI e importarlo. No lo olvidemos. Aunque en soluciones domésticas típicamente todo lo movemos dentro de la misma estructura de directorios de la PKI.
# en la PKI:
## importamos el certificado, si no se ha hecho la petición desde aquí
## se asume que se copió el fichero de certificate request en
## /the-path/mqtt.example.tld.req
./easyrsa import-req /the-path/mqtt.example.tld.req mqtt.example.tld
## esto colocará una copia del fichero .req en la PKI y algunos enlaces.
## Se le pide a la PKI que firme el certificado
./easyrsa sign-req server mqtt.example.tld
### esto generará el fichero .key y .crt necesarios para armar el servidor.
Creación y firma de la petición de certificado
En un solo comando, en el caso de hacerlo todo desde la PKI (en un solo servidor) podemos obtener: clave privada, request de certificador y certificado firmado. Este certificado será de tipo servidor.
./easyrsa build-server-full mqtt.example.tld
# sin cifrado de la clave privada:
./easyrsa build-server-full mqtt.example.tld --no-pass
IMPORTANTE, no tiene sentido hacer este comando si previamente hemos hecho la creación de request, (importación) y firmado.
Configuración de Mosquitto con certificados
Esta configuración es muy sencilla y solo pondrá en marcha un Mosquitto en el puerto TCP/8883 además del puerto WSS/8884.
mosquitto.conf
# mqtt secure
listener 8883 0.0.0.0
cafile certs/ca.crt
keyfile certs/mqtt.example.tld.key
certfile certs/mqtt.example.tld.crt
crlfile certs/pki.example.tld.crl
allow_anonymous true
require_certificate false
# websockets secure
listener 8884 0.0.0.0
protocol websockets
cafile certs/ca.crt
keyfile certs/mqtt.example.tld.key
certfile certs/mqtt.example.tld.crt
crlfile certs/pki.example.tld.crl
require_certificate false
Para lanzar el servicio, simplemente debemos usar el comando:
mosquitto -c mosquitto.conf
Cuando lancemos el servicio se nos pedirá la passphrase de la clave privada dos veces, una para cada uno de los listerners que hemos puesto la configuración, el TCP y el WSS.
Negociación de la conexión (OpenSSL s_client)
OpenSSL es la herramienta que hay por debajo de easy-rsa
y la librería de referencia para SSL/TLS; además de disponer de herramientas que nos permitirán depurar enlaces de cifrados. Para este caso de uso usaremos openssl s_client
que nos va a permitir ver todos los detalles de la negociación SSL/TLS con el servidor de MQTT.
# self-signed fail chechking
openssl s_client mqtt.example.tld:8883
# using my CA, self-signed succesful checking
openssl s_client -CAfile certs/ca.crt mqtt.example.tld:8883
En el vídeo de demostración puedes ver más detalles de cómo se analiza la negociación entre el servidor y el cliente.
Clientes Mosquitto
Usando mosquitto_sub
y mosquitto_pub
se demuestra cómo se intercambia tráfico a través de un enlace cifrado. Nada del otro mundo, pero que en entornos cifrados y para evoluciones de este escenario veremos que tiene algunas limitaciones.
Me suscribo al topic llamado topic1
y esperando que se publiquen mensajes en él:
mosquitto_sub --cafile pki/ca.crt -d -h mqtt.example.tld -p 8883 -t topic1 -v
Publico un mensaje en el topic con el contenido: value
1
mosquitto_pub --cafile pki/ca.crt -d -h mqtt.example.tld -p 8883 -t topic1 -m message1
Clientes desarrollados en Python
Se trata de un par de scripts muy sencillos que solo pretenden ilustrar lo sencillo que es especificar nuestra propia CA en la inicialización de la conexión segura con el servidor MQTT.
# se requiere uv, si no lo tienes instalado:
curl -LsSf https://astral.sh/uv/install.sh | sh
# lanzamos el suscriptor
uv run mqtt_sub.py
# lanzamos el publicador
uv run mqtt_pub.py
Clientes MQTTX-cli usando TCP y Websockets
Al lanzar el bróker de MQTT expusimos el endpoint en TCP y el de Websockets, pero no hemos usado el segundo para nada todavía. Así, pues, vamos a aprovechar la potencia del cliente de EMQ llamado MQTTX para repetir las mismas funciones que antes, pero ahora haciendo la suscripción con Websockets y la publicación con TCP.
# para no tener que instalar mqttx-cli lo usaremos desde Docker:
alias mqttx-cli='docker run -it --rm --entrypoint /usr/local/bin/mqttx -v ./mosquitto/certs/ca.crt:/tmp/ca.crt emqx/mqttx-cli'
# suscripción
mqttx-cli sub \
--ca /tmp/ca.crt
-h mqtt.example.tld \
-p 8884 -l wss \
-t topic1
# publicación
mqttx-cli pub \
--ca /tmp/ca.crt
-h mqtt.example.tld \
-p 8883 -l mqtts \
-t topic1 -m message1
Referencias
- Repositorio github/kiss-mqtt-client-private-key
- https://mqttx.app/
- https://mqttx.app/docs/cli
- Creating and Using Client Certificates with MQTT and Mosquitto
- Using A Lets Encrypt Certificate on Mosquitto
- SSL and SSL Certificates Explained For Beginners
- Mosquitto.conf man
- How to Configure MQTT over WebSockets with Mosquitto Broker
- Getting the Client’s Real IP When Using the NGINX Reverse Proxy for EMQX