104 lines
5.4 KiB
Markdown
104 lines
5.4 KiB
Markdown
# 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 на каждый брейн** — экстремальная изоляция, не нужна на нашем масштабе
|