Skip to main content

01 — Онбординг и жизненный цикл клиента

Как клиент попадает в систему, кто хранит приватный ключ, чем отличаются управляемые и личные устройства, и в каком состоянии клиент живёт в базе.

Одноразовая ссылка — что в ней на самом деле

Главное правило: в URL лежит не конфиг, а opaque-токен. Конфиг резолвит control plane по этому токену. Если положить конфиг прямо в ссылку — он утечёт в историю браузера, логи прокси, мессенджеры и т.д.

Что нужно заложить в механику токена:

  • TTL — короткое время жизни (минуты–часы). Протух — показываем понятную страницу, не 500-ку.
  • Строгий single-use — пометка «использован» ставится атомарно (compare-and-set в БД), иначе при двойном клике или гонке выдашь конфиг дважды.
  • Идемпотентность доставки — реальный сценарий: клиент дёрнул ссылку, MDM-пуш ушёл, но сеть моргнула до подтверждения. Повторный клик в пределах TTL должен до-доставить тот же конфиг, а не выпустить новый ключ. После TTL — только re-issue.
  • Привязка к платформе — токен знает, для какого канала доставки он выпущен (см. ниже), чтобы не гадать по User-Agent.
  • Re-issue (перевыпуск) — на случай потери устройства: новый ключ + удаление старого пира со всех серверов, где он мог осесть. Это требует, чтобы в БД хранился список инсталляций пира (см. машину состояний), иначе останется «сиротский» пир.

Кто генерирует приватный ключ

Это и есть суть модели безопасности, а не деталь.

  • Серверная генерация (control plane создаёт пару и кладёт приватник в конфиг): неизбежна для MDM-доставки на Apple и для подписки sing-box — там приватник по определению рождается на твоей стороне. Значит приватник какое-то время живёт в канале доставки → защищаем канал (TLS, пер-девайс токен, single-use, ротация) и нигде не логируем ключ.
  • Клиентская генерация (устройство само делает пару, наверх отдаёт только pubkey): самый чистый вариант — в транзите нет секрета вообще, доставляемый конфиг это публичная информация. Но требует агента/кооперирующего приложения на устройстве. Реально доступно на Linux/Windows (свой агент) и на Android (если выкатываешь своё приложение). На штатных WG-app и в sing-box клиентской генерации с отдачей одного pubkey нет.

Вывод на пальцах: где можешь поставить свой агент — генерируй ключ на клиенте и проблему защиты конфига растворяешь. Где не можешь (iOS, sing-box) — приватник едет, защищай транспорт.

Определяй канал доставки, а не «тип устройства»

User-Agent ненадёжен, и важен не тип, а как именно доставить:

ПлатформаКанал доставки конфига
Apple (iOS/macOS).mobileconfig (VPN payload) через MDM, либо подписка sing-box
Androidmanaged config (Android Enterprise) через MDM, либо подписка sing-box
Windows.conf + скрипт (/installtunnelservice) через MDM/агент, либо подписка
Linux.conf + wg syncconf через свой агент

Один «готовый конфиг» на выходе — это четыре разных механизма. Канал лучше фиксировать в токене, а не вычислять на лету.

Managed vs unmanaged — развилка под одним URL

MDM может доставить тихо только на заэнролленное устройство. Отсюда два потока под одной ссылкой:

  • Устройство уже в MDM → ссылка просто триггерит (ре-)пуш через MDM API. Тихо.
  • Устройство не в MDM (BYOD) → тихого пути нет; ссылка ведёт на энроллмент или на ручную установку профиля/импорт конфига. Не тихо.

Поэтому одноразовая ссылка и MDM-пуш не заменяют друг друга — они закрывают две разные популяции устройств. Если весь парк managed, ссылка по сути схлопывается в триггер энроллмента.

Модель адресации B (выбрана)

Клиент получает разный tunnel-IP в каждом регионе (пул на регион). Каждый региональный сервер владеет своим агрегатом — BGP чистый, /32 никуда не мигрируют.

Следствие для миграции: при смене региона у клиента меняется не только endpoint, а минимум:

  • Address — новый IP из пула нового региона;
  • Endpoint — новый сервер;
  • Peer PublicKeyпубличный ключ сервера в каждом регионе свой (это часто забывают);
  • возможно AllowedIPs — если маршрутизируемые ресурсы регион-зависимы.

Это полноценный перенос пира, см. файл 03. (Альтернатива — модель A, фиксированный глобальный IP клиента, мигрирующий по BGP /32 — отвергнута из-за риска двойного анонса/split-brain маршрута.)

Машина состояний клиента

Минимальный жизненный цикл, который надо вести в БД:

issued ──(клик по ссылке, доставка)──▶ delivered ──(handshake виден)──▶ active

┌──────────────(миграция)─────────┘

migrating ──(handshake на новом сервере)──▶ active
active/любое ──(re-issue / потеря / отзыв)──▶ revoked

Что хранить на клиента (помимо состояния):

  • client_id, telegram_id (или иной principal);
  • current_region, assigned_ip (пул региона);
  • wg_pubkey клиента (приватник на сервере не храним дольше доставки);
  • peer_installations — список серверов, где сейчас стоит пир этого клиента. Без него миграция и отзыв рано или поздно оставят сиротские пиры.

Грабли

  • Не клади конфиг в URL — только токен.
  • Single-use должен быть атомарным, иначе гонка выдаст ключ дважды.
  • На миграции меняется и серверный pubkey, а не только endpoint.
  • Веди peer_installations, иначе отзыв неполный.