Перейти к содержанию

Ротация путей Core → Entry

·3 минуты·
Участок Entry → Core — наиболее уязвимый с точки зрения обнаружения: это трансграничный канал, проходящий через периметр с повышенным вниманием DPI. Core-ноды периодически меняют gRPC-пути на этом участке. Клиентские подключения при этом не затрагиваются.

Проблема #

VLESS поверх gRPC практически неотличим от легитимного HTTPS: TLS 1.3 с валидным сертификатом, HTTP/2, браузерный TLS-фингерпринт. Распознать такой трафик по сигнатурам крайне сложно.

Однако даже полностью зашифрованный трафик оставляет метаданные. Постоянное соединение по одному и тому же gRPC-пути между двумя хостами — это паттерн. Для реальных gRPC-микросервисов подобная картина нетипична: сервисы обычно обращаются к множеству разных эндпоинтов в разное время. Статический путь, неизменный неделями, может быть выделен статистическим анализом из общего потока.

Решение: менять serviceName автоматически, с заданной периодичностью. Со стороны это выглядит как активность различных сервисов на одном хосте — нормальная картина для любого сервера с gRPC-API.

Что ротируется #

Ротация затрагивает только внутренний канал между Entry и Core. Клиентская сторона остаётся неизменной — клиентам не нужно обновлять конфигурацию.

Клиент ──[фиксированный путь]──► Entry Nginx → Entry Xray inbound
                                                       │
                                          Entry Xray outbound
                                                       │
                                            [ротируемый путь]
                                                       │
                                                       ▼
                                          Core Nginx → Core Xray inbound
НодаКомпонентПри ротации
CoreNginxОбновляется location для gRPC-проксирования
CoreXray inboundОбновляется serviceName в grpcSettings
EntryXray outboundОбновляется serviceName → Core
EntryNginxНе меняется — клиентский путь фиксирован
EntryXray inboundНе меняется — клиентский serviceName фиксирован

Механизм ротации #

Core-нода является управляющим центром процесса. По расписанию запускается скрипт, который выполняет следующую последовательность:

  1. Генерирует новый serviceName формата api.v2.rpc.<16 hex-символов>.
  2. Создаёт резервные копии конфигов Nginx и Xray на Core.
  3. Вносит изменения, валидирует конфиги (xray -test, nginx -t). При ошибке — откат из резервной копии, Entry-ноды не трогаются.
  4. Перезагружает Nginx и Xray на Core.
  5. По SSH подключается к каждой Entry-ноде и обновляет только serviceName в outbound Xray. Nginx и inbound Xray на Entry не затрагиваются.
  6. Валидирует конфиг на Entry, перезагружает Xray. При ошибке — откат на Entry.
  7. Обновляет запись маршрута в registry: новый serviceName и дата ротации. Коммитит изменения.

Обработка ошибок #

  • Конфиги валидируются перед каждой перезагрузкой. При ошибке — откат из резервной копии, сервисы не перезагружаются.
  • Если обновление Core не удалось — Entry-ноды не обновляются. Старый путь остаётся рабочим на обоих концах.
  • Если SSH-подключение к Entry-ноде недоступно — ошибка логируется, скрипт продолжает с остальными нодами.
  • При следующем запуске недоступные Entry-ноды получат актуальный путь.

Расписание #

Скрипт запускается по systemd timer:

  • Интервал: каждый час (OnCalendar=hourly).
  • Случайное смещение: 0–30 минут (RandomizedDelaySec=1800).
  • Persistent=true — если запуск был пропущен (нода выключена), выполняется при следующем старте.

Случайное смещение исключает предсказуемость момента ротации: каждый час путь меняется в случайную минуту в пределах получаса.

Что происходит во время ротации #

При перезагрузке сервисов активные соединения кратковременно обрываются. Клиент переподключается к Entry-ноде автоматически по прежнему URI — смена пути на внутреннем участке Entry → Core для него незаметна.

Схема #

sequenceDiagram participant C as Core-нода participant E as Entry-нода participant R as Registry C->>C: Генерация нового serviceName C->>C: Обновление Nginx + Xray, перезагрузка C->>E: SSH: обновить outbound serviceName E->>E: Валидация конфига, перезагрузка Xray E-->>C: OK C->>R: Обновить core_service_name, last_rotation C->>R: Коммит

Сводная таблица #

КомпонентГде хранитсяКогда меняется
serviceName Entry → CoreRegistry, Nginx/Xray Core, Xray outbound EntryПо расписанию (каждый час ± 30 мин)
serviceName Client → EntryRegistry, Nginx/Xray EntryНезависимая ротация
Клиентский URIТолько на устройствеНе меняется при ротации Core → Entry

Перспектива #

В дальнейшем SSH-обновление Entry-нод может быть заменено на управление через Xray gRPC API (hot reload без перезапуска сервисов). Это позволит выполнять ротацию без разрыва активных соединений.