main
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# ADR-0002: Two-zone deployment (внутренний + публичный контур)
|
||||
|
||||
**Дата:** 2026-05-20
|
||||
**Статус:** Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
ZBrain должен обслуживать:
|
||||
|
||||
1. **Внутренних пользователей** — owner (Алексей) и будущая команда работают через VPN/локальную сеть ZETIT. Им нужен полный UI: дашборд, управление брейнами, токенами, источниками, аудит.
|
||||
|
||||
2. **Внешних агентов** — Claude Code на серверах клиентов (TeleraPharma, Fontvielle, и т.д.), которым нужен MCP-доступ к чтению/записи брейнов. У этих серверов нет VPN-доступа к ZETIT.
|
||||
|
||||
Простое решение "выставить всё на публичный домен с auth" имеет недостатки:
|
||||
- Любая уязвимость в админ-UI становится публично доступной
|
||||
- OAuth flow на админ-эндпоинтах усложняет admin интеграции (curl, скрипты)
|
||||
- Утечка session cookie с админ-привилегиями через CSRF/XSS критична
|
||||
|
||||
## Решение
|
||||
|
||||
Развести функционал по двум сетевым контурам с разными доменами:
|
||||
|
||||
### Внутренний контур (brain.zetit.local)
|
||||
|
||||
- Доступен **только** из сети ZETIT и через VPN
|
||||
- Plain HTTP допустим (доверенная сеть, нет MITM риска)
|
||||
- Endpoints: **всё** — `/`, `/api/*`, `/mcp/*`, `/api/events` (SSE)
|
||||
- Auth: session cookie через local login (email/password) или OAuth
|
||||
- Используется: для web-UI, админских скриптов, debug'а
|
||||
|
||||
### Публичный контур (brain.zetit.ru)
|
||||
|
||||
- Доступен из интернета через TLS
|
||||
- Endpoints: **только** `/mcp/*`, `/oauth/*`, `/health`
|
||||
- Auth: Bearer tokens (для MCP) или OAuth flow (для подключения нового клиента)
|
||||
- Любой другой path → 404 (без подсказок что вообще существует)
|
||||
- Rate limiting per IP и per token
|
||||
- Используется: для удалённого Claude Code/Cursor, для машинного доступа
|
||||
|
||||
## Реализация
|
||||
|
||||
Один Node.js процесс запускает два независимых Express app на разных портах:
|
||||
|
||||
```typescript
|
||||
// apps/api/src/main.ts
|
||||
const internalApp = createInternalApp(); // полный набор routes
|
||||
const publicApp = createPublicApp(); // только /mcp и /oauth
|
||||
|
||||
internalApp.listen(3000, '127.0.0.1');
|
||||
publicApp.listen(3010, '127.0.0.1');
|
||||
```
|
||||
|
||||
Nginx разруливает по hostname:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name brain.zetit.local;
|
||||
location / { proxy_pass http://127.0.0.1:3000; }
|
||||
}
|
||||
|
||||
server {
|
||||
server_name brain.zetit.ru;
|
||||
listen 443 ssl;
|
||||
location /mcp/ { proxy_pass http://127.0.0.1:3010; }
|
||||
location /oauth/ { proxy_pass http://127.0.0.1:3010; }
|
||||
location / { return 404; }
|
||||
}
|
||||
```
|
||||
|
||||
## Последствия
|
||||
|
||||
### Плюсы
|
||||
- Атака на админ-UI требует VPN или физического доступа к сети
|
||||
- Невозможно случайно выставить админ-API в публичный контур (architectural enforcement)
|
||||
- Rate limit на публичном контуре более агрессивный, не мешает админам
|
||||
- Раздельные access логи nginx для security audit
|
||||
|
||||
### Минусы
|
||||
- Сложнее тестировать: нужно эмулировать два контура локально
|
||||
- DNS: две A-записи на разных серверах (внутренний и edge)
|
||||
- Невозможно "быстро дать админский доступ" партнёру не давая VPN
|
||||
|
||||
### Митигация для разработки
|
||||
- Локально оба app слушают `127.0.0.1` на разных портах, host header проверяется в middleware
|
||||
- `.env.development` имеет флаг `DISABLE_ZONE_CHECK=true` для упрощённого dev режима
|
||||
- Integration тесты гоняют оба контура
|
||||
|
||||
## Альтернативы рассмотренные
|
||||
|
||||
1. **Один контур с строгой ролевой моделью** — отвергнуто, ошибка в RBAC становится критичной уязвимостью
|
||||
2. **Два разных процесса/контейнера** — overkill для одной VM, осложняет shared state (token cache)
|
||||
3. **API Gateway (Kong/Traefik)** — лишняя сущность для проекта такого размера
|
||||
Reference in New Issue
Block a user