Пентест (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>
6.2 KiB
Сужение раскрытия 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 по умолчанию.
Решение:
app/config.py:docs_enabled: bool = FalseвSettings(управляетсяDOCS_ENABLEDв.env).app/main.py: при созданииFastAPI(...)—docs_url,redoc_url,openapi_url=None, еслиnot settings.docs_enabled. Тогда маршрутов просто нет (404), а не «спрятаны».DOCS_PATHSв middleware оставить: приdocs_enabled=trueзаголовки на них по-прежнему не навешиваются..env.example: строка# DOCS_ENABLED=true — открыть Swagger/OpenAPI (по умолчанию выключено).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 — делается только по решению пользователя (см. ниже).
Решение (без смены умолчаний):
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).- Готовый reverse-proxy как отдельный опциональный compose-профиль
tls(docker-compose.yml): сервисcaddy(caddy:2-alpine) сCaddyfile, проксирующий наapp:8000, том для сертификатов. Профиль не поднимается по умолчанию (profiles: ["tls"]), поэтому текущий стенд не меняется. Caddyfileв корне (шаблон): домен из переменной,reverse_proxy app:8000, автоматический TLS. Комментарий, что при внутреннем домене нуженtls internalили свой сертификат..env.example:# APP_BIND=127.0.0.1и# TRUSTED_PROXIES=<сеть Docker прокси>— с пояснением связки.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) — организационная, отдельной доработкой не оформляется.
Проверка (выполняет ревьюер)
venv/bin/python -c 'import app.main'; пересборка стенда,apphealthy./docs,/openapi.json→ 404 при выключенном флаге, 200 приDOCS_ENABLED=true.- В логе старта — предупреждение о прямой HTTP-экспозиции при текущих
APP_BIND/TRUSTED_PROXIES. docker compose config --profile tlsвалиден; профильtlsне поднимается без флага.pytest -q— регрессия зелёная (эндпоинты не менялись).