155 lines
9.7 KiB
Markdown
155 lines
9.7 KiB
Markdown
# План доработок 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 (гигиена).
|