feat(crypto,cli,docs): russian SNI palette + RF-billing deployment scenario
Adds a way to make the outer-TLS SNI rotate among popular Russian-language
domains so that Russian carriers — who may start metering "foreign traffic"
separately — see the user's first hop as a domestic CDN/site request, not
as an exotic foreign destination.
- aura-crypto::masks:
- SNI_PALETTE_RUSSIAN (15 real domains: mail.yandex.ru, vk.com, www.ozon.ru,
dzen.ru, ya.ru, www.gosuslugi.ru, www.wildberries.ru, rutube.ru,
news.rambler.ru, hh.ru, www.tinkoff.ru, lenta.ru, www.kinopoisk.ru,
afisha.yandex.ru, music.yandex.ru).
- enum SniPalette { Default, Russian, Mixed } (Default = v2 behavior).
- derive_mask_for_msk_date_with_palette(...): pick from chosen palette,
Mixed flips ~50/50 by HKDF okm[8]&1. Old derive_mask_for_msk_date kept
as a thin wrapper -> byte-for-byte unchanged Default.
- aura-cli::masks::MaskRotator gains new_with_palette(...); the spawn loop
uses the stored palette. Old new() preserves Default.
- aura-cli config: [transport.masks] palette = "default"|"russian"|"mixed"
(serde rename_all = "lowercase", default Default).
- server.rs/client.rs read cfg.transport.masks.palette and pass it to the
rotator at startup; logged at INFO so the operator sees the choice.
- docs/deployment.md: new §7 "Сервер в РФ против тарификации иностранного
трафика" — context, ASCII topology, recommended RF providers, full
server.toml + client.toml examples wiring [server.relay] + russian
palette + LE outer cert + multi-hop, plus an honest list of what this
does and does not give.
- config/{server,client}.toml.example updated with palette = "default".
Workspace: 284 tests passed (+8 new = 4 crypto + 2 cli masks + 2 config),
clippy -D warnings clean, fmt clean. 276 baseline tests untouched.
Backward-compatible: configs without palette default to Default, identical
to v2 wire behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+260
-7
@@ -412,17 +412,270 @@ aura status
|
||||
выводят одинаковый `MaskSet` из общего seed (SHA-256 от CA-сертификата) + UTC-даты через
|
||||
HKDF-SHA256, без сетевой координации. Конфиг: `[transport.masks] enabled = true` (по
|
||||
умолчанию). Новые подключения берут текущую маску; уже установленные остаются на своих.
|
||||
Палитры: 16 SNI, 10 User-Agent, 5 Server-headers, 4 padding-профиля; профиль 0 байт-в-байт
|
||||
совместим с v1-паддингом (бэк-совместимость).
|
||||
Палитры: 16 SNI default + 15 SNI russian, 10 User-Agent, 5 Server-headers, 4 padding-профиля;
|
||||
профиль 0 байт-в-байт совместим с v1-паддингом (бэк-совместимость). v3.2 добавляет
|
||||
`palette = "default" | "russian" | "mixed"` для случая, когда нужно, чтобы outer SNI выглядел
|
||||
как обращение к российскому сайту (см. сценарий в §7).
|
||||
- ✓ **Multi-hop / onion routing v3.1 + v3.2.** Цепочка из 2-3 хопов: client →
|
||||
entry-relay → (опционально middle) → exit. Entry-relay не знает destination, exit-узел не
|
||||
знает клиентский IP. v3.2: per-hop client cert (CN entry и exit различаются — нельзя
|
||||
слинковать handshakes по identity), cell padding (constant-size cells устраняют per-packet
|
||||
size-fingerprinting), CIDR whitelist на relay'е. Конфиг — `[client.circuit]` /
|
||||
`[server.relay]`. См. сценарий §7 для деплоймента «российский entry, иностранный exit».
|
||||
- ✓ **Let's Encrypt outer-TLS cert.** `[server.outer_cert] cert_path / key_path` — outer-TLS
|
||||
слой QUIC и TCP использует настоящий CA-trusted сертификат вместо self-signed Aura cert;
|
||||
внутренний Aura mutual-auth handshake продолжает аутентификацию против Aura CA.
|
||||
|
||||
### Остающиеся честные ограничения v2
|
||||
### Остающиеся честные ограничения
|
||||
|
||||
- **TUN всё ещё требует root** для **создания** интерфейса (это OS-уровень). Privilege drop
|
||||
минимизирует окно работы под root, но саму операцию обойти нельзя.
|
||||
- **IPv6 в OS-маршрутах и iptables MASQUERADE** не реализован — только IPv4 (план v3).
|
||||
- **Windows OS-маршруты** — заглушка с лог-warning (план v3). Windows admin pipe **работает**.
|
||||
- **IPv6 в OS-маршрутах и iptables MASQUERADE** не реализован — только IPv4 (план v3.3).
|
||||
- **Windows OS-маршруты** — заглушка с лог-warning (план v3.3). Windows admin pipe **работает**.
|
||||
- **Нативного Go-клиента для телефона нет** — через sing-box (Option B нативный Go-outbound,
|
||||
по `protocol.md` + KAT из Rust, см. [`sing-box.md`](sing-box.md)). Сейчас доступен только
|
||||
десктоп-клиент / process-bridge. Это явно исключённый из v2 пункт.
|
||||
- **Multi-hop / onion routing**: пока один сервер на путь (план v3 — цепочка из 2-3
|
||||
серверов так, чтобы entry-узел не знал destination, а exit-узел не знал клиентский IP).
|
||||
- **Bridge-discovery без хардкода IP в конфиге** — план v3.3. Сейчас `[client] bridges`
|
||||
хардкодит список запасных IP; если их все заблокируют (включая российские entry-узлы из
|
||||
сценария §7), восстановление требует обновления конфига клиента вручную.
|
||||
|
||||
---
|
||||
|
||||
## 7. Сценарий: российский entry-узел против тарификации иностранного трафика
|
||||
|
||||
### 7.1. Контекст и угроза
|
||||
|
||||
Российские операторы связи могут начать тарифицировать «иностранный трафик» отдельно: классификация
|
||||
выполняется по destination IP исходящего пакета пользователя. Если первый IP, к которому
|
||||
обращается устройство, — российский, биллинг считает соединение «российским», даже если внутри
|
||||
этого соединения трафик уходит дальше за рубеж. Цель в этом сценарии — добиться того, чтобы
|
||||
оператор биллил трафик пользователя как «российский», при этом сохраняя VPN-выход за рубежом.
|
||||
|
||||
Решение опирается на три компонента, уже реализованные в AuraVPN:
|
||||
|
||||
1. **Multi-hop / onion routing v3.1+** (`[client.circuit]` / `[server.relay]`) — entry-узел в РФ
|
||||
не знает destination, exit-узел за рубежом не знает клиентский IP.
|
||||
2. **Палитра SNI «russian»** (v3.2) — `[transport.masks] palette = "russian"` ротирует outer-TLS
|
||||
SNI среди крупных российских доменов (`vk.com`, `www.ozon.ru`, `mail.yandex.ru`, ...).
|
||||
3. **OS-уровень kill-switch** (`[tunnel.os_routes] enabled = true`) — гарантия, что системный
|
||||
трафик (push-уведомления, OS-сервисы) не обходит туннель и не попадает напрямую к иностранным
|
||||
серверам в обход entry-узла.
|
||||
|
||||
### 7.2. Топология
|
||||
|
||||
```
|
||||
[устройство]
|
||||
|
|
||||
| весь трафик через TUN (kill-switch)
|
||||
v
|
||||
[оператор] <-- видит только UDP/443 на RU_VPS_IP, SNI = "vk.com"
|
||||
|
|
||||
v
|
||||
[Russian VPS / entry-relay] <-- v3.1 relay: forward to next hop, never decodes IP packets
|
||||
|
|
||||
| inner Aura handshake (PQ-encrypted, opaque)
|
||||
v
|
||||
[Foreign VPS / exit] <-- настоящий VPN-выход в интернет
|
||||
|
|
||||
v
|
||||
[internet]
|
||||
```
|
||||
|
||||
Оператор видит только трафик до **entry-узла**: один UDP-поток с SNI крупного российского сайта.
|
||||
Внутри этого потока — зашифрованный многохоп; entry-relay не имеет ключей внутреннего рукопожатия
|
||||
и видит только AEAD-ciphertext, который он форвардит на exit. Exit видит только IP entry-узла, а
|
||||
не IP клиентского устройства.
|
||||
|
||||
### 7.3. Что покупать
|
||||
|
||||
**Подходящие провайдеры для entry-узла в РФ** (юрисдикция РФ, IP в российских AS):
|
||||
|
||||
- **Selectel** (Москва, СПб).
|
||||
- **Beget** (СПб).
|
||||
- **Yandex.Cloud** (Москва).
|
||||
- **VK Cloud** (бывш. Mail.ru Cloud Solutions).
|
||||
- **Timeweb Cloud**.
|
||||
|
||||
**Неподходящие для роли entry-узла в РФ**:
|
||||
|
||||
- **Hetzner** (Германия/Финляндия) — IP классифицируется как «иностранный».
|
||||
- **DigitalOcean / Vultr / Linode** (США/EU) — то же самое.
|
||||
- **AWS / GCP / Azure** даже с российскими DC-локациями — IP-блоки за пределами российских AS у
|
||||
большинства операторов.
|
||||
|
||||
Для **exit-узла** наоборот — берите любой удобный иностранный VPS (Hetzner, DigitalOcean, Vultr,
|
||||
любой подходящий по юрисдикции и пропускной способности).
|
||||
|
||||
### 7.4. Конфиг сервера в РФ (entry-relay)
|
||||
|
||||
`server.toml` на российском VPS (например, Selectel с IP `RUSSIAN_VPS_IP`):
|
||||
|
||||
```toml
|
||||
[server]
|
||||
name = "aura-ru-entry-1"
|
||||
listen = "0.0.0.0:443"
|
||||
|
||||
[pki]
|
||||
ca_cert = "/etc/aura/pki/ca.crt"
|
||||
cert = "/etc/aura/pki/server/server.crt"
|
||||
key = "/etc/aura/pki/server/server.key"
|
||||
|
||||
[tunnel]
|
||||
# Pool нужен формально (для v1-fallback-пути), но в роли чистого relay он не используется —
|
||||
# bridged-клиенты не получают IP из пула и не регистрируются в ServerRouter.
|
||||
pool_cidr = "10.7.0.0/24"
|
||||
mtu = 1420
|
||||
|
||||
# v3.1: relay-режим. Принимаем ExtendBridge от клиента и сплайсим на foreign exit.
|
||||
[server.relay]
|
||||
enabled = true
|
||||
allow_extend_to = ["EXIT_FOREIGN_IP:443"] # IP вашего иностранного exit-узла
|
||||
# v3.2 cell padding: relay сам не декодирует — это сквозной байт-форвардинг. Знаки опции тут
|
||||
# для симметрии конфига; реальный декод цельных ячеек — на exit'е.
|
||||
cell_padding = true
|
||||
cell_size = 1280
|
||||
|
||||
[transport.masks]
|
||||
enabled = true
|
||||
# v3.2: outer-TLS SNI крутится среди крупных российских доменов. Каждый день — другой домен.
|
||||
palette = "russian"
|
||||
|
||||
# Опционально: настоящий outer-TLS сертификат (Let's Encrypt) поверх UDP/QUIC и TCP. Без него
|
||||
# работает self-signed Aura, но с настоящим LE-сертификатом outer-handshake становится
|
||||
# неотличим от обычного HTTPS на CA-trusted сайт.
|
||||
[server.outer_cert]
|
||||
cert_path = "/etc/letsencrypt/live/relay.example.ru/fullchain.pem"
|
||||
key_path = "/etc/letsencrypt/live/relay.example.ru/privkey.pem"
|
||||
```
|
||||
|
||||
И аналогичный `server.toml` на **иностранном exit-узле** — обычный VPN-сервер БЕЗ `[server.relay]`,
|
||||
но с `cell_padding_for_circuit_clients = true` в секции `[server]`, чтобы он понимал
|
||||
constant-size cells от клиента:
|
||||
|
||||
```toml
|
||||
[server]
|
||||
name = "aura-exit-1"
|
||||
listen = "0.0.0.0:443"
|
||||
# v3.2: exit для cell-padded клиентов — декодирует ячейки внутреннего рукопожатия.
|
||||
cell_padding_for_circuit_clients = true
|
||||
|
||||
[pki]
|
||||
ca_cert = "/etc/aura/pki/ca.crt"
|
||||
cert = "/etc/aura/pki/server/exit.crt"
|
||||
key = "/etc/aura/pki/server/exit.key"
|
||||
|
||||
[tunnel]
|
||||
pool_cidr = "10.7.0.0/24"
|
||||
|
||||
[server.nat]
|
||||
auto = true # включить IP-форвардинг и MASQUERADE на egress-интерфейсе
|
||||
egress_iface = "eth0"
|
||||
|
||||
[transport.masks]
|
||||
# На exit'е SNI палитра не критична (клиент видит exit только через relay) — оставим default.
|
||||
palette = "default"
|
||||
```
|
||||
|
||||
### 7.5. Конфиг клиента
|
||||
|
||||
```toml
|
||||
[client]
|
||||
name = "laptop"
|
||||
server_addr = "RUSSIAN_VPS_IP:443" # entry-узел в РФ; именно этот IP видит оператор
|
||||
sni = "relay.example.ru" # SAN серверного outer-TLS сертификата (если есть LE)
|
||||
|
||||
[pki]
|
||||
ca_cert = "~/.aura/ca.crt"
|
||||
cert = "~/.aura/client.crt"
|
||||
key = "~/.aura/client.key"
|
||||
|
||||
[tunnel]
|
||||
tun_name = "aura0"
|
||||
local_ip = "10.7.0.2"
|
||||
prefix = 24
|
||||
mtu = 1420
|
||||
|
||||
[tunnel.split]
|
||||
default = "VPN"
|
||||
|
||||
# КРИТИЧНО: kill-switch — весь трафик через TUN, OS-уровень. Без этого push-уведомления и
|
||||
# OS-сервисы могут уйти напрямую в иностранные сервера в обход entry-узла, и оператор
|
||||
# зачтёт это как «иностранный» трафик.
|
||||
[tunnel.os_routes]
|
||||
enabled = true
|
||||
|
||||
# v3.1 / v3.2: цепочка хопов client -> RU_entry -> foreign_exit.
|
||||
[client.circuit]
|
||||
enabled = true
|
||||
cell_padding = true
|
||||
cell_size = 1280
|
||||
|
||||
[[client.circuit.hops]]
|
||||
addr = "RUSSIAN_VPS_IP:443" # entry в РФ — то, что видит оператор
|
||||
cert_path = "~/.aura/circuit/entry.crt"
|
||||
key_path = "~/.aura/circuit/entry.key"
|
||||
|
||||
[[client.circuit.hops]]
|
||||
addr = "EXIT_FOREIGN_IP:443" # exit за рубежом, к которому привязаны DNS/маршруты внутри VPN
|
||||
cert_path = "~/.aura/circuit/exit.crt"
|
||||
key_path = "~/.aura/circuit/exit.key"
|
||||
|
||||
[transport.masks]
|
||||
enabled = true
|
||||
# Должно совпадать с palette = "russian" на entry-узле — иначе SNI в логах двух сторон
|
||||
# не будут симметричны (на проводе это не ошибка, но удобнее для отладки).
|
||||
palette = "russian"
|
||||
```
|
||||
|
||||
Сертификаты двух хопов — разные (`entry.crt` != `exit.crt`). Это v3.2 identity-unlinkability:
|
||||
entry-relay видит только клиентский cert для роли entry, exit-узел видит только cert для роли
|
||||
exit, и они не пересекаются (см. `aura provision-client --circuit-hops 2 ...`).
|
||||
|
||||
### 7.6. Что это даёт
|
||||
|
||||
- **Оператор биллит как «российский».** На проводе оператор видит один UDP-поток на
|
||||
`RUSSIAN_VPS_IP:443` — это российский IP в российской AS, классификатор биллинга его не
|
||||
обозначает как иностранный.
|
||||
- **SNI выглядит как обращение к российскому сайту.** В пакетах outer-TLS / outer-QUIC
|
||||
hostname-камуфляж берётся из `SNI_PALETTE_RUSSIAN`: каждый день — другой домен (`vk.com`,
|
||||
`www.ozon.ru`, `mail.yandex.ru`, ...). DPI видит «нормальный HTTPS на крупный российский
|
||||
сайт».
|
||||
- **Реальный VPN-выход — за рубежом.** Внутри multi-hop клиент дозванивается до иностранного
|
||||
exit-узла; именно его IP видят внешние ресурсы. Entry-узел в РФ форвардит зашифрованный
|
||||
трафик, не зная destination и не имея ключей внутреннего рукопожатия.
|
||||
- **Kill-switch предотвращает обход.** `[tunnel.os_routes] enabled = true` программирует
|
||||
системную таблицу маршрутов так, что весь трафик идёт через TUN — push-уведомления, OS-сервисы
|
||||
и любые «прямые» обращения в обход VPN заблокированы, поэтому ничто из устройства не уйдёт
|
||||
напрямую к иностранному IP в обход entry-узла.
|
||||
|
||||
### 7.7. Что это НЕ даёт (честное ограничение)
|
||||
|
||||
- **Не скрывает сам факт VPN-использования** от российских органов. DPI с deep-inspection может
|
||||
по статистическим паттернам трафика (timing, размеры, поведение в течение сессии) узнать
|
||||
Aura-протокол; ротация масок и `palette = "russian"` маскирует пассивного наблюдателя, но не
|
||||
активного аналитика. Для дополнительной защиты включайте `[transport.knock]` и
|
||||
`[transport.cover]` (port-knocking + cover traffic).
|
||||
- **Не освобождает от ответственности за заходы на запрещённые ресурсы.** Кто и за что отвечает
|
||||
при заходе на запрещённый ресурс через VPN — вопрос юрисдикции exit-узла и применимого
|
||||
законодательства, не технический.
|
||||
- **Не защищает от блокировки самого entry-IP.** Если СОРМ-система или Роскомнадзор начнут
|
||||
активно блокировать конкретные VPS-IP, придётся ротировать IP / bridges. Сейчас это решается
|
||||
через `[client] bridges = [...]` — список запасных российских entry-узлов; клиент пробует их
|
||||
в случайном порядке при отказе primary. Полноценный bridge-discovery (без хардкода IP в
|
||||
конфиге) — план v3.3.
|
||||
- **Cell padding не скрывает наличие туннеля.** Constant-size cells устраняют per-packet
|
||||
size-fingerprinting внутри multi-hop, но не делают сам поток неотличимым от HTTPS — общий
|
||||
объём и временные паттерны остаются. Это компромисс между обфускацией и накладными расходами.
|
||||
|
||||
### 7.8. Что менять при ротации
|
||||
|
||||
При смене IP entry-узла (например, при блокировке текущего) обновите три места:
|
||||
|
||||
1. `[[client.circuit.hops]] addr` первого хопа → новый `RUSSIAN_VPS_IP:443`.
|
||||
2. `[client] server_addr` → тот же новый IP.
|
||||
3. На новом VPS — поднять PKI, выпустить cert для entry-роли, перенести `server.toml` с
|
||||
`[server.relay]` и `palette = "russian"`.
|
||||
|
||||
Перевыпускать сертификаты двух хопов не нужно — они остаются те же, меняется только wire-адрес
|
||||
entry-узла. На сертификате entry-сервера должен быть SAN, совпадающий с `[client] sni`
|
||||
(см. `aura pki issue-server --domain relay.example.ru`).
|
||||
|
||||
Reference in New Issue
Block a user