Множество тенанти
Всяка таблица, принадлежаща на тенант, има tenant_id. Изолацията се
осигурява от два независими слоя, така че грешка в единия не води до
изтичане на данни.
Слой 1: приложението
- Маршрутите на тенантите съдържат
{tenantID}.TenantScopeпроверява дали извикващият принадлежи към този тенант (tenant_memberships) и в противен случай отговаря с 404; глобалните администратори могат да използват всеки активен тенант. Един потребител може да принадлежи към няколко тенанта с различна роля във всеки: правата се проверяват спрямо ролята в тенанта от URL адреса, а спрените тенанти отпадат от тенантите на потребителя. - Методите на хранилищата приемат тенанта като първи аргумент
(
ListDevices(ctx, tenantID, filter)), а заявките на sqlc съдържатtenant_id = $1. - Списъци, обхващащи няколко тенанта, съществуват само в
internal/adminи изискват глобален администратор. - Fuzz тест за авторизацията (82 случая между тенанти) и тестът на RBAC матрицата (всеки маршрут × всеки вид извикващ) гарантират, че това остава вярно.
Слой 2: защита на ниво ред (row-level security) в PostgreSQL
Миграция 0007 включва и налага принудително RLS за всяка таблица, принадлежаща на тенант.
- Сървърът работи с ролята
rmm_app(NOBYPASSRLS), като се свързва директно с нея или превключва към нея чрезSET ROLE(RMM_DATABASE_ROLE). - Обхватът се пренася в контекста на заявката:
db.WithTenant(ctx, id): задава се отAuthorizeза маршрутите на тенантите и от автентикацията на агента и конектора;db.WithGlobal(ctx): worker процеси, администраторски маршрути и CLI команди.
- Кука (hook)
PrepareConnна pgx задава настройките на сесиятаapp.tenant_idилиapp.bypassпреди всяко използване на връзка от пула. Сесия наrmm_appбез тях не вижда нито един ред. - Политиките извикват
rmm_tenant_ok(tenant_id). Споделените редове (tenant_id IS NULL: глобални пакети, скриптове, правила за аларми) могат да се четат от всеки тенант. - Дъщерните таблици проверяват своята родителска таблица. Съставните
външни ключове
(tenant_id, x_id) → parent(tenant_id, id)запазват препратките в рамките на един тенант, защото проверките на външните ключове заобикалят RLS. audit_logе само за добавяне заrmm_app; изчистването минава през функциятаpurge_audit_logсъсSECURITY DEFINER.COPYне е позволено при RLS, затова масовите вмъквания минават през временна таблица (db.StagedCopy).- Операторите, които не са leakproof, не могат да използват индекси при
RLS, затова филтрите по софтуер минават през функцията
rmm_devices_with_softwareсъсSECURITY DEFINER, която прилага същото правило за тенанта.
Ad-hoc SQL от името на приложението
SET ROLE rmm_app;
SET app.tenant_id = '<tenant uuid>';
SELECT count(*) FROM devices;
Последици за администраторите на системата
- PgBouncer трябва да работи в сесиен режим: пулът на ниво транзакция би смесил настройките на сесиите на различни тенанти.
- Миграциите и резервните копия се изпълняват от собственика
rmm, който заобикаля RLS.