montana/Node/External-Audit/OPERATIONS.md
2026-05-21 03:44:38 +03:00

140 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Эксплуатационный runbook
Версия: **2026-05-18**
## Добавление нового VPN-узла
### 1. Подготовка сервера
```
# Минимум Ubuntu 22.04+, root, IPv4 публичный, открытые :22 :443 :8444
```
### 2. Получить секреты у admin
- `VPN_PRIVKEY` — Reality privateKey (общий для всех узлов)
- `TOKEN` — admin token orchestrator
Передача — через зашифрованный канал (encrypted age/gpg, signal, encrypted email). НЕ через github/telegram cleartext.
### 3. Запустить join.sh
```
ROLE=vpn-backend \
TOKEN=<admin_token> \
VPN_PRIVKEY=<reality_privatekey> \
ALIAS=cologne LABEL=Köln COUNTRY=DE HOSTING=Hetzner \
COORDS="50.94,6.96" \
bash <(curl -sL https://hub.montana.quest/efir369999/montana/raw/branch/main/Node/join.sh)
```
Скрипт:
1. ставит зависимости, fail2ban
2. ufw: 22, 80, 443, 8444 → ALLOW; остальное DENY
3. ставит pinned версию xray, dedicated user `xray:xray`
4. пишет config.json с UUID/PBK/SID = универсальными, privateKey из env (никогда в скрипт)
5. drop-in `Restart=always StartLimitBurst=10`
6. `ExecStopPost=/usr/local/bin/montana-vpn-deregister` — при штатной остановке узла дёргает `/vpn/node/deregister`
7. ставит montana-node :8444 (TimeChain p2p)
8. POST `https://montana.quest/vpn/node/register` с admin TOKEN → CF добавляет IP в multi-A
### 4. Проверка
```
ssh new-node 'systemctl is-active xray fail2ban montana-node'
curl https://montana.quest/vpn/node/pool | jq '.records[].ip' # должен видеть наш IP
openssl s_client -connect <new-node-ip>:443 -servername www.googletagmanager.com -tls1_3 </dev/null 2>&1 | grep Verification
```
## Удаление узла
```
# 1. На самом узле:
systemctl stop xray # триггерит ExecStopPost → /deregister
# 2. Если узел уже мёртв — admin вручную:
TOKEN=$(security find-generic-password -s montana-orchestrator-admin -a token -w)
curl -X POST -H 'Content-Type: application/json' \
https://montana.quest/vpn/node/deregister \
--data "{\"ip\":\"<dead-ip>\",\"secret\":\"$TOKEN\"}"
# 3. Если orchestrator недоступен — напрямую через CF API:
CF=$(security find-generic-password -s cloudflare-api-token -a montana-quest -w)
ZONE=2bc47161267258960d48bedfdf476f1a
REC=$(curl -sH "Authorization: Bearer $CF" \
"https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records?type=A&name=cdn.montana.quest" \
| jq -r '.result[] | select(.content=="<dead-ip>") | .id')
curl -X DELETE -H "Authorization: Bearer $CF" \
"https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records/$REC"
```
## Ротация ключей Reality
Когда нужно: подозрение на утечку privateKey, plan-rotation каждые 6 месяцев.
```
# 1. Сгенерировать новый keypair (на любом узле):
ssh montana-finland 'xray x25519'
# → PrivateKey: <NEW_PRIV>
# Password: <NEW_PBK> (этот идёт в URL как pbk=)
# Hash32: ...
# 2. Сгенерировать новый shortId:
openssl rand -hex 8 # → <NEW_SID>
# 3. Раскатать NEW_PRIV/SID на каждый узел одной командой:
for n in montana-finland montana-frankfurt montana-us; do
ssh $n "python3 -c \"
import json
p='/usr/local/etc/xray/config.json'
c=json.load(open(p))
ib=c['inbounds'][0]
ib['streamSettings']['realitySettings']['privateKey']='<NEW_PRIV>'
ib['streamSettings']['realitySettings']['shortIds']=['<NEW_SID>']
json.dump(c,open(p,'w'),indent=2)\" && systemctl restart xray"
done
# 4. Обновить /vpn/sub (отдаётся с Moscow :5008) — поменять pbk= и sid= в источнике подписки.
# 5. Уведомить пользователей — у них в Happ профиль больше не работает (другой pbk).
```
Стоимость ротации: все клиенты должны переустановить профиль. Делать только при необходимости.
## Падение orchestrator
```
ssh montana-moscow 'systemctl status montana-orchestrator'
# если crashed:
ssh montana-moscow 'journalctl -u montana-orchestrator -n 50 --no-pager'
ssh montana-moscow 'systemctl restart montana-orchestrator'
# multi-A остаётся как есть → существующие подключения продолжают работать
# (deg-graceful: только новых узлов нельзя зарегистрировать пока orch down)
```
## Падение CF API
```
# DNS остаётся как есть (CF resolvers независимы от API).
# Невозможно add/remove IP. Существующий pool работает.
# При длительном outage — ждать восстановления CF.
```
## Доступ к секретам
| Секрет | Где |
|---|---|
| Reality privateKey | macOS Keychain `montana-vpn-privkey` (служба), и `/etc/montana/vpn-privkey` (0600) на узле |
| Admin token | macOS Keychain `montana-orchestrator-admin` / `token`; `/etc/montana/orchestrator-admin-token` (0600) на Moscow |
| CF API token | macOS Keychain `cloudflare-api-token` / `montana-quest`; `/etc/montana/cf-api-token` (0600) на Moscow |
| SSH ключи к узлам | macOS Keychain (через ssh-add) + `~/.ssh/montana-*` (0600) |
## Аптайм-критерии
- xray на узле: `Restart=always`, StartLimitBurst=10/5min — выживает все аварии кроме битого конфига
- orchestrator: `Restart=on-failure`, RestartSec=10
- fail2ban: `Restart=on-failure`
- nginx (Moscow): `Restart=on-failure`
- Все critical units `enabled` — переживают reboot