Autentificación para un broker Mosquitto

por Ricardo Vega el 26/10/2016

Las anteriores semanas nos entretuvimos bastante en dos posts detallando como el protocolo MQTT nos podía ayudar (y mucho) en la tarea de comunicar nuestros sensores y actuadores en general (con Arduino en particular) con un elemento central que haga las veces de servidor.

Este servidor puede ser un ordenador, un servidor coorporativo, un hosting, una solución "on cloud" o un mini-ordenador como las Raspberry Pi. Los procesos de instalación que tenemos que llevar en ellos son muy semejantes ya que seguramente todos trabajen con un sistema operativo Linux.

En este post, voy a centrarme en una Raspberry Pi aunque los pasos serán exactamente iguales en cualquier distribución Debian y tendrá modificaciones ligeras en otros entornos.

MQTT Mosquitto Seguridad

Llevas ya tres parrafos leídos y aún no te he dicho de qué va a ir exactamente esta entrada. Nuestro objetivo para hoy será aprender a configurar un broker MQTT con autentificación para securizar un poco el acceso al mismo de forma que podamos exponerlo en un servidor público y aún así tengamos zonas privadas.

Concretamente, y como ya es habitual en este blog, emplearemos Mosquitto como broker. Dicho sistema deberá estar ya instalado en nuestra Raspberry Pi tal y como hemos detallado en otros posts.

Sin embargo, por si aún no lo tienes instalado en tu Raspberry Pi. Estas son los pasos que debes seguir:

git clone https://github.com/eclipse/mosquitto.git
cd mosquitto
sudo apt-get install build-essential
sudo apt-get install libc-ares-dev
sudo apt-get install uuid-dev
make binary
make install

Como ves, estamos descargando Mosquitto directamente desde el repositorio y compilándolo por nostros mismos lo que nos asegura la instalación de la última versión disponible.

Una vez conseguido esto, añadiremos la funcionalidad de la autentificación a través de un plugin llamado mosquitto-auth-plug. Veamos como configurarlo.

En primer lugar clonamos el repositorio GIT desde Github:

git clone https://github.com/jpmens/mosquitto-auth-plug.git
cd mosquitto-auth-plug

Si te entretienes un rato en leer el README del proyecto (cosa que siempre te recomiendo hagas) puede que te llame la atención como habla de la posibilidad de trabajar con distintos backends:

  • MySQL
  • PostgreSQL
  • CDB
  • SQLite3
  • Redis
  • TLS PSK
  • LDAP
  • HTTP
  • JWT
  • MongoDB

Estas distintas opciones responden a la necesidad de usar un backend que almacene los usuarios con permisos en el sistema, es decir, una especie de almacén que guarde usuario/contraseña.

Aunque creo que no es la mejor opción, vamos a emplear la opción que con nuestros conocimientos actuales de APIs creo que mejor se adapta a la mayoría de lectores de este blog: APIs HTTP que podemos escribir nosotros mismos con NodeJS o Python como hemos visto anteriormente.

Necesitaremos renombrar el fichero ´config.mk.in´ como ´config.mk´ y editarlo como se muestra a continuación:

# Select your backends from this list
BACKEND_CDB ?= no
BACKEND_MYSQL ?= no
BACKEND_SQLITE ?= no
BACKEND_REDIS ?= no
BACKEND_POSTGRES ?= no
BACKEND_LDAP ?= no
BACKEND_HTTP ?= yes
BACKEND_MONGO ?= no

# Specify the path to the Mosquitto sources here
MOSQUITTO_SRC = /home/USER/mqtt/mosquitto

# Specify the path the OpenSSL here
OPENSSLDIR = /usr/bin

Además, para instalar este plugin, necesitaremos instalar un serie de dependencias:

sudo apt-get install openssl
sudo apt-get install libssl-dev
sudo apt-get install libcurl4-openssl-dev

Ahora podemos construir mosquitto-auth-plugin ejecutando el siguiente comando.

sudo make

Esta operación creará un fichero llamado auth-plug.so en el mismo directorio. Deberemos copiar dicho fichero a la raíz de Mosquitto:

cp auth-plug.so ../mosquitto
cd ../mosquitto

Ahora editaremos la configuración de Mosquitto editando el fichero mosquitto.conf. Concretamente editaremos la sección "Security". Añadiremos lo siguiente:

# =================================================================
# Security
# =================================================================

# If set, only clients that have a matching prefix on their
# clientid will be allowed to connect to the broker. By default,
# all clients may connect.
# For example, setting "secure-" here would mean a client "secure-
# client" could connect but another with clientid "mqtt" couldn't.
#clientid_prefixes

