Когда приложение получает несколько организаций или изолированных окружений, появляется соблазн решить задачу одним столбцом realm_id. Это начало модели, но ещё не изоляция.
Почему ключа недостаточно
Предположим, что таблица пользователей содержит уникальные UUID и идентификатор realm. Если API получает UUID пользователя и выбирает его без ограничения по realm, он может вернуть объект из соседнего контура. Даже если внешний UI показывает только «своих», клиент способен отправить другой идентификатор напрямую.
Изоляция — свойство всего маршрута запроса: HTTP-обработчика, сервисного слоя, SQL-фильтра, кэша и событий. Каждая часть должна сохранять контекст арендатора.
Где граница чаще всего теряется
- Поиск по ID. UUID трудно угадать, но он не заменяет авторизацию.
- Связанные сущности. При назначении роли пользователю нужно проверить realm обеих записей.
- Кэш. Ключ и область инвалидирования не должны смешивать разные домены полномочий.
- Административные операции. Передача realm из параметра запроса ещё не означает права управлять этим realm.
Практический тест
Создайте realm A и realm B, пользователей и роли в каждом. Затем попытайтесь от имени оператора A прочитать или изменить сущность B через каждый административный endpoint. Недостаточно проверить только ответы на чтение: операции назначения и удаления связей столь же важны.
Как это связано с RustCloak
В проекте роль realm прослеживается в слое RBAC и административных операциях. Для уверенности нужны интеграционные тесты, которые доказывают невозможность межарендных операций после любых изменений.
Отдельно важно проверять негативные сценарии и поведение после инвалидирования кэша. Доступ, который был отозван, не должен оставаться доступным дольше, чем допускает выбранная модель консистентности.
Безопасный запрос к данным
Вместо поиска пользователя только по ID используйте одновременно ID субъекта и realm, который был установлен и проверен серверной частью. Это особенно важно для административных mutations, где участвующие сущности могут быть переданы разными аргументами.
-- Концептуальный пример, не SQL проекта
SELECT id, email
FROM users
WHERE id = $1 AND realm_id = $2;Однако даже такой фильтр не заменяет проверку, вправе ли вызывающий оператор работать в указанном realm. Иными словами: фильтрация данных и решение об авторизации — две отдельные задачи, и обе обязательны.
Проверяйте операции в обе стороны
Частая ошибка — тестировать только попытку читать пользователя другого realm. Изменяющие методы требуют отдельной матрицы. Например: пользователь из A, роль из B; пользователь из A, группа из B; удаление назначения из B через endpoint realm A; попытка назначить составную роль, включающую запрещённую роль.
Негативные сценарии полезно запускать от обычного администратора realm, от глобального оператора и от пользователя без административных прав. Статус ответа должен соответствовать договорённой политике ошибок и не раскрывать чувствительную информацию из соседнего realm.
События тоже содержат контекст
Запись в журнале административных операций должна позволять понять, какой realm был затронут, кем и какой результат получен. Но аудит не должен превращаться в хранилище секретов, JWT или персональных данных сверх потребности расследования.
Критерий завершения
Изоляцию можно считать проверяемой, когда для каждого публичного и административного маршрута существует тест на чужой realm, а все добавляемые методы проходят те же инварианты. Конкретную реализацию этого инварианта нужно подтверждать тестами и ревью, а не доверять только наличию SQL-фильтра.