Embedded

Безопасность MQTT: TLS, аутентификация, ACL

Введение

Базовая конфигурация Mosquitto из статьи 312 (allow_anonymous true, незащищённое подключение) подходит только для полностью изолированного, доверенного тестового окружения. В заключительной перед итоговым мини-проектом статье про безопасность разберём три уровня защиты MQTT-инфраструктуры — шифрование транспорта через TLS, аутентификацию клиентов по логину/паролю, и контроль доступа к конкретным топикам через ACL (Access Control List).

Концепция

TLS защищает сами данные, передаваемые по сети, от прослушивания и подмены — аналогично HTTPS для веб-трафика, MQTT поверх TLS (порт 8883) шифрует весь трафик между клиентом и брокером. Аутентификация по логину/паролю гарантирует, что только известные, авторизованные клиенты могут подключиться к брокеру вообще. ACL — более тонкий уровень контроля, определяющий, какие конкретные топики разрешено публиковать или на какие подписываться каждому отдельному аутентифицированному пользователю — что важно, если разные устройства/приложения должны иметь разные права (датчик может только публиковать свои данные, но не должен иметь возможность подписаться на топики управления другими устройствами).

Пример кода: Безопасность MQTT

# /etc/mosquitto/mosquitto.conf — production-конфигурация с TLS, аутентификацией и ACL

listener 8883
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
cafile /etc/mosquitto/certs/ca.crt

allow_anonymous false
password_file /etc/mosquitto/passwd

acl_file /etc/mosquitto/acl.conf
# Создание пользователей с паролями через утилиту mosquitto_passwd
sudo mosquitto_passwd -c /etc/mosquitto/passwd esp32_livingroom_01
sudo mosquitto_passwd /etc/mosquitto/passwd qt_dashboard_app
# /etc/mosquitto/acl.conf — разграничение прав по топикам для каждого пользователя

user esp32_livingroom_01
topic write home/livingroom/temperature
topic write home/livingroom/humidity
topic write home/livingroom/light/state
topic read home/livingroom/light/set

user qt_dashboard_app
topic read home/+/temperature
topic read home/+/humidity
topic read home/+/light/state
topic write home/+/light/set
// firmware/src/main.cpp — подключение ESP32 к брокеру с аутентификацией (без TLS для простоты примера)
mqttClient.connect(DEVICE_ID, "esp32_livingroom_01", "device_password_here");
// Подключение Qt-клиента с TLS и аутентификацией
m_client.setHostname("mqtt.mycompany.com");
m_client.setPort(8883);
m_client.setUsername("qt_dashboard_app");
m_client.setPassword("dashboard_password_here");

QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
m_client.connectToHostEncrypted(sslConfig);

Пояснения к коду

acl.conf показывает практическое разграничение прав, отражающее принцип наименьших привилегий — конкретное устройство esp32_livingroom_01 может публиковать только в свои собственные топики данных и команды состояния, и может только читать (но не публиковать) команды управления, предназначенные ему — оно физически не имеет прав опубликовать что-либо в топик другой комнаты или подписаться на чужие команды управления, даже если бы попыталось это сделать после получения корректных credentials для собственного, ограниченного набора операций.

Qt-клиент подключается через connectToHostEncrypted() вместо обычного connectToHost(), используя порт 8883 (стандартный для MQTT через TLS) — это обеспечивает шифрование транспортного уровня независимо от уровня прав конкретного пользователя, определяемого ACL; оба механизма (TLS и ACL) дополняют друг друга, решая разные аспекты безопасности — шифрование канала против контроля прав внутри уже установленного, зашифрованного канала.

Подводные камни

  • Реализация только одного из трёх уровней защиты (например, только аутентификации без TLS) при ложном ощущении полной защищённости системы — аутентификация по паролю без TLS означает, что сам пароль передаётся по сети в открытом виде и может быть перехвачен при прослушивании трафика — для действительно защищённой production-конфигурации нужны все три уровня одновременно, а не выбор только одного из них как «достаточного».
  • Самоподписанные TLS-сертификаты без правильной настройки доверия на стороне клиентов — если сертификат сервера (server.crt) самоподписан (что типично для домашних/небольших проектов без покупки сертификата от публичного удостоверяющего центра), клиентские приложения (включая прошивку ESP32 и Qt-приложение) должны быть явно настроены доверять именно этому конкретному сертификату/CA, иначе TLS-подключение будет завершаться ошибкой проверки сертификата — это требует дополнительной настройки на каждом клиенте, не нужной при использовании сертификата от широко доверяемого публичного CA.
  • Управление паролями устройств через mosquitto_passwd без продуманной стратегии их хранения и ротации — пароли, зашитые непосредственно в код прошивки (аналогично паролю Wi-Fi из статьи 310), создают те же риски при утечке исходного кода или физическом доступе к устройству; для более серьёзных проектов стоит продумать механизм безопасного хранения credentials на самом устройстве (например, в защищённой области флеш-памяти) и план ротации паролей при компрометации.
  • Слишком сложная, детальная настройка ACL без реальной необходимости для небольшого, полностью домашнего проекта — для совсем небольшого личного проекта с несколькими доверенными устройствами в изолированной домашней сети полная инфраструктура TLS+ACL может быть избыточным усложнением; важно соотносить уровень инвестиций в безопасность с реальной моделью угроз конкретного проекта — то, что обязательно для коммерческого продукта с тысячами развёрнутых устройств, может быть избыточным для одного учебного устройства в полностью приватной сети, и наоборот, что подчёркивает необходимость сознательного решения, а не слепого копирования любой из двух крайностей.