5.4 KiB
5.4 KiB
ADR-0003: Изоляция брейнов через отдельные PostgreSQL БД
Дата: 2026-05-20 Статус: Принято
Контекст
ZBrain хостит несколько брейнов с разным уровнем чувствительности данных:
- zetit — внутренние процессы ZETIT, не публичные
- telerapharma — корпоративная инфраструктура с NDA
- personal — личные данные владельца
- community — данные о Смоленской 10, дом-чат, ЖКХ
Утечка MCP токена с правами на один брейн не должна давать доступ к другим брейнам.
Варианты изоляции в Postgres (от слабой к сильной):
- Одна БД, одна схема, отдельные таблицы с префиксами — слабая изоляция, любой SQL injection даёт доступ ко всему
- Одна БД, отдельные schema'ы (zetit.pages, telerapharma.pages) — изоляция через
search_path, но один Postgres user видит всё - Одна БД, отдельные пользователи с row-level security — сложно, легко допустить ошибку в RLS политиках
- Отдельные БД с отдельными пользователями — изоляция на уровне СУБД
Решение
Вариант 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 под своим пользователем и физически не видит другие БД:
-- Под пользователем gbrain_zetit
\l
-- Покажет только gbrain_zetit (и template/postgres)
\c gbrain_telerapharma
-- ERROR: permission denied for database gbrain_telerapharma
Brainhub использует свою БД brainhub под пользователем brainhub (тоже не видит остальные).
Реализация
В scripts/create-brain.sh:
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-юзера генерируется случайно и хранится:
- В
/var/lib/zbrain/brains/<name>/config.json(chmod 600, owner zbrain) - В 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, одного брейна за раз достаточно
Альтернативы рассмотренные
- Row-level security в одной БД — сложно, требует консистентного применения политик на каждой таблице gbrain (а у gbrain их десятки). Один пропущенный CREATE POLICY = утечка
- Отдельные Postgres-кластеры на каждый брейн — overkill, 4× процессов, 4× shared_buffers
- Отдельные VM на каждый брейн — экстремальная изоляция, не нужна на нашем масштабе