14 KiB
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 <token> при 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переведены на общий helpergetInstanceStateWithRetryс единым retry/HTTP-поведением.
- debug-лог
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.
- обновлён marker генерации на
TOOLS/resource-generator/internal/templates/subresource.go:- обновлён marker генерации на
Code generated by TOOLS/resource-generator.
- обновлён marker генерации на
TOOLS/resource-generator/internal/templates/action.go:- обновлён marker генерации на
Code generated by TOOLS/resource-generator.
- обновлён marker генерации на
provider/internal/resources_gen/registry.go:- обновлён marker генерации на
Code generated by TOOLS/resource-generator.
- обновлён marker генерации на
scripts/s3_notification_example.sh:- убрана передача секрета в аргументах процесса;
- для
mcиспользуется временный--config-dirи переменнаяMC_HOST_<alias>.
provider/internal/core/client.go:- debug log path переведён на
os.TempDir(); - добавлен override директории через
NUBES_DEBUG_DIR.
- debug log path переведён на