Corp/docs/ops/hardening-2026-09-10.md
Игорь Надеин 1f64bb9dee
All checks were successful
Deploy production / deploy (push) Successful in 12m37s
Pin verified pip bootstrap and resume truncated package downloads
2026-09-10 17:37:24 +03:00

11 KiB
Raw Permalink Blame History

CORP: исправления по диагностике 10 сентября 2026

Состав изменений

  • Исправлены блокировки PostgreSQL для подтверждения выплат, переноса зарплаты, создания оборудования и автоматизаций задач. Проверки используют PostgreSQL, а не только SQLite. Рабочие финансовые записи вручную не изменяются.
  • В requirements закреплён WebSocket-транспорт wsproto. Деплой проверяет зависимости, настоящий WebSocket upgrade/обмен кадром и отказ анонимному подключению до переключения трафика.
  • Исправлены структурированные журналы: маскирование OAuth/WS-параметров, сохранение начала и конца длинной трассировки, доверенные прокси для IP. Nginx access log не записывает query string и Referer.
  • Уточнены OpenAPI-контракты; конфигурация push_notifications согласована с AutoField в поставляемых миграциях без изменения таблиц.
  • Исторические миграции django_fsm проверены с поддерживаемым django-fsm-2.
  • Для коротких аббревиатур, отбрасываемых PostgreSQL как стоп-слова, добавлен ограниченный поиск по целому токену заголовка с прежними проверками доступа.
  • Файлы документов выдаются через API с проверкой ACL; приватные загрузки не предоставляют прямой Filer URL. Frontend передаёт авторизацию при загрузке.
  • Вложения заявлений и чатов переведены на проверяемую выдачу. Для временных вложений чата добавляется nullable-владелец: новые загрузки можно присоединять только от своего имени; старые незавершённые загрузки без владельца нужно загрузить повторно. Готовые сообщения и их файлы сохраняются.
  • Пересылка сообщения проверяет доступ к исходному чату, а изменение сообщения — право использовать каждое присоединяемое вложение. Списки сообщений фильтруются по действующим правам до пагинации, включая отозванное членство.
  • Очистка временных файлов повторно проверяет отсутствие привязки под блокировкой строки: параллельное присоединение файла к сообщению не приводит к его удалению. Удаление файла и миниатюры происходит только после фиксации транзакции.

Системная конфигурация

Внешний адрес остаётся https://corp.robotail.pro. Сертификат и его обновление обслуживает Synology. Внутренний маршрут: Synology 172.11.1.254 → HTTP 172.11.0.11:80. Приложение на этом порту доступно только NAS и loopback; устаревший TLS listener CORP отключён. Внутренний участок не зашифрован: его безопасность зависит от доверенной сети и сетевых ограничений.

ops/security/install-corp-proxy-security.py проверяет ожидаемую структуру Nginx, создаёт резервные копии и проверяет конфигурацию перед reload. Флаг --protect-media применяется после релиза frontend/backend с защищённой выдачей файлов. До этого блокировка raw URL сломала бы старые клиенты.

Redis: AOF everysec, лимит 256 MiB, политика noeviction. При достижении лимита новые записи могут быть отклонены: это защита памяти, не гарантия доставки очередей. Перед применением проверяются использование памяти, дисковый запас и готовность AOF; ошибки вызывают попытку полного отката.

На CORP установлен ImageMagick из репозитория Ubuntu: он требуется уже существующему ограниченному преобразованию больших изображений. Его отсутствие теперь обнаруживается до сборки релиза. Обновление остальных системных пакетов и автоматическое удаление пакетов не выполнялись.

Journald ограничен 512 MiB/14 днями и резервом 2 GiB. Старые имена Celery log-файлов добавлены в logrotate. Эти ограничения дополняют, а не заменяют, дисковые проверки сборки, очистку неудачных релизов и хранение двух успешных.

Runner и деплой

Runner остаётся глобальным по решению владельца. Контейнер работает как непривилегированный UID 10001, без Docker socket/capabilities, с read-only корневой ФС и ограниченными временными каталогами. SSH-доступ к CORP ограничен forced command и актуальным SHA master; сервер Gitea проверяется по закреплённому SSH host key. URL источника и Git hooks нельзя незаметно подменить конфигом.

UID 10001 имеет настоящую запись пользователя runner в производном образе; перед заменой контейнера отдельно проверяется OpenSSH без сети и ключей. Для frontend требуется Node.js 22.13+ (22.x) или 24.x LTS, проверка выполняется до создания кандидата. npm ci использует строгую проверку engines и повторяет только сетевые сбои: максимум три попытки под общим таймаутом и контролем диска. Ошибки зависимостей и postinstall не маскируются повторными попытками. В новой Python-venv закреплён pip 26.2.1 с проверкой SHA-256 bootstrap-wheel и ограниченной докачкой оборванных загрузок. Несовпадение хешей по-прежнему останавливает установку; активная venv и системный pip не обновляются.

Остановка Gunicorn использует SIGTERM и достаточный таймаут мягкого завершения. Код 143 Next.js при штатной остановке не считается аварией. Telegram-транспорт и его fallback-механизмы сохранены; ссылка использует номер запуска Gitea.

Граница доверия: глобальный deploy:host не изолирует авторов репозиториев друг от друга. Все имеющие право менять workflow должны быть доверенными. Для недоверенных проектов нужны отдельные одноразовые среды исполнения. Перед обновлением контейнера следует приостановить выдачу заданий и дождаться простоя; одного снимка списка процессов недостаточно.

Ротация ключа Django

Префикс django-insecure- вызывает предупреждение, но сам по себе не доказывает утечку или слабую случайность. ops/security/rotate-django-key.py предназначен для плановой ротации после выкладки поддержки SECRET_KEY_FALLBACKS и отдельного JWT_SIGNING_KEY. Старые JWT и ключ шифрования интеграций сохраняются, чтобы не оборвать сессии и не потерять доступ к сохранённым учётным данным.

Новый ключ начинает действовать при следующем штатном деплое. Старый fallback удаляется после максимального срока старых сессий/password-reset подписей; безопасность при подтверждённой компрометации требует отдельного немедленного отзыва, а не сохранения старых подписей.

Что требует отдельной инфраструктурной проверки

  • Зависание jbd2 более 122 секунд и watchdog journald утром 10 сентября: нужен журнал Hyper-V и хранилища. Текущая нормальная нагрузка не объясняет исторический сбой и не доказывает его устранение.
  • Ранние рестарты Gitea совпадали с недоступностью PostgreSQL при загрузке; сама Gitea позднее работала стабильно. Необходима проверка порядка готовности ВМ/БД на гипервизоре.
  • Проверка каталогов и чтения архивов pg_dump не равна полноценной репетиции восстановления. Резервирование приложения/медиа и восстановление всей системы должны быть проверены отдельно в изолированной среде.

Это устранение найденных дефектов и усиление защиты, не доказательство отсутствия всех возможных уязвимостей во всём приложении и инфраструктуре.