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

6.5 KiB
Raw Blame History

Incident response — VPN-сеть Montana

Версия: 2026-05-18

Severity матрица

Sev Симптом Reaction time
S1 ВСЕ узлы в pool недоступны (нет VPN ни у кого) < 15 минут
S2 Один из узлов недоступен > 5 минут после автоматического prune не отрабатывает < 30 минут
S3 Утечка любого секрета подтверждена < 1 час, по ротации
S4 DPI начал блокировать наш Reality в одной стране < 4 часа, переключение SNI
S5 Падение orchestrator < 1 час (multi-A продолжает работать)

Playbook

S1: вся сеть лежит

# 1. Проверить multi-A:
dig @1.1.1.1 cdn.montana.quest A
# если пусто или мало → CF API упал, либо watchdog ошибся и удалил всё

# 2. Прямой probe каждого узла:
for n in 91.132.142.42 89.19.208.158 86.104.72.12; do
  echo "$n: $(timeout 4 openssl s_client -connect $n:443 -servername www.googletagmanager.com -tls1_3 </dev/null 2>&1 | grep -c 'Verification: OK')"
done

# 3. Если узлы живы, а pool пуст — force re-register:
TOKEN=$(security find-generic-password -s montana-orchestrator-admin -a token -w)
for spec in "91.132.142.42|helsinki|FI" "89.19.208.158|frankfurt|DE" "86.104.72.12|newyork|US"; do
  ip=$(echo $spec|cut -d'|' -f1); alias=$(echo $spec|cut -d'|' -f2); c=$(echo $spec|cut -d'|' -f3)
  curl -sX POST -H 'Content-Type: application/json' \
    https://montana.quest/vpn/node/register \
    --data "{\"ip\":\"$ip\",\"alias\":\"$alias\",\"country\":\"$c\",\"hosting\":\"x\",\"label\":\"$alias\",\"coords\":[0,0],\"secret\":\"$TOKEN\"}"
done

# 4. Если узлы тоже мертвы — каскад инфраструктурного фейла:
#    a) Проверить статус хостинга (Timeweb dashboard / THE.Hosting)
#    b) Если хостинг ОК — SSH к узлу, проверить xray
ssh montana-finland 'systemctl status xray && tail -50 /var/log/xray/error.log'

S2: один узел мёртв > 5 мин, не выпал

# 1. Проверить watchdog работает:
ssh montana-moscow 'cat /var/lib/montana-orchestrator/pool-health.json | jq'

# 2. Проверить probe от Moscow к узлу:
ssh montana-moscow 'python3 -c "import ssl,socket;
ctx=ssl.create_default_context()
s=socket.create_connection((\"<dead-ip>\",443),timeout=5)
ws=ctx.wrap_socket(s,server_hostname=\"www.googletagmanager.com\")
print(ws.getpeercert())"'

# Если timeout — но узел живой → проверить ufw/CrowdSec на узле
ssh <dead-node> 'cscli decisions list 2>/dev/null | grep 176.124.208.93'

# 3. Если watchdog не работает — restart orchestrator:
ssh montana-moscow 'systemctl restart montana-orchestrator'

# 4. Ручное удаление IP:
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\"}"

S3: утечка секрета

S3a: утечка Reality privateKey

  1. Ввести ускоренную ротацию (см. OPERATIONS.md → "Ротация ключей Reality").
  2. Уведомить пользователей через канал коммуникации (Telegram, sitemontana.quest баннер).
  3. Заблокировать старый pbk (xray на узле не примет старый клиент после смены privateKey).

S3b: утечка admin TOKEN

# 1. Сгенерировать новый:
NEW=$(openssl rand -hex 32)
# 2. Положить на Moscow:
ssh montana-moscow "install -m 600 /dev/stdin /etc/montana/orchestrator-admin-token <<<'$NEW'"
ssh montana-moscow 'systemctl restart montana-orchestrator'
# 3. Обновить Keychain:
security add-generic-password -s montana-orchestrator-admin -a token -w "$NEW" -U

S3c: утечка CF API token

  1. Сразу revoke в Cloudflare dashboard.
  2. Создать новый scoped token (Zone montana.quest, Permissions: Zone.DNS.Edit).
  3. Обновить в Keychain cloudflare-api-token и в /etc/montana/cf-api-token на Moscow.
  4. Restart orchestrator.

S4: DPI блокировка в стране

# Временное решение: сменить SNI в realitySettings на другой популярный домен.
# Все узлы должны иметь тот же SNI (привязан к pbk).

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']['serverNames']=['www.gstatic.com']
ib['streamSettings']['realitySettings']['dest']='www.gstatic.com:443'
json.dump(c,open(p,'w'),indent=2)\" && systemctl restart xray"
done

# Обновить /vpn/sub: в URL sni=www.gstatic.com
ssh montana-moscow 'sed -i "s/www.googletagmanager.com/www.gstatic.com/g" <path-to-vpn-sub-source>'

S5: orchestrator упал, не поднимается

ssh montana-moscow 'journalctl -u montana-orchestrator -n 200 --no-pager'
# Типичные причины:
# - синтакс ошибка в server.py → откатить из *.bak
ssh montana-moscow 'ls /opt/montana-orchestrator/server.py.bak-*'
# - /etc/montana/cf-api-token нечитаем → chmod 600 root:root
# - Flask недоступен → apt install python3-flask

# Существующие подключения клиентов НЕ затронуты до DNS TTL expire (120s).
# Если orch будет down > 30 минут — manual prune через CF API напрямую.

Эскалация

Уровень Кто Канал
L1 Архитектор сети efir369999@gmail.com
L2 Cloudflare support (DNS issues) https://dash.cloudflare.com → support
L3 Хостинг провайдер Timeweb / THE.Hosting dashboard

После инцидента

  1. Записать в OPEN-RISKS.md если выявлен новый риск.
  2. Если фикс уровня кода — соответствующее изменение в Montana/Node/join.sh или orchestrator.
  3. Записать дату и причину в AUDIT-READINESS.md → История проверки.