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

145 lines
6.5 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.

# 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 → История проверки`.