Преминете към основното съдържание

Множество тенанти

Всяка таблица, принадлежаща на тенант, има 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.