Deploy contracts-flask / validate (push) Canceled after 0s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
158 lines
12 KiB
Markdown
158 lines
12 KiB
Markdown
# Инцидент: ошибки загрузки файлов через 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 Контрактора и обработка документов ещё не проверены.
|