# Boolean value that determines whether clients that connect
# without providing a username are allowed to connect. If set to
# false then a password file should be created (see the
# password_file option) to control authenticated client access.
# Defaults to true.
#allow_anonymous true

# In addition to the clientid_prefixes, allow_anonymous and TLS
# authentication options, username based authentication is also
# possible. The default support is described in "Default
# authentication and topic access control" below. The auth_plugin
# allows another authentication method to be used.
# Specify the path to the loadable plugin and see the
# "Authentication and topic access plugin options" section below.
#auth_plugin

auth_plugin /home/USER/mosquitto/auth-plug.so
auth_opt_backends http
auth_opt_http_ip 127.0.0.1
auth_opt_http_port 80
#auth_opt_http_hostname example.org
auth_opt_http_getuser_uri /mosquitto/authplugin/auth
auth_opt_http_superuser_uri /mosquitto/authplugin/superuser
auth_opt_http_aclcheck_uri /mosquitto/authplugin/acl

Como ves, definimos las URLS de nuestra API REST que servirá para indicar si la autentificación es o no correcta. Mosquitto enviará peticiones a dichos endpoints comprobando si un determinado usuario tiene o no acceso a un determinado topic.

La definición de esta API es bastante sencilla, pudiendo crearla por ejemplo con Python.

from flask import Flask, Response, request

app = Flask(__name__)

@app.route('/auth', methods=['POST'])
def auth():
    response = Response(content_type='text/plain', status=403)
    if all((w in request.args for w in ['username', 'password'])):
        username = request.args['username']
        password = request.args['password']
        if username == '[email protected]' and password == '13579':
            response.status_code = 200
    return response

@app.route('/superuser', methods=['POST'])
def superuser():
    response =  Response(content_type='text/plain', status=403)
    if all((w in request.args for w in ['username', 'password'])):
        username = request.args['username']
        password = request.args['password']
    if username == '[email protected]' and password == '13579':
        response.status_code = 200
    return response


@app.route('/acl', methods=['POST'])
def acl():
    response =  Response(content_type='text/plain', status=403)
    if all((w in request.args for w in ['username', 'topic'])):
        username = request.args['username']
        topic    = request.args['topic']
    if username == '[email protected]' and topic == 't/1':
        response.status_code = 200
    return response

if __name__ == '__main__':
    app.run()

Por favor, el código mostrado arriba es altamente inseguro pero te dará una API funcionando rápidamente. Si quieres usar este método de forma correcta, deberías acceder a una base de datos o semejante para obtener los valores que se esperan.

No escribas directamente en tu código usuarios, contraseñas, etc.

Ya tenemos todos los elementos que necesitamos para probar la seguridad. Para ejecutar mosquitto con esta configuración, tan sólo tendremos que que ejecutar:

mosquitto -c /home/USER/mosquitto/mosquitto.conf

A partir de este momento, podrás hacer un uso normal con publicadores y subscriptores pero deberemos pasar en esta ocasión los datos de usuario y contraseña:

mosquitto_pub -t "test" -m "hola_mundo" -u "[email protected]" -p "13579"
mosquitto_sub -t "test" -u "[email protected]" -p "13579"

Y en el caso de estar empleando Arduino y el cliente Arduino Client for MQTT deberemos añadir el username y el password en la conexión:

client.connect("Arduino Client", "[email protected]", "13579")

Como ves, no es mucho más complicado tener un broker securizado. Gracias a esta opción, podríamos abrirlo a Internet y marcar como privados (con seguridad) determinados "topics" (o todos) para información que no queramos compartir con nadie.

Esto nos permite, por ejemplo, poner un cliente Arduino con MQTT en nuestra casa del pueblo y conectarnos al broker que tenemos configurado en nuestra casa en la ciudad de forma segura, pudiendo por ejemplo montar un sistema de riego remoto manejado desde nuestra casa.

Como te dije en el anterior post, las posibilidades que se nos abren son casi infinitas y ya no tenemos escusas para no dejar volar nuestra imaginación.

Espero que te haya gustado este post. La próxima semana abandonaremos un poco MQTT para hablar de otros temas que estoy seguro también te interesan.

Como te digo siempre, puedes utilizar Twitter para ponerte en contacto conmigo así como emplear los comentarios para plantear tus dudas sobre esta entrada.

¡Un saludo!

Apoya al blog


Si te ha gustado este artículo, valora apoyarme económicamente a través de Patreon, una plataforma de Micro-mecenazgo con la que puedes hacerme un donativo que ayude a la continuidad del blog. Una pequeña ayuda significa mucho. 😃

Permanezcamos en contacto!


¿Quieres enterarte de todas las novedades del sector? ¿Te gustaría trabajar conmigo? ¡Puedes contactar conmigo de forma muy sencilla!