Files
IoT/doc/thinking/remediation-plan-2026-10-05.md
T

155 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План доработок IoT — до production-ready (2026-10-05)
> Основа: два независимых анализа + ревью Опуса (построчные ссылки) + моя
> перепроверка по коду. Сгруппировано в 4 трека, внутри — по приоритету.
> Приоритеты: P0 — потеря/компрометация данных или чужие данные; P1 — важные
> риски эксплуатации; P2 — обвязка/гигиена; ПЛАТФОРМА — вне нашего кода.
---
## Трек 1. Безопасность / tenant-изоляция
### P0-1. Связать JWT `sub` с `{namespace}`
- Сейчас: `validateJWT` проверяет только наличие `sub`/`exp`/подписи, `sub`
никак не сверяется с `{namespace}` из URL (`middleware/auth.go:62`); обработчики
берут ns из пути (`handler/devices.go:91`, `handler/telemetry.go:19`).
- Что: извлекать `sub` из claims, передавать в контекст запроса, в middleware
(`/v1`) сверять с `{namespace}` → 403 при несовпадении.
- Файлы: `middleware/auth.go`, `router.go`, `handler/*.go`.
### P0-2. Сделать проверку подписи обязательной
- Сейчас: при пустом `JWT_HMAC_SECRET` подпись не проверяется — токен
подделывается (`auth.go:70`); дефолт секрета пустой, не в required
(`config.go:118`).
- Что: при `AUTH_TEST_MODE=false` требовать `JWT_HMAC_SECRET` (иначе отказ на
старте); `exp` сделать обязательным.
- Файлы: `config.go`, `auth.go`.
### P0-3. Не отдавать пароль устройства повторно + хешировать
- Сейчас: пароль в БД открытым текстом (`store/open.go:21` mqtt_password TEXT),
отдаётся через `GET /devices/{name}` (`handler/devices.go:189`); EMQX
`password_hash_algorithm name=plain` (`emqx.conf:46`).
- Что: пароль отдавать единожды при создании; в БД хранить hash; EMQX перевести
на алгоритм хэша. Миграция существующих строк.
- Файлы: `store/open.go`, `handler/devices.go`, `emqx/emqx.conf`.
### P1-4. Удалить HTTP-auth/ACL `/internal/mqtt/auth|acl`
- Сейчас: EMQX ходит по PG-auth напрямую (`emqx.conf`, `backend=postgresql`).
HTTP-эндпоинты — неиспользуемый fallback, висят на публичном порту 9090 без
аутентификации (`router.go:50-51`) → перебор паролей устройств.
- Что: удалить роуты и обработчики `MQTTAuth`/`MQTTAcl` (и зависимый код).
- Файлы: `router.go`, `handler/mqtt_auth.go`.
### P1-5. Включить `AUTH_TEST_MODE=false` на проде
- Сейчас: включён для диагностики DevOps (HISTORY §38); любой Bearer проходит.
- Что: выставить false + валидный `JWT_HMAC_SECRET`, убедиться в деплое.
### P2-6. TLS верификация PG и EMQX
- Сейчас: `sslmode=require` (без verify-full) и EMQX `verify=verify_none`
(`emqx.conf:40`) — шифрование без проверки сертификата.
- Что: `sslmode=verify-full` при наличии CA; убрать `verify_none` где возможно.
---
## Трек 2. Надёжность доставки телеметрии
### P0-7. Устранить окно потери сообщения (SQS-удаление до записи в PG)
- Сейчас: `consumer.go:78` удаляет из SQS после `processTelemetry`==nil, но
`InsertTelemetry` при неполном батче лишь кладёт в память и возвращает nil
(`iot_telemetry_store.go:356-376`); flush-ошибка неполного батча только
логируется (`iot_telemetry_store.go:133`). Краш/сбой flush = безвозвратная потеря.
- Что: либо синхронная запись (убрать «возврат nil до записи»), либо явный
acknowledge только после подтверждения flush + при ошибке возвращать сообщение
в очередь (видимость + не удалять).
- Файлы: `consumer/processor.go`, `iotpg/iot_telemetry_store.go`.
### P0-8. Идемпотентность входа (dedup)
- Сейчас: `iot_telemetry` id BIGSERIAL без ключа дедупликации; envelope без
message-id (`bridge/handler.go:19`). At-least-once SQS → молчаливые дубли.
- Что: добавить message-id в envelope, UNIQUE-индекс/ON CONFLICT DO NOTHING по
(namespace, message_id) в таблице телеметрии.
- Файлы: `bridge/handler.go`, `iotpg/iot_telemetry_store.go`, схема БД.
### P1-9. `/health` отражает реальное состояние
- Сейчас: `healthHandler` всегда `{"status":"ok"}` (`router.go:72`), не отражает
bridge/consumer/PG/SQS; liveness зелёный при упавшем consumer (`main.go:106`).
- Что: ready-проверки подсистем; `main().Run` bridge/consumer — не глотать
ошибку, а помечать неготовность; `/healthz` (liveness) отделить от `/ready`
(readiness).
- Файлы: `router.go`, `main.go`.
### P1-10. Bridge: поведение при переполнении и ретраях — осознанно
- Сейчас: дроп при переполнении RAM-очереди (`sender.go:73`) и после 3 ретраев
(`sender.go:112`) — потери без метрик.
- Что: метрики дропов; при исчерпании ретраев — не молчаливый дроп, а
лог+счётчик (или при желании DLQ в shared-sqs).
- Файлы: `bridge/sender.go`.
---
## Трек 3. Тесты, CI, метрики
### P1-11. Go-тесты
- Сейчас: `*_test.go` нет ни одного.
- Что: unit-тесты критичных мест — `validateJWT` (tenant-привязка),
`InsertTelemetry`/`flushAll` (потеря), `processor` (дубли), `devices` (CRUD +
пароль), `mqtt_auth` (constant-time). Приоритет — на путях из P0-1..P0-8.
- Файлы: `*_test.go` рядом с пакетами.
### P1-12. CI
- Сейчас: `.github/workflows` нет; в `.github/` только правила.
- Что: workflow: `go vet` + `go test ./...` + `gofmt` check; сборка образа.
- Файлы: `.github/workflows/ci.yml`.
### P2-13. `/metrics` + rate-limiting
- Сейчас: `/metrics` заявлен в комментарии (`config/defaults.go:6`), но роут не
зарегистрирован в `router.go`; prometheus — только indirect dep. Rate-limit нет.
- Что: зарегистрировать `/metrics` (если решено), добавить rate-limiting на `/v1`.
- Файлы: `router.go`, `middleware/`.
---
## Трек 4. Гигиена репозитория и документов
### P2-14. Вычистить легаси
- Сейчас: `cmd/iot-operator`, `controllers/`, `internal/api/`, `cmd/mqtt-bridge`,
`cmd/sqs-consumer`; k8s/controller-runtime в `go.mod` (12 строк); бинарники в
`bin/`, `ws-probe/ws-probe`. `LEGACY.md` обещает убрать после переписывания
хендлеров — оно сделано.
- Что: удалить легаси-код (после P1-4), `go mod tidy`, убрать бинарники из дерева.
- Файлы: `go.mod`, дерево.
### P2-15. Согласовать доки с кодом (дрейф)
- Сейчас: `doc/deployment-nubes-production.md` — образы `v0.1.2`; Makefile —
`v0.1.13`; `loadtests/README.md` — visibility 30с (в коде 120с) и старая
блокировка MQTT-обработчика (уже исправлена).
- Что: привести версии и параметры в доках к фактическим.
- Файлы: `doc/deployment-nubes-production.md`, `loadtests/README.md`.
### P2-16. Emqx cookie из env
- Сейчас: `cookie = "iot-emqx-cookie-mvp"` захардкожен (`emqx.conf`).
- Что: задавать cookie через env/деплой.
- Файлы: `emqx/emqx.conf`.
---
## ПЛАТФОРМА (вне нашего кода, но блокирует прод)
1. **wss обрывы ~150с** на внешнем шлюзе (детерминированно, доказано с 3 точек).
Тикет Nubes; до фикса — устройства должны авто-переподключаться (MQTT
keepalive ≤60с).
2. **MSS/MTU-баг** — обрыв ответов >~15–64 КБ. Обход уже есть (offset-пагинация,
внутренний SQS endpoint); нужен фикс на стороне Nubes.
---
## Рекомендуемый порядок выполнения
1. P0-1, P0-2 (tenant-граница + подпись) — закрывают горизонтальный доступ.
2. P0-7, P0-8 (доставка + dedup) — закрывают потерю/дубли.
3. P0-5 (выключить AUTH_TEST_MODE) — быстро, на проде.
4. P0-3, P1-4, P1-5 (пароли + лишние эндпоинты).
5. P1-9, P1-10 (health + метрики дропов).
6. P1-11, P1-12 (тесты + CI).
7. P2-13..P2-16 (гигиена).