This commit is contained in:
zuevav
2026-05-20 19:33:02 +03:00
commit f4bca8449e
30 changed files with 4152 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
# ADR-0003: Изоляция брейнов через отдельные PostgreSQL БД
**Дата:** 2026-05-20
**Статус:** Принято
## Контекст
ZBrain хостит несколько брейнов с разным уровнем чувствительности данных:
- **zetit** — внутренние процессы ZETIT, не публичные
- **telerapharma** — корпоративная инфраструктура с NDA
- **personal** — личные данные владельца
- **community** — данные о Смоленской 10, дом-чат, ЖКХ
Утечка MCP токена с правами на один брейн **не должна** давать доступ к другим брейнам.
Варианты изоляции в Postgres (от слабой к сильной):
1. **Одна БД, одна схема, отдельные таблицы с префиксами** — слабая изоляция, любой SQL injection даёт доступ ко всему
2. **Одна БД, отдельные schema'ы (zetit.pages, telerapharma.pages)** — изоляция через `search_path`, но один Postgres user видит всё
3. **Одна БД, отдельные пользователи с row-level security** — сложно, легко допустить ошибку в RLS политиках
4. **Отдельные БД с отдельными пользователями** — изоляция на уровне СУБД
## Решение
Вариант 4: **отдельная БД + отдельный Postgres-пользователь на каждый брейн**.
```
PostgreSQL cluster
├── brainhub (БД)
│ └── владелец: brainhub
│ └── таблицы: users, sessions, tokens, brains, sources, projects, audit_log
├── gbrain_zetit (БД)
│ └── владелец: gbrain_zetit
│ └── полная схема gbrain (pages, chunks, embeddings, links, ...)
├── gbrain_telerapharma (БД)
│ └── владелец: gbrain_telerapharma
│ └── ...
├── gbrain_personal (БД)
│ └── владелец: gbrain_personal
│ └── ...
└── gbrain_community (БД)
└── владелец: gbrain_community
└── ...
```
Каждый gbrain-instance подключается к Postgres под своим пользователем и **физически не видит** другие БД:
```sql
-- Под пользователем gbrain_zetit
\l
-- Покажет только gbrain_zetit (и template/postgres)
\c gbrain_telerapharma
-- ERROR: permission denied for database gbrain_telerapharma
```
Brainhub использует свою БД `brainhub` под пользователем `brainhub` (тоже не видит остальные).
## Реализация
В `scripts/create-brain.sh`:
```sql
CREATE USER gbrain_${BRAIN_NAME} WITH PASSWORD '${random_password}';
CREATE DATABASE gbrain_${BRAIN_NAME} OWNER gbrain_${BRAIN_NAME};
GRANT ALL PRIVILEGES ON DATABASE gbrain_${BRAIN_NAME} TO gbrain_${BRAIN_NAME};
-- НЕ выдаём этому юзеру ничего на other databases
```
Пароль для каждого gbrain-юзера генерируется случайно и хранится:
1. В `/var/lib/zbrain/brains/<name>/config.json` (chmod 600, owner zbrain)
2. В DATABASE_URL внутри systemd unit (Environment="DATABASE_URL=...")
`brainhub` НЕ хранит эти пароли в своей БД (они не нужны для admin-операций) — только знает на каком порту слушает gbrain instance.
## Последствия
### Плюсы
- Физическая изоляция через ACL Postgres
- SQL injection в одном брейне не даёт доступ к другим
- pg_dump каждого брейна тривиально по отдельности
- Удаление брейна = одна команда `DROP DATABASE gbrain_X`
- Разные параметры тюнинга на разные брейны (если потребуется)
### Минусы
- Нельзя сделать JOIN между брейнами в SQL (но это и не нужно — поиск через brainhub)
- Каждая БД имеет свои индексы, общая нагрузка на shared_buffers Postgres делится
- Backup делает дамп каждой БД отдельно (несложно)
### Митигация
- shared_buffers задан с запасом (2GB на 8GB RAM)
- HNSW индексы строятся в `maintenance_work_mem=512MB`, одного брейна за раз достаточно
## Альтернативы рассмотренные
1. **Row-level security в одной БД** — сложно, требует консистентного применения политик на каждой таблице gbrain (а у gbrain их десятки). Один пропущенный CREATE POLICY = утечка
2. **Отдельные Postgres-кластеры на каждый брейн** — overkill, 4× процессов, 4× shared_buffers
3. **Отдельные VM на каждый брейн** — экстремальная изоляция, не нужна на нашем масштабе