# Лог мышления — 2026-04-04-02 ## Агент: GitHub Copilot (Claude Sonnet 4.6) Продолжение сессии от 2026-04-04 (файл 2026-04-04.md достиг 681 строки). --- ## Задача: MQTT WebSocket через nginx-ingress — отладка 404 → 400 ### Контекст Порт 1883 заблокирован NSX-T Edge firewall на уровне облака. Временный workaround: EMQX WebSocket listener (8083) → nginx-ingress → `iot.kube5s.ru/mqtt`. ### Что нашёл **Проблема 1 — 404:** Ingress не получил ADDRESS (IP не присвоен). - Причина: annotation `nginx.ingress.kubernetes.io/configuration-snippet` заблокирован в nginx-controller v1.12.6. По умолчанию `allow-snippet-annotations=false`. - Решение: убрал `configuration-snippet` из Ingress. - Результат: Ingress получил sync (Scheduled for sync), но ADDRESS всё ещё пустой — это нормально для данного контроллера. **Проблема 2 — 400:** После удаления snippet стало 400 вместо 404. - nginx конфиг для `iot.kube5s.ru` правильный: - `proxy_set_header Upgrade $http_upgrade` — ЕСТЬ (nginx-ingress добавляет автоматически) - `proxy_http_version 1.1` — ЕСТЬ - `proxy_send_timeout/read_timeout 3600s` — ЕСТЬ - `proxy_pass → sless-emqx-ws-8083` — ЕСТЬ - Значит 400 приходит от EMQX, а не от nginx. ### Гипотезы почему EMQX возвращает 400 EMQX WebSocket listener конфиг: ``` listeners.ws.default { bind = "0.0.0.0:8083" max_connections = 512 } ``` Нет явного `websocket.path`. По умолчанию EMQX ожидает путь `/mqtt`. Но nginx из-за `pathType: Prefix` на `/mqtt` может передавать путь без trailing slash. **Вариант A**: nginx передаёт запрос на `/mqtt` (без `/`) — EMQX ожидает `/mqtt` → должно работать. **Вариант B**: EMQX в 5.5.1 ожидает `ws://host/mqtt` без всякого proxy path rewrite и есть баг с `X-Forwarded-Proto` — nginx шлёт `http`, а EMQX может редиректить. **Вариант C**: `ssl_redirect: true` в nginx конфиге означает что HTTP запрос перенаправляется на HTTPS, и там 400. ### Следующий шаг Проверить `$ssl_redirect = true` в nginx server block для нашего хоста: - Если да — nginx делает redirect HTTP→HTTPS - Добавить аннотацию `nginx.ingress.kubernetes.io/ssl-redirect: "false"` или тестировать через HTTPS ### Правило (новое) Если файл `doc/thinking/ГГГГ-ММ-ДД.md` стал большим — создавать `ГГГГ-ММ-ДД-02.md`, `ГГГГ-ММ-ДД-03.md` и т.д. --- ## Задача: Анализ безопасности MQTT multitenancy ### Что исследовал Пользователь спросил об угрозах межтенантного проникновения. Изучил: 1. `emqx.conf` в поде — `authorization { no_match = allow }` — после CONNECT всё разрешено 2. `internal/api/handler/iot_device_handler.go` — `MQTTAuth` возвращает только `{"result":"allow"}` без ACL rules ### Вывод **Auth (CONNECT) защищён:** - HTTP auth endpoint проверяет namespace+deviceId+password (constant-time compare) - enabled=true проверяется - Secret изолирован по namespace **ACL на pub/sub НЕТ:** - `no_match = allow` — аутентифицированный клиент может SUBSCRIBE на любой топик - EMQX HTTP auth plugin поддерживает возврат ACL rules в ответе на auth - Формат ответа: `{"result":"allow","acl":[{"permission":"allow","action":"all","topic":"sless/ns/+"}]}` - Текущий `mqttAuthResponse` содержит только `Result string` — ACL поле отсутствует ### Риски по приоритету 1. **Критично**: User A может SUBSCRIBE `sless/#` и читать все IoT данные всех пользователей 2. **Средне**: Нет rate limit на MQTT — один клиент может flood брокер 3. **Низко**: Нет TLS на 8083 (WebSocket без шифрования) — данные видны в сети ### План фикса Добавить в `mqttAuthResponse` поле `ACL []aclRule` и возвращать из `MQTTAuth`: ```json { "result": "allow", "acl": [ {"permission": "allow", "action": "publish", "topic": "sless/{ns}/{deviceId}"}, {"permission": "allow", "action": "subscribe", "topic": "sless/{ns}/{deviceId}"}, {"permission": "deny", "action": "all", "topic": "#"} ] } ``` Ждём подтверждения от пользователя перед реализацией.