# Code Review провайдера — Opus — 2026-08-31 **Источник:** анализ и код-ревью через VS Code Copilot Chat **Статус:** анализ завершён; часть исправлений внесена 2026-08-31 ## Область анализа Проверены: - рукописное ядро провайдера в `provider/internal/core` и `provider/internal/resources_core`; - CRUD, state management и валидация; - HTTP-слой и `client.go`; - регистрация провайдера и TLS-настройки; - генераторы Go-ресурсов, YAML и build-пайплайн; - Python- и shell-скрипты; - gateway. ## Критичные находки ### 1. Отладочный лог с данными инстансов пишется в `/tmp` безусловно В `provider/internal/core/client.go:629-637` замыкание `debug()` в `FindInstanceByDisplayName` всегда пишет в `/tmp/nubes_find_debug.log` с правами `0644`. В лог попадают `instanceUid`, `displayName` и `serviceId`. Файл не защищён условием `NUBES_DEBUG_HTTP`, не ротируется и не очищается. Это создаёт риск раскрытия данных и неконтролируемого роста файла. **Рекомендация:** убрать постоянную запись либо включать её только через явный debug-флаг; использовать безопасный путь и контролируемую ротацию. ### 2. Bearer-токен попадает в stderr при HTTP-отладке В `provider/internal/core/client.go:1100-1101` вызов `httputil.DumpRequestOut(req, ...)` выводит полный исходящий запрос вместе с заголовком `Authorization: Bearer ` при `NUBES_DEBUG_HTTP=1`. Токен может попасть в логи CI/CD или окружения выполнения. **Рекомендация:** перед дампом удалять или маскировать `Authorization`; не выводить секреты ни в одном режиме. ### 3. В Python-скрипте сетевые вызовы выполняются без таймаутов В `scripts/check_cloud_instances.py:87-88` вызовы `self.session.get(...)` не передают `timeout=`. При зависании API процесс может ожидать ответ бесконечно. **Рекомендация:** добавить явные таймауты ко всем HTTP-вызовам и определить единое значение или конфигурационный параметр. ## Существенные находки ### 4. Retry сетевых ошибок применяется к POST-запросам В `provider/internal/core/client.go:1113-1120` при сетевой ошибке повторяется любой HTTP-метод, включая POST к `/instances` и `/instanceOperations`. Если сервер принял запрос, но ответ потерян, повтор может создать дубликат инстанса или операции. Идемпотентность POST не гарантирована. **Рекомендация:** ограничить retry идемпотентными методами либо использовать идемпотency key и явную серверную поддержку повторов. ### 5. Ответ `401 Unauthorized` включён в retryable В `provider/internal/core/client.go:1150-1156` статус `401` считается повторяемым. Протухший или неверный токен приводит к трём попыткам с задержкой, маскируя исходную ошибку авторизации и увеличивая время отказа. **Рекомендация:** исключить `401` из retryable; возвращать ошибку авторизации сразу. ### 6. Gateway раскрывает внутренние upstream-адреса В `gateway/server.js:60-71` корневой endpoint `/` и обработчик 404 возвращают наружу адреса `upstream` для маршрутов. Публичный ответ раскрывает внутреннюю топологию сервисов. **Рекомендация:** убрать `upstream` из публичных ответов; внутренние адреса оставлять только в серверных логах с необходимой санацией. ### 7. Некорректное определение неуспешной операции в Python В `scripts/check_cloud_instances.py:187-189` используется сравнение `last_op.get("isSuccessful") == False`. При отсутствии поля возвращается `None`, поэтому состояние `OPERATION_FAILED` не определяется. **Рекомендация:** использовать проверку `is False` либо явно обрабатывать отсутствие ключа согласно контракту API. ## Умеренные находки ### 8. Retry-логика дублируется в трёх местах В `provider/internal/core/client.go:777-905` похожие циклы retry присутствуют в `doRequest`, `GetInstanceState` и `GetInstanceStateRaw`. Дублирование увеличивает риск расхождения поведения и повторного появления ошибок безопасности. **Рекомендация:** вынести общую retry-логику в единый внутренний helper с параметрами метода, таймаутов и политики повторов. ### 9. Пагинация имеет тихий предел 10 000 инстансов В fallback-ветке `FindInstanceByDisplayName` (`provider/internal/core/client.go:747-749`) поиск прекращается после `page > 100` при размере страницы `100`. При большем количестве инстансов совпадение может не быть найдено без предупреждения. **Рекомендация:** убрать произвольный предел либо возвращать диагностируемую ошибку/предупреждение при достижении лимита. ### 10. Ошибка `gofmt` не останавливает генерацию `FormatSourceOrWarn` в `TOOLS/resource-generator/writers.go:61` при ошибке форматирования только выводит предупреждение и записывает исходник. В результате pipeline может сохранить неформатированный или потенциально некомпилируемый Go-код. **Рекомендация:** считать ошибку форматирования фатальной для генерации либо выполнять последующую обязательную компиляционную проверку. ### 11. Секрет передаётся в командной строке shell-скрипта В `TOOLS/s3_notification_example.sh:74` значение `SECRET_KEY` передаётся аргументом в `mc alias set`. Секрет может быть виден через `ps` или аналогичный список процессов. **Рекомендация:** использовать механизм передачи секрета через stdin, переменную окружения, конфигурационный файл с безопасными правами или другой поддерживаемый секретный канал. ## Дополнительные замечания - В `provider/internal/core/client.go` ссылка на `tools/gen_v2/generate_resources_v2.go` обновлена на актуальный путь `TOOLS/resource-generator/internal/templates/instance.go`. - В исходниках генератора (`TOOLS/resource-generator/internal/templates/*`, `TOOLS/resource-generator/internal/writers/writers.go`) метка `Code generated by tools/gen_v2` обновлена на `Code generated by TOOLS/resource-generator`. - Текущий `provider/internal/resources_gen/registry.go` обновлён на новую метку генератора. - `TOOLS/resource-generator/main.go` переведён на `run()` с корректным `exit code=1` и агрегированным отчётом по ошибкам записи ресурсов (instance/subresource/action). - Пути debug-логов в `provider/internal/core/client.go` переведены на `os.TempDir()` с override через `NUBES_DEBUG_DIR` (без хардкода `/tmp`). ## Что выглядит хорошо - Сериализация операций на инстансе через `instanceMutexes` в `client.go` защищает от параллельных операций API. - TLS настроен с `MinVersion: TLS 1.2`; `InsecureSkipVerify` по умолчанию равен `false`. - `api_token` отмечен как `Sensitive: true` в схеме провайдера. - Канонизация JSON для сравнения state устраняет ложные различия из-за порядка ключей. ## Итоговый статус | Находка | Статус | |---|---| | Безусловная запись данных инстансов в `/tmp` | Исправлено: debug gated + права `0600` | | Bearer-токен в HTTP debug dump | Исправлено: `Authorization` маскируется | | Python HTTP-вызовы без таймаутов | Исправлено: добавлен `REQUEST_TIMEOUT` | | Retry POST-запросов | Исправлено: retry сетевых ошибок только для GET | | `401` в retryable | Исправлено: исключён из retryable | | Раскрытие upstream в gateway | Исправлено: `upstream` удалён из root-ответа | | Ошибка определения `OPERATION_FAILED` | Исправлено: сравнение через `is False` | | Дублирование retry-логики | Исправлено: общий helper для чтения состояния | | Тихий предел пагинации | Частично исправлено: добавлена явная ошибка при достижении лимита | | Некритичная ошибка `gofmt` в генераторе | Исправлено: fail-fast при ошибке форматирования | | Секрет в аргументах shell-команды | Исправлено: исключена передача в argv | ## Выполненные изменения (2026-08-31) - `provider/internal/core/client.go`: - debug-лог `FindInstanceByDisplayName` теперь пишется только при `NUBES_DEBUG_HTTP=1`; - права debug-логов снижены до `0600`; - в stderr-дампе HTTP-запроса маскируется заголовок `Authorization`; - retry сетевых ошибок ограничен методом `GET`; - `401 Unauthorized` удалён из `isRetryable`; - при достижении лимита fallback-пагинации возвращается явная ошибка. - `GetInstanceState` и `GetInstanceStateRaw` переведены на общий helper `getInstanceStateWithRetry` с единым retry/HTTP-поведением. - `scripts/check_cloud_instances.py`: - добавлен `REQUEST_TIMEOUT = 30` и применён ко всем `session.get(...)`; - проверка failed-операции изменена на `is False`. - `gateway/server.js`: - удалено поле `upstream` из публичного ответа `GET /`. - `TOOLS/resource-generator/internal/helpers/helpers.go`: - `FormatSourceOrWarn` переведён на fail-fast: возвращает ошибку при сбое `gofmt`. - `TOOLS/resource-generator/internal/writers/writers.go`: - все вызовы форматирования обрабатывают ошибку и прерывают генерацию. - `TOOLS/resource-generator/main.go`: - убраны `panic` на первом сбое записи ресурса; - добавлена агрегация ошибок генерации с отчётом по каждому ресурсу; - завершение с `exit code=1` и человекочитаемым сообщением в stderr. - `TOOLS/resource-generator/internal/templates/instance.go`: - обновлён marker генерации на `Code generated by TOOLS/resource-generator`. - `TOOLS/resource-generator/internal/templates/subresource.go`: - обновлён marker генерации на `Code generated by TOOLS/resource-generator`. - `TOOLS/resource-generator/internal/templates/action.go`: - обновлён marker генерации на `Code generated by TOOLS/resource-generator`. - `provider/internal/resources_gen/registry.go`: - обновлён marker генерации на `Code generated by TOOLS/resource-generator`. - `scripts/s3_notification_example.sh`: - убрана передача секрета в аргументах процесса; - для `mc` используется временный `--config-dir` и переменная `MC_HOST_`. - `provider/internal/core/client.go`: - debug log path переведён на `os.TempDir()`; - добавлен override директории через `NUBES_DEBUG_DIR`.