Перейти к основному содержимому

Четыре имени вместо одного

Раньше вся система жила на одном имени: панель в корне, документация в подкаталоге /docs/, обновления в /assets/, API в /api/. Теперь имён четыре, и это не наведение красоты — разделение решает конкретную задачу безопасности.

Кто есть кто

ИмяЧто раздаётКто туда ходит
panel.1.router.thepragmaticdev.supportпанель оператора и её /api/оператор из браузера
api.1.router.thepragmaticdev.supportтолько /device/*, всё остальное — 404постаматы
ota.1.router.thepragmaticdev.supportманифест обновлений и ассетыпостаматы
docs.1.router.thepragmaticdev.supportэту документациюлюди

Все четыре имени указывают на один и тот же адрес — разводит их nginx по имени, которое пришло в запросе. Сертификат один на все четыре, автопродление включено.

Конфигурация — infra/machine-b/postamat.nginx.conf, сами имена — infra/config.env (DOMAIN_PANEL, DOMAIN_API, DOMAIN_OTA, DOMAIN_DOCS). Правятся они там, а не на машине: следующая выкладка перезапишет файл.

Зачем разделили: постамат больше не видит панель

В проде постамат выходит в сеть по Wi-Fi, и на роутере объекта стоит фильтр — наружу разрешено ходить только до нашего backend. Фильтр работает по именам. Пока имя было одно, запись в фильтре «разрешить наш backend» означала «разрешить весь хост целиком»: вместе с эндпоинтами устройства терминал получал и страницу входа в панель оператора. Разделить их было нечем — разделять было нечего.

Теперь в фильтр объекта заносятся только api и ota. Панель оператора постамату недоступна вовсе — не «закрыта паролем», а не существует с его точки зрения.

Почему это стоило отдельной работы

Постамат стоит на улице. Залочка на нём не сделана намеренно, корпус вскрывается, планшет уносится. Считать его доверенным устройством нельзя, а значит всё, до чего он дотягивается, надо считать доступным тому, кто до него добрался.

Отдельное имя убирает панель из этого списка целиком, и убирает на роутере, до всякой проверки пароля. Это дешевле и надёжнее любой настройки на нашей стороне.

Имя api устроено так же строго и со своей стороны: там разрешён единственный префикс /device/, а всё остальное отдаёт 404 — ни панели, ни документации, ни ассетов с этого имени не получить в принципе.

CORS при этом не появился

Обычная плата за разделение хостов — запросы между источниками, преднастройка OPTIONS и возня с cookie. Здесь её нет: панель держит свой /api/ на своём же имени, nginx проксирует его на ту же машину с сервером. Для браузера оператора это один источник, как и раньше:

  • CORS не нужен вообще;
  • cookie сессии остаётся httpOnly; SameSite=Strict; Secure;
  • адрес API в панели задаётся при сборке и по умолчанию равен /api — относительному пути, а не полному имени.

То есть постамат и панель ходят к одному и тому же серверу, но по разным именам и разными маршрутами, и это единственное, что между ними общее.

Обращение по голому адресу рвётся намеренно

На неизвестное имя и на обращение по IP nginx отвечает разрывом соединения (return 444), не показывая ничего. Без этого блока такой запрос попал бы в первый попавшийся раздел конфигурации, то есть показал бы панель оператора тому, кто её не искал.

⚠️ Практическое следствие: «по IP не открывается» — это правильное поведение, а не поломка развёртывания. Проверять надо по имени.

Что из этого следует для кода

  • Киоск не должен знать имени панели. Его адреса — api и ota, и только они попадают в фильтр на роутере. Ссылка на панель, случайно попавшая в код киоска, на объекте просто не откроется, а выглядеть будет как сетевая ошибка.
  • Обновления живут на ota целиком — и манифест, и ассеты. Так их однажды можно увести на CDN, не трогая ни панель, ни API. Подробнее — что обновляется по воздуху.
  • Документация живёт в корне своего имени. Подкаталога /docs/ больше нет, baseUrl в docusaurus.config.ts равен /. Возврат к /docs/ сломает сайт тихо: страницы будут открываться, но без оформления. Разбор — развёртывание.
  • Новое имя — это работа в трёх местах сразу: infra/config.env, блок server в конфигурации nginx и перевыпуск сертификата на все имена разом. По отдельности любая из трёх правок даёт нерабочее или незащищённое имя.