Gabriel Cachadiña

Seguridad en MQTT

· Gabriel Cachadiña

MQTT es un protocolo de comunicación que usa TCP/IP para que dispositivos se subscriban/publiquen a tópicos. El reparto de la información de estos tópicos es gestionado por un dispositivo llamado broker, que gestiona el envío y recepción de información entre dispositivos.

MQTT Basic

El problema surge cuando un proyecto hobby quiere pasar a un ámbito más industrial, en estos casos una configuración por defecto de un servidor MQTT no cumple los estándares de seguridad ya que:

Solución de estos problemas

Identificación del broker

Usando una Autoridad Certificadora (CA) propia, que firma el certificado del broker, los clientes pueden verificar que están conectándose al broker legítimo y así evitar la suplantación del mismo, ya que todos los certificados deberán ser dados por él y cualquiera otro broker que aparezca en la red no tendrá las credenciales correctas. Este certificado se puede conseguir de la siguiente forma:

 1# Construct subject strings
 2INFO_CA="/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=CA/CN=${PROJECT}-Root-CA"
 3INFO_SERVER="/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=Server/CN=mqtt.${PROJECT}.com"
 4
 5
 6# Step 1: Generate CA
 7openssl req -x509 -nodes -sha256 -newkey rsa:2048 \
 8-subj "$INFO_CA" -days 99999 \
 9-keyout ca.key -out ca.crt
10
11# Step 2: Generate server SAN config (include both DNS and IP)
12cat > server_ext.cnf <<EOF
13subjectAltName=DNS:mqtt.${PROJECT}.com,IP:${SERVER_IP}
14EOF
15
16# Step 3: Generate Server Key + CSR
17openssl req -nodes -sha256 -newkey rsa:2048 \
18-subj "$INFO_SERVER" -keyout server/server.key -out server/server.csr
19
20# Step 4: Sign Server Cert with CA
21openssl x509 -req -sha256 -in server/server.csr \
22-CA ca.crt -CAkey ca.key -CAcreateserial \
23-out server/server.crt -days 99999 \
24-extfile server_ext.cnf

Identificación de los clientes

Todos los clientes deberán tener una llave firmada por el broker, lo cual creará una comunicación segura entre el broker y el cliente. Se puede tener una única llave para todos los clientes o una llave por cliente, en función de la seguridad que se desee del sistema.

 1# --- Generate client certificate signed by the project CA ---
 2INFO_CLIENT="/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=Client/CN=${CLIENT}"
 3
 4# Generate client key + CSR
 5openssl req -new -nodes -sha256 \
 6-subj "$INFO_CLIENT" -out "${CLIENT_DIR}/client.csr" -keyout "${CLIENT_DIR}/client.key"
 7
 8# Sign client cert with project CA
 9openssl x509 -req -sha256 -in "${CLIENT_DIR}/client.csr" \
10-CA "${PROJECT_DIR}/ca.crt" -CAkey "${PROJECT_DIR}/ca.key" -CAcreateserial \
11-out "${CLIENT_DIR}/client.crt" -days 99999

Limitación de los clientes

Aunque los clientes cuenten con una llave única para cada dispositivo, no se debería dejar que cualquier dispositivo pueda decir o leer lo que le plazca, es por ello que se deberán limitar los clientes a ciertos tópicos.

Usuarios

Los usuarios permiten un acceso de los recursos, que puede hacerse más granular. Para crear un usuario se deberá establecer un nombre y una contraseña.

1USER="mqttuser"
2PASS="mypassword"
3HASH=$(python3 -c "import crypt; print(crypt.crypt('$PASS', crypt.mksalt(crypt.METHOD_SHA512)))")
4echo "$USER:$HASH" | sudo tee /etc/mosquitto/passwd > /dev/null

Restricción de usuarios a tópicos

Para conseguir esto se editara el archivo aclfile con los permisos de cada usuario y los tópicos que podrá leer o sobre los que podrá publicar.

Por ejemplo, en este caso se tienen 2 usuarios uno que tiene acceso a la lectura y escritura de cualquier tópico y otro usuario que solo tiene acceso a la lectura de tópicos que comiencen por test/ (# es usado como wildcard).

user username1  
topic write #  
topic read #  
  
user username2  
topic read test/#

MQTT Security

Configuración de MQTT

Ya sabiendo todo esto, se podrá configurar un servidor MQTT mediante lo siguiente:

.
├── certs
│   ├── aclfile
│   ├── ca.crt
│   ├── passwd
│   ├── server.crt
│   └── server.key
└── mosquitto.conf
allow_anonymous false
listener 8883

password_file /mosquitto/config/certs/passwd
acl_file /mosquitto/config/certs/aclfile

cafile /mosquitto/config/certs/ca.crt
certfile /mosquitto/config/certs/server.crt
keyfile /mosquitto/config/certs/server.key

require_certificate true
use_identity_as_username true
 1services:
 2  mqtt5:
 3    image: eclipse-mosquitto:latest
 4    container_name: mqtt5
 5    volumes:
 6      - ./Mqtt:/mosquitto/config/
 7      - ./Mqtt/data:/mosquitto/data:rw
 8      - ./Mqtt/log:/mosquitto/log:rw
 9    ports:
10      - '8883:8883'
11    networks:
12      - default
13    restart: unless-stopped
Test:$6$PHvuLoqs2dHc4Xfb$4SlRmtoPzWrmZkOn7x/0J1MiQVZsPvoXdftuXnmnOR25xFzPAVFyVtg4cOJActJ4LJf1erz2G/S5uVsjRiRm7Q==
user username1  
topic write #  
topic read #  
  
user username2  
topic read test/#

Script

A continuación se muestra un script para generar estas credenciales y un tester para probar esta conexión.

  1#!/bin/bash
  2
  3clear
  4tree .
  5
  6# -----------------------------
  7# Location info and base dirs
  8# -----------------------------
  9COUNTRY="ES"
 10STATE="Badajoz"
 11CITY="Badajoz"
 12ORG="SScertificate"
 13BASE_DIR="./"
 14
 15# =========================================================
 16# Create Project (CA + Server certificate)
 17# =========================================================
 18create_project() {
 19
 20  echo "Creating new project..."
 21
 22  read -p "Enter project name: " PROJECT
 23  read -p "Enter server IP address: " SERVER_IP
 24
 25  PROJECT_DIR="${BASE_DIR}${PROJECT}"
 26  SERVER_DIR="${PROJECT_DIR}/server"
 27
 28  INFO_CA="/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=CA/CN=${PROJECT}-Root-CA"
 29  INFO_SERVER="/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=Server/CN=mqtt.${PROJECT}.com"
 30
 31  mkdir -p "${SERVER_DIR}"
 32  cd "${PROJECT_DIR}" || exit 1
 33
 34  # --- Generate CA ---
 35  openssl req -x509 -nodes -sha256 -newkey rsa:2048 \
 36    -subj "$INFO_CA" -days 99999 \
 37    -keyout ca.key -out ca.crt
 38
 39  # --- Server SAN (DNS + IP) ---
 40  cat > server_ext.cnf <<EOF
 41subjectAltName=DNS:mqtt.${PROJECT}.com,IP:${SERVER_IP}
 42EOF
 43
 44  # --- Server key + CSR ---
 45  openssl req -nodes -sha256 -newkey rsa:2048 \
 46    -subj "$INFO_SERVER" \
 47    -keyout server/server.key \
 48    -out server/server.csr
 49
 50  # --- Sign server cert ---
 51  openssl x509 -req -sha256 \
 52    -in server/server.csr \
 53    -CA ca.crt -CAkey ca.key -CAcreateserial \
 54    -out server/server.crt \
 55    -days 99999 \
 56    -extfile server_ext.cnf
 57
 58  cp ca.crt server/ca.crt
 59
 60  rm -f server/server.csr server_ext.cnf *.srl
 61
 62  echo "Project '${PROJECT}' initialized (Server IP: ${SERVER_IP})"
 63}
 64
 65# =========================================================
 66# Add Client
 67# =========================================================
 68add_client() {
 69
 70  echo "Adding new client..."
 71
 72  read -p "Enter project name: " PROJECT
 73  read -p "Enter client name: " CLIENT
 74  read -s -p "Enter password for ${CLIENT}: " CLIENT_PASS
 75  echo
 76  read -s -p "Confirm password: " CLIENT_PASS2
 77  echo
 78
 79  [[ "$CLIENT_PASS" != "$CLIENT_PASS2" ]] && echo "Passwords do not match." && exit 1
 80
 81  PROJECT_DIR="${BASE_DIR}${PROJECT}"
 82  CLIENT_DIR="${PROJECT_DIR}/${CLIENT}"
 83  PASSWORD_FILE="${PROJECT_DIR}/passwd"
 84
 85  [[ ! -d "$PROJECT_DIR" ]] && echo "Project not found." && exit 1
 86
 87  mkdir -p "$CLIENT_DIR"
 88
 89  if [[ ! -f "$PASSWORD_FILE" ]]; then
 90    mosquitto_passwd -c -b -H sha512 "$PASSWORD_FILE" "$CLIENT" "$CLIENT_PASS"
 91  else
 92    mosquitto_passwd -b -H sha512 "$PASSWORD_FILE" "$CLIENT" "$CLIENT_PASS"
 93  fi
 94
 95  chmod 0700 "$PASSWORD_FILE"
 96
 97  INFO_CLIENT="/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=Client/CN=${CLIENT}"
 98
 99  openssl req -new -nodes -sha256 \
100    -subj "$INFO_CLIENT" \
101    -out "${CLIENT_DIR}/client.csr" \
102    -keyout "${CLIENT_DIR}/client.key"
103
104  openssl x509 -req -sha256 \
105    -in "${CLIENT_DIR}/client.csr" \
106    -CA "${PROJECT_DIR}/ca.crt" \
107    -CAkey "${PROJECT_DIR}/ca.key" \
108    -CAcreateserial \
109    -out "${CLIENT_DIR}/client.crt" \
110    -days 99999
111
112  cp "${PROJECT_DIR}/ca.crt" "${CLIENT_DIR}/ca.crt"
113
114  rm -f "${CLIENT_DIR}/client.csr" "${PROJECT_DIR}"/*.srl
115
116  echo "Client '${CLIENT}' added to project '${PROJECT}'"
117}
118
119# =========================================================
120# Test MQTT (IP SELECTABLE)
121# =========================================================
122test_mqtt() {
123
124  echo "Testing MQTT..."
125
126  PROJECTS=($(find "$BASE_DIR" -maxdepth 1 -mindepth 1 -type d))
127  [[ ${#PROJECTS[@]} -eq 0 ]] && echo "No projects found." && exit 1
128
129  echo "Available projects:"
130  select PROJECT in "${PROJECTS[@]##*/}"; do
131    [[ -n "$PROJECT" ]] && break
132  done
133
134  PROJECT_DIR="${BASE_DIR}${PROJECT}"
135
136  CLIENTS=($(find "$PROJECT_DIR" -maxdepth 1 -mindepth 1 -type d ! -name "server"))
137  [[ ${#CLIENTS[@]} -eq 0 ]] && echo "No clients found." && exit 1
138
139  echo "Available clients:"
140  select CLIENT_DIR in "${CLIENTS[@]}"; do
141    [[ -n "$CLIENT_DIR" ]] && CLIENT=$(basename "$CLIENT_DIR") && break
142  done
143
144  read -s -p "Enter password for ${CLIENT}: " PASSWORD
145  echo
146
147  read -p "Enter MQTT server IP or hostname: " SERVER_IP
148
149  echo "Choose action:"
150  select ACTION in "Publish" "Subscribe"; do
151    case $ACTION in
152      Publish ) MODE="pub"; break ;;
153      Subscribe ) MODE="sub"; break ;;
154    esac
155  done
156
157  read -p "Enter topic: " TOPIC
158
159  BASE_CMD="mosquitto_${MODE} \
160    --cafile ${CLIENT_DIR}/ca.crt \
161    --cert ${CLIENT_DIR}/client.crt \
162    --key ${CLIENT_DIR}/client.key \
163    -h ${SERVER_IP} \
164    -p 8883 \
165    -u ${CLIENT} \
166    -P ${PASSWORD} \
167    -t ${TOPIC}"
168
169  if [[ "$MODE" == "pub" ]]; then
170    echo "Publishing mode (Ctrl+C to exit)"
171    while true; do
172      read -p "Message: " MESSAGE
173      [[ -n "$MESSAGE" ]] && $BASE_CMD -m "$MESSAGE"
174    done
175  else
176    echo "Subscribing..."
177    $BASE_CMD
178  fi
179}
180
181# =========================================================
182# Main Menu
183# =========================================================
184echo "Select an action:"
185select ACTION in "Create Project" "Add Client" "Test MQTT" "Exit"; do
186  case $ACTION in
187    "Create Project" ) create_project ;;
188    "Add Client" ) add_client ;;
189    "Test MQTT" ) test_mqtt ;;
190    "Exit" ) exit 0 ;;
191    * ) echo "Invalid choice" ;;
192  esac
193done

#self-hosted

Reply to this post by email ↪