Files
ayurishchevandClaude Opus 5.5 744a025960 Задачи 032-033: ролевая модель с привязкой к организации, исправления по ревью
Пентест (docs/reviews/2026-09-27-pentest.md) и план 031 (Swagger, TLS) — план, не реализован.
032 Роль superadmin (без организации) и привязка admin/viewer к одной организации:
    users.organization_id + CHECK, audit_log.organization_id (миграции 0010-0012);
    require_org/scope_org во всех чтениях и записях, журнал и «Обзор» в границах
    организации; пользователи, организации, типы устройств, настройки журнала — только superadmin.
033 Исправление находок ревью 032 (docs/reviews/2026-09-27-changes-032-review.md,
    docs/reviews/2026-09-27-codebase-review.md):
    - FK audit_log.organization_id ON DELETE SET NULL (миграция 0013) — удаление организаций;
    - проверка организации в предпросмотре подсети;
    - инвариант «роль — организация» по итоговому состоянию (повышение снимает организацию,
      понижение требует её), 422/404 вместо обезличенных 409;
    - одинаковый 404 для чужих и несуществующих объектов (VRF, устройство, parent_id, оператор);
    - отказы удаления в журнале организации, счётчики типов в пределах организации;
    - UI: живое поле «Организация» в диалоге пользователя, бейдж superadmin; род в текстах 404.
README актуализирован под ролевую модель.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:26:57 +03:00

6.2 KiB
Raw Permalink Blame History

Сужение раскрытия API и HTTP-экспозиции (изменение 031)

Источник: docs/reviews/2026-09-27-pentest.md, находки № 1 (открытый Swagger) и № 2 (HTTP на 0.0.0.0 без TLS). Обе — про периметр: приложение по чёрному ящику устойчиво, но раскрывает лишнее. Ниже — только код и конфигурация.

Часть 1 — Swagger и OpenAPI по умолчанию выключены (находка № 1)

Проблема: /docs, /redoc, /openapi.json открыты анонимно (проверено на стенде: карта из 28 эндпоинтов без авторизации). SecurityHeadersMiddleware уже знает о DOCS_PATHS, но сами маршруты создаёт FastAPI по умолчанию.

Решение:

  1. app/config.py: docs_enabled: bool = False в Settings (управляется DOCS_ENABLED в .env).
  2. app/main.py: при создании FastAPI(...) — docs_url, redoc_url, openapi_url = None, если not settings.docs_enabled. Тогда маршрутов просто нет (404), а не «спрятаны». DOCS_PATHS в middleware оставить: при docs_enabled=true заголовки на них по-прежнему не навешиваются.
  3. .env.example: строка # DOCS_ENABLED=true — открыть Swagger/OpenAPI (по умолчанию выключено).
  4. README.md: в «Быстром старте» отметить, что Swagger включается DOCS_ENABLED=true; для разработки его можно держать включённым.

Проверка: при пустом .env /docs и /openapi.json → 404; при DOCS_ENABLED=true → 200. UI и API не затронуты.

Часть 2 — предупреждение о небезопасной экспозиции + готовый TLS-профиль (находка № 2)

Проблема: приложение слушает 0.0.0.0:8088 по HTTP, токены и пароли идут по LAN открытым текстом. Рекомендация из изменения 022 (reverse-proxy + APP_BIND=127.0.0.1 + TRUSTED_PROXIES) документирована, но на стенде не задействована. Смена APP_BIND по умолчанию отключит текущий доступ к стенду 192.168.5.9:8088 — делается только по решению пользователя (см. ниже).

Решение (без смены умолчаний):

  1. app/main.py рядом с validate_secrets(): функция-предупреждение при старте (logging.warning, не отказ), если одновременно APP_BIND не 127.0.0.1 и TRUSTED_PROXIES пуст — «приложение принимает прямые HTTP-соединения из сети без TLS; поставьте reverse-proxy и задайте APP_BIND=127.0.0.1 (README «Публикация»)». APP_BIND читать через Settings (добавить поле app_bind: str = "0.0.0.0", только для этой проверки; фактическую привязку по-прежнему делает compose).
  2. Готовый reverse-proxy как отдельный опциональный compose-профиль tls (docker-compose.yml): сервис caddy (caddy:2-alpine) с Caddyfile, проксирующий на app:8000, том для сертификатов. Профиль не поднимается по умолчанию (profiles: ["tls"]), поэтому текущий стенд не меняется.
  3. Caddyfile в корне (шаблон): домен из переменной, reverse_proxy app:8000, автоматический TLS. Комментарий, что при внутреннем домене нужен tls internal или свой сертификат.
  4. .env.example: # APP_BIND=127.0.0.1 и # TRUSTED_PROXIES=<сеть Docker прокси> — с пояснением связки.
  5. README.md, «Публикация»: заменить текстовый пример на запуск профиля docker compose --profile tls up -d и настройку Caddyfile.

Требует решения пользователя (в план внесено, но не выполняется без ответа):

  • Менять ли APP_BIND по умолчанию на 127.0.0.1. Плюс — приложение перестаёт напрямую торчать в LAN; минус — текущий доступ к стенду по 192.168.5.9:8088 пропадёт до подъёма прокси. По умолчанию план оставляет 0.0.0.0 и только предупреждает в логе.

Файлы

app/config.py, app/main.py, docker-compose.yml, Caddyfile (новый), .env.example, README.md, docs/changes/031-expose-hardening/SUMMARY.md.

Вне объёма

  • Аутентификация на Swagger вместо выключения (сложнее: /openapi.json нужен UI-докам; при docs_enabled=false проблема снята полностью).
  • Реальные сертификаты и DNS — эксплуатационная настройка, не код.
  • Находка № 3 пентеста (дефолтный логин admin) — организационная, отдельной доработкой не оформляется.

Проверка (выполняет ревьюер)

  1. venv/bin/python -c 'import app.main'; пересборка стенда, app healthy.
  2. /docs, /openapi.json → 404 при выключенном флаге, 200 при DOCS_ENABLED=true.
  3. В логе старта — предупреждение о прямой HTTP-экспозиции при текущих APP_BIND/TRUSTED_PROXIES.
  4. docker compose config --profile tls валиден; профиль tls не поднимается без флага.
  5. pytest -q — регрессия зелёная (эндпоинты не менялись).