montana/Node/External-Audit/ARCHITECTURE.md

97 lines
6.1 KiB
Markdown
Raw Normal View History

2026-05-21 03:44:38 +03:00
# Архитектура VPN-сети Montana
Версия: **2026-05-18**
## Поток подключения клиента
```
┌──────────────┐ 1. DNS resolve cdn.montana.quest
│ iPhone Happ │ ─────────────────────────────────────┐
└──────┬───────┘ │
│ ▼
│ ┌──────────────────────────┐
│ │ Cloudflare DNS multi-A │
│ │ TTL=120, не proxied │
│ │ → FI 91.132.142.42 │
│ │ → FRA 89.19.208.158 │
│ │ → US 86.104.72.12 │
│ └────────┬─────────────────┘
│ │
│ 2. TLS handshake (Reality) ───────────┘
│ SNI=www.googletagmanager.com
│ pbk=EkTs2aGKnFNgFZ0f7wgft2sJp3VjwFQqIrwkZKM4gD8
┌────────────────────────────────────────────────────┐
│ xray :443 на одном из узлов (любом — один keypair) │
│ vless + xtls-rprx-vision + reality │
│ user=xray:xray, Restart=always, StartLimitBurst │
│ outbound: freedom (direct internet) │
└────────────────────────────────────────────────────┘
```
## Поток управления (health watchdog)
```
┌────────────────────────────────────────────────┐
│ montana-orchestrator.service на Moscow │
│ /opt/montana-orchestrator/server.py :5020 │
│ │
│ Flask: │
│ POST /register {ip, alias, …, secret} │ ← admin
│ POST /deregister {ip, secret} │ ← узел при shutdown
│ GET /pool — CF records + state │ ← аудитор
│ GET /health — кол-во live │ ← мониторинг
│ │
│ Background thread (каждые 30 сек): │
│ for ip in registrypool: │
│ ok = tls_probe(ip:443, SNI) │
│ if ok && success≥2 && not in_pool: │
│ cf_api POST /dns_records │
│ if !ok && fails≥2 && in_pool: │
│ cf_api DELETE /dns_records/<id>
└────────────────────────────────────────────────┘
Cloudflare API
```
## Компоненты
| Узел | Хост | Роль | Сервисы |
|---|---|---|---|
| Helsinki | 91.132.142.42 | VPN-backend | xray |
| Frankfurt | 89.19.208.158 | VPN-backend | xray |
| New York | 86.104.72.12 | VPN-backend | xray |
| Moscow | 176.124.208.93 | orchestrator + landing + p2p (НЕ VPN) | nginx, montana-orchestrator, montana-node |
## Криптографические параметры
| Параметр | Значение | Где |
|---|---|---|
| Reality protocol | `vless + xtls-rprx-vision` | inbound config |
| Server SNI / dest | `www.googletagmanager.com:443` | realitySettings.dest |
| Universal UUID | `e6d355e2-2d79-4c96-a373-3b0e6b6f4b0d` | clients[0].id |
| Reality publicKey | `EkTs2aGKnFNgFZ0f7wgft2sJp3VjwFQqIrwkZKM4gD8` | URL `pbk=` |
| Reality shortId | `302805bc0c25e504` | URL `sid=` |
| Reality privateKey | (хранится только на узлах) | `/usr/local/etc/xray/config.json` (600) |
## Сетевые потоки
| От | До | Порт | Назначение |
|---|---|---|---|
| Любой клиент | VPN-узел | 443/tcp | Reality VPN |
| Moscow orchestrator | VPN-узел | 443/tcp | watchdog TLS-probe |
| Moscow orchestrator | api.cloudflare.com | 443/tcp | DNS API |
| Узел | Любой destination | 443/tcp etc. | freedom outbound |
| Любой p2p-узел | Любой узел | 8444/tcp | montana-node Bootstrap+VDF |
| Admin | Moscow `montana.quest/vpn/node/register` | 443/tcp | POST с secret |
## Решения и обоснования
- **Universal keypair на всех backends** — Reality требует, чтобы клиент знал `publicKey` сервера. Multi-A pool с разными keypair невозможен без подписки с несколькими `vless://`. Принято: один keypair с trade-off shared-secret (см. THREAT-MODEL.md T-2).
- **DNS multi-A вместо подписки с несколькими URL** — даёт «один ключ распределяет» без логики на клиенте; iOS native resolver сам ротирует ANSWER.
- **TTL 120s** — баланс между скоростью переключения при отказе и нагрузкой на DNS.
- **Watchdog interval 30s, threshold 2** — реакция за 60120 секунд при минимальной нагрузке на узлы (≈ 1 probe в минуту с одной точки).
- **orchestrator на Moscow, не на VPN-узле** — изоляция управления от data-plane; падение orchestrator не роняет существующие подключения.
- **Moscow исключена из VPN pool** — на :443 nginx (efir.org), :2053 — отдельный xray не для публики.