# Инцидент: ошибки загрузки файлов через VM-буфер — 2026-10-30 ## Краткий вывод Контрактор отправлял выбранные файлы на `https://contracts.kube5s.ru/contracts-upload/`. На момент проверки 2026-10-30 имя `contracts.kube5s.ru` возвращало `NXDOMAIN` у публичных DNS-резолверов `1.1.1.1` и `8.8.8.8`. Поэтому браузер не мог разрешить адрес назначения и показывал общую ошибку «Сетевая ошибка при отправке файла в буфер». Запрос не доходил до Nginx и WebDAV. Форматы и содержимое выбранных документов не были причиной этого сбоя. Реальные файлы из пользовательской папки не отправлялись; проверки upload-инфраструктуры ограничились DNS, чтением конфигураций и безопасными HTTP `OPTIONS`-запросами. ## Подтверждённые факты ### Настройка Контрактора - Развёрнутая страница Контрактора показывала версию `2.0.23`. - Загруженный браузером [`site/static/app.js`](../site/static/app.js) содержит `VM_UPLOAD_URL = 'https://contracts.kube5s.ru/contracts-upload/'`. - [`site/config.py`](../site/config.py) задаёт тот же домен для `VM_UPLOAD_URL` и `VM_UPLOAD_PREFIX`. Backend регистрирует `upload_refs` с этим `VM_UPLOAD_PREFIX` через [`site/routes/__init__.py`](../site/routes/__init__.py). - [`deploy/nginx-contracts.conf`](../deploy/nginx-contracts.conf) содержит WebDAV location `/contracts-upload/` с каталогом `/var/www/contracts-upload/` и CORS origin `https://contractor.pythonk8s.dev.nubes.ru`. Этот location находится в virtual host `contracts.kube5s.ru`. ### DNS и доступные буферы - Запросы к `1.1.1.1` и `8.8.8.8` для `contracts.kube5s.ru A` вернули `NXDOMAIN`. - После первоначальной диагностики владелец DNS добавил `contracts-upload.sinefo.ru A -> 5.172.178.213`. Повторная проверка обоих публичных резолверов подтвердила эту запись. - При первой проверке до настройки Nginx новый hostname отдавал чужой TLS- сертификат. После установки HTTP challenge vhost сертификат Let's Encrypt выпущен для `contracts-upload.sinefo.ru`; срок действия до 2027-01-28. - Nginx virtual host для нового hostname теперь установлен на VM213, а `nginx -t` и reload прошли успешно. - `upload.sinefo.ru` разрешался в `5.172.178.213`, но `https://upload.sinefo.ru/contracts-upload/...` отвечал `404`. - На `upload.sinefo.ru/drhider-upload/` WebDAV-маршрут существует, но `Access-Control-Allow-Origin` возвращается только для DrHider origin `https://drhider.pythonk8s.dev.nubes.ru`, не для Контрактора. - На `obfuscator-upload.sinefo.ru/obfuscator-upload/` маршрут существует, но `Access-Control-Allow-Origin` возвращается только для Obfuscator origin `https://obfuscator.pythonk8s.services.ngcloud.ru`, не для Контрактора. - Запросы `OPTIONS` для origin Контрактора к обоим существующим маршрутам возвращали `204`, но без `Access-Control-Allow-Origin`. Такой ответ не разрешает браузерный запрос; кроме того, путь `/contracts-upload/` на этих хостах не настроен. - На VM213 (`5.172.178.213`) при проверке существовали каталог `/var/www/contracts-upload/` и старый Nginx location `/contracts-upload/` внутри virtual host `contracts.kube5s.ru`. Наличие каталога само по себе не делает его доступным по другому hostname. ## Почему прямое исправление URL недостаточно Замена только домена в Контракторе не решит проблему: 1. `/contracts-upload/` на `upload.sinefo.ru` сейчас не настроен и отвечает `404`. 2. Существующий `/drhider-upload/` предназначен для DrHider и не разрешает origin Контрактора в CORS. 3. Буфер Obfuscator имеет отдельные hostname и путь, отдельную конфигурацию и allowlist origin. Он не является готовым Contracts endpoint. 4. URL на фронтенде и доверенный префикс backend должны совпадать. Иначе браузер может загрузить объект, но `upload_refs` отклонит URL при SSRF-проверке либо backend будет пытаться забрать объект с другого адреса. 5. Backend `VM_UPLOAD_PREFIX` может быть переопределён переменной окружения и должен совпадать с URL буфера. URL браузерной загрузки задаётся константой `VM_UPLOAD_URL` в `site/static/app.js`; переменная окружения с таким именем сама по себе статический JavaScript не меняет. ## Отличие от Obfuscator Актуальный Obfuscator использует согласованную отдельную конфигурацию: - hostname `obfuscator-upload.sinefo.ru`; - путь `/obfuscator-upload/`; - отдельный каталог `/var/www/obfuscator-upload/`; - CORS allowlist для `https://obfuscator.pythonk8s.services.ngcloud.ru`; - один и тот же URL-префикс на фронтенде и backend. Это описано в [`OBFUSCATOR/README.md`](../../../drhider/OBFUSCATOR/README.md) и настроено в [`obfuscator-upload.conf`](../../../drhider/OBFUSCATOR/deploy/vm213/nginx/obfuscator-upload.conf). Контрактор должен получить аналогично согласованный endpoint, а не использовать Obfuscator/DrHider путь без изменения и проверки его конфигурации. ## План исправления Первоначальный план обновлён после предоставления DNS-записи `contracts-upload.sinefo.ru`. Код и source Nginx изменены локально; VM213 и развёрнутое приложение ещё не обновлялись. 1. **Hostname и DNS — выполнено.** Выбран `contracts-upload.sinefo.ru`; публичные DNS `1.1.1.1` и `8.8.8.8` возвращают `5.172.178.213`. 2. **Настроить VM213 — выполнено.** Проверен отдельный каталог `/var/www/contracts-upload/` и права пользователя Nginx. Для первоначального выпуска сертификата установить [`deploy/nginx-contracts-upload-acme.conf`](../deploy/nginx-contracts-upload-acme.conf) как `/etc/nginx/sites-enabled/contracts-upload.conf`, выполнить `nginx -t` и только при успехе reload. Первая попытка Certbot завершилась временным CAA DNS timeout; повторная попытка выпустила сертификат. После этого временный конфиг заменён полным [`deploy/nginx-contracts-upload.conf`](../deploy/nginx-contracts-upload.conf), снова выполнить `nginx -t` и reload. Полный vhost задаёт отдельный WebDAV путь, `PUT`/`GET`/`DELETE`, `OPTIONS`, ограничение тела 50 MiB и CORS allowlist через `map` для точного origin `https://contractor.pythonk8s.dev.nubes.ru`. Предел `50m` соответствует backend-лимиту 50 MiB; не копировать 1024 MiB из Obfuscator, поскольку Contracts не принимает файлы такого размера. Установить [`deploy/contracts-upload-cleanup.cron`](../deploy/contracts-upload-cleanup.cron) как `/etc/cron.d/contracts-upload-cleanup`, чтобы удалять оставшиеся объекты старше 30 минут. Cron установлен в `/etc/cron.d/contracts-upload-cleanup`. Не менять маршруты DrHider или Obfuscator и не объединять их каталоги с Contracts. 3. **Код приложения — выполнено локально, deploy ожидает выполнения.** Frontend `VM_UPLOAD_URL` и backend `VM_UPLOAD_PREFIX` default приведены к `https://contracts-upload.sinefo.ru/contracts-upload/`; обновлены frontend/backend defaults, пример конфига и default URL нагрузочного теста. Версия приложения поднята до `2.0.24`. Проверить переменные окружения развёртывания: `VM_UPLOAD_PREFIX` не должен переопределять новый endpoint старым значением. 4. **Синхронизировать конфигурацию VM — выполнено.** Источник Nginx вынесен в отдельные bootstrap- и TLS-конфиги; старый upload location удалён из [`deploy/nginx-contracts.conf`](../deploy/nginx-contracts.conf). Конфигурация TLS-vhost и cron установлена на VM213; Nginx config test/reload успешны. Деплой приложения Контрактора — отдельная операция, ещё не выполнена. 5. **Проверить endpoint до обработки документов — выполнено.** - DNS разрешается в `5.172.178.213`; TLS сертификат валиден. - `OPTIONS` с origin Контрактора отвечает `204` и содержит точный `Access-Control-Allow-Origin`, разрешённые методы и заголовки. С посторонним origin `Access-Control-Allow-Origin` не отправляется. - Синтетический объект прошёл `PUT` (`201`) → `GET` (байты совпали) → `DELETE` (`204`). Объект удалён. 6. **Проверить путь через приложение безопасно.** В тестовой сессии/батче загрузить синтетический небольшой файл, убедиться в успешном `POST /api/upload_refs` и очистить созданную тестовую запись/объект. Не использовать пользовательские документы для проверки, пока синтетический тест не пройден. 7. **Только после успешной проверки** проверить обычную загрузку и последующие этапы обработки выбранных документов. ## Статус - Причина исходной сетевой ошибки подтверждена: используемый hostname `contracts.kube5s.ru` вернул `NXDOMAIN`. - DNS, TLS, Nginx vhost, CORS и прямой синтетический WebDAV цикл проверены успешно. - Причина установлена до этапов HTTP/WebDAV, CORS и обработки документов. - URL/defaults, версия приложения и source Nginx обновлены локально. - Конфигурация буфера и cron уже установлены на VM213. - Изменения приложения ещё не запушены и не redeployed; end-to-end загрузка через UI Контрактора и обработка документов ещё не проверены.