Fix Contracts upload buffer endpoint
Deploy contracts-flask / validate (push) Canceled after 0s

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Repinoid
2026-10-30 12:08:55 +03:00
co-authored by Copilot
parent 961d60a79e
commit bb5dd6538e
11 changed files with 250 additions and 24 deletions
+157
View File
@@ -0,0 +1,157 @@
# Инцидент: ошибки загрузки файлов через 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 Контрактора и обработка документов ещё не проверены.