WEB: Common Profiles for multiple vhosts

This commit is contained in:
Alexey
2026-10-30 19:10:01 +03:00
parent 5035d549bd
commit e7e588ebe5
2 changed files with 347 additions and 12 deletions
+170 -6
View File
@@ -115,8 +115,170 @@ max_streams_per_session = 64
`decoy_fasttrack_mode` управляет только capability processing для `GET/HEAD` на настроенном base root. Значение `off` по умолчанию сохраняет полный прежний scan без fast-track counters. `shadow` учитывает, какие структурно невозможные запросы могли бы обойти scan, но всё равно выполняет его полностью. `enforce` обходит capability work только для `HEAD` либо отсутствующего или неканонического query `bridge`. Каждый точный канонический bridge GET на base root выполняет полный scan всех профилей выбранного vhost и при совпадении, и при промахе. Настройка требует перезапуска процесса: reload сохраняет желаемое значение, но сообщает `web.decoy_fasttrack_mode` как deferred. Fast-track не защищает от злонамеренной CPU-нагрузки, потому что scanner всегда может отправлять канонические candidates; кроме того, `enforce` может создать публично наблюдаемый timing class формы запроса, особенно при статическом decoy. Не включайте этот режим без внешних timing measurements через production TLS-терминатор.
## Общие профили для нескольких доменов
Замените селектор `host` непустым списком `hosts`, чтобы несколько доменов
использовали общие определения профилей и один снимок decoy:
```toml
[[web.vhosts]]
hosts = ["proxy.example.com", "backup.example.com"]
base_path = "telegram/web"
public_addr = "203.0.113.10:443"
[web.vhosts.decoy]
mode = "http_upstream"
upstream = "http://127.0.0.1:18081"
[[web.vhosts.profiles]]
user = "web-user"
secret_mode = "dd"
max_sessions = 8
max_streams = 512
max_streams_per_session = 64
```
Этот фрагмент дополняет пользователя доступа и WEB listener из предыдущего
примера. Укажите ровно один селектор: `host` или `hosts`. При загрузке имена
нормализуются; совпадающие после нормализации имена запрещены. Шаблоны wildcard
и `*` требуют явного включения режима, описанного ниже. У всех доменов группы
общие `base_path`, `public_addr`, профили и decoy. Если эти настройки различаются,
создайте отдельные группы. TLS-терминатор должен обслуживать каждый домен из
списка и передавать исходный канонический `Host`.
У каждого домена свои capability, bootstrap/session credentials и квота профиля.
В примере профиль допускает до восьми сессий на каждом домене с учётом общих
ограничений процесса, IP и пользователя доступа. При запуске ссылки формируются
отдельно для каждого выбранного пользователя/профиля и конкретного домена.
Группировка не перенаправляет клиентов и не позволяет переносить credentials
между доменами.
В режиме точных имён `max_vhosts` учитывает каждый домен, а `max_profiles` —
каждую пару домен/профиль. Для восьми доменов со 100 профилями нужны пределы
не менее восьми vhosts и 800 profiles. Статические файлы и байты снимка одной
группы учитываются один раз; у отдельных групп снимки отдельные. Настроенные
максимумы сессий не резервируют сессии заранее. Общая неизменяемая политика
уменьшает дублирование, но индексы capability растут с числом пар домен/профиль.
Сначала установите новую сборку с прежней конфигурацией одиночных `host`, затем
объедините одинаково настроенные записи через reload. Группировка и перестановка
доменов сохраняют совместимые bootstrap credentials, идентичность квот и данные
обучения профиля. Удаление домена запрещает новые запросы к нему, не вводя новых
правил завершения существующих соединений. Увеличение лимитов требует перезапуска
и должно вступить в силу до добавления записей сверх действующих пределов.
Перед откатом на сборку без поддержки `hosts` разверните группы в одиночные
записи `host` и повторно проверьте бюджет статических снимков.
## Wildcard-хосты
По умолчанию используются точные `host` / `hosts`. Для произвольных поддоменов
установите `web.allow_wildcard_hosts = true` и перезапустите Telemt. После этого
селекторы также принимают `*.example.com` и `*`; точные имена и шаблоны можно
смешивать в одном списке:
```toml
[web]
enabled = true
allow_wildcard_hosts = true
[web.limits]
# Additional wildcard reservations must fit the declared envelope.
memory_envelope_bytes = 1610612736
[web.limits.wildcard]
max_rules = 32
max_profiles_per_request = 256
cache_hosts = 256
cache_bytes = 8388608
cold_hmac_per_minute = 1000000
cold_hmac_burst = 65536
[[web.vhosts]]
hosts = ["proxy.example.com", "*.example.com"]
base_path = "relay"
public_addr = "203.0.113.10:443"
[web.vhosts.decoy]
mode = "http_upstream"
upstream = "http://127.0.0.1:8080"
[[web.vhosts.profiles]]
user = "web-user"
secret_mode = "dd"
```
Этот пример дополняет ранее показанные listener и пользователя доступа. Запрос
выбирает одну группу: сначала точное имя, затем самый длинный подходящий суффикс,
затем `*`. Суффикс охватывает потомков любой глубины, включая `a.b.example.com`,
но не сам `example.com`. Неуспешная аутентификация не переключает запрос на
другое правило. Встроенные wildcard и регулярные выражения запрещены, как и
дубли селекторов после нормализации между группами. Выбранная группа сохраняет
свои фиксированные `public_addr`, базовый путь, профили и decoy. Заголовок Host
не выбирает адрес upstream.
В этом режиме HTTP Host нормализуется без учёта регистра ASCII; удаляется только
необязательный буквальный порт `:443`. Повторные Host, IP-адреса вместо домена,
некорректные DNS/ACE-метки, завершающая точка и другие порты запрещены.
`X-Forwarded-Host` не используется. Capability вычисляется по фактическому
каноническому имени с существующим клиентским алгоритмом; менять клиентов не
нужно. Квоты сессий/потоков/профиля остаются привязаны к конкретной тройке
`(host, user, secret_mode)`. Ограничения процесса, IP и пользователя доступа
продолжают действовать. Ссылки при запуске выводятся только для точных имён.
Frontend **обязан ограничивать допустимые Host**, а незашифрованный WEB backend
должен оставаться в закрытой сети. Сертификаты TLS должны покрывать каждое
фактическое имя клиента. Это явная граница безопасности: capability неизвестного
или вытесненного из кэша wildcard-домена, отправленная на другой домен, может
попасть в его decoy, в том числе на точный домен смешанной конфигурации.
Capability текущего домена, конечного списка точных доменов, известного
положительного кэша и подлинные bootstrap/session tokens процесса не передаются
в decoy в URI и заголовках, включая percent-encoded формы. Публичные URL, cookies
и заголовки не отклоняются лишь за сходство с credentials. Диагностические копии,
напротив, консервативно маскируют похожие строки, включая обрезанные префиксы;
байты публичных запросов/ответов и исходные хеши User-Agent не меняются. Поэтому
диагностика может маскировать и обычные длинные строки.
Перед вычислениями холодный запрос атомарно резервирует HMAC-бюджет для всех
профилей выбранной группы. При нехватке сразу возвращается HTTP 503 с no-store,
без очереди и частичного перебора. В пределах запроса одна таблица используется
для защиты от утечки credentials и аутентификации bridge. Попадание в кэш не
требует вычислений. Аутентифицированные carriers полностью обходят кэш таблиц
capability и их вычисление. Режимы fast-track Off/Shadow/Enforce по-прежнему
управляют проверкой корня; ошибочно размещённые credentials текущего домена
удерживаются локально в любом режиме.
FIFO-кэш заполняется только после успешной выдачи bootstrap для bridge/recovery.
`cache_hosts = 0` отключает хранение. Пределы количества записей и байтов независимы;
вытесненная запись продолжает занимать байтовый бюджет, пока запрос удерживает
ссылку на неё. Неудача вставки не отменяет корректный bootstrap; вытеснение не
сбрасывает квоты. Ключ кэша содержит уникальный идентификатор неизменяемого WEB
снимка, в том числе при reload внутри одного поколения runtime. Записи не
удерживают decoy или поколение runtime.
`max_vhosts` учитывает точные имена; `max_profiles` — точные привязки и один
общий набор профилей на каждую группу с wildcard. `max_rules` ограничивает число
шаблонов, а `max_profiles_per_request` — полный перебор группы wildcard. Все
пределы, кроме `cache_hosts`, должны быть положительными; HMAC burst должен
вмещать полную таблицу запроса. Проверяемый бюджет памяти включает шаблоны/правила,
кэш, рабочую память обработчиков и живые привязки доменов только при включённом
wildcard. С остальными стандартными лимитами прежних 1280 MiB недостаточно;
1290 MiB достаточно по текущему расчёту, а пример задаёт 1536 MiB. Бюджет и
обработка режима только с точными именами не меняются.
Флаг включения и все `web.limits` требуют перезапуска. После включения селекторы
и профили можно менять через reload. API может сохранить флаг и достаточные
лимиты как отложенные изменения. Добавление шаблонов до фактического перезапуска
отклоняется атомарно, даже если флаг уже записан в желаемой конфигурации. Для
отключения сначала замените шаблоны точными именами, затем перезапустите процесс
с выключенным флагом. Перед откатом на старую сборку удалите wildcard-селекторы,
новый флаг и таблицу лимитов.
## Server-side negotiation carrier
Для `websocket-lanes` отказ или таймаут до Upgrade отдельной lane запускает общее восстановление сессии. Сбой после Upgrade остаётся локальным для lane. Это свойство протокола не подтверждает совместимость восстановления нативных macOS/iOS клиентов после фонового режима.
Неизменяемая основа bridge кэшируется, а nonce, bootstrap, host и base path подставляются заново для каждого ответа. Обработка истечения сроков использует консервативные оценки ближайших сроков, не продлевая сроки. Эти внутренние оптимизации не меняют форматы протокола, лимиты приёма или таймауты и сами по себе не доказывают сокращения полного времени подключения.
Auto-negotiation необязателен и выключен, пока `carriers` не задан явным непустым массивом. Настроенный `carrier` остаётся последним fallback и добавляется ровно один раз, даже если уже присутствует в массиве:
```toml
@@ -175,7 +337,9 @@ WebSocket codec buffers и находящиеся в обработке read/wri
Для WEB-listener обязательны `proxy_protocol = false` и `reuse_allow = false`. В нём нельзя использовать `client_mss`, `synlimit`, `announce` и `announce_ip`. Массив `web_trusted_proxy_cidrs` должен быть непустым и содержать только непосредственные адреса NGINX или HAProxy; сети `/0` запрещены.
HTTP decoy origin должен быть loopback, link-local или private IP literal. Для обычных запросов Telemt сохраняет method, path, query, headers, streamed body, response status, headers и body, удаляя hop-by-hop headers. Перед отправкой некорректного carrier-запроса в decoy Telemt удаляет из него carrier credentials и body. Literal decoy endpoint отклоняется, если он точно совпадает с effective WEB-listener либо покрывается wildcard address того же IP-семейства на том же порту. Косвенные loops через DNS, NGINX, HAProxy или другой forwarding layer нельзя доказать из конфигурации Telemt; оператор обязан исключить их самостоятельно.
Для `http_upstream` параметр `resolve` принимает `"never"` (по умолчанию) или `"startup"`. По умолчанию нужен IP-адрес из loopback, link-local или private диапазона. `startup` также разрешает DNS-имена: асинхронная подготовка конфигурации разрешает каждую уникальную пару имя/порт, проверяет все ответы по допустимым классам адресов и действующим WEB listeners и закрепляет первый адрес в порядке ответа resolver. Ошибка, пустой ответ, публичный адрес или прямая петля к listener отклоняют подготовку. В upstream authority сохраняется настроенное имя. DNS при обработке запросов и автоматического перехода к другим ответам нет; reload готовит новый снимок. `web.timeouts.decoy_resolve_secs` ограничивает отдельный DNS-запрос для каждой уникальной пары имя/порт. Запросы выполняются последовательно, общего таймаута подготовки нет. Косвенные петли через другие прокси оператор должен исключить самостоятельно.
В обычных запросах сохраняются метод, путь, query, заголовки, потоковое тело, статус и тело ответа, кроме hop-by-hop заголовков. Из некорректных carrier-запросов перед передачей в decoy удаляются credentials и тело.
Вместо origin можно использовать immutable snapshot статического сайта:
@@ -307,7 +471,7 @@ proxy.example.com {
}
handle {
# обычный сайт
# ordinary site
}
}
```
@@ -335,7 +499,7 @@ log {
| Конфигурация | Поведение runtime |
| --- | --- |
| Состав WEB-listeners, bind address и trust policy | Принадлежат процессу; перезапустите Telemt. |
| Любое значение `[web.limits]` | Process-owned контракт памяти и ресурсов; перезапустите Telemt. |
| `web.allow_wildcard_hosts` и любое значение `[web.limits]` | Process-owned контракт памяти и ресурсов; перезапустите Telemt. |
| `web.enabled`, policy carrier/negotiation, `web.debug`, timeouts, vhosts, profiles и decoys | Применяются config watcher или runtime generation reload. |
| Изменение `base_path` vhost | Атомарно переключает маршрутизацию новых HTTP-запросов и derivation capability. Выпустите новую сгенерированную ссылку. Уже upgraded WebSockets и начатые маршрутизированные обмены продолжаются. Последующие запросы к старому base с подлинным для процесса bootstrap- или session-token получают локальный no-store `404`, а ставшая неактивной прежняя capability обрабатывается как обычный decoy traffic. Существующий session bearer остаётся пригодным только на новом точном base, а неиспользованный bootstrap, выданный для старой capability, не может создать session на новом base. |
| Operator pause/drain state | Process-owned и ephemeral; переживает generation reload, никогда не записывает конфигурацию и после перезапуска процесса возвращается в `running`. |
@@ -356,7 +520,7 @@ HTTP idle accounting защищает только явно ограниченн
| Операция | Поддержка API |
| --- | --- |
| Чтение или изменение `[web]`, vhosts, profiles, decoys, timeouts или limits | Да, через `GET` или `PATCH /v1/config`. Производный snapshot `web.runtime` не возвращается и недоступен для записи. Вложенные tables сливаются по полям; arrays целиком заменяют прежний array. Любое изменение `[web.limits]` принимается как desired configuration, но помечается deferred до перезапуска процесса. |
| Чтение или изменение `[web]`, vhosts, profiles, decoys, timeouts или limits | Да, через `GET` или `PATCH /v1/config`. Производный snapshot `web.runtime` не возвращается и недоступен для записи. Вложенные tables сливаются по полям; arrays целиком заменяют прежний array. Изменения `web.allow_wildcard_hosts` и `[web.limits]` откладываются до перезапуска процесса. Добавлять шаблоны можно только после включения wildcard в действующем процессе. |
| Сохранение `server.listeners` | Да, через `PATCH /v1/config`, но изменённый WEB-listener остаётся deferred до перезапуска процесса. |
| Применение WEB-конфигурации, изменённой вне API | Да, через `POST /v1/system/reload` с последующей проверкой статуса операции. |
| Просмотр bounded серверных WEB request- и lifecycle-деталей | Да, через аутентифицированный `GET /web-status`. |
@@ -441,7 +605,7 @@ curl -sS http://127.0.0.1:9091/v1/system/reload/RELOAD_ID \
-H "Authorization: ${TELEMT_API_AUTH}"
```
Терминальный статус `succeeded` подтверждает активацию runtime. Изменённые carrier, candidates, deadlines или learning policy используют новые bridge sessions; существующие сессии и начатые attempt chains не мигрируют. Если `deferred_process_fields` содержит `server.listeners` или `web.limits`, файл валиден и сохранён, но эти настройки всё ещё требуют перезапуска Telemt.
Терминальный статус `succeeded` подтверждает активацию runtime. Изменённые carrier, candidates, deadlines или learning policy используют новые bridge sessions; существующие сессии и начатые attempt chains не мигрируют. Если `deferred_process_fields` содержит `server.listeners`, `web.allow_wildcard_hosts` или `web.limits`, файл валиден и сохранён, но эти настройки всё ещё требуют перезапуска Telemt.
Операции с access users используют существующие endpoints, например:
@@ -485,7 +649,7 @@ curl -sS -X POST http://127.0.0.1:9091/v1/users/web-user/rotate-secret \
| Симптом | Что проверить |
| --- | --- |
| WEB-конфигурация валидна на диске, но поведение listener’а не изменилось | Проверьте `deferred_process_fields`; listener и `[web.limits]` требуют перезапуска. |
| WEB-конфигурация валидна на диске, но поведение listener’а не изменилось | Проверьте `deferred_process_fields`; listener и `web.allow_wildcard_hosts` / `[web.limits]` требуют перезапуска. |
| Carrier-запросы попадают в decoy | Проверьте точный vhost, secret mode ссылки, CIDR непосредственного proxy и единственное корректно разбираемое значение `X-Forwarded-For`. |
| Ссылка перестала работать после изменения `base_path` | Импортируйте заново напечатанную path-ссылку и убедитесь, что полный новый prefix без изменений попадает в Telemt. Существующие sessions могут восстановиться только через новый точный base; старые capabilities нельзя использовать повторно. |
| `/telegram/web` перенаправляет на `/telegram/web/` | Добавьте точный non-WEB handler для path без слеша. В WEB-контракт Telemt входит только настроенное поддерево с завершающим слешем. |