Ролевое управление доступом (RBAC)
RBAC привязывает разрешения к ролям, а пользователей к ролям, поэтому на любой вопрос авторизации отвечает join. Всё чисто, пока организации не нужны исключения — тогда роли плодятся быстрее людей, и вы тонете в почти-дубликатах. Здесь роли перестают масштабироваться.
Ваше приложение вышло с тремя ролями: admin, editor, viewer. Восемнадцать месяцев спустя в таблице ролей 240 строк. Есть editor, editor-no-delete, editor-eu-only, editor-finance-readonly, editor-eu-no-delete-finance-readonly. Никто не скажет вам, что на самом деле даёт role_id = 187, не прочитав миграцию-сидер. Заявка на доступ нового сотрудника висит три дня, потому что согласующий не может сообразить, какой из четырёх вариантов manager — правильный. Модель, начавшаяся как чистая абстракция над разрешениями, тихо превратилась в кашу хуже той, что заменила проверки по каждому пользователю. Это и есть взрыв ролей — предсказуемый сбой RBAC, когда реальные правила авторизации в организации перестают быть про должности и становятся про атрибуты.
К концу этого урока вы будете точно знать, как работает косвенность RBAC, почему взрыв ролей происходит комбинаторно, а не постепенно, и где модель перестаёт себя окупать.
Ключевая косвенность
Вся идея RBAC — один слой косвенности: вместо выдачи разрешений напрямую пользователям, вы выдаёте разрешения ролям, а пользователей назначаете на роли. Разрешение — это одно допустимое действие над типом ресурса: invoice:read, user:delete, report:export. Роль — именованный набор разрешений: accountant может объединять invoice:read, invoice:create, report:export. Пользователь получает одну или несколько ролей. Тогда авторизация сводится к вопросу, на который отвечает join по базе данных: содержит ли хоть одна роль этого пользователя то разрешение, которое требует запрос?
Выигрыш реален, и поэтому RBAC — выбор по умолчанию для большинства корпоративных систем. Логика выдачи живёт в одном месте на роль, а не размазана по каждому пользователю. Когда бухгалтерия получает право экспортировать новый тип отчёта, вы один раз добавляете report:export-tax в роль accountant, и сорок бухгалтеров наследуют это атомарно — никакого обновления сорока строк, никакого дрейфа, где про 23-го сотрудника забыли. Онбординг становится «назначить роль accountant» вместо «восстановить, что бухгалтеру вообще разрешено». Аудит становится «перечислить, кто держит admin» вместо обхода таблиц выдач по каждому пользователю. Отображение бизнес-языка («она менеджер») в принуждение («она может одобрять возвраты до $5k») явно и проверяемо.
Где модель окупается — и её пределы
RBAC блистает, когда авторизация действительно отслеживает организационную структуру: стабильные рабочие функции, их обозримое число и разрешения, чисто группирующиеся по функции. Больница, где каждый клиницист с ролью attending может делать ровно один и тот же набор вещей, — родная стихия RBAC. NIST формализовал это в модели, ставшей стандартом ANSI INCITS 359, включая иерархии ролей (роль senior-editor наследует все разрешения editor плюс несколько) и статическое разделение обязанностей — ограничения, запрещающие одному пользователю держать две конфликтующие роли, например никто не может быть одновременно payment-initiator и payment-approver, так что ни один человек не проведёт деньги от начала до конца в одиночку.
Эта возможность разделения обязанностей — не сноска, а зачастую причина, по которой организация вообще принимает RBAC. Sarbanes-Oxley, PCI-DSS и большинство финансовых контролей строятся на посылке, что чувствительные действия требуют двух разных людей. RBAC кодирует это напрямую как ограничение между ролями, и аудитор может проверить его, осмотрев назначения ролей, а не прослеживая каждую транзакцию.
| Понятие | Что это | Пример | Почему важно |
|---|---|---|---|
| Разрешение | Одно действие над типом ресурса | invoice:export | Атомарная единица; пользователям напрямую не выдаётся |
| Роль | Именованный набор разрешений | accountant | Правка выдач в одном месте; 40 пользователей наследуют атомарно |
| Иерархия ролей | Роль наследует разрешения другой | senior-editor ⊃ editor | Избегает повторного перечисления общих разрешений |
| Разделение обязанностей | Запрет на две конфликтующие роли | not(initiator ∧ approver) | Контроль SOX/PCI; никто не двигает деньги в одиночку |
| Взрыв ролей | Роли плодятся, кодируя каждое исключение | editor-eu-no-delete | Сбой: ролей больше, чем людей |
Взрыв ролей: комбинаторный сбой
Вот механизм, и важно, что он комбинаторный, а не линейный. У RBAC ровно одно измерение вариативности — роль. Любое правило авторизации, которое вы хотите выразить, приходится сворачивать в то, какую роль держит пользователь. Пока ваши правила про «какую работу ты делаешь», всё в порядке. Беда начинается в тот момент, когда правило зависит от чего-то, что не является работой, — от региона (данные ЕС против США), атрибута ресурса (только свой отдел), окружения (prod против staging), области видимости (вот этот один проект). RBAC не может сказать «editor, но только для записей ЕС». Он может лишь отчеканить новую роль: editor-eu.
Теперь усложните. Добавьте исключение «без удаления»: нужны editor-eu, editor-eu-no-delete, editor-no-delete. Добавьте три отдела, два существующих региона и флаг удаления: это потенциально 3 × 2 × 2 = 12 ролей, чтобы выразить то, что на самом деле — одна роль плюс три независимых атрибута. Каждое новое ортогональное измерение умножает число ролей, а не прибавляет к нему. Реальные системы рутинно переваливают за 1000 ролей на несколько тысяч пользователей; в этой точке таблица ролей уже не абстракция — это денормализованный кэш пользовательской политики с интерфейсом хуже того, что он заменил. Ревью становятся бессмысленными, потому что ни один человек не удержит 1000 определений ролей в голове, а избыточная выдача прав подкрадывается, потому что путь наименьшего сопротивления — выдать чуть слишком мощную существующую роль, а не чеканить ещё одну точную.
▸Почему это работает
Почему добавление атрибута умножает роли, а не прибавляет одну? Потому что RBAC некуда положить атрибут, кроме как внутрь идентичности роли. Правило «может править, в ЕС, кроме удалений» — это три независимых факта да/нет. Всё с N независимыми бинарными атрибутами требует до 2^N ролей, чтобы представить каждую достижимую комбинацию, потому что роль — единственный носитель состояния в модели. Лекарство — не больше ролей, а вторая ось у движка политики (атрибуты ресурса и запроса), что и есть скачок к атрибутному управлению доступом. Изящество RBAC и его потолок — одно и то же свойство: одно измерение.
Когда роли перестают масштабироваться — и что делает зрелый инженер
Зрелое суждение — распознать взрыв рано, пока таблица ролей не окаменела. Признак прост: когда имена ролей начинают нести атрибуты (-eu, -no-delete, -readonly, -projectX), ваши правила авторизации переросли одно измерение. Дисциплинированные ответы, грубо по порядку: держите роли строго про рабочую функцию и сдвиньте контекстную часть решения (принадлежит ли запись отделу вызывающего? это ли тенант ЕС?) в проверку авторизации приложения как ограниченное условие — роль даёт edit И record.region == user.region. Этот гибрид сохраняет проверяемые грубые выдачи RBAC, позволяя небольшому набору проверок отношений/атрибутов справляться с разрастанием. Полное обобщение — ABAC, где политика есть функция от атрибутов субъекта, ресурса, действия и окружения, но это более тяжёлый движок, и большинство команд сначала тянется к гибриду, потому что чистый RBAC — всё ещё самое аудируемое, что можно вручить ревьюеру комплаенса.
Существует роль `editor`. Продукт теперь хочет, чтобы редакторы меняли только записи своего региона. Выберите ответ, лучше всего избегающий взрыва ролей и остающийся принуждаемым.
Какова корневая причина взрыва ролей в RBAC?
Почему разделение обязанностей часто и есть причина, по которой организация принимает RBAC изначально?
Упорядочьте шаги, которые RBAC делает, отвечая на запрос авторизации, от первого к последнему:
- 1 Определить разрешение, нужное запросу (например, invoice:read)
- 2 Резолвить, какие роли назначены вызывающему пользователю
- 3 Развернуть каждую роль в её набор разрешений
- 4 Разрешить, только если какая-то роль содержит нужное разрешение; иначе запрет по умолчанию
- 01Объясните ключевую косвенность RBAC и конкретный операционный выигрыш, который она даёт по сравнению с выдачей разрешений напрямую пользователям.
- 02Опишите взрыв ролей: почему он комбинаторный, а не постепенный, и что зрелый инженер делает вместо чеканки новых ролей.
RBAC вставляет один слой косвенности — разрешения принадлежат ролям, пользователям назначаются роли, — поэтому любой вопрос авторизации схлопывается в join: содержит ли хоть одна роль вызывающего нужное разрешение? Это даёт реальный рычаг: выдачи, управляемые в одном месте на рабочую функцию, атомарные изменения для всех в роли, тривиальные онбординг и аудит и встроенные ограничения разделения обязанностей, кодирующие контроли SOX/PCI, проверяемые аудиторами. RBAC прекрасно масштабируется, пока авторизация отслеживает организационную структуру. Он перестаёт масштабироваться, когда правила начинают зависеть от атрибутов, которые роль не может выразить, — региона, владения, окружения, области видимости, — потому что единственный ответ RBAC — отчеканить новую роль, а ортогональные атрибуты умножают роли комбинаторно (до 2^N для N бинарных атрибутов), порождая взрыв ролей: 1000+ ролей, непроверяемых, избыточно выданных. Зрелый ход — заметить признак (имена ролей, несущие атрибуты) и сдвинуть контекстную часть решения в ограниченную серверную проверку, держа роли про рабочую функцию. Теперь, видя роль с именем editor-eu-no-delete, вы знаете: модель говорит вам, что у неё кончились измерения.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.