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

5.9 KiB
Raw Blame History

Эксплуатационный 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