montana/Node/External-Audit/INCIDENT-RESPONSE.md

145 lines
6.5 KiB
Markdown
Raw Normal View History

2026-05-21 03:44:38 +03:00
# 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 → История проверки`.