diff --git a/doc/thinking/opus-review-prompt.md b/doc/thinking/opus-review-prompt.md new file mode 100644 index 0000000..b022589 --- /dev/null +++ b/doc/thinking/opus-review-prompt.md @@ -0,0 +1,74 @@ +# Промпт для ревью (Opus) — IoT готовность к production + +Ты ревьюишь Go-проект IoT. Цель — выявить блокеры, мешающие назвать сервис +production-ready. Дай ответ МАКСИМАЛЬНО сжато, без воды: списком, короткими +строками, с точными ссылками на файл и строку. Никаких пояснений-простыней. + +**Запрещено:** +- Не читай память, историю сессий, заметки, предыдущие анализы, `HISTORY/`, + `LEGACY.md`, ничего в `legacy/`. Только код и документация из списка ниже. +- Не читай старые k8s-манифесты, CRD, оператор — это легаси, не актуально. +- Не предлагай рефакторинг ради рефакторинга — только то, что блокирует прод. + +**Компоненты продукта (актуально):** монолит `iot-service` (API + MQTT-bridge + +SQS-consumer в одном бинарнике) + EMQX + shared-SQS + managed PostgreSQL. +Конвейер: устройство → wss → EMQX → бридж → SQS → consumer → PG → REST API. + +**Смотри ТОЛЬКО эти файлы (актуальный код):** + +Конфигурация и точка входа: +- `cmd/iot-service/main.go` +- `internal/service/config/config.go` +- `internal/service/config/defaults.go` + +HTTP API: +- `internal/service/api/router.go` +- `internal/service/api/middleware/auth.go` +- `internal/service/api/middleware/logging.go` +- `internal/service/api/handler/handler.go` +- `internal/service/api/handler/devices.go` +- `internal/service/api/handler/mqtt_auth.go` +- `internal/service/api/handler/telemetry.go` +- `internal/service/api/handler/admin.go` +- `internal/service/api/handler/sqsstats.go` + +Хранение: +- `internal/service/store/open.go` +- `internal/service/store/device.go` +- `internal/service/store/devices.go` +- `internal/service/store/bridge.go` +- `internal/storage/iotpg/iot_telemetry_store.go` + +Бридж и consumer: +- `internal/service/bridge/bridge.go` +- `internal/service/bridge/handler.go` +- `internal/service/bridge/sender.go` +- `internal/service/consumer/consumer.go` +- `internal/service/consumer/processor.go` +- `internal/service/sqsclient/sqsclient.go` + +Инфраструктура: +- `Dockerfile` +- `Makefile` +- `go.mod` +- `emqx/emqx.conf` +- `loadtests/README.md` (только для понимания критериев, не весь код) + +**Вопросы (ответь строго по пунктам, каждому — вердикт + файл:строка):** + +1. Tenant-изоляция: как `sub` из JWT связывается с `{namespace}` в URL? + Есть ли путь доступа к чужому namespace валидным токеном? +2. Гарантии доставки: есть ли окно потери сообщения между буфером записи, + ответом об успехе и удалением из SQS? Где именно? +3. Идемпотентность: защищён ли вход от дублей при at-least-once SQS? +4. `/health`: отражает ли реальную работоспособность bridge/consumer или + всегда `ok`? +5. Auth JWT: валидация sub/exp/подписи — что при пустом `JWT_HMAC_SECRET`? +6. Пароли устройств: как хранятся, как отдаются через API. +7. `/internal/mqtt/auth` и `/internal/mqtt/acl`: нужны ли ещё, защищены ли? +8. Ошибки записи в PG: что происходит с сообщением при сбое flush-батча? +9. Есть ли тесты (`*_test.go`), CI, rate-limiting, метрики, TLS-верификация PG? + +**Формат ответа:** по каждому пункту — 1 строка вердикт (OK / ПРОБЛЕМА) + +`файл:строка` + при проблеме — 1 строка суть. В конце — список +«блокеры для прода» (только критические, без ранжирования по вкусу). diff --git a/doc/thinking/readiness-assessment-2026-10-05.md b/doc/thinking/readiness-assessment-2026-10-05.md new file mode 100644 index 0000000..74eeeae --- /dev/null +++ b/doc/thinking/readiness-assessment-2026-10-05.md @@ -0,0 +1,140 @@ +# Оценка готовности IoT к production (2026-10-05) + +> Три взгляда на один репозиторий: анализ A, анализ B и моё мнение. +> Все три — только чтением кода и документации, без сборки и запуска тестов +> (правила репо: команды — только через SSH на ВМ; сборку/прогоны не выполняли). + +--- + +## 1. Анализ A (первый) — итог 5/10 + +**Вердикт:** цельный MVP, но по коду не считать production-ready для +надёжной multi-tenant эксплуатации. + +### Блокеры +- **Не проверяется принадлежность namespace пользователю.** Middleware + проверяет JWT, но не передаёт `sub` обработчикам для сравнения с namespace + из URL. CRUD и выдача пароля устройства берут namespace из запроса. + Валидный токен не ограничивает пользователя его tenant-ом: + `auth.go:20`, `router.go:54`, `devices.go:179`. `exp` фактически необязателен + (`auth.go:120`). +- **Путь потери уже принятых сообщений.** Flusher заменяет буфер на пустой + ДО записи в PG, при ошибке только логирует. Consumer при этом удаляет + сообщения из SQS после добавления в буфер: `iot_telemetry_store.go:129`, + `:135`, `consumer.go:81`. +- **Health зелёный при остановившемся pipeline.** Ошибки запуска bridge/consumer + только логируются; горутина завершается, а `/health` всегда отдаёт `ok`: + `main.go:106`, `router.go:74`. +- **Доставка допускает потери и дубли.** Переполненная RAM-очередь bridge + сбрасывает сообщения; исчерпание SQS-ретраев заканчивается drop. При + at-least-once нет идемпотентного ключа против дублей: `sender.go:70`, `:112`. + +### Что хорошо +Компоненты сведены в понятный единый сервис; есть MQTT-reconnect с повторной +подпиской, SQS-ретраи, graceful shutdown, разделение хранения устройств и +телеметрии, широкий набор нагрузочных сценариев. + +### Чего не хватает до уверенного релиза +- Автоматических Go-тестов (`*_test.go` не найдено) и CI. +- Согласованности доков с кодом: гайд деплоя — `iot-service:v0.1.2`, Makefile — + `v0.1.13`; loadtest README описывает уже исправленную блокировку + MQTT-обработчика и visibility 30с, код задаёт 120с: + `deployment-nubes-production.md:3`, `Makefile:4`, `README.md:85`, `config.go:114`. + +--- + +## 2. Анализ B (второй) — итог ~60–65% + +**Вердикт:** рабочий MVP / технический прототип, не продакшн-готовый сервис. + +### Что хорошо +- Конвейер устройство → EMQX → мост → SQS → consumer → PG (per-tenant) + работает end-to-end; подтверждено HISTORY и нагрузочными прогонами. +- Прогоны: baseline 2390/2390 без потерь/дублей; ACL-нарушения отсекаются; + reconnect-storm прошёл чисто. +- Код небольшой (~2,4 тыс. строк в монолите), аккуратный: graceful shutdown, + асинхронный SQS-диспатчер с ретраями, batch-insert, backoff, constant-time + сравнение паролей. +- Документация подробная: деплой без kubectl, интеграция с SQS, лог ревью и + фиксов, 10 нагрузочных сценариев. +- Auth и ACL в EMQX работают напрямую через PostgreSQL, без зависимости от монолита. + +### Что мешает назвать готовым +1. **Нет ни одного Go-теста и нет CI.** Только Python-нагрузочные скрипты; + в `.github/` одни правила. +2. **Изоляция тенантов слабая.** JWT проверяет только `sub` и `exp`; namespace + из URL не сверяется с токеном. Без `JWT_HMAC_SECRET` подпись не проверяется. + Любой «валидный» токен читает устройства и пароли любого namespace. +3. **Пароли устройств в БД открытым текстом** и отдаются через GET. EMQX — + `password_hash_algorithm = plain`. +4. **`AUTH_TEST_MODE=true`** (любой Bearer) был включён на проде для диагностики. + HISTORY §38 — «вернуть false»; закрытия не видно. +5. **`/internal/mqtt/auth` и `/internal/mqtt/acl`** открыты на публичном порту + без авторизации; дублируют PG-auth в EMQX и уже не нужны. ACL выдаёт права + моста любому клиенту с `clientid`, совпадающим с мостовым. +6. **Нерешённая платформенная проблема:** плавающий обрыв ответов >~15–64 КБ и + WebSocket-обрывы ~150с; стабильного воспроизведения нет, разбор с DevOps + идёт. В коде обходится пагинацией. +7. **Отложено:** whitelist тенантских БД (любой namespace создаёт базу), DLQ, + soak 24ч. +8. **Легаси не вычищено:** `cmd/iot-operator`, `controllers/`, `internal/api/`, + `cmd/mqtt-bridge`, `cmd/sqs-consumer`; в `go.mod` 12 строк k8s/controller-runtime; + бинарники в `bin/` и `ws-probe/ws-probe`. `LEGACY.md` обещает убрать после + переписывания хендлеров, а оно сделано. +9. **Дрейф конфигурации:** `cookie = "iot-emqx-cookie-mvp"` захардкожен в + `emqx.conf`; параметры EMQX задаются только через env в deck-UI. +10. **Нет TLS-верификации** к PG (`verify_none`). Нет rate limiting и метрик + (только `/health` и admin-статистика). + +### Минимум для продакшна +- привязать namespace к JWT, сделать `JWT_HMAC_SECRET` обязательным; +- убедиться, что `AUTH_TEST_MODE` выключен; +- хешировать пароли устройств или не отдавать повторно; +- удалить `/internal/mqtt/*` и легаси; +- добавить тесты и CI; +- закрыть вопрос обрывов на платформе. + +--- + +## 3. Моё мнение (перепроверено по коду) + +Оба анализа компетентны и в главном совпадают. Расхождение 5/10 vs 60–65% — +не в фактах, а в весе, который они дают одним и тем же находкам. Спорные места +я перепроверил сам — оба правы по существу. + +### Подтверждается кодом (не предположение) +1. **Изоляция тенантов слабая** — верно. `middleware/auth.go` проверяет JWT + только на `sub`+`exp`; `sub` не сверяется с `{namespace}` (claims не + передаются в обработчики, `router.go` не извлекает `sub`). `exp` необязателен + (`if claims.Exp > 0`), без `JWT_HMAC_SECRET` подпись не проверяется. +2. **Путь потери принятых сообщений** — верно. `processTelemetry` → `InsertTelemetry` + кладёт в in-memory буфер и возвращает nil, после чего consumer безусловно + удаляет сообщение из SQS. `flushAll` подменяет буфер на пустой ДО записи в PG + и при ошибке только логирует. Данные могут молча пропасть. +3. **`/health` зелёный при упавшем pipeline** — верно. `main.go` ошибки + bridge/consumer только логирует; горутина завершается, `healthHandler` всегда + отдаёт `ok`. +4. **Нет Go-тестов и CI** — верно. `*_test.go` отсутствует, `.github/` — правила. +5. **`AUTH_TEST_MODE` и пароли plaintext** — верно и задокументировано + (`password_hash_algorithm=plain`, `AUTH_TEST_MODE=true` для DevOps, §38). +6. **Дрейф доков** — верно: гайд `v0.1.2` vs Makefile `v0.1.13`; loadtest README + 30с vs код 120с. Анализ A зафиксировал точнее. + +### Где анализ B точнее/дополняет +- Явно оговорил, что сборку/тесты не запускал (ограничение правил — SSH). +- Глубже разобрал открытые `/internal/mqtt/*`, TLS `verify_none`, захардкоженный + `cookie`, невычищенное легаси и k8s-зависимости в `go.mod`. + +### Где я бы поправил обоих +- **Проценты условны.** Оба дают один вывод «MVP/прототип, не production-ready»; + цифры — риторика, а не измерение. +- **Приоритет блокеров:** главные технические — потеря сообщений и tenant-граница. + Plaintext-пароли и unauthed `/internal/mqtt/*` — важные, но второго эшелона + (периметр платформы + EMQX уже на PG-auth напрямую снижают остроту). + +### Итог +**Рабочий MVP, пригодный для демо/пилота, но не production-ready.** Главные +блокеры: слабая tenant-изоляция, гарантированная потеря сообщений при сбое +flush, «слепой» health, отсутствие Go-тестов/CI и не закрытая платформенная +проблема wss ~150с / MTU. Моя оценка — между A и B, ближе к +«функционально готов, эксплуатационно — нет». diff --git a/doc/thinking/remediation-plan-2026-10-05.md b/doc/thinking/remediation-plan-2026-10-05.md new file mode 100644 index 0000000..7c71a95 --- /dev/null +++ b/doc/thinking/remediation-plan-2026-10-05.md @@ -0,0 +1,154 @@ +# План доработок 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 (гигиена).