От объекта к контексту
Аннотация
Большинство моделей доступа начинается с вопроса о том, что разрешено пользователю. Но в реальных системах этого недостаточно.
Доступ зависит не только от пользователя и его роли. Он зависит от объекта, отношений между объектами и субъектами, области действия этих отношений, состояния объекта, публикации, делегирования и других фактов, имеющих значение для конкретного действия.
Поэтому доступ становится не свойством пользователя и не свойством объекта. Он становится результатом отношения между ними. Эта книга рассматривает путь от объекта к контексту доступа.
Сначала объект рассматривается как предмет действия. Затем вокруг него выделяются отношения и факты, имеющие значение для безопасности. Из них формируется контекст. Правила применяются к этому контексту и порождают эффективное разрешение. После этого разрешение должно быть не только вычислено, но и применено к физическим данным. Так появляется полная цепочка:
Объект
→ Отношения
→ Контекст
→ Правила
→ Эффективное разрешение
→ Авторизация
→ Доступ к данным
Особое внимание уделено производным security-моделям, проекциям, согласованности, восстановлению и границе между авторизацией и физическим применением ограничений к данным.
При этом книга не предлагает единственный технологический способ реализации. Одна и та же модель может быть реализована в приложении, отдельном сервисе авторизации, базе данных или их комбинации.
Главная идея книги проста:
Доступ нельзя правильно построить, не понимая отношений, которые существуют вокруг объекта.
Поэтому архитектура безопасности начинается не с таблицы разрешений. Она начинается с понимания самого объекта и контекста, в котором над ним совершается действие.
кто способен видеть то, что связывает отдельное.
Когда человек смотрит на чашку чая и видит в ней остывающее Солнце, а в шуме вентилятора ноутбука — отголосок грядущей тепловой смерти Вселенной, он преодолевает хаос.
Он объединяет «отдельное».
Чашка перестаёт быть просто чашкой. Шум — просто шумом. За отдельными явлениями обнаруживается связь, а за связью — целое.
В этом и начинается понимание: не в способности увидеть больше объектов, а в способности увидеть то, что их связывает.
Пролог
Обычно разговор о доступе начинается с пользователя.
Есть пользователь.
У него есть роль.
У роли есть разрешения.
Получается простая цепочка:
User → Role → Permission
Для простой системы этого может быть достаточно. Но затем появляется первый объект, доступ к которому нельзя описать только ролью. Один документ доступен одному отделу, но не другому. Один проект доступен нескольким группам. Один пользователь является участником проекта, но не владельцем конкретного документа. Один объект опубликован в одной области, но остаётся скрытым в другой.
Пользователь может иметь право читать объект, но не изменять его. Другой пользователь может иметь право изменять, но только пока объект находится в определённом состоянии. А иногда доступ возникает не из одного отношения, а из нескольких.
Например:
User
↓ member of
Group
↓ has role in
Project
↓ contains
Object
Ни одна из этих связей сама по себе не является разрешением. Но вместе они могут стать основанием для него. Здесь привычная модель начинает ломаться.
Проблема не в том, что в системе недостаточно ролей. Проблема в том, что роль описывает только одну часть отношений, существующих вокруг объекта.
От пользователя к объекту
Чтобы понять доступ, приходится задать другой вопрос:
Что именно пользователь пытается сделать?
Теперь появляются три элемента:
Subject → Action → Object
Субъект выполняет действие над объектом. Но и этого недостаточно. Нужно знать, почему субъект связан с объектом.
Он владелец? Участник проекта? Член группы? Получил делегирование? Имеет роль в определённой области? Объект опубликован там, где эта роль действует?
Одного ответа может быть недостаточно. Поэтому между объектом и решением возникает ещё один уровень.
Контекст.
Что означает контекст
Слово «контекст» легко превратить в ещё один технический термин.
Компания.
Проект.
Группа.
Tenant.
Scope.
Все они могут оказаться частью конкретного контекста. Но ни один из них сам по себе контекстом не является.
Контекст — это совокупность фактов и отношений, которые имеют значение для конкретного решения.
Поэтому один и тот же объект может иметь разные контексты доступа для разных субъектов. И даже для одного субъекта контекст может отличаться в зависимости от действия.
Например:
User A → READ → Object X
и:
User A → UPDATE → Object X
могут использовать разные правила.
Контекст не является ещё одним разрешением. Он является тем, на основании чего разрешение может быть вычислено.
Отношения важнее списка разрешений
Если смотреть только на итоговое право, легко потерять причину его возникновения.
Можно увидеть:
User A → READ → Object X
и не знать, почему это разрешение существует. Но в реальной системе причина может быть важнее самого результата. Пользователь получил доступ потому, что:
User A
↓
member of Group G
↓
Group G
↓
has role in Project P
↓
Project P
↓
contains Object X
Если удалить одно из отношений, доступ может исчезнуть. Следовательно, изменение отношения становится одновременно изменением модели безопасности. Это одна из центральных идей книги.
Отношения не являются разрешениями
Здесь легко совершить другую ошибку. Если группа связана с проектом, это ещё не означает, что каждый член группы может выполнять над каждым объектом проекта любые действия. Отношение сообщает:
Кто с кем связан.
Правило определяет:
Что эта связь означает для конкретного действия.
Поэтому между отношением и решением появляется ещё один уровень:
Relationship
↓
Context
↓
Rule
↓
Effective permission
↓
Decision
Именно здесь находится основная работа модели доступа.
От логической модели к данным
Даже после получения ALLOW задача не заканчивается. Разрешение относится к логическому объекту. Но данные находятся физически. Один объект может соответствовать нескольким строкам. Несколько объектов могут находиться в одной таблице. Один запрос может читать данные сразу для множества объектов. Поэтому возникает второй вопрос:
Как не просто принять решение, а физически не допустить запрещённые данные?
Здесь появляются Row-Level Security (RLS, ограничение доступа на уровне строк), фильтрация запросов, представления и другие механизмы. Но это уже другой уровень ответственности.
Авторизация определяет допустимость действия. Механизм применения ограничивает физические данные.
Зачем нужна вся эта сложность
Можно задать справедливый вопрос:
Не проще ли просто использовать роли?
Иногда — да. Если система проста, простая модель лучше сложной. Но сложность нельзя устранить, если она уже существует в предметной области. Если в системе есть:
много субъектов
+
много объектов
+
отношения между ними
+
иерархии
+
области действия
+
публикации
+
делегирование
+
массовый доступ к данным
то эти отношения всё равно существуют. Их можно либо выразить в модели явно, либо спрятать в наборе специальных исключений, условий и фильтров. Второй вариант не делает систему проще. Он только делает модель менее заметной.
О чём эта книга
Эта книга не начинается с конкретной технологии. Она начинается с вопроса:
Что должно быть известно системе, чтобы принять правильное решение о доступе?
Ответ постепенно раскрывается. Нужно знать объект. Затем отношения вокруг него. Затем выделить среди них те, которые имеют значение для конкретного действия.
Из этих фактов формируется контекст. К контексту применяются правила. Получается эффективное разрешение.
После этого решение должно быть применено к данным. Так возникает последовательность:
Object
↓
Relationships
↓
Relevant facts
↓
Context
↓
Rules
↓
Effective permission
↓
Authorization
↓
Data enforcement
Эта последовательность и будет предметом книги. Не как конкретный продукт. Не как конкретный фреймворк. И не как единственно возможная реализация.
А как способ смотреть на систему, в которой доступ определяется не одним свойством пользователя, а отношениями между объектами, субъектами и контекстом их взаимодействия.
Глава 1. Почему user → role → permission перестаёт работать
Часть I. Почему доступ становится сложным
Глава 1. Почему user → role → permission перестаёт работать
В большинстве информационных систем доступ начинается с простой модели.
Есть пользователь. У пользователя есть роль. У роли есть разрешения. Например:
Пользователь → Роль → Разрешение
Пользователь Alice имеет роль Engineer. Роль Engineer позволяет читать и изменять данные. Значит, можно записать:
Alice → Engineer → READ, UPDATE
Модель проста. Она легко объясняется, легко реализуется и хорошо работает, пока система действительно отвечает на простой вопрос:
Что этот пользователь вообще может делать?
Но в реальной системе этого вопроса обычно недостаточно.
1.1 Право должно относиться к чему-то
Представим, что в системе есть два проекта:
Project A
Project B
Alice работает с первым проектом. Bob — со вторым. При этом оба имеют одну и ту же роль:
Alice → Engineer
Bob → Engineer
И одинаковые разрешения:
READ
UPDATE
Если смотреть только на роли, пользователи полностью эквивалентны. Но это не означает, что они должны иметь одинаковый доступ. Alice должна иметь возможность изменить документ проекта A. Bob — документ проекта B. Поэтому вопрос уже нельзя сформулировать просто:
Может ли Alice выполнять
UPDATE?
Нужно спросить:
Может ли Alice выполнить
UPDATEнад этим объектом?
Появляется объект.
Пользователь → Разрешение → Объект
Например:
Alice → UPDATE → Document A
Это уже другая модель. Разрешение перестаёт быть свойством пользователя.
Оно становится отношением между пользователем, действием и объектом.
1.2 Но одного объекта тоже недостаточно
Теперь добавим структуру. Пусть проект содержит группы:
Project A
├── Design
├── Manufacturing
└── Archive
Документы находятся внутри этих групп. Alice работает с Design. Bob — с Manufacturing. Тогда два документа одного проекта могут иметь разный уровень доступности для одного и того же пользователя.
Alice → Design ✓
Alice → Manufacturing ?
Если Document A находится в Design, а Document B — в Manufacturing, вопрос доступа выглядит так:
Alice → READ → Document A
Alice → READ → Document B
Но чтобы ответить на него, недостаточно знать только Alice и документ. Нужно знать, как документ связан с группой. И как группа связана с проектом. И как Alice связана с этой структурой. Возникает цепочка отношений:
Alice
↓
Design
↓
Project A
↓
Document A
Теперь доступ определяется уже не одним фактом.
Он определяется структурой отношений.
1.3 Роль не исчезает
Это не означает, что роли больше не нужны. Наоборот, роль по-прежнему удобно описывает набор действий. Например:
Engineer
READ
UPDATE
Manager
READ
UPDATE
APPROVE
Проблема в другом. Роль отвечает на вопрос:
Какие действия в принципе доступны субъекту?
Но она не отвечает на вопрос:
Над какими объектами эти действия доступны?
Поэтому две части модели нужно разделить. Первая:
Engineer → READ, UPDATE
Вторая:
Alice → Project A
Только вместе они позволяют получить ответ:
Alice
+ Engineer
+ Project A
↓
READ / UPDATE
↓
объекты Project A
Роль стала частью модели доступа, но перестала быть всей моделью.
1.4 Появляется область действия
Можно попробовать исправить исходную модель, добавив область действия:
Пользователь → Роль → Разрешение → Область
Например:
Alice
↓
Engineer
↓
UPDATE
↓
Project A
Это уже ближе к реальной системе. Но сразу возникает следующий вопрос:
Что именно является областью действия?
Проект?
Группа?
Организация?
Конкретный объект?
Или несколько из них одновременно?
Например, документ может быть доступен пользователю потому, что:
- пользователь работает в той же организации;
- пользователь имеет роль в проекте;
- пользователь состоит в определённой группе;
- пользователь является владельцем объекта;
- объект опубликован в доступной области;
- доступ был явно предоставлен другому пользователю или организации.
Все эти случаи различны. Но результат у них один:
субъект получает возможность выполнить действие над объектом.
Следовательно, нам недостаточно просто добавить ещё одно поле scope. Нужно понять, каким образом разные факты участвуют в формировании доступа.
1.5 Право может зависеть и от объекта
До сих пор мы рассматривали права пользователя. Но объект тоже может ограничивать доступ.
Представим, что Alice имеет:
READ
UPDATE
APPROVE
А объект опубликован только с разрешениями:
READ
UPDATE
Наличие APPROVE у Alice само по себе ещё не означает, что она может утвердить этот объект. Получается пересечение:
Права субъекта
∩
Ограничения объекта
∩
Требуемое действие
Например:
Права Alice:
READ + UPDATE + APPROVE
Права публикации:
READ + UPDATE
Запрошено:
APPROVE
Результат:
APPROVE ∉ (Alice ∩ Publication)
Доступ не возникает.
Таким образом, право уже нельзя представить как простое свойство пользователя. Оно возникает из нескольких независимых источников.
1.6 Владелец — тоже отдельный случай
Есть ещё одна распространённая ситуация.
Пользователь создаёт объект. Можно сказать:
Создатель имеет доступ к созданному объекту.
Но это не обязательно означает, что создатель и владелец — одно и то же понятие. И тем более это не означает, что создатель навсегда сохраняет все права на объект.
Объект может быть передан другому владельцу. Может быть опубликован для других пользователей. Может быть закрыт. Может получить делегированный доступ.
Поэтому в модели появляются разные факты:
Кто создал объект?
Кто является владельцем?
Кому объект опубликован?
Какие действия разрешены?
В какой области действует разрешение?
Если свести всё это к одному полю user_id, модель начнёт терять смысл.
1.7 Доступ становится отношением
На этом этапе полезно изменить сам способ мышления. Вместо:
У пользователя есть разрешение
нужно говорить:
У субъекта есть возможность выполнить действие
над определённым объектом
в определённых условиях.
Это уже не свойство одного объекта. Это отношение. В самом общем виде его можно записать:
Subject → Action → Object
Но и этого недостаточно. Потому что один и тот же субъект может иметь разные права на один и тот же объект в разных обстоятельствах. Поэтому появляется четвёртая составляющая:
Subject → Action → Object
↑
Context
Именно здесь возникает понятие контекста. Пока мы не определяем его точно. Это важно. Контекст — не просто ещё одно поле.
Это совокупность условий и отношений, относительно которых определяется доступ.
1.8 Контекст может быть составным
Например, один и тот же объект может рассматриваться в разных контекстах:
Организация A
↓
Проект P
↓
Группа G
↓
Документ D
Для одного пользователя контекстом может быть организация. Для другого — проект. Для третьего — группа. В другом сценарии существенным окажется владение объектом. В ещё одном — явное предоставление доступа.
Поэтому контекст нельзя заранее свести к одному универсальному справочнику. Он возникает из самой модели системы. Это важное отличие от попытки просто добавить к роли ещё одно поле scope. Scope может быть одним из элементов контекста.
Но контекст шире.
1.9. Почему нельзя вычислять всё заново при каждом запросе
Можно попытаться оставить модель полностью динамической. При каждом обращении к объекту выполнять примерно такой алгоритм:
1. Найти субъекта.
2. Найти его роли.
3. Найти его отношения с группами.
4. Обойти иерархию.
5. Найти связи объекта.
6. Найти публикации.
7. Проверить владельца.
8. Проверить дополнительные предоставления.
9. Сопоставить разрешения.
10. Принять решение.
Для небольшой системы это возможно. Но по мере роста системы стоимость такого подхода увеличивается. Причём растёт не только количество данных. Растёт количество отношений. Изменение одного факта может затронуть множество результатов.
Например:
Alice вступила в Group G
может изменить доступ к сотням объектов.
Изменение роли:
Alice получила UPDATE в Project P
может изменить доступ к тысячам объектов проекта.
Изменение публикации:
Document D опубликован в Project P
может изменить доступ для большого числа пользователей.
Получается интересная ситуация. Исходных фактов относительно немного:
роль
членство
иерархия
владелец
публикация
предоставление доступа
Но производных решений может быть очень много.
Это подводит нас к следующему архитектурному вопросу:
Нужно ли каждый раз заново вычислять последствия изменения исходных фактов?
1.10. От исходных фактов к модели доступа
Вместо этого можно разделить два процесса.
Первый процесс изменяет исходные факты:
Alice вступила в Group G
Второй определяет, какие последствия это имеет для модели доступа:
Alice
↓
Group G
↓
доступные области
↓
доступные объекты
То есть появляется промежуточный слой.
Исходные факты
↓
Модель безопасности
↓
Решение о доступе
Это важное архитектурное разделение. Исходные данные описывают что произошло.
Модель безопасности описывает к чему это приводит с точки зрения доступа.
А проверка доступа отвечает уже на конкретный вопрос:
Может ли Alice выполнить UPDATE над Document D?
1.11. Меняется и смысл роли
В такой модели роль перестаёт быть центром системы. Она становится одним из исходных фактов.
Например:
Alice имеет роль Engineer
Это ещё не готовое разрешение на конкретный объект. Такое разрешение может появиться только после учёта других фактов:
Alice
├── имеет роль Engineer
├── состоит в Group G
├── Group G находится в Project P
└── Document D доступен через Project P
Из этих фактов можно получить производный результат:
Alice → UPDATE → Document D
Но этот результат уже не является непосредственно назначенной ролью. Он является эффективным правом — результатом применения модели безопасности к совокупности исходных фактов.
Это различие станет одним из центральных в дальнейшем.
1.12. От простой таблицы ролей к модели отношений
В начале у нас была простая схема:
User → Role → Permission
Затем появился объект:
User → Permission → Object
Затем области и отношения:
User
├── Role
├── Group
├── Project
└── Ownership
Object
├── Group
├── Project
├── Publication
└── Ownership
И наконец появляется модель:
Subject
│
┌────────┼────────┐
│ │ │
Role Group Ownership
│ │ │
└────────┼────────┘
↓
Context
↓
Object
↓
Action
Это уже не модель назначения ролей. Это модель отношений. Она позволяет задать более точный вопрос:
Имеет ли данный субъект право выполнить данное действие над данным объектом в данном контексте?
Именно этот вопрос мы будем развивать дальше.
Но прежде чем строить механизм его вычисления, необходимо разобраться с самим понятием контекста. Потому что если назвать контекстом всё, что влияет на доступ, не определив его границы, мы просто перенесём сложность из одной таблицы в другую.
Следующая задача — понять, что такое контекст и почему он является частью самой модели объекта, а не просто параметром запроса.
Глава 2. Доступ — это отношение
Глава 2. Доступ — это отношение
2.1. От разрешения к действию над объектом
Простая модель доступа выглядит так:
User → Role → Permission
Она полезна, пока вопрос звучит достаточно абстрактно:
Что вообще может делать этот пользователь?
Но реальные системы задают другой вопрос:
Может ли этот субъект выполнить конкретное действие над конкретным объектом?
Тогда появляется объект:
Subject → Action → Object
Subject — субъект действия.
Action — действие.
Object — объект, над которым выполняется действие.
Например:
Иван → READ → Документ X
или:
Сервис A → UPDATE → Заказ Y
Теперь разрешение нельзя рассматривать как свойство одного пользователя. Оно относится к конкретному отношению между субъектом, действием и объектом.
2.2. Субъект и объект
Субъектом может быть пользователь, группа, сервис или другая сущность, которой модель разрешает действовать.
Объектом является то, над чем выполняется действие. Важно, что объект не обязан быть строкой базы данных. Физическая строка — это способ хранения. Объект — понятие предметной модели. Один объект может быть представлен несколькими строками, таблицами или даже несколькими хранилищами. Поэтому вопрос доступа начинается не с SQL-запроса:
к какой строке разрешён доступ?
а с вопроса:
c каким объектом выполняется действие?
Только после этого можно определить, какие физические данные должны быть доступны.
2.3. Действие
Даже субъект и объект ещё не определяют решение. Нужно знать действие. Для одного и того же объекта могут существовать разные права:
READ
UPDATE
CLOSE
APPROVE
Субъект может иметь право читать объект, но не изменять его. Может изменять, но не закрывать. Может согласовывать, но не изменять. Поэтому:
Access(Object)
слишком грубая формулировка. Нужна как минимум:
Can(Subject, Action, Object)
2.4. Откуда появляется отношение
Связь между субъектом и объектом редко возникает непосредственно. Она может быть следствием других отношений. Например:
User
↓ member of
Group
↓ participates in
Project
↓ publishes
Object
Или:
User
↓ assigned
Role
↓ valid in
Project
↓ applies to
Object
Или:
User
↓ owns
Object
Каждая такая связь является фактом. Но ни одна из них сама по себе ещё не обязана быть разрешением. Членство в группе — не право READ. Владение объектом — не обязательно право APPROVE. Публикация объекта — не разрешение выполнить любое действие.
Чтобы получить разрешение, нужно определить смысл отношений в конкретной модели.
2.5. Одно право может иметь несколько оснований
Предположим, пользователь может читать документ потому, что:
- он владелец;
- он участник проекта;
- его группа получила доступ;
- объект опубликован в области, доступной его роли.
Это разные основания. Но результат может быть один:
READ
Следовательно, модель должна различать:
основание доступа
и
эффективное разрешение
Несколько отношений могут привести к одному результату. Изменение одного отношения при этом не обязательно изменит итоговое право: другое основание может его сохранить.
Поэтому проверять только одну связь недостаточно.
2.6. Доступ как отношение между сущностями
Такой способ мышления приводит к классу моделей, в которых доступ определяется отношениями между сущностями. Один из распространённых терминов для такого подхода — ReBAC (Relationship-Based Access Control, управление доступом на основе отношений).
Смысл здесь не в конкретном продукте или протоколе. Смысл в том, что отношения становятся частью основания решения:
Subject
↓
Relationships
↓
Object
Роль, членство, владение, публикация, принадлежность к проекту или делегирование становятся входными данными модели. Но это ещё не означает, что любая существующая связь автоматически даёт право.
Связь должна иметь определённую семантику.
2.7. Отношение ещё не объясняет решение
Пусть известно:
User U is member of Group G
Из этого нельзя автоматически вывести:
U can READ Object O
Нужно знать:
- связан ли
GсO; - в какой области действует связь;
- какое действие рассматривается;
- какие права имеет отношение;
- есть ли дополнительные ограничения;
- не изменяет ли состояние объекта применимость правила.
Поэтому между отношением и разрешением появляется ещё один уровень.
Это контекст.
Контекст связывает релевантные факты с конкретным вопросом доступа.
2.8. От отношения к контексту
Первый вопрос системы:
Can(Subject, Action, Object)?
Но чтобы на него ответить, нужно знать не только субъект, действие и объект. Нужно определить:
какие отношения и факты имеют значение именно для этого решения?
Это уже другой вопрос. Из него возникает следующий уровень модели:
Object
↓
Relationships
↓
Relevant facts
↓
Context
↓
Rules
↓
Effective permission
↓
Access decision
В следующей главе мы разберём, что именно означает контекст и почему он не является просто ещё одним названием для области действия права.
Глава 3. Что такое контекст
В предыдущей главе мы пришли к простой, но важной формуле:
Subject → Action → Object
Она показывает, что доступ — это отношение.
Но для сложной системы этого всё ещё недостаточно.
Одного объекта недостаточно, чтобы определить доступ к нему.
Одного субъекта тоже недостаточно.
Даже сама связь между ними не всегда однозначна.
Один и тот же пользователь может иметь разные права на один и тот же объект в зависимости от того, в рамках какой ситуации рассматривается запрос.
Эту совокупность условий и отношений будем называть контекстом.
3.1. Контекст — это не место
В обычной речи слово «контекст» часто означает окружение некоторого события.
Для архитектуры этого определения недостаточно.
Контекст доступа — это не просто место, где находится объект.
Это не обязательно:
Company
Project
Group
Tenant
Scope
Каждый из этих элементов может быть частью контекста, но ни один из них сам по себе контекстом не является.
Контекст возникает тогда, когда несколько фактов вместе определяют смысл отношения между субъектом, действием и объектом.
Например:
Alice
│
├── роль → Engineer
│
├── участник → Project A
│
└── член → Group G
Document D
│
├── принадлежит → Project A
├── опубликован → Group G
└── владелец → Bob
Каждая строка здесь описывает отдельный факт.
Но решение о доступе возникает только тогда, когда эти факты рассматриваются вместе.
3.2. Контекст отвечает на вопрос «в какой ситуации?»
Сравним два запроса.
Alice → READ → Document A
Alice → UPDATE → Document A
Субъект и объект одинаковы.
Но действия разные.
Теперь другой случай:
Alice → READ → Document A
Alice → READ → Document B
Субъект и действие одинаковы.
Но объекты разные.
Наконец, может измениться не субъект, не действие и не объект, а условия, при которых рассматривается запрос.
Например:
Alice → READ → Document A
может быть допустимо в рамках одного проекта и недопустимо в рамках другого.
Само утверждение Alice → READ → Document A не содержит информации, почему оно допустимо.
Контекст как раз и связывает отдельные факты в одну ситуацию, в которой это решение становится определённым.
3.3. Контекст состоит из фактов
Важно не превращать контекст в ещё один магический объект.
Контекст — это прежде всего набор значимых фактов.
Например:
Subject:
Alice
Action:
READ
Object:
Document A
Relevant facts:
Alice — member of — Group G
Group G — access to — Document A
Alice — role in — Project P
Document A — belongs to — Project P
Эти факты образуют контекст рассматриваемого запроса.
Другой запрос может использовать другой набор фактов.
Поэтому контекст нельзя понимать как заранее заданную папку, область или контейнер.
Он формируется относительно конкретной ситуации.
3.4. Почему одного факта недостаточно
Предположим:
Alice → Engineer
Этого недостаточно.
Нужно знать:
Engineer → какие действия?
Engineer → где действует роль?
Engineer → над какими объектами?
Теперь допустим, мы знаем:
Alice → Engineer → Project A
Это уже больше информации.
Но всё ещё может потребоваться:
Document A → опубликован в Project A
Или:
Document A → относится к Group G
Alice → member of → Group G
Каждый новый факт уточняет ситуацию.
Можно представить это как постепенное сужение множества возможных решений:
Пользователь
↓
роль
↓
область действия роли
↓
отношение к объекту
↓
свойства объекта
↓
требуемое действие
↓
решение
Контекст появляется не в одной точке.
Он формируется из фактов, которые имеют значение для этого решения.
3.5. Контекст связывает отношения
В предыдущей главе мы рассматривали отношения по отдельности.
Например:
Alice → member of → Group G
Group G → access to → Document A
Теперь можно увидеть, что эти отношения связаны.
Первое отвечает на вопрос:
К какой группе относится Alice?
Второе:
К каким объектам относится Group G?
Вместе они могут дать основание для ответа:
Может ли Alice читать Document A?
То есть контекст позволяет рассматривать не отдельные связи, а их совокупность.
Это принципиально важно.
Сложный доступ возникает не потому, что в системе существует много разрешений.
Он возникает потому, что решение зависит от большого количества взаимосвязанных фактов.
3.6. Контекст зависит от объекта
Один и тот же пользователь может находиться сразу в нескольких контекстах.
Alice может:
иметь роль Engineer в Project A;
иметь роль Reviewer в Project B;
быть участником Group G;
быть владельцем Document C.
Поэтому нельзя сказать просто:
«Контекст Alice такой-то».
Контекст всегда рассматривается относительно некоторого вопроса.
Например:
Can(Alice, READ, Document A)
и
Can(Alice, READ, Document B)
могут использовать разные части информации о Alice.
Контекст — не постоянная характеристика пользователя.
Он возникает относительно конкретного отношения доступа.
3.7. Контекст зависит и от действия
Это ещё одно важное свойство.
Для чтения объекта может быть достаточно одной связи:
Alice → member of → Group G
Для изменения может потребоваться другая:
Alice → role → Editor
Для утверждения:
Alice → role → Approver
Поэтому нельзя построить один универсальный контекст доступа и считать, что он одинаково отвечает на все вопросы.
Контекст определяется не только субъектом и объектом, но и тем, какое действие рассматривается.
3.8. Контекст может включать временные условия
Иногда отношения действуют не постоянно.
Например:
Alice → delegated access → Document A
может действовать только:
с 1 сентября
по 30 сентября.
Само отношение существует.
Но для запроса 15 сентября оно действительно, а для запроса 1 октября — уже нет.
Значит, время становится частью контекста.
То же самое относится к другим условиям:
состояние объекта;
тип операции;
организационная область;
источник запроса;
делегирование;
статус отношения.
Не каждое из них обязательно присутствует в каждой системе.
Но принцип остаётся тем же:
Контекст содержит те факты, без которых нельзя корректно интерпретировать отношение доступа.
3.9. Контекст не равен области действия
Здесь особенно легко сделать ошибку.
Допустим, у нас есть:
Project A
Project B
Можно сказать:
Роль Alice действует в Project A.
Это область действия роли.
Но сам контекст шире.
Он может включать:
Alice → роль → Engineer
Engineer → UPDATE
роль → действует → Project A
Document D → находится → Project A
Document D → опубликован → Group G
Alice → member of → Group G
Project A здесь только один из фактов.
Если заменить весь контекст одним Project A, мы потеряем остальные отношения.
Именно поэтому попытка решить сложную модель доступа одним полем scope быстро упирается в ограничения.
3.10. Контекст не является разрешением
Есть ещё одно принципиальное различие.
Контекст отвечает на вопрос:
Какие факты относятся к рассматриваемой ситуации?
Разрешение отвечает на другой вопрос:
Какое действие разрешено?
Например:
Context:
Alice — Engineer
Engineer — действует в Project A
Document D — находится в Project A
Document D — опубликован группе G
Alice — member of G
Из этого контекста правила системы могут вывести:
READ
UPDATE
Но контекст сам по себе не является ни READ, ни UPDATE.
Поэтому полезно разделять:
Факты
↓
Контекст
↓
Правила
↓
Эффективное разрешение
↓
Решение о доступе
Это разделение станет основой дальнейшей модели.
3.11. Контекст не обязан существовать как отдельная запись
Есть ещё одна тонкость.
Когда мы говорим:
«Контекст содержит такие-то факты»,
это не означает, что в базе данных должна существовать запись с названием Context.
Контекст — архитектурное понятие.
Он может быть представлен:
-
набором связанных объектов;
-
отношениями между ними;
-
атрибутами;
-
ролями;
-
публикациями;
-
состояниями;
-
производными данными.
В одной системе контекст может собираться непосредственно во время запроса.
В другой — часть его может быть рассчитана заранее.
В третьей — некоторые факты могут приходить из внешней системы.
Архитектурная идея от этого не меняется.
Нам важно не наличие сущности с названием Context, а способность системы определить какие факты имеют значение для конкретного решения.
3.12. Почему контекст становится центральным понятием
Теперь можно вернуться к исходной проблеме.
Модель:
User → Role → Permission
не исчезает.
Она становится частью более общей конструкции:
Subject
↓
Relationships
↓
Context
↓
Rules
↓
Effective Permission
↓
Access Decision
Это уже другой способ смотреть на безопасность.
Мы больше не спрашиваем:
Какие разрешения есть у Alice?
Мы спрашиваем:
Какие факты связывают Alice с данным объектом и действием, и какое решение следует из этих фактов?
В первом случае разрешение выглядит как свойство пользователя.
Во втором — как результат интерпретации ситуации.
Это гораздо ближе к тому, как устроены реальные системы.
3.13. Контекст — это часть модели объекта
Есть ещё более общий вывод.
Если доступ зависит от отношений между объектами, то контекст не может быть полностью отделён от самой модели предметной области.
Представим:
User
Group
Project
Document
и связи:
User → Group
Group → Project
Document → Project
Эти связи создавались не только ради безопасности.
Они описывают саму структуру системы.
Но в определённых правилах те же отношения становятся основаниями для доступа.
Получается:
Модель безопасности использует структуру отношений предметной области.
Это один из главных переходов от простой системы ролей к архитектуре, в которой доступ является частью общей модели данных.
3.14. Но контекст не сводится к предметной области
При этом было бы ошибкой считать, что любой факт предметной области автоматически относится к безопасности.
У объекта может быть:
название;
дата создания;
цена;
статус;
версия;
автор;
владелец;
проект;
группа.
Но только некоторые из этих фактов могут участвовать в конкретном решении.
Например, цена документа может вообще не иметь отношения к праву чтения.
А владелец — иметь.
Поэтому контекст — это не «все данные вокруг объекта».
Это значимая для данного решения часть окружающих фактов.
3.15. Формальное определение
Теперь можно дать рабочее определение.
Контекст доступа — это совокупность фактов и отношений, которые имеют значение для определения допустимости конкретного действия конкретного субъекта над конкретным объектом.
В сокращённом виде:
Context(Subject, Action, Object)
=
relevant facts and relationships
А решение можно представить как функцию:
Can(Subject, Action, Object)
=
Rules(Context(Subject, Action, Object))
Это ещё не алгоритм реализации.
Это модель мышления.
Она говорит, что решение о доступе нельзя рассматривать отдельно от ситуации, в которой оно принимается.
3.16. Контекст и границы системы
На этом этапе возникает следующий вопрос.
Если контекст состоит из множества фактов, то какие именно факты должны в него входить?
Можно ли просто взять все доступные отношения?
Очевидно, нет.
Система должна иметь способ определить границы контекста.
Например, пользователь может одновременно:
состоять в десяти группах;
участвовать в пяти проектах;
иметь несколько ролей;
владеть десятками объектов.
Но конкретный запрос касается одного объекта и одного действия.
Следовательно, контекст должен быть не только богатым, но и ограниченным.
Нужно понимать:
-
какие отношения относятся к объекту;
-
какие отношения относятся к субъекту;
-
какие из них связаны между собой;
-
какие правила применимы;
-
какие границы нельзя пересекать.
И здесь мы приходим к следующей проблеме.
Контекст может содержать несколько независимых измерений.
Он может быть одновременно связан:
с компанией;
с проектом;
с группой;
с объектом;
с субъектом;
с ролью;
с отношением;
с действием.
Попытка представить всё это одним идентификатором неизбежно теряет информацию.
Поэтому в следующей главе мы разберём, почему контекст нельзя свести к одному scope.
Глава 4. Почему контекст нельзя свести к одному scope
В предыдущей главе мы определили контекст как совокупность фактов и отношений, значимых для конкретного решения о доступе.
Но возникает естественный вопрос:
А нельзя ли упростить эту модель и описывать контекст одним
scope?
Во многих системах именно так и пытаются сделать.
У пользователя есть scope.
У объекта есть scope.
У разрешения есть scope.
А дальше система проверяет, совпадают ли они.
Такой подход действительно работает для простых моделей.
Проблема начинается тогда, когда одно и то же действие зависит сразу от нескольких независимых отношений.
4.1. Что такое scope
В этой книге слово scope используется в архитектурном смысле — как область действия правила, отношения или разрешения.
Это значение не следует смешивать со scope в OAuth 2.0 (Open Authorization 2.0) и связанных с ним механизмах OpenID Connect (OIDC).
Там scope обозначает набор запрашиваемых или предоставляемых полномочий, например:
read:documents
или:
write:users.
Это другой уровень модели.
В OAuth 2.0 scope описывает, какие полномочия предоставлены клиенту или субъекту.
В рассматриваемой здесь модели scope описывает, где или в каких границах действует некоторое правило или отношение.
Поэтому scope в этой главе — не OAuth 2.0 scope из токена.
И проблема не в том, что scope — плохое понятие.
Проблема возникает тогда, когда область действия пытаются использовать как замену всему контексту.
4.2. Scope отвечает только на один вопрос
Scope отвечает примерно на вопрос:
Где действует это правило?
Например:
Permission: READ
Scope: Project P
означает, что некоторое разрешение действует в пределах P.
Но остаются другие вопросы:
-
кто выполняет действие;
-
над каким объектом;
-
какое именно действие выполняется;
-
какое отношение связывает субъекта с этой областью;
-
есть ли дополнительные ограничения.
Scope отвечает только на часть этих вопросов.
4.3. Объект и scope — не одно и то же
Пусть правило действует в некоторой области S.
Это ещё не означает, что конкретный объект D автоматически доступен субъекту.
Нужно установить отношение объекта к этой области.
Условно:
Subject
↓
Scope S
↓
Object D
Само существование S не отвечает на вопрос:
Can(Subject, Action, D).
Нужно ещё знать, как D связан с S и какие правила действуют для этого действия.
4.4. Один объект может участвовать в нескольких областях
Объект может одновременно находиться в нескольких структурах.
Например, один объект может быть связан:
-
с организацией;
-
с проектом;
-
с группой;
-
с другим объектом;
-
с субъектом-владельцем.
Если каждую такую границу назвать scope, получится несколько областей.
Object D
├── Scope A
├── Scope B
└── Scope C
Уже этого достаточно, чтобы увидеть проблему модели «один объект — один scope».
Объект может иметь несколько независимых областей действия.
4.5. Разные отношения могут иметь разные области
Пусть субъект имеет одну роль в области A и другую роль в области B.
При этом один и тот же объект связан с обеими областями.
Тогда решение зависит не только от самого scope, но и от того, какое отношение действует в этом scope.
Условно:
Subject
├── relation R1 ──→ Scope A
└── relation R2 ──→ Scope B
Object
├── relation ─────→ Scope A
└── relation ─────→ Scope B
Нельзя заменить эту структуру одним значением:
scope = X.
Потому что исчезает информация о характере отношений.
4.6. Scope не определяет субъекта
Одна и та же область может содержать множество субъектов.
Но разные субъекты могут иметь разные отношения с этой областью.
Например:
Alice → role R1 → S
Bob → role R2 → S
Carol → member → S
Если просто записать:
scope = S
мы ничего не узнали о том, что именно разрешено Alice, Bob и Carol.
Следовательно:
Scope ≠ Subject
И scope нельзя использовать как замену отношения субъекта с областью.
4.7. Scope не определяет объект
Та же область может содержать множество объектов:
S
├── D1
├── D2
├── D3
└── D4
Но доступ к D1 и D2 может различаться.
Например, D1 может быть опубликован для всех участников области, а D2 — только для владельца.
Поэтому:
Scope ≠ Object
Scope задаёт границу, но не описывает все свойства конкретного объекта внутри неё.
4.8. Scope не определяет действие
Даже если субъект имеет доступ к объекту в определённой области, остаётся вопрос:
Что именно ему разрешено делать?
Например:
READ
может быть разрешён, а:
UPDATE
— нет.
Поэтому одного scope недостаточно и здесь:
Scope
+
Action
дают больше информации, чем один scope.
4.9. Контекст имеет несколько независимых измерений
Для одного решения могут одновременно иметь значение:
-
субъект;
-
объект;
-
действие;
-
область действия;
-
роль;
-
членство;
-
владение;
-
публикация;
-
состояние объекта;
-
другие ограничения.
Это разные измерения ситуации.
Условно:
Context
│
┌────────────┼────────────┐
↓ ↓ ↓
Subject Object Action
│ │ │
└─────┬──────┴──────┬─────┘
↓ ↓
Relations Scope
Ни одно из этих измерений не обязано быть главным.
Их значение определяется конкретным правилом.
4.10. Нельзя просто сделать составной scope
Можно попытаться решить проблему иначе:
scope =
Company A /
Project P /
Group G
На первый взгляд проблема исчезает.
Но на самом деле мы просто упаковали несколько измерений в одну строку.
Сразу возникают новые вопросы:
-
что произойдёт при изменении одного элемента;
-
обязаны ли все элементы существовать;
-
независимы ли они;
-
как описать альтернативные отношения;
-
как представить несколько областей одновременно;
-
как учитывать свойства, которые вообще не являются областями.
Составной scope становится контейнером для контекста.
Но от этого он контекстом не становится.
4.11. Не все элементы контекста являются областями
Некоторые факты вообще невозможно естественно представить как scope.
Например:
Object.state = REVIEW
или:
Subject owns Object
или:
Action = APPROVE
Это не области действия.
Это состояние объекта, отношение и действие.
Поэтому модель:
Context = Scope
изначально ограничивает возможное содержание контекста.
4.12. Контекст зависит от конкретного действия
Предположим, нужно определить:
Can(Alice, READ, D)
и затем:
Can(Alice, APPROVE, D).
Набор значимых фактов может различаться.
Для чтения может иметь значение публикация объекта.
Для утверждения может дополнительно иметь значение состояние объекта и роль субъекта.
Следовательно:
Context(Alice, READ, D)
не обязан совпадать с:
Context(Alice, APPROVE, D)
Это одна из причин, почему контекст нельзя превратить в постоянное свойство объекта или пользователя.
4.13. Контекст зависит не только от субъекта
Нельзя сказать:
У Alice такой-то контекст.
Контекст возникает для конкретного решения.
У Alice может быть совершенно разная ситуация при работе с разными объектами:
Alice → READ → D1
Alice → READ → D2
Даже если субъект и действие одинаковы, контекст может отличаться.
Потому что отличаются объекты и отношения вокруг них.
4.14. Контекст зависит не только от объекта
Обратное тоже верно.
Для одного объекта:
Alice → READ → D
Bob → READ → D
контексты могут различаться.
Объект один.
Действие одно.
Но отношения субъектов с объектом и окружающей моделью разные.
Поэтому контекст нельзя определить только по объекту.
4.15. Что тогда представляет собой контекст
Контекст — это не одно поле и не один идентификатор.
Это релевантная часть ситуации, в которой принимается конкретное решение.
Можно записать:
Context(Subject, Action, Object) = Relevant Facts and Relations
А само решение:
Can(Subject, Action, Object) = Rules(Context(Subject, Action, Object))
В такой модели scope остаётся полезным понятием.
Если некоторое правило действует только в определённой области, эта область является частью контекста.
Но она не исчерпывает его.
Поэтому:
Scope ⊂ Context
там, где scope присутствует как элемент модели.
И:
Context ≠ Scope.
4.16. Главный вывод
Scope — полезный способ описать границу действия отношения, правила или разрешения.
Но он отвечает только на один вопрос:
Где действует правило?
Контекст должен отвечать на более широкий вопрос:
В какой ситуации это правило должно применяться к данному субъекту, действию и объекту?
Поэтому контекст может включать:
-
субъекта;
-
объект;
-
действие;
-
отношения;
-
области действия;
-
состояние;
-
владение;
-
публикацию;
-
другие релевантные факты.
Именно поэтому нельзя построить универсальную модель доступа, просто присвоив каждому объекту и пользователю по одному scope.
Нам нужно сначала определить объект, затем разобраться, какие факты и отношения связывают его с остальной системой, и только после этого определить, какие из этих отношений становятся основаниями доступа.
С этого начинается следующая часть модели.
Глава 5. Объект как предмет действия
В предыдущих главах мы пришли к формуле:
Subject → Action → Object
Она важна не своей простотой, а тем, что заставляет задать точный вопрос:
Над чем именно субъект пытается выполнить действие?
Ответом является объект.
5.1. Объект — это предмет действия
Если Alice читает документ D:
Alice → READ → D
объектом является D.
Если Alice изменяет проект P:
Alice → UPDATE → P
объектом является P.
Если администратор изменяет данные пользователя B:
Administrator → UPDATE → B
объектом является B.
Таким образом, объект в модели доступа — это не обязательно «документ», «запись» или какая-либо другая заранее определённая сущность.
Объект — это сущность, над которой в данном решении выполняется действие.
Это определение специально дано через действие. Один и тот же тип сущности может быть объектом для одного действия и не иметь отношения к другому.
5.2. Объект имеет собственную идентичность
Чтобы принять решение о доступе, нужно понимать, какой именно объект является предметом действия.
Недостаточно знать:
Alice → READ → Document
Нужно знать:
Alice → READ → Document D
Потому что у Alice может быть право читать один документ и не быть права читать другой.
Следовательно, объект должен иметь устойчивую идентичность.
Она позволяет различать:
D1 ≠ D2
даже если оба объекта имеют одинаковый тип и одинаковый набор свойств.
5.3. Идентичность объекта не равна его физическому хранению
В информационной системе объект может храниться в нескольких таблицах, документах или других структурах.
Одна физическая запись может быть частью более сложного объекта.
И наоборот, один логический объект может быть представлен несколькими физическими записями.
Поэтому:
Object ≠ Database Row
Идентичность объекта принадлежит модели системы, а способ его хранения — реализации.
Это разделение особенно важно для безопасности: отношения и права должны ссылаться на устойчивую сущность, а не зависеть от случайного устройства хранения.
5.4. Объект существует до решения о доступе
Сначала существует объект.
Затем возникает вопрос, какое действие над ним может выполнить конкретный субъект.
Поэтому право доступа не является частью самой идентичности объекта.
Условно:
Object D
↓
кто может выполнить действие?
↓
Access Decision
Один и тот же объект может быть доступен одному субъекту, недоступен другому и доступен третьему только для другого действия.
Сам объект от этого не становится другим объектом.
5.5. Один объект может участвовать в разных решениях
Для одного объекта могут существовать разные запросы:
Alice → READ → D
Alice → UPDATE → D
Bob → READ → D
Carol → APPROVE → D
Это четыре разных решения.
Даже если речь идёт об одном и том же объекте, результат может отличаться в зависимости от:
-
субъекта;
-
действия;
-
других фактов, участвующих в решении.
Поэтому нельзя говорить просто:
«Пользователь имеет доступ к объекту».
Точнее сказать:
«Для данного субъекта допустимо определённое действие над данным объектом».
5.6. Объект не определяет право доступа
Из существования объекта ничего не следует о том, кто может с ним работать.
Наличие документа не говорит, кто может его читать.
Наличие проекта не говорит, кто может его изменять.
Наличие пользователя в системе не говорит, кто может просматривать его данные.
Право возникает из отношений и правил, связанных с объектом.
Поэтому модель:
Object → Permission
так же недостаточна, как и старая модель:
User → Role → Permission.
Для решения нужно сохранить как минимум тройку:
Subject → Action → Object
а затем определить, какие факты и правила связывают эти три элемента.
5.7. Один и тот же объект может быть предметом разных отношений
Объект не существует в модели доступа как изолированная точка.
Он может иметь владельца, находиться в проекте, быть опубликованным для группы, иметь определённое состояние и участвовать в других отношениях.
Но сами эти отношения не являются частью определения объекта.
Это принципиальное разделение:
Object
+
Relations
+
Rules
↓
Access Decision
Объект задаёт над чем выполняется действие.
Отношения и правила позволяют определить, допустимо ли это действие.
5.8. Почему объект — отправная точка, а не вся модель
До этого момента мы сознательно говорили преимущественно о субъекте.
Теперь становится видно, почему этого недостаточно.
Нельзя определить доступ, зная только:
Who?
Нужно также знать:
What action?
и:
Over what object?
Но и это пока не даёт ответа.
Для следующего шага нам нужно понять, какие факты связывают объект с субъектом и другими объектами.
Именно из этих фактов начинает формироваться конкретная ситуация доступа.
5.9. Главный вывод
Объект в модели доступа — это конкретная сущность, над которой субъект пытается выполнить действие.
Он имеет собственную идентичность и не обязан совпадать с физической записью в базе данных.
При этом объект сам по себе не определяет доступ.
Базовая конструкция остаётся:
Subject → Action → Object
Но для принятия решения этого недостаточно.
Следующий вопрос:
Какие факты должны быть известны о субъекте, объекте и их отношениях, чтобы принять это решение?
Глава 6. Факты вокруг объекта
Мы определили объект как предмет действия.
Теперь возникает следующий вопрос: если объект сам по себе не определяет доступ, что ещё нужно знать, чтобы принять решение?
Ответ начинается с простого понятия — факта.
6.1. Объект существует внутри множества фактов
Рассмотрим документ D.
О системе может быть известно:
Alice created D
Bob owns D
D belongs to Project P
Alice is member of Group G
Group G has access to D
D is in state REVIEW
Сам документ — только один элемент этой картины.
Остальные утверждения описывают отношения и свойства, связанные с ним.
Для разных действий значимыми окажутся разные факты.
6.2. Факт — это утверждение о состоянии системы
Факт отвечает на некоторый конкретный вопрос.
Например:
-
Alice создала D;
-
Bob владеет D;
-
Alice состоит в G;
-
D опубликован в P;
-
P связан с G.
Каждый такой факт может существовать независимо от конкретного запроса.
Но при выполнении запроса часть фактов становится релевантной.
Например, для:
Can(Alice, READ, D)
может иметь значение членство Alice в группе.
Для другого действия тот же факт может вообще не использоваться.
6.3. Факт и отношение
Многие факты имеют форму отношения:
Subject → Relation → Object
Например:
Alice → member_of → G
Bob → owns → D
D → published_in → P
Так появляется важное различие между объектом и связями между объектами.
Объект отвечает на вопрос:
Что является предметом действия?
Отношение отвечает на вопрос:
Как этот объект связан с другими участниками модели?
6.4. Не каждый факт является фактом безопасности
Предметная модель обычно содержит гораздо больше информации, чем требуется для авторизации.
Документ может иметь:
-
название;
-
дату создания;
-
автора;
-
размер;
-
формат;
-
статус;
-
проект;
-
владельца.
Но конкретному решению о доступе может быть нужна только часть этих сведений.
Поэтому нельзя считать, что весь объект автоматически является контекстом безопасности.
Нужны только релевантные факты.
6.5. Релевантность определяется запросом
Предположим, система получает запрос:
Can(Alice, READ, D)
Для ответа может потребоваться знать:
-
кто владеет D;
-
опубликован ли D;
-
состоит ли Alice в группе, которой доступен D;
-
какую роль Alice имеет в соответствующем проекте.
Но если запрос касается другого действия, набор значимых фактов может измениться.
Например, для APPROVE может иметь значение состояние документа.
Таким образом:
RelevantFacts = f(Subject, Action, Object)
Набор фактов определяется не объектом вообще, а конкретным решением.
6.6. Один объект — множество отношений
Объект может одновременно находиться в нескольких структурах.
Например:
Alice ──owns──────→ D
D ──belongs───→ P
Alice ──member────→ G
G ──access────→ D
Эти отношения не являются взаимозаменяемыми.
Владение — не членство.
Членство — не публикация.
Публикация — не право изменения.
Каждое отношение имеет собственный смысл.
6.7. Отношения могут соединять не только пользователя и объект
Отношение может связывать:
-
пользователя и объект;
-
пользователя и группу;
-
группу и проект;
-
объект и проект;
-
объект и другой объект;
-
организацию и пользователя;
-
сервис и ресурс.
Поэтому модель доступа нельзя представить только набором связей:
User → Object.
В реальной системе отношения образуют сеть.
Но сама по себе сеть ещё не является разрешением.
6.8. Связь не равна праву
Пусть:
Alice → member_of → G
Это ещё не означает:
Alice → READ → D.
Чтобы получить второе утверждение, необходимо дополнительное правило.
Например:
Alice ∈ G
G has access to D
↓
Alice can READ D
Но другое правило может дать другой результат.
Следовательно:
отношение является фактом, а право — результатом применения правил к фактам.
Это одно из главных разделений всей модели.
6.9. Изменение отношения может изменить доступ
Теперь можно увидеть важное свойство модели.
Предположим:
Alice ∈ G
G has access to D
и поэтому Alice может читать D.
Если Alice покидает G:
Alice ∉ G
сам D не изменился.
Но набор релевантных фактов изменился.
Следовательно, может измениться и результат:
Can(Alice, READ, D)
Это означает, что доступ может измениться без изменения объекта.
Изменилось отношение вокруг объекта.
6.10. Отношение может быть многошаговым
Связь между субъектом и объектом не всегда существует напрямую.
Например:
Alice
↓
member of
↓
Group G
↓
member of
↓
Project P
↓
contains
↓
Document D
Или:
Alice
↓
role in Project P
↓
Project P
↓
publication
↓
Document D
В обоих случаях конечное разрешение может зависеть от нескольких исходных фактов.
Это уже не простая таблица:
User → Permission.
Это структура отношений, из которой система получает производные факты.
6.11. Иерархия тоже создаёт отношения
Иерархические структуры являются частным случаем сети отношений.
Например:
Group A
↓
Group B
↓
Group C
Но из самой иерархии ещё не следует, что право автоматически распространяется между всеми уровнями.
Нужно отдельное правило, определяющее, как отношение на одном уровне влияет на другой.
Поэтому:
Hierarchy ≠ Permission
Иерархия предоставляет факты.
Правила определяют, какие последствия эти факты имеют для доступа.
6.12. Факты могут быть исходными и производными
Не все факты обязательно записаны непосредственно в предметной модели.
Например, исходные факты:
Alice ∈ Group G
G has access to D
могут позволить получить производный факт:
Alice can READ D
Производный факт может быть вычислен заранее и храниться отдельно от исходных данных.
Это особенно важно для больших систем.
Вместо того чтобы при каждом запросе заново обходить все отношения, система может поддерживать заранее вычисленное состояние безопасности.
6.13. Производный факт не меняет исходную модель
Если система вычислила:
Alice can READ D
это не означает, что в предметной области появился новый объект или новое бизнес-отношение.
Это результат применения модели.
Условно:
Исходные факты
↓
Правила
↓
Производный security-факт
Поэтому полезно разделять:
источник истины и результат вычисления.
Предметные отношения описывают состояние системы.
Производные факты помогают эффективно использовать это состояние для принятия решений.
6.14. Факты могут меняться независимо от объекта
Документ D может оставаться неизменным в течение месяца.
При этом:
-
пользователь может войти в группу;
-
выйти из группы;
-
получить роль;
-
потерять роль;
-
получить доступ;
-
потерять доступ;
-
изменить владельца объекта.
В каждом случае изменяются отношения вокруг D.
Поэтому безопасность объекта имеет собственную динамику.
Она не обязана совпадать с изменениями самого объекта.
6.15. Что именно входит в контекст
Теперь можно уточнить определение из главы 3.
Контекст конкретного решения можно рассматривать как множество релевантных фактов:
Context(Subject, Action, Object) = RelevantFacts
Это не означает, что контекст — отдельная сущность в базе данных.
Это означает, что для конкретного решения существует некоторый набор фактов, который система должна учитывать.
Часть этих фактов может быть исходной.
Часть — производной.
Часть может храниться рядом с объектом.
Часть — в отдельных структурах безопасности.
6.16. Контекст строится не вокруг объекта одного
Хотя объект является предметом действия, контекст формируется из нескольких элементов:
Subject
+
Action
+
Object
+
Relevant Facts
+
Relations
Поэтому нельзя сказать:
«Контекст — это свойства объекта».
И нельзя сказать:
«Контекст — это свойства пользователя».
Контекст возникает на пересечении субъекта, действия, объекта и релевантных отношений.
6.17. От фактов к основаниям доступа
На этом этапе появляется следующий вопрос.
Мы знаем, что:
Alice ∈ Group G
G has access to D
Но почему это означает право Alice читать D?
Потому что существует правило, связывающее эти факты с разрешением.
Значит, нам нужно отделить:
Факт
↓
Основание
↓
Разрешение
Одно и то же разрешение может иметь несколько оснований.
И наоборот, один факт может вообще не давать никаких прав, пока не будет применено соответствующее правило.
Именно это различие станет предметом следующей главы.
6.18. Главный вывод
Объект не является изолированным элементом модели доступа.
Вокруг него существует множество фактов и отношений.
Но не каждый факт имеет значение для каждого решения.
Поэтому модель должна различать:
Object
Fact
Relation
Effective Permission
И самое важное:
из факта ещё не следует право.
Право появляется тогда, когда определённое правило связывает релевантные факты с конкретным действием над конкретным объектом.
Следующий шаг — разобраться с самими основаниями доступа: кто создал объект, кто им владеет, кто получил роль, кто состоит в группе и почему каждое из этих отношений может давать совершенно разные последствия.
Глава 7. Основания доступа
Теперь у нас есть три уровня:
Subject → Action → Object
и:
Facts + Relations
Но пока отсутствует главное связующее звено.
Почему некоторый факт должен приводить к разрешению?
Потому что этот факт является основанием доступа, а правила системы определяют, какие права из него следуют.
7.1. Одно действие может иметь несколько оснований
Предположим, Alice хочет прочитать документ D.
У системы может быть несколько независимых причин предоставить ей доступ:
Alice owns D
или:
Alice is member of Group G
G has access to D
или:
Alice has a role in Project P
D is published in P
Результат может быть одним:
Alice → READ → D
Но путь к этому результату различается.
Поэтому важно разделять:
право и основание, по которому это право возникло.
7.2. Создатель — основание, а не универсальное право
Если Alice создала D:
Alice → created → D
это факт происхождения объекта.
Некоторые системы могут использовать его как основание доступа.
Например:
создатель может редактировать объект до его передачи.
Но это не универсальное свойство понятия «создатель».
После передачи объекта:
Alice → created → D
Bob → owns → D
Alice остаётся создателем.
Если правило владения определяет текущий доступ, владельцем уже является Bob.
Поэтому:
Creator ≠ Owner
и факт создания не должен автоматически подменяться отношением владения.
7.3. Владелец — тоже основание, а не готовое разрешение
Если:
Bob → owns → D
система может определить, что Bob имеет определённые права.
Например:
Bob → READ → D
Bob → UPDATE → D
Но набор этих прав зависит от правил системы.
В другой модели владелец может иметь право изменять объект, но не иметь права утверждать его.
Следовательно:
Owner → Permission
это не универсальная истина.
Правильнее:
Owner relation
↓
правило системы
↓
определённые permissions
7.4. Роль — другое основание
Роль отвечает на другой вопрос.
Например:
Alice → Editor
а роль Editor определяет:
Editor → UPDATE
Но этого всё ещё недостаточно для конкретного объекта.
Нужно знать, где действует эта роль.
Alice может быть редактором одного проекта и не иметь той же роли в другом.
Поэтому роль становится частью более сложного отношения:
Alice
↓
Role: Editor
↓
Project P
И уже затем определяется, какие объекты находятся в области действия этой роли.
7.5. Членство в группе тоже не является разрешением
Рассмотрим:
Alice → member_of → G
Само по себе членство не говорит, что Alice может читать любой объект, связанный с G.
Нужно ещё знать, какое значение имеет группа для конкретного объекта.
Например:
Alice ∈ G
G → access → D
может дать:
Alice → READ → D.
Но другая связь группы с объектом может означать другое действие.
Поэтому:
Membership ≠ Permission
Членство — это факт.
Право — результат применения правил к этому факту.
7.6. Проект также не является разрешением
Аналогично:
Alice → role in → Project P
D → published in → P
может создать основание для доступа Alice к D.
Но само участие в проекте не означает автоматически доступ ко всем объектам проекта.
Это зависит от модели.
Проект определяет некоторую структуру отношений.
Правила определяют, какие права из неё следуют.
7.7. Публикация отвечает на другой вопрос
Публикация особенно хорошо показывает разницу между фактом и правом.
Например:
D → published → Project P
Этот факт говорит, что объект опубликован в определённой области.
Но он не отвечает сам по себе на вопрос:
Может ли Alice выполнить UPDATE над D?
Для этого нужно учитывать ещё и отношения Alice с этой областью, соответствующие разрешения и ограничения самого действия.
Поэтому:
Publication ≠ Permission
Публикация определяет видимость или область, в которой объект может быть доступен.
Она не является универсальным разрешением на любые действия.
7.8. Несколько оснований могут дать одно эффективное право
Предположим:
Alice owns D
и одновременно:
Alice ∈ G
G has READ access to D
И ещё:
Alice has READ permission in Project P
D is published in P
Три независимых основания приводят к одному результату:
Alice → READ → D
Это важно по двум причинам.
Во-первых, удаление одного основания не обязательно лишает пользователя права.
Во-вторых, нельзя определить причину права, посмотрев только на итоговое разрешение.
Поэтому полезно различать:
Access Grounds
↓
Effective Permission
7.9. Эффективное разрешение — результат, а не исходный факт
Пусть исходные данные таковы:
Alice ∈ G
G has access to D
А правило системы говорит:
член группы получает READ для опубликованных группе объектов.
Тогда:
Alice → READ → D
является эффективным разрешением.
Оно может быть вычислено из исходных отношений.
Поэтому эффективное разрешение не обязательно хранится там же, где находятся исходные предметные факты.
7.10. Основания могут конфликтовать
Не всегда разные отношения ведут в одну сторону.
Например:
Alice owns D
может давать право UPDATE.
Но:
D is closed
может запрещать UPDATE.
Тогда одного основания недостаточно.
Решение должно учитывать одновременно:
Основание доступа
+
Ограничение объекта
+
Требуемое действие
В результате эффективное право определяется не просто наличием основания, а правилами их совместного применения.
7.11. Право зависит от действия
Одно и то же основание может давать разные права для разных действий.
Например, владелец может:
READ
и:
UPDATE
но не:
APPROVE.
Или член проекта может:
READ
но не:
CLOSE.
Поэтому нельзя говорить:
«У Alice есть доступ к D».
Без указания действия утверждение неполно.
Точнее:
Can(Alice, READ, D)
или:
Can(Alice, UPDATE, D)
7.12. Основание доступа не обязано быть прямым
Иногда между субъектом и объектом находится цепочка отношений:
Alice
↓
member of
↓
Group G
↓
published to
↓
Document D
В этом случае Alice не имеет прямой связи READ с D.
Она получает право через цепочку.
Именно такие модели делают авторизацию отношенческой: результат зависит не от одного свойства субъекта, а от структуры отношений.
7.13. Одно отношение может участвовать в разных решениях
Факт:
Alice ∈ G
может быть использован сразу в нескольких правилах.
Например:
-
для чтения документов группы;
-
для доступа к проекту;
-
для определения области видимости;
-
для других операций.
Но это не означает, что членство само по себе содержит все эти права.
Оно является исходным фактом, который разные правила могут использовать по-разному.
7.14. Отношения имеют разный смысл
В реальной модели могут одновременно существовать:
created
owns
member_of
role_in
published_in
access
delegated_to
Нельзя объединить их в универсальное:
related_to.
Такое объединение уничтожило бы семантику.
Создатель — не владелец.
Владелец — не участник группы.
Участник группы — не обязательно редактор.
Редактор — не обязательно утверждающий.
Публикация — не право изменения.
Именно различия между отношениями позволяют построить точную модель.
7.15. Основания доступа образуют вход модели разрешений
Теперь можно представить более полную цепочку:
Предметные факты
↓
Отношения
↓
Основания доступа
↓
Правила
↓
Эффективное разрешение
При этом основание доступа не обязательно является отдельным типом данных.
Это скорее роль, которую определённый факт играет в конкретном правиле.
Например:
Alice owns D
является обычным фактом.
Но в правиле:
владелец может UPDATE
тот же факт становится основанием для UPDATE.
7.16. Почему это важно для архитектуры
Если система хранит только конечные permissions, теряется информация о том, почему пользователь получил право.
Если система хранит только отношения, приходится вычислять права непосредственно во время каждого запроса.
Оба подхода имеют последствия.
Поэтому в сложных системах полезно разделять:
Исходные факты
↓
Производные security-факты
↓
Эффективные permissions
Так модель остаётся выраженной через отношения, а механизм проверки может работать с заранее подготовленным результатом.
7.17. Что мы теперь знаем о контексте
После глав 5–7 модель стала существенно конкретнее.
Для запроса:
Can(Alice, UPDATE, D)
мы можем рассматривать:
Subject
Alice
Action
UPDATE
Object
D
Relevant facts
owner
membership
role
publication
state
...
Некоторые из этих фактов могут стать основаниями доступа.
Некоторые — ограничениями.
Некоторые могут вообще оказаться нерелевантными.
Именно совокупность релевантных элементов образует контекст конкретного решения.
7.18. Главный вывод
Доступ редко возникает из одного свойства пользователя или объекта.
Он может иметь несколько оснований:
-
владение;
-
создание;
-
роль;
-
членство;
-
публикация;
-
делегирование;
-
другие отношения.
Но каждое из них является фактом или отношением, а не готовым разрешением.
Поэтому:
Fact ≠ Permission
Relation ≠ Permission
Role ≠ Access to Object
Эффективное разрешение появляется тогда, когда правила системы преобразуют релевантные основания в допустимые действия над конкретным объектом.
Теперь модель готова сделать следующий шаг: перейти от оснований к самим ролям и разрешениям и разобраться, что именно означает permission и как несколько независимых оснований превращаются в одно эффективное право.
Глава 8. Публикация объекта
Мы уже различаем объект, отношения и основания доступа.
Но остаётся ещё один важный вопрос:
Как объект вообще становится доступным другим участникам системы?
Владелец может существовать.
Группа может существовать.
Роль может быть назначена.
Но объект может оставаться недоступным для остальных.
Значит, между существованием объекта и возможностью работать с ним есть ещё одно отношение.
Это отношение можно назвать публикацией.
8.1. Объект может существовать, оставаясь невидимым
Предположим, Alice создаёт документ D.
Документ существует:
Alice → created → D
Alice может иметь над ним необходимые права.
Но Bob пока ничего о D не знает и не имеет основания его видеть.
Это нормальная ситуация.
Объект может находиться в системе, но не входить в доступную Bob область объектов.
Поэтому:
Object exists ≠ Object is visible
А тем более:
Object exists ≠ User can use Object
8.2. Публикация меняет область видимости объекта
Предположим, Alice публикует D для проекта P:
D → published_in → P
Теперь D связан с новой областью.
Если Bob является участником P, у него появляется основание рассматривать D как потенциально доступный объект.
Но это ещё не обязательно означает, что Bob может выполнять любое действие над D.
Здесь важно сохранить два разных вопроса:
Где объект опубликован?
и:
Что конкретный субъект может с ним делать?
8.3. Публикация — это отношение
Публикация не является свойством пользователя.
Она также не является просто свойством объекта.
Это отношение:
Object → Publication → Area
Например:
Document D
│
▼
published
│
▼
Project P
Изменение публикации меняет отношения объекта с остальной системой.
Сам документ при этом может вообще не измениться.
8.4. Публикация не равна передаче владения
Предположим:
Alice → owns → D
и:
D → published_in → P
После публикации Alice не перестаёт быть владельцем.
Публикация создаёт дополнительное отношение.
Получается:
Alice ──owns──────→ D
D ──published─→ P
Поэтому:
Publication ≠ Ownership
Это особенно важно в системах совместной работы.
Владелец сохраняет своё отношение к объекту, одновременно открывая объект для определённой области.
8.5. Публикация не равна разрешению
Это ещё более важное различие.
Пусть:
D → published_in → P
Сам факт публикации не отвечает на вопрос:
Can(Bob, UPDATE, D)
Он говорит только, что D опубликован в P.
Чтобы определить действие Bob, нужно учитывать дополнительные отношения:
Bob
↓
relation with P
↓
D published in P
↓
access rules
↓
Can(Bob, Action, D)
Поэтому:
Publication ≠ Permission
Публикация может быть основанием для дальнейшего определения доступа, но не является универсальным разрешением.
8.6. Видимость и право действия — разные вопросы
Это различие можно выразить двумя вопросами.
Вопрос 1:
Может ли субъект обнаружить или рассматривать объект как доступный ему?
Вопрос 2:
Может ли субъект выполнить конкретное действие над объектом?
Например, Bob может видеть документ, но не иметь права изменять его.
Или может иметь право читать документ, но не утверждать его.
Поэтому:
Visibility
≠
Permission
Хотя видимость может быть одним из необходимых условий для последующего действия.
8.7. Публикация создаёт связь объекта с областью
Объект может быть опубликован не только в одной области.
Например:
D
├── published in P1
├── published in P2
└── published in G1
Это означает, что у объекта существует несколько независимых публикационных отношений.
Каждое из них может иметь собственные последствия.
Поэтому модель:
Object → one Scope
уже здесь оказывается слишком простой.
8.8. Разные публикации могут открывать объект разным субъектам
Пусть:
D → published_in → Project P
D → published_in → Group G
Bob может быть связан с P, но не с G.
Carol может быть связана с G, но не с P.
Оба субъекта работают с одним объектом, но приходят к нему через разные отношения.
Bob → P ──→ D
Carol → G ──→ D
Это хороший пример того, почему доступ нельзя свести к одному свойству объекта.
8.9. Публикация может быть отозвана
Публикация — не обязательно постоянное состояние.
Если:
D → published_in → P
отношение может быть удалено:
D ↛ P
После этого объект продолжает существовать.
Владелец может продолжать иметь к нему доступ.
Другие субъекты могут потерять доступ, если публикация была их основанием.
То есть:
изменился publication relation
↓
изменился набор релевантных фактов
↓
может измениться доступ
Сам объект при этом не менялся.
8.10. Публикация может иметь несколько уровней
Не всегда объект публикуется непосредственно пользователю.
Можно публиковать объект:
-
организации;
-
проекту;
-
группе;
-
другой области предметной модели.
Затем субъект получает отношение с этой областью.
Например:
D
↓
published in
↓
Project P
↓
member
↓
Bob
В этом случае связь Bob с D является не прямой, а производной.
Она возникает из нескольких исходных отношений.
8.11. Публикация и прямой доступ — разные основания
У пользователя может быть прямое право на объект независимо от публикации.
Например:
Bob → READ → D
Даже если D не опубликован в проекте, прямое основание может предоставить Bob доступ.
В другой модели публикация может быть обязательным условием.
Поэтому нельзя считать публикацию единственным способом получить доступ.
Она является одним из механизмов связывания объекта с областью субъектов.
8.12. Публикация не обязательно означает публичность
Слово «публикация» легко понять как:
сделать объект доступным всем.
В модели доступа это не обязательно так.
Можно опубликовать объект:
-
только внутри организации;
-
только участникам проекта;
-
только членам группы;
-
только в определённой области системы.
Поэтому публикация означает не «сделать публичным», а:
создать отношение, благодаря которому объект входит в определённую область видимости.
8.13. Публикация связывает объект с контекстом
До публикации мы могли рассматривать:
Object D
После публикации появляется новый факт:
D → published_in → P
Если субъект Alice имеет отношение к P, в контексте запроса появляется дополнительная информация:
Alice → relation → P
D → published → P
Теперь эти два факта могут использоваться правилом доступа.
То есть публикация не является самим контекстом.
Она добавляет в контекст определённое отношение.
8.14. Один объект — несколько путей видимости
Пусть D опубликован одновременно в проекте и группе:
D → P
D → G
А Alice связана и с P, и с G.
Тогда доступ к D может иметь два основания:
Alice → P → D
Alice → G → D
Если одно отношение исчезнет, второе может сохраниться.
Это означает, что эффективное право может быть устойчивым к изменению одного из оснований.
Так мы снова приходим к различию между:
основанием доступа
и:
эффективным разрешением.
8.15. Публикация может быть частью жизненного цикла доступа
В некоторых системах объект проходит последовательность:
создан
↓
доступен владельцу
↓
опубликован
↓
доступен определённой области
↓
публикация изменена или отозвана
↓
область доступа изменилась
Важно, что публикация не обязательно меняет сам объект как предметную сущность.
Она изменяет его положение относительно других субъектов.
Это ещё один пример того, как безопасность живёт в отношениях вокруг объекта.
8.16. Публикация не должна смешиваться с физическим размещением
Объект может физически находиться в одном хранилище, но быть опубликованным в нескольких областях.
И наоборот, физическое нахождение объекта в некоторой структуре ещё не означает, что эта структура является областью его доступа.
Поэтому:
Physical Location ≠ Publication Scope
То, где объект хранится, и то, кому он опубликован, — разные архитектурные вопросы.
Это особенно важно в распределённых системах, где физическое размещение может определяться производительностью, репликацией или другими техническими причинами.
8.17. Что теперь добавилось к модели
После этой главы у нас есть ещё один элемент:
Object
↓
Publication
↓
Area
Он соединяется с тем, что мы уже знаем:
Subject
↓
Relations
↓
Area
Object
↓
Publication
↓
Area
И из пересечения этих отношений могут возникать основания доступа.
Но сама область всё ещё не объясняет всю ситуацию.
Остаётся разобраться, как именно область действия отношения влияет на разрешение и почему разные отношения могут действовать в разных границах.
8.18. Главный вывод
Публикация отделяет существование объекта от его доступности в определённой области.
Она:
-
не меняет идентичность объекта;
-
не обязательно меняет владельца;
-
не является сама по себе разрешением;
-
может быть одним из оснований доступа;
-
может существовать в нескольких вариантах;
-
может быть изменена или отозвана независимо от объекта.
Упрощённо:
Object
↓
Publication
↓
Area
↓
Relations with Subject
↓
Access Decision
Но здесь возникает следующий вопрос:
Если отношения действуют в разных областях, как определить границу их действия и что происходит, когда таких границ несколько?
Именно это приводит нас к следующей главе — области действия отношений.
Глава 9. Область действия отношения
9.1 Одного отношения недостаточно
В предыдущей главе мы рассмотрели публикацию объекта.
Публикация связывает объект с некоторой областью видимости. Например, объект может быть опубликован внутри организации, проекта или группы.
Но сама по себе такая связь ещё не отвечает на вопрос о доступе.
Пусть объект Document A опубликован в области Project P.
Из этого пока нельзя сделать вывод:
любой пользователь, связанный с
Project P, может читатьDocument A.
Потому что отношение пользователя к проекту тоже должно иметь значение.
Один пользователь может быть участником проекта, другой — его владельцем, третий — иметь только право согласования. Ещё один пользователь вообще может не иметь отношения к проекту.
Поэтому область действия должна рассматриваться не как свойство объекта, а как часть конкретного отношения.
9.2 Что означает область действия отношения
Возьмём отношение:
Subject → Relation → Object
У отношения может быть область, в которой оно действительно.
Например:
User A → member of → Project P
Здесь область определяется самим проектом.
Но возможна и другая ситуация:
User A → role → Project P
Здесь область действия роли также связана с проектом, однако смысл отношения уже другой.
Таким образом, два отношения могут ссылаться на одну и ту же область, но означать совершенно разные вещи.
Это важное различие:
область отвечает на вопрос, где действует отношение; само отношение отвечает на вопрос, что именно оно означает.
9.3 Область не является свойством субъекта
Нельзя сказать, что пользователь просто «имеет доступ к проекту».
Такое утверждение слишком грубое.
У одного и того же пользователя могут существовать разные отношения:
User A → member → Project P
User A → role → Project P
User A → member → Group G
User A → owner → Object X
Они могут существовать одновременно и иметь разные области действия.
Поэтому область нельзя хранить только как свойство субъекта.
Субъект не находится постоянно в одном единственном контексте доступа.
Контекст определяется конкретным запросом и конкретными отношениями, которые для него релевантны.
9.4 Область не является свойством объекта
По той же причине нельзя считать, что объект просто «принадлежит области».
Объект может участвовать сразу в нескольких отношениях.
Например:
Object A → published in → Project P
Object A → published in → Group G
Object A → owned by → User B
Здесь появляются три разных отношения и потенциально три разных основания для доступа.
Если представить область как единственное свойство объекта, эти различия исчезают.
Объекту пришлось бы иметь один scope.
Но реальная модель может требовать нескольких независимых областей.
9.5 Одна область — разные отношения
Рассмотрим проект P.
В его пределах могут существовать отношения:
-
пользователь является участником проекта;
-
пользователь имеет роль в проекте;
-
объект опубликован в проекте;
-
пользователь получил делегированное право в проекте.
Все они используют одну область, но не становятся одним отношением.
Это принципиально.
Если область сама начинает определять смысл доступа, модель быстро превращается в набор специальных исключений:
если пользователь находится в этой области, разрешить действие.
Но тогда теряется информация о том, почему пользователь оказался в области и какое именно отношение между ним и областью существует.
Поэтому область должна оставаться дополнительным измерением отношения, а не заменять его.
9.6 Одно отношение — несколько областей
Обратная ситуация тоже возможна.
Одно логическое отношение может распространяться на несколько областей.
Например, роль пользователя может быть назначена:
-
для всей организации;
-
для конкретного проекта;
-
для конкретной группы.
Смысл отношения один — пользователь получает определённую роль, — но область действия различается.
Получается:
Role(User A, R, Company C)
и
Role(User A, R, Project P)
и
Role(User A, R, Group G).
Это не три разных пользователя и не три разных роли.
Это однотипные отношения с разной областью действия.
9.7 Область может быть иерархической
Области не обязательно образуют плоский список.
Они могут находиться в иерархии:
Company
│
├── Project
│ │
│ ├── Group
│ └── Group
│
└── Project
Тогда возникает дополнительный вопрос:
распространяется ли отношение из более широкой области на более узкую?
Например, если пользователь имеет определённое отношение к организации, означает ли это автоматически наличие такого же отношения к каждому проекту внутри неё?
Ответ не может быть универсальным.
Иногда — да.
Иногда — нет.
Это уже правило модели.
Сама иерархия области ничего автоматически не разрешает.
9.8 Иерархия области и наследование — разные вещи
Это различие легко потерять.
Пусть:
Project P ∈ Company C
Это означает, что проект находится внутри организации.
Но из этого ещё не следует:
Relation(User, Company C) ⇒ Relation(User, Project P).
Для такого перехода должно существовать отдельное правило наследования.
И наоборот:
Relation(User, Project P)
не обязательно означает наличие отношения к Company C.
Иерархия описывает структуру областей.
Наследование описывает правила переноса отношений между ними.
Это разные элементы модели.
9.9 Область может ограничивать действие, но не создавать его
Представим разрешение:
READ
и отношение пользователя к проекту.
Если отношение действительно только внутри проекта P, то наличие пользователя в другом проекте Q не должно автоматически давать ему то же право.
Но сама область P не создаёт право READ.
Она только ограничивает место, в котором соответствующее основание может быть применено.
Поэтому полезно разделять:
Relation
↓
Area
↓
Rule
↓
Effective Permission
Область является частью входных данных правила.
Она не является результатом правила.
9.10 Область публикации и область разрешения могут совпадать
Иногда область, в которой объект опубликован, совпадает с областью, в которой действует разрешение.
Например:
Object A
│
└── published in → Project P
User B
│
└── role in → Project P
Если правила модели связывают эти два отношения, из них может возникнуть эффективное разрешение:
User B
↓
relation with Project P
↓
Object A published in Project P
↓
READ
Но это не означает, что публикация сама является разрешением.
Разрешение возникает только после применения правила, связывающего отношения пользователя и объекта через общую область.
9.11 Области разных отношений могут пересекаться
Не все отношения должны использовать одну и ту же область.
Например:
User A → role → Project P
Object B → published in → Group G
Group G → belongs to → Project P
Теперь появляется цепочка отношений.
Пользователь связан с проектом.
Объект опубликован в группе.
Группа связана с проектом.
Можно ли через эту цепочку получить доступ к объекту?
Это уже вопрос правила.
Сама цепочка ещё ничего не разрешает.
Она лишь создаёт набор фактов, из которых правило может вывести эффективное разрешение.
Именно поэтому в сложных моделях важно хранить не только факт существования связи, но и область, в которой эта связь действует.
9.12 Область отношения может быть частью ограничения
Область может использоваться не только для расширения доступа, но и для его ограничения.
Например, пользователь может иметь право UPDATE в проекте P, но не иметь его в проекте Q.
То есть одно и то же разрешение:
UPDATE
может существовать для одного объекта в одном контексте и отсутствовать для другого.
Поэтому нельзя рассматривать permission как самостоятельный глобальный атрибут пользователя.
Более точной становится конструкция:
Subject
+
Action
+
Object
+
Relevant Relations
+
Their Areas
↓
Effective Permission
9.13 Несколько областей не означают несколько контекстов
Здесь легко сделать ещё одну ошибку.
Если объект опубликован в трёх областях, это не означает автоматически наличие трёх независимых контекстов доступа.
Контекст формируется для конкретного запроса:
Can(User A, READ, Object B)?
Для ответа на него могут оказаться релевантными сразу несколько отношений:
User A → role → Project P
User A → member → Group G
Object B → published in → Project P
Object B → published in → Group G
Эти факты вместе участвуют в одном решении.
Поэтому несколько областей являются частью контекста, но не определяют его структуру полностью.
9.14 Область не обязана быть физическим контейнером
Область действия отношения может совпадать с физическим контейнером, но это не обязательно.
Например, документы проекта могут физически храниться в одной таблице:
documents
а логически относиться к разным проектам.
И наоборот, документы одного проекта могут физически храниться в разных таблицах, базах или хранилищах.
Следовательно:
физическое размещение данных и область действия отношения — разные уровни модели.
Это особенно важно для архитектур, где физическое хранение меняется независимо от логической модели доступа.
9.15 Область действия может изменяться
Отношение не обязательно существует с одной областью действия навсегда.
Пользователь может получить роль на уровне организации, затем отдельную роль в проекте.
Объект может быть опубликован в проекте, а затем публикация может быть отозвана.
Группа может быть перемещена внутри иерархии.
В каждом таком случае меняется не обязательно сам объект или пользователь.
Может измениться только отношение или его область действия.
А значит, изменяется и контекст последующих запросов.
Это один из ключевых моментов всей модели:
изменение доступа может происходить без изменения самого объекта.
9.16 Область действия — одно из измерений контекста
Теперь можно собрать предыдущие главы в одну конструкцию.
У нас есть:
-
субъект;
-
действие;
-
объект;
-
отношения между ними и связанными сущностями;
-
основания доступа;
-
области действия этих отношений;
-
дополнительные ограничения и условия.
Из этого набора формируется контекст конкретного решения.
Поэтому:
Scope ⊂ Relation
в том смысле, что область может быть характеристикой отношения,
но одновременно:
Scope ⊂ Context
в том смысле, что релевантная область входит в контекст конкретного запроса.
При этом:
Context ≠ Scope
потому что контекст содержит гораздо больше информации.
9.17 Отношение связывает, область ограничивает
Теперь можно сформулировать более точное разделение.
Отношение говорит:
что связывает эти сущности.
Область действия говорит:
где действует это отношение.
Правило говорит:
какое значение имеет это отношение для конкретного действия.
Эффективное разрешение говорит:
что субъекту в итоге разрешено сделать с конкретным объектом.
Например:
User A
│
└── role ──────────────→ Project P
│
Object B
│ │
└── published in ──────────┘
Здесь обе связи используют проект как область.
Но право READ возникает не из самого существования Project P.
Оно возникает из правила, которое интерпретирует эти отношения.
9.18 Что теперь известно о контексте
После предыдущих глав модель стала существенно конкретнее.
Мы начали с объекта и фактов вокруг него.
Затем выделили основания доступа.
После этого увидели, что объект может быть опубликован в некоторой области.
Теперь мы можем описать следующий уровень:
отношения имеют области действия, и эти области участвуют в определении того, какие отношения релевантны конкретному решению.
Поэтому контекст нельзя представить как одно значение:
Context = Project P
Более точная модель выглядит так:
Context =
Subject
+
Action
+
Object
+
Relevant Facts
+
Relevant Relations
+
Areas of those Relations
+
Rules and Constraints
Это не означает, что все перечисленные элементы обязательно хранятся вместе.
Это логическая структура решения, а не требование к физической структуре данных.
9.19 Главный вывод
Область действия не является разрешением.
Она не является пользователем, объектом или контекстом.
Она является характеристикой отношения, показывающей, где это отношение действует.
Одно отношение может иметь разные области.
Одна область может участвовать в разных отношениях.
Иерархия областей сама по себе не означает наследование отношений.
Публикация связывает объект с областью, но не превращает область в разрешение.
Поэтому для определения доступа недостаточно знать только:
User
Role
Permission
Scope
Необходимо понимать, какие отношения существуют между субъектом, объектом и связанными с ними сущностями, в каких областях эти отношения действуют и какие правила превращают их в эффективное разрешение.
На этом уровне модель уже позволяет перейти от описания отдельных отношений к вопросу о том, как из множества оснований возникает одно эффективное разрешение.
Этим и занимается следующая часть книги.
Глава 10. Субъект
10.1 Субъект — это тот, от чьего имени выполняется действие
В начале книги мы использовали конструкцию:
Subject → Action → Object
Теперь можно рассмотреть её первый элемент подробнее.
Субъект — это сущность, от имени которой выполняется действие над объектом.
В простейшем случае субъектом является пользователь:
User A
│
│ READ
▼
Document B
Но пользователь — не единственный возможный субъект.
Действие может выполняться от имени:
-
пользователя;
-
группы;
-
сервиса;
-
технической учётной записи;
-
другого типа сущности, которому модель безопасности разрешает выступать субъектом.
Поэтому субъект — это не синоним пользователя.
10.2 Субъект определяется конкретным запросом
Один и тот же пользователь может выполнять множество действий.
Например:
User A → READ → Document X
User A → UPDATE → Document X
User A → READ → Document Y
Во всех трёх случаях субъект один.
Но контекст решений различается, потому что различаются действие и объект.
Поэтому нельзя заранее определить один «контекст пользователя», который полностью описывает все его будущие действия.
Контекст строится вокруг конкретного запроса:
Can(Subject, Action, Object).
10.3 Субъект не равен его правам
Пользователь существует независимо от того, какие разрешения ему выданы.
Это кажется очевидным, но именно здесь часто возникает архитектурная ошибка.
Можно представить модель:
User
│
└── Permissions
и начать считать permissions свойством пользователя.
Но разрешение почти никогда не существует само по себе.
Оно связано с:
-
определённым действием;
-
определённой областью;
-
определёнными отношениями;
-
иногда с определённым объектом;
-
иногда с дополнительными условиями.
Поэтому правильнее рассматривать разрешение как результат применения модели к субъекту и остальным фактам.
10.4 Один субъект может иметь несколько оснований доступа
Вернёмся к объекту Document A.
Пользователь Alice может иметь отношение к нему сразу несколькими способами:
Alice
├── owner ───────────────→ Document A
├── role ────────────────→ Project P
└── member ──────────────→ Group G
Document A
├── published in ────────→ Project P
└── published in ────────→ Group G
В этом случае один и тот же субъект участвует сразу в нескольких основаниях доступа.
Нельзя сказать, что существует одно «право Alice».
Существуют разные отношения, которые могут участвовать в одном решении.
10.5 Субъект может быть связан с другими субъектами
Субъект не обязательно связан с объектом напрямую.
Например:
Alice
│
└── member of
│
▼
Group G
│
└── relation with → Document A
Здесь между субъектом и объектом находится другое отношение.
В более сложном случае цепочка может выглядеть так:
Alice
│
▼
Group G
│
▼
Project P
│
▼
Document A
Каждая стрелка является отдельным фактом.
Наличие всей цепочки ещё не означает наличие права.
Но цепочка может оказаться частью основания, которое правило использует при принятии решения.
10.6 Субъект может иметь несколько ролей
Один пользователь может одновременно иметь разные роли.
Например:
Alice → role R1 → Company C
Alice → role R2 → Project P
Это не противоречие.
Роли имеют разные области действия.
В одной области пользователь может обладать одним набором возможностей, в другой — другим.
Поэтому вопрос:
«Какая роль у Alice?»
часто поставлен неправильно.
Более точный вопрос:
«Какие ролевые отношения Alice существуют в контексте данного действия над данным объектом?»
Это уже вопрос не о пользователе вообще, а о конкретном решении.
10.7 Субъект может получать доступ через группу
Группа позволяет отделить индивидуального пользователя от организационного отношения.
Например:
Alice
│
└── member of → Group G
Group G
│
└── relation with → Object A
При изменении членства меняется и результат последующих проверок доступа.
Сам объект при этом может вообще не измениться.
Это показывает ещё одну важную особенность модели:
доступ субъекта может зависеть от фактов, которые не являются свойствами самого субъекта и не являются свойствами самого объекта.
Они находятся между ними.
10.8 Группа не превращает пользователя в группу
Если пользователь является членом группы, это не означает, что субъект запроса автоматически заменяется группой.
Субъектом по-прежнему может оставаться:
Alice.
Группа является частью отношений, через которые определяется её доступ.
Это различие важно для аудита и для дальнейшей обработки запроса.
Система должна понимать:
кто выполняет действие
и отдельно:
через какое отношение он получил возможность его выполнить
Это разные вопросы.
10.9 Субъект и инициатор запроса могут различаться
В распределённых системах запрос часто проходит через несколько компонентов.
Например:
User A
│
▼
Frontend
│
▼
Service X
│
▼
Service Y
│
▼
Database
Физически запрос к базе выполняет сервис.
Но это ещё не означает, что субъектом бизнес-действия является сервис.
Нужно различать как минимум:
-
технического инициатора запроса;
-
субъект бизнес-действия;
-
источник аутентификации;
-
компонент, который непосредственно обращается к данным.
Если эти понятия смешать, система может проверять права не того участника, от имени которого фактически выполняется операция.
10.10 Субъект и аутентификация — разные понятия
Аутентификация отвечает на вопрос:
кто предъявил системе свои учётные данные?
Авторизация отвечает на другой вопрос:
может ли этот субъект выполнить конкретное действие?
Установленный identity ещё не означает разрешённого действия.
Например:
Authenticated User A
│
▼
Can(User A, READ, Object B)?
Первый факт подтверждает личность субъекта.
Второй требует анализа модели доступа.
Поэтому наличие действующей сессии, токена или другого механизма аутентификации не является само по себе основанием для доступа к объекту.
10.11 Субъект может быть техническим
Не каждое действие выполняется непосредственно человеком.
Например, сервис может:
-
создавать записи;
-
запускать обработку;
-
читать данные другого сервиса;
-
выполнять автоматическую операцию по расписанию.
Тогда субъектом модели может быть сервисная сущность.
Но сам принцип не меняется:
Service A → Action → Object B
Для такого субъекта также необходимо определить релевантные отношения и основания доступа.
Это позволяет применять одну модель к пользовательским и межсервисным операциям, не смешивая при этом их конкретные механизмы аутентификации.
10.12 Субъект может быть составным
Иногда действие выполняется не просто от имени одного пользователя.
Например:
Alice
+
Service A
+
Delegated Authority
В таком случае возникает вопрос, какие именно условия должны быть выполнены одновременно.
Одного факта:
Alice имеет право
может быть недостаточно.
Может требоваться:
Alice имеет право
и
Service A действует от имени Alice
и
Delegation действительна.
То есть субъект запроса может быть простым, а контекст его действия — составным.
10.13 Субъект не определяет область доступа
Сам факт существования субъекта ничего не говорит о том, где действуют его отношения.
У одного пользователя могут быть:
Role → Company A
Role → Project B
Membership → Group C
Ownership → Object D
Поэтому нельзя построить корректную модель:
Subject → Scope → Permission
как универсальную замену отношениям.
Scope относится к конкретным отношениям.
Субъект лишь участвует в этих отношениях.
10.14 Субъект не определяет объект
То же относится к объекту действия.
Один субъект может иметь совершенно разные основания доступа к разным объектам.
Например:
Alice → owner → Document A
Alice → reader → Document B
Alice → no relevant relation → Document C
То, что Alice имеет право на Document A, ничего само по себе не говорит о Document B или Document C.
Поэтому нельзя переносить разрешение с одного объекта на другой без соответствующего правила.
10.15 Субъект участвует в построении эффективного разрешения
Теперь можно вернуться к общей модели.
У нас есть:
Subject
│
├── Relations
│
└── Roles / Memberships / Other Grounds
│
▼
Context
│
▼
Rules
│
▼
Effective Permission
Субъект является одной из координат решения.
Но он не содержит решение внутри себя.
Чтобы определить эффективное разрешение, необходимо сопоставить субъекта:
-
с действием;
-
с объектом;
-
с релевантными отношениями;
-
с областями действия этих отношений;
-
с другими фактами и ограничениями.
10.16 Один субъект — разные результаты
Пусть существует два запроса:
Can(Alice, READ, Document A)
Can(Alice, UPDATE, Document A)
Субъект одинаков.
Объект одинаков.
Но действия различаются.
Поэтому результат может быть разным.
Теперь изменим объект:
Can(Alice, READ, Document B)
Даже при том же субъекте и том же действии результат снова может измениться.
Таким образом:
Permission ≠ Property(Subject)
Эффективное разрешение возникает из отношения субъекта к конкретному действию и конкретному объекту в определённом контексте.
10.17 Что важно сохранить в модели
Из этого следуют несколько ограничений.
Нельзя:
-
считать пользователя единственным возможным субъектом;
-
хранить все права как глобальные свойства пользователя;
-
заменять отношения пользователя группой;
-
считать членство в группе готовым разрешением;
-
переносить право одного объекта на другой;
-
считать аутентификацию авторизацией;
-
считать технического исполнителя запроса автоматически субъектом бизнес-действия.
Все эти упрощения могут работать в маленькой системе.
Но по мере роста числа отношений они начинают скрывать сам механизм возникновения доступа.
10.18 Субъект — точка входа, но не источник разрешения
Теперь можно уточнить исходную формулу.
Мы начали с:
Subject → Action → Object
Она определяет что именно требуется проверить.
Но для самой проверки этого недостаточно.
Необходимо найти факты, связывающие субъекта с объектом и окружающими его сущностями:
Subject
│
├── direct relations
├── memberships
├── roles
├── ownership
├── delegations
└── other relevant facts
│
▼
Context
│
▼
Rules
│
▼
Effective Permission
Следовательно, субъект является не источником разрешения, а участником отношения, из которого разрешение может быть выведено.
10.19 Главный вывод
Субъект — это участник конкретного действия, а не контейнер всех своих прав.
Им может быть пользователь, группа, сервис или другая сущность, допускаемая моделью безопасности.
Один субъект может иметь множество отношений, ролей и оснований доступа.
Эти отношения могут действовать в разных областях и приводить к разным результатам для разных объектов и действий.
Поэтому эффективное разрешение нельзя определить только по субъекту.
Нужна вся конструкция:
Subject
+
Action
+
Object
+
Relevant Relations
+
Areas
+
Rules
↓
Effective Permission
Но пока мы говорили в основном о том, откуда берутся основания, а не о том, что именно они разрешают.
Следующий шаг — разобраться с ролью и разрешением: что такое permission, где появляется роль и почему даже наличие permission ещё не означает доступа к конкретному объекту.
Глава 11. Роль и разрешение
11.1 Роль и разрешение — не одно и то же
В простой модели доступа часто используется цепочка:
User → Role → Permission
Она полезна как первый уровень абстракции.
Например:
Alice → Manager → READ, UPDATE
Из такой записи кажется, что роль непосредственно содержит права пользователя.
Но в более сложной модели этого недостаточно.
Нужно различать как минимум два понятия:
роль отвечает на вопрос:
какое положение или отношение субъекта в данной модели имеет значение для доступа?
разрешение отвечает на вопрос:
какое действие допускается этой моделью?
Роль — это основание.
Permission — это описание допустимого действия.
Они участвуют в одном решении, но выполняют разные функции.
11.2 Permission описывает действие
Начнём с permission.
Permission — это формализованное право выполнить определённое действие.
Например:
READ
UPDATE
APPROVE
CLOSE
Но само по себе наличие READ ещё не означает:
пользователь может читать любой объект.
Permission должно быть применено к конкретному субъекту, объекту и контексту.
Поэтому:
Permission = READ
и
Can(Alice, READ, Document A)
— совершенно разные утверждения.
Первое описывает допустимое действие.
Второе является результатом конкретного решения о доступе.
11.3 Permission не определяет объект
Предположим, у пользователя есть READ.
Это не означает, что он может читать:
Document A
Document B
Document C
Право должно быть сопоставлено с объектом.
Например:
Alice
│
└── READ
│
├── Document A
├── Document B
└── Document C
Какие из этих объектов действительно доступны — определяется другими фактами.
Поэтому permission не является ACL объекта и не является публикацией объекта.
Оно лишь определяет допустимый тип действия.
11.4 Роль группирует разрешения
Роль нужна для того, чтобы не назначать каждое разрешение отдельно каждому субъекту.
Например:
Manager
├── READ
├── UPDATE
└── APPROVE
Роль объединяет набор permission.
Это делает модель управляемой.
Но здесь появляется важное ограничение:
роль определяет набор возможностей, но сама по себе не определяет, над какими объектами эти возможности действуют.
Например:
Alice → Manager
ещё не говорит:
Alice → UPDATE → Document A
Необходимо знать, где действует роль и какие объекты связаны с этой областью.
11.5 Роль всегда должна рассматриваться вместе с областью
Сравним:
Alice → Manager
и:
Alice → Manager → Project P
Это разные утверждения.
Во втором случае роль ограничена конкретной областью.
У Alice может быть:
Manager → Project P
Reviewer → Project Q
При этом набор разрешений для ролей может различаться.
Даже если одна и та же роль существует в разных областях, правила её применения могут зависеть от области.
Поэтому роль без области часто является только неполным описанием отношения.
11.6 Роль — это отношение субъекта к модели
Теперь можно связать предыдущую главу с текущей.
Мы уже знаем, что субъект может иметь множество отношений.
Роль — один из видов таких отношений.
Например:
Alice → role → Manager → Project P
Здесь:
-
Alice— субъект; -
Manager— роль; -
Project P— область действия; -
набор permission — значение роли для модели доступа.
То есть роль не заменяет отношение.
Она сама является частью отношения.
11.7 Роль не является разрешением
Это различие особенно важно при изменении модели.
Предположим:
Manager → READ + UPDATE
Позже политика системы меняется:
Manager → READ
Роль осталась прежней.
Изменилось значение этой роли в модели разрешений.
Поэтому:
Role ≠ Permission
Роль определяет положение субъекта в модели.
Permission определяет допустимое действие.
Связь между ними задаётся правилами модели.
11.8 Один permission может входить в разные роли
Например:
Manager → READ, UPDATE, APPROVE
Reviewer → READ, APPROVE
Auditor → READ
READ присутствует во всех трёх ролях.
Следовательно, permission не идентифицирует роль.
Оно является самостоятельным элементом модели.
Это позволяет изменять состав ролей независимо от самих действий.
11.9 Одна роль может использоваться для разных субъектов
Роль является частью модели, а не персональным свойством конкретного пользователя.
Например:
Alice → Manager → Project P
Bob → Manager → Project P
Carol → Manager → Project Q
Здесь одна и та же роль назначена разным субъектам.
Но область действия отношений различается.
Поэтому одинаковая роль не означает одинаковый доступ ко всем объектам.
11.10 Одна роль может иметь разные результаты
Пусть Alice имеет роль Manager в проекте P.
Объект Document A опубликован в P.
Другой объект Document B опубликован в Q.
Тогда наличие одной и той же роли может участвовать в двух разных решениях:
Alice → Manager → Project P
Document A → published in → Project P
Alice → Manager → Project P
Document B → published in → Project Q
В первом случае отношения пересекаются по области.
Во втором — нет.
Следовательно, роль сама по себе не определяет результат.
Она становится основанием доступа только вместе с областью и отношениями объекта.
11.11 Permission тоже может иметь область применения
В некоторых моделях permission назначается не просто субъекту, а субъекту в определённой области.
Тогда фактически возникает конструкция:
Subject
+
Permission
+
Area
Например:
Alice → READ → Project P
Это уже существенно точнее, чем:
Alice → READ
Потому что второе утверждение выглядит глобальным.
Первое задаёт границу действия разрешения.
Но даже такая конструкция ещё не гарантирует доступ к конкретному объекту.
Нужно определить, как объект связан с этой областью.
11.12 Permission не заменяет публикацию
Пусть:
Alice → READ → Project P
и:
Document A → published in → Project P
Тогда эти два факта могут быть использованы правилом для получения:
Alice → READ → Document A
Но если объект вообще не связан с Project P, одного permission недостаточно.
Это важное разделение:
Permission
↓
что разрешено
Publication
↓
где объект доступен для рассмотрения
Rule
↓
как эти факты связываются
Ни один из этих элементов не заменяет остальные.
11.13 Permission не заменяет отношение
Предположим, пользователю назначено:
READ
Но модель требует, чтобы пользователь был связан с объектом через определённую область.
Тогда:
READ
без соответствующего отношения не даёт доступа.
Это особенно важно в системах, где один permission используется для множества объектов.
Permission отвечает на вопрос:
какое действие допустимо?
Отношение отвечает на другой вопрос:
почему этот субъект вообще участвует в данном контексте?
11.14 Роль может быть только одним из оснований
У пользователя может существовать несколько оснований:
Alice
├── owner → Document A
├── role → Project P
├── member → Group G
└── delegated access → Document A
Роль является только одним из них.
Она не должна автоматически поглощать ownership, membership или delegation.
Иначе разные отношения начинают превращаться в один универсальный механизм:
User → Role → Everything
Такой механизм удобен только пока модель проста.
При появлении разных типов отношений он начинает скрывать происхождение права.
11.15 Несколько ролей могут дать одно permission
Пусть:
Manager → UPDATE
Editor → UPDATE
Alice одновременно имеет обе роли.
Это не означает, что у неё существует два разных UPDATE.
Для конкретного решения эффективный набор разрешений обычно рассматривается как множество возможностей.
Например:
Manager ──┐
├── UPDATE
Editor ──┘
Два основания приводят к одному эффективному permission.
Это важная идея для следующей главы: эффективное разрешение является результатом объединения оснований, а не копией каждого из них.
11.16 Несколько ролей могут давать разные permission
Теперь:
Manager → UPDATE
Reviewer → APPROVE
Alice имеет обе роли.
Тогда эффективный набор возможностей может быть:
READ
UPDATE
APPROVE
если READ также предоставляется одной из ролей или другим основанием.
Таким образом, роли могут агрегироваться.
Но агрегация сама по себе не отвечает на вопрос, к каким объектам применяются полученные возможности.
Для этого снова нужны области и отношения объектов.
11.17 Permission может быть ограничено объектом
Даже если субъект имеет:
UPDATE
объект может ограничивать применение этого права.
Например, объект может быть доступен пользователю только для чтения.
Тогда модель должна учитывать одновременно:
Subject Permission
∩
Object Constraint
∩
Action
В результате:
UPDATE ∩ READ_ONLY = READ
или, если модель устроена иначе, UPDATE просто не проходит проверку.
Конкретный механизм зависит от системы.
Принцип остаётся общим:
permission субъекта и допустимость действия над объектом — не одно и то же.
11.18 Permission имеет смысл только в отношении действия
Нельзя говорить о permission вообще, не определив, какое действие оно представляет.
Например:
READ
UPDATE
APPROVE
— это разные возможности.
Наличие READ не означает UPDATE.
Наличие APPROVE не означает CLOSE.
Поэтому разрешение должно быть связано с конкретным действием.
Это позволяет формализовать проверку:
Can(Subject, Action, Object)
где Action сопоставляется с требуемым permission.
11.19 Роль и permission находятся на разных уровнях
Теперь можно провести границу.
SUBJECT
│
▼
RELATION / ROLE
│
▼
PERMISSION
│
▼
ACTION
Но эта схема всё ещё неполна.
Не хватает объекта и области:
Subject
│
├── Role
│ │
│ └── Permission
│
└── other relations
│
▼
Area
Object
│
└── Publication / other relations
│
▼
Area
Только сопоставив эти элементы, можно перейти к конкретному решению.
11.20 Почему старая модель всё ещё полезна
После всей этой критики может показаться, что модель User → Role → Permission нужно выбросить.
Нет.
Она остаётся полезной.
Она хорошо решает задачу определения:
какие действия в принципе может выполнять субъект определённого типа или положения?
Например:
Manager → READ, UPDATE, APPROVE
Это компактный и удобный способ описать базовый набор возможностей.
Проблема возникает только тогда, когда эту модель пытаются использовать как полное описание доступа к конкретным объектам.
11.21 От роли к эффективному разрешению
Теперь у нас есть все необходимые элементы для следующего шага.
Роль связывает субъекта с набором permission.
Permission описывает допустимое действие.
Область ограничивает применение отношения.
Публикация связывает объект с областью.
Другие отношения могут создавать дополнительные основания.
Получается:
Subject
│
├── Role ──→ Permission
│
├── Membership
│
├── Ownership
│
└── Other Relations
│
▼
Relevant
Context
│
▼
Object Relations
│
▼
Rules
│
▼
Effective Permission
Здесь впервые становится видна разница между назначенным и эффективным разрешением.
Назначенное разрешение — это факт модели.
Эффективное разрешение — результат его применения к конкретной ситуации.
11.22 Назначенное право не равно эффективному праву
Пусть Alice имеет:
Manager → UPDATE
Это означает, что UPDATE входит в набор возможностей роли.
Но для конкретного объекта ещё нужно установить:
-
действует ли роль в релевантной области;
-
связан ли объект с этой областью;
-
нет ли дополнительных ограничений;
-
относится ли запрошенное действие к данному permission.
Только после этого можно получить:
Can(Alice, UPDATE, Document A)
Поэтому полезно различать:
Assigned Permission
и
Effective Permission
Первое — исходный факт.
Второе — результат модели.
11.23 Это не только вопрос ролей
На этом этапе легко снова сделать слишком простой вывод:
значит, нужно просто правильно вычислить роли.
Но роль — лишь одно из оснований.
Эффективное разрешение может зависеть от:
-
роли;
-
членства;
-
владения;
-
публикации;
-
делегирования;
-
иерархии;
-
ограничений объекта;
-
других отношений.
Поэтому архитектура не должна превращать все основания в роли только ради удобства вычисления.
Если разные отношения имеют разный смысл, этот смысл должен сохраняться в модели.
11.24 Что теперь можно формализовать
Мы можем записать:
Permissions(Subject, Context)
= правила, определяющие набор допустимых действий
Но окончательный вопрос всё ещё звучит иначе:
Can(Subject, Action, Object)?
Чтобы ответить на него, нужно объединить:
Subject
+
Action
+
Object
+
Relations
+
Areas
+
Permissions
+
Constraints
И только затем определить эффективное разрешение.
То есть permission является не финальным ответом, а одним из элементов вычисления ответа.
11.25 Главный вывод
Роль и permission решают разные задачи.
Роль описывает положение или тип отношения субъекта в модели доступа.
Permission описывает допустимое действие.
Роль может предоставлять несколько permission.
Один permission может входить в несколько ролей.
Один субъект может иметь несколько ролей в разных областях.
Но ни роль, ни permission сами по себе не определяют доступ к конкретному объекту.
Для этого необходимо учитывать объект, его отношения, области действия и другие основания доступа.
Поэтому старая конструкция:
User → Role → Permission
не исчезает.
Она становится частью более широкой модели:
Subject
+
Roles / Other Relations
+
Permissions
+
Areas
+
Object Relations
+
Rules
↓
Effective Permission
Именно эффективное разрешение, а не назначенная роль или отдельный permission, является непосредственным результатом модели доступа.
Следующая глава отвечает на главный практический вопрос:
как из нескольких отношений, ролей и разрешений получается одно эффективное разрешение для конкретного действия над конкретным объектом?
Глава 12. Как возникает эффективное разрешение
12.1. Назначенное и эффективное разрешение
Ранее мы различили отношение и разрешение.
Теперь нужно провести ещё одно различие.
В системе может существовать исходный факт:
Role R has permission READ
Это назначенное разрешение.
Но пользователь получает его не просто потому, что где-то существует такая запись.
Нужно определить:
-
относится ли роль к этому субъекту;
-
действует ли она в рассматриваемой области;
-
относится ли область к данному объекту;
-
применимо ли правило к конкретному действию;
-
существуют ли ограничения.
Результат этих вычислений — эффективное разрешение.
Оно отвечает уже не на вопрос:
Какие права вообще существуют?
а на вопрос:
Какие права действуют для данного субъекта в отношении данного объекта в данном контексте?
12.2. Эффективное разрешение является производным фактом
Это важное различие.
Исходный факт может выглядеть так:
User U has Role R
Другой:
Role R has READ permission
Третий:
Role R applies to Project P
А результат:
User U has effective READ permission
in Project P
уже является производным фактом.
Он получен из других фактов и правил.
Поэтому:
Source facts ≠ Effective permissions
Эффективное разрешение может быть материализовано в базе данных, но это не превращает его в исходный факт.
12.3. Основания доступа и разрешение — разные уровни
У пользователя может быть несколько оснований доступа.
Например:
Owner
Role in Project
Group membership
Publication
Delegation
Они отвечают на вопрос:
Почему субъект вообще может участвовать в данном решении?
Эффективное разрешение отвечает на другой вопрос:
Какое действие в результате разрешено?
Поэтому полезно различать:
Access grounds
↓
Rules
↓
Effective permission
Основание не равно праву.
Право не равно окончательному решению.
12.4. Контекст определяет применимость правил
Пусть существует правило:
Project role → READ
Само наличие такого правила ничего не говорит о конкретном запросе.
Нужно определить его контекст:
Subject = User A
Action = READ
Object = Document X
и релевантные отношения:
User A → Role in Project P
Document X → published in Project P
Тогда правило может быть применимо.
Но для другого объекта тот же пользователь может иметь ту же роль и получить другой результат.
Следовательно, эффективное разрешение нельзя считать постоянным свойством субъекта.
12.5. Универсальная схема вычисления
На концептуальном уровне процесс можно выразить следующим образом:
function effectivePermission(context):
applicableRules = findApplicableRules(context)
return evaluate(applicableRules, context)
Это псевдокод архитектурного уровня.
Он не предполагает конкретную базу данных, язык программирования или способ хранения правил.
Важна последовательность:
Context
↓
Applicable rules
↓
Evaluation
↓
Effective permission
При этом конкретная модель определяет, как именно комбинируются результаты.
Это может быть объединение разрешений, пересечение ограничений, применение условий или другая логика.
Универсального оператора combine() здесь нет.
12.6. Эффективное разрешение не равно ALLOW
Даже после получения эффективного разрешения решение ещё не обязательно принято.
Например:
Effective permission = READ
Requested action = UPDATE
Тогда право существует, но запрошенное действие ему не соответствует.
Поэтому нужно различать:
Effective permission
и
Access decision
Схема становится такой:
Facts
↓
Context
↓
Access grounds
↓
Rules
↓
Effective permission
↓
Check requested action
↓
ALLOW / DENY
12.7. Почему эффективное разрешение удобно материализовать
Если каждый запрос требует заново обходить все отношения, стоимость проверки растёт вместе со сложностью модели.
Поэтому эффективные разрешения можно вычислять заранее.
Например:
Role assignments
↓
Group/project relations
↓
Security rules
↓
Effective permissions
После этого запрос может работать уже с подготовленным результатом.
Это не меняет смысл модели.
Источник истины остаётся в исходных фактах и правилах.
Материализованное эффективное разрешение — производное представление.
12.8. Главное различие
Таким образом, в модели существуют как минимум четыре разных уровня:
Факт
↓
Отношение
↓
Контекст и основания
↓
Эффективное разрешение
А уже после этого появляется решение:
Effective permission
↓
Requested action?
↓
ALLOW / DENY
Это различие понадобится дальше.
Если эффективное разрешение вычисляется из большого количества отношений, возникает вопрос: как комбинировать несколько прав и ограничений, если они одновременно относятся к одному объекту?
Этому посвящена следующая глава.
Глава 13. Пересечение прав
13.1. Одно действие — несколько оснований
Для одного субъекта и объекта может существовать несколько независимых оснований доступа.
Например:
Owner → READ
ProjectRole → READ
Group → UPDATE
Если модель допускает независимые положительные основания, они могут совместно сформировать эффективный набор прав:
READ ∪ UPDATE
Но это не универсальное правило.
В одной модели несколько оснований действительно расширяют набор прав.
В другой одно отношение может ограничивать другое.
Поэтому нельзя заранее утверждать, что все права всегда объединяются.
Способ комбинирования является частью правил конкретной модели.
13.2. Право и ограничение
Особенно важно различать:
permission
и
constraint
Право отвечает:
Какое действие может быть выполнено?
Ограничение отвечает:
При каких условиях это действие допустимо?
Например:
Role → UPDATE
может существовать одновременно с условием:
Object state = DRAFT
Тогда право UPDATE существует, но применяется только в определённом состоянии.
Получается:
Permission + Applicability condition
а не просто один бит разрешения.
13.3. Область действия остаётся частью результата
Если субъект получил:
READ
нельзя потерять информацию о том, где это право действует.
Например:
READ in Project A
и
READ in Project B
— разные результаты.
Поэтому эффективное разрешение концептуально должно сохранять область действия:
Permission + Area
а не превращаться в глобальное:
READ
Иначе система может случайно распространить право за пределы исходного отношения.
13.4. Отсутствие права и явный запрет
Ещё одно важное различие:
permission absent
и
explicit deny
не обязательно означают одно и то же.
В модели без явных запретов отсутствие разрешения может означать:
Deny
Но в модели, где существуют отрицательные правила, нужно отдельно учитывать:
ALLOW
DENY
и правила их взаимодействия.
Нельзя объявлять приоритет DENY универсальным свойством любой модели доступа.
Он является частью конкретной политики.
13.5. Что происходит в Guardian
В текущей модели Guardian нет общего механизма произвольного конфликта ALLOW/DENY.
Проверка использует маски разрешений.
Концептуально обычная ветка выглядит так:
effective_user_permission
∩
object_scope
∩
required_permission
≠ ∅
В текущей реализации это выражено через побитовое пересечение:
(effective_permission
& object_scope_permission
& required_mask) != 0
Это не универсальная формула для всех систем.
Это конкретное правило текущей модели Guardian.
effective_user_group при этом не проверяется непосредственно во время has_object_permission().
Он участвует раньше — при построении производных разрешений и обработке Scope Barrier.
13.6. Почему нельзя просто сложить все права
Предположим:
Role A → UPDATE
Role B → APPROVE
Из этого может следовать:
UPDATE + APPROVE
Но если одно из правил означает ограничение области:
Role A → UPDATE in Project A
Role B → APPROVE in Project B
то объединять их в:
UPDATE + APPROVE everywhere
нельзя.
Следовательно, эффективный результат должен сохранять не только сами действия, но и условия их применимости.
13.7. Пересечение как свойство конкретной модели
Таким образом, «пересечение прав» — это не один математический оператор, применимый ко всем системам.
В разных моделях встречаются:
-
объединение независимых разрешений;
-
пересечение ограничений;
-
приоритет одного правила;
-
явные запреты;
-
условия применимости;
-
ограничения по области;
-
ограничения по состоянию.
Поэтому правильный архитектурный вопрос звучит не так:
Какой оператор использовать для прав?
а так:
Какие правила определяют результат, если несколько оснований одновременно применимы?
13.8. Эффективное разрешение как результат правил
Итоговая схема:
Source facts
↓
Relevant context
↓
Applicable grounds
↓
Rules
↓
Combination / constraints
↓
Effective permission
↓
Requested action
В Guardian конкретное правило для обычной проверки объекта можно представить как:
if
(effective_user_permission.permission_mask
&
object_scope.permission_mask
&
required_mask) != 0
then
ALLOW
else
DENY
Отдельно существует ветка владельца, где право владельца проверяется через соответствующий OWNER-scope объекта.
Это уже не абстрактная модель, а пример того, как общая архитектура превращается в конкретное правило.
Глава 14. Когда одного отношения недостаточно
14.1 Прямое отношение — только самый простой случай
До сих пор мы часто использовали простую форму:
Subject → Relation → Object
Например:
Alice → owner → Document A
Здесь всё понятно.
Субъект непосредственно связан с объектом.
Но во многих системах такая связь отсутствует.
Например:
Alice → member → Group A
Group A → member of → Project P
Project P → contains → Document A
Alice не связана с Document A напрямую.
Тем не менее эта цепочка может иметь значение для доступа.
14.2 Отношение может быть промежуточным
Рассмотрим другой пример:
Alice → employee of → Company C
Company C → customer of → Service S
Service S → owns → Document A
Чтобы определить доступ Alice к документу, необходимо пройти несколько отношений.
Ни одно из них само по себе не отвечает на вопрос:
Can(Alice, READ, Document A)?
Но вместе они могут образовать основание для ответа.
Получается:
Alice
↓
Company C
↓
Service S
↓
Document A
Доступ определяется не одним отношением, а структурой отношений.
14.3 Цепочка отношений не означает автоматически наличие права
Здесь легко сделать ошибочный вывод:
если между субъектом и объектом существует путь, значит доступ разрешён.
Это неверно.
Путь только показывает, что между сущностями существует определённая связь.
Например:
Alice → member → Group A
Group A → related to → Document A
Из этого ещё не следует:
Alice → READ → Document A
Потому что отношение related to может вообще не иметь значения для безопасности.
Даже если оно имеет значение, правила должны определить, какое именно значение.
14.4 Путь становится основанием только по правилу
Предположим, модель определяет:
member(Group)
+
contains(Group, Object)
→
READ(Object)
Тогда путь:
Alice → member → Group A
Group A → contains → Document A
становится основанием для READ.
Но это происходит не потому, что система «нашла путь».
Происходит другое:
правило интерпретирует определённую структуру отношений как основание доступа.
Это принципиальное различие.
14.5 Отношения могут иметь разную семантику
Рассмотрим три пути:
Alice → member → Group A
Alice → owner → Document A
Alice → reviewer → Document A
Все три являются отношениями.
Но их смысл различается.
owner может давать одни действия.
reviewer — другие.
member может вообще не давать непосредственного доступа к объекту.
Поэтому нельзя заменить все отношения одним понятием:
related
Отношение должно сохранять свою семантику.
14.6 Цепочка может состоять из разных типов отношений
Например:
Alice
↓ member
Group A
↓ belongs to
Project P
↓ contains
Document A
Здесь используются три разных отношения:
member
belongs to
contains
Их нельзя рассматривать как три одинаковых шага.
У каждого отношения есть собственный смысл и собственные правила.
Именно поэтому модель доступа работает не с простой топологией графа, а с семантическими отношениями.
14.7 Длина цепочки тоже имеет значение
Пусть модель разрешает:
Alice → member → Group A
Group A → member of → Project P
Project P → contains → Document A
Можно ли пройти ещё один уровень?
Например:
Alice
→ Group A
→ Group B
→ Project P
→ Document A
Это уже другой вопрос.
Если модель разрешает транзитивность, путь может продолжаться.
Если нет — цепочка заканчивается.
Следовательно, само наличие графа отношений ещё не определяет правила его обхода.
14.8 Иерархия не равна наследованию прав
Особенно часто эти понятия смешивают.
Пусть:
Group A
↓
Group B
↓
Group C
Это означает, что между группами существует иерархическое отношение.
Но из этого не следует автоматически:
право Group A
↓
право Group B
↓
право Group C
Наследование прав — отдельное правило.
Иерархия создаёт структуру.
Правило доступа определяет, как эта структура используется.
14.9 Иерархия может использоваться в обеих сторонах отношения
Иерархия может находиться со стороны субъекта:
Alice → member → Group C
Group C → child of → Group B
Group B → child of → Group A
Или со стороны объекта:
Document A → belongs to → Folder C
Folder C → child of → Folder B
Folder B → child of → Folder A
Возможен и более сложный случай:
Alice
↓
Group C
↓
Project P
↓
Folder B
↓
Document A
Тогда контекст доступа включает отношения с обеих сторон.
14.10 Один путь может быть недостаточным
Предположим:
Alice → member → Group A
Group A → contains → Document A
и одновременно:
Document A → status → Draft
Правило может требовать:
Membership
+
Publication
+
Object state
Тогда одного графового пути недостаточно.
Необходимо ещё проверить состояние объекта.
Это возвращает нас к главному принципу предыдущих глав:
контекст состоит не только из отношений между субъектом и объектом.
14.11 Цепочка отношений — часть контекста
Теперь можно уточнить понятие контекста.
Если доступ определяется через:
Alice
→ member of Group A
→ member of Project P
→ access to Document A
то для конкретного запроса релевантным является не только факт:
Alice → member → Group A
Но и структура отношений, связывающая субъект с объектом.
Таким образом:
Context
=
Direct relations
+
Relevant relation chains
+
Areas
+
Conditions
+
Object facts
Это не означает, что весь граф системы становится частью каждого запроса.
В контекст попадают только релевантные отношения.
14.12 Контекст не обязан содержать весь граф
В большой системе могут существовать миллионы отношений.
Для проверки:
Can(Alice, READ, Document A)?
нет необходимости рассматривать все отношения Alice.
Тем более не нужно рассматривать отношения всех пользователей системы.
Необходим только тот фрагмент модели, который может повлиять на конкретное решение.
Именно поэтому контекст — это не копия графа.
Это релевантная часть модели отношений для конкретного решения.
14.13 Один субъект может иметь несколько путей к объекту
Например:
Path A:
Alice → owner → Document A
Path B:
Alice → member → Group A
Group A → access to → Document A
Path C:
Alice → role → Reviewer
Reviewer → applies to → Project P
Project P → contains → Document A
Все три пути могут привести к одному результату:
READ
Но основания различаются.
Это полезно по двум причинам.
Во-первых, удаление одного пути не обязательно удалит доступ.
Во-вторых, для аудита можно определить, почему доступ существовал.
14.14 Один путь может давать несколько разрешений
Цепочка отношений также не обязана соответствовать одному permission.
Например:
Alice → owner → Document A
правило может определить:
READ
UPDATE
CLOSE
То есть отношение является основанием, а набор разрешений определяется правилом.
Поэтому:
Relation ≠ Permission
остаётся верным и для многошаговых отношений.
14.15 Разные пути могут давать разные разрешения
Теперь:
Path A → READ
Path B → UPDATE
Path C → APPROVE
Если все три пути применимы, эффективный результат может быть:
READ
UPDATE
APPROVE
Но снова важно не потерять область действия каждого пути.
Один путь может действовать только для одного проекта.
Другой — только для определённого типа объектов.
Третий — только при определённом состоянии объекта.
Поэтому путь всегда нужно рассматривать вместе с его условиями применимости.
14.16 Отношения могут зависеть друг от друга
Не все цепочки являются независимыми.
Например:
Alice → member → Group A
Group A → access to → Project P
Если Alice больше не является членом Group A, второй факт сам по себе не должен продолжать давать ей доступ.
То есть:
Path validity
=
Validity(Relation 1)
AND
Validity(Relation 2)
В этом смысле цепочка отношений похожа на логическое условие.
Каждое звено необходимо для сохранения пути.
14.17 Изменение промежуточного отношения может изменить доступ
Это особенно важно для архитектуры.
Пусть:
Alice → member → Group A
Group A → access → Project P
Project P → contains → Document A
Изменяется только:
Alice → member → Group A
Сам документ не изменился.
Проект не изменился.
Правило доступа не изменилось.
Но доступ Alice к документу может исчезнуть.
Следовательно:
источник изменения доступа может находиться далеко от самого объекта.
Это одна из причин, почему модель безопасности нельзя строить только вокруг изменений объектов.
14.18 Изменение объекта может изменить сразу много путей
Обратная ситуация.
Пусть объект:
Document A
участвует сразу в нескольких отношениях:
published in Project P
belongs to Group A
owned by Alice
Изменение одного свойства объекта может сделать недействительными несколько оснований доступа.
Например, изменение состояния объекта:
Draft → Archived
может ограничить действия, которые раньше были допустимы.
Таким образом, изменение контекста может происходить как через отношения субъектов, так и через факты самого объекта.
14.19 Цепочки отношений образуют граф
На этом уровне удобно использовать графовое представление:
Group A
↑ ↓
member contains
↑ ↓
Alice → Document A
Вершины графа — сущности.
Рёбра — отношения.
Но для авторизации одного графа недостаточно.
Нужно ещё определить:
какие отношения значимы;
какие пути допустимы;
какая длина пути допустима;
какие области применяются;
какие permissions возникают;
какие ограничения действуют.
Именно правила превращают граф отношений в модель доступа.
14.20 Это уже другой уровень сложности
При прямом отношении вопрос выглядит так:
Does Alice relate to Document A?
При многошаговом:
Is there a valid relation path
from Alice to Document A
that satisfies the access rules?
Это принципиально разные задачи.
Вторая требует учитывать структуру отношений.
Поэтому с ростом числа типов сущностей и отношений простая модель:
User → Role → Permission
становится всё менее достаточной.
14.21 Но граф сам по себе не является моделью разрешений
Важно не сделать следующий шаг слишком быстро.
Можно построить огромный граф отношений и всё равно не получить работающую модель безопасности.
Потому что граф отвечает на вопрос:
что с чем связано?
А модель доступа должна отвечать на другой вопрос:
какие из этих связей и каким образом влияют на конкретное действие?
Поэтому:
Graph
+
Semantics
+
Rules
=
Access Model
14.22 Связи между субъектами тоже могут быть частью основания
Ранее мы в основном рассматривали путь:
Subject → Group → Object
Но отношения могут быть сложнее:
Alice → manager of → Bob
Bob → owner of → Document A
Модель может разрешать Alice выполнять определённые действия над объектами Bob.
В этом случае объект вообще не связан с Alice напрямую.
Но и здесь нельзя автоматически считать manager of разрешением.
Необходимо правило, которое определяет смысл этой связи.
14.23 Делегирование особенно хорошо показывает многошаговость
Рассмотрим:
Alice → delegates → Bob
Bob → reviewer of → Document A
Если модель разрешает делегирование полномочий, то Alice может получить право, связанное с ролью Bob.
Но это уже требует нескольких проверок:
делегирование существует
AND
делегирование действительно
AND
Bob имеет соответствующее отношение
AND
правило разрешает передачу этого права
Одного отношения здесь явно недостаточно.
14.24 Не всякая транзитивность безопасна
Если система разрешает:
A → related → B
B → related → C
это не означает, что:
A → related → C
должно считаться эквивалентным.
Для некоторых отношений транзитивность естественна.
Для других она совершенно неверна.
Например, parent of может образовывать иерархию.
Но reviewed by не означает, что связанные через два шага объекты автоматически имеют такое же отношение.
Следовательно:
транзитивность — это свойство конкретного отношения или правила, а не общего свойства графа.
14.25 То же относится к наследованию
Если:
A → parent → B
то возможны разные правила:
rights(A) → rights(B)
или:
rights(B) → rights(A)
или вообще:
no rights inheritance
Сама связь parent ничего из этого не определяет.
Это должна определить модель.
14.26 Почему многошаговые отношения усложняют систему
С появлением цепочек возникают дополнительные вопросы:
-
какие отношения можно соединять;
-
в каком направлении;
-
какие пути допустимы;
-
где заканчивается поиск;
-
что считается валидным путём;
-
какие отношения дают permission;
-
какие только ограничивают путь;
-
как учитывать области;
-
как учитывать состояние объектов;
-
что происходит при нескольких путях;
-
что происходит при конфликте путей.
Именно поэтому сложная авторизация — это не просто большое количество permissions.
Это большая модель отношений.
14.27 Но сложность должна оставаться объяснимой
Хорошая модель должна позволять ответить не только:
ALLOW
но и:
почему ALLOW?
Например:
Alice
→ member of Group A
→ Group A has access to Project P
→ Document A belongs to Project P
→ rule grants READ
Такой результат можно проверить.
Если же решение возникает из длинной цепочки скрытых исключений, модель становится практически неуправляемой.
14.28 Что добавилось к нашей модели
После этой главы мы можем представить доступ так:
Subject
│
▼
Direct Relations
│
▼
Relation Chains
│
▼
Relevant Context
│
┌──────┴──────┐
▼ ▼
Access Grounds Constraints
│ │
└──────┬──────┘
▼
Effective Permission
│
▼
Access Decision
Теперь контекст включает не только отдельные отношения, но и те структуры отношений, которые имеют значение для конкретного решения.
14.29 Главный вывод
Одного отношения достаточно только для простейшего случая:
Subject → Relation → Object
В реальных системах основание доступа часто строится из нескольких отношений:
Subject
↓
Relation
↓
Intermediate Object
↓
Relation
↓
Object
Но наличие пути само по себе не означает наличие права.
Путь становится основанием доступа только тогда, когда правило модели придаёт этому пути соответствующий смысл.
Поэтому нужно различать:
Связь → что соединяет сущности
Путь → как отношения образуют цепочку
Контекст → какие из этих отношений релевантны
Основание → почему путь может участвовать в доступе
Permission → какое действие становится допустимым
И здесь появляется следующий уровень вопроса.
Если модель строится из множества исходных отношений, а некоторые отношения и состояния меняются независимо друг от друга, возникает проблема: что считать исходным фактом, а что уже результатом вычисления модели безопасности?
С этого начинается следующий этап — разделение исходных и производных фактов.
Глава 15. Исходные факты и производные факты
15.1 Не всё, что используется для доступа, произошло непосредственно
В предыдущей главе мы рассмотрели цепочки отношений.
Например:
Alice
↓ member
Group A
↓ belongs to
Project P
↓ contains
Document A
Из этой структуры можно получить вывод:
Alice → READ → Document A
Но сам этот вывод не является исходным фактом.
В системе действительно существуют:
Alice is member of Group A
Group A belongs to Project P
Project P contains Document A
А утверждение:
Alice can READ Document A
уже является результатом применения модели к этим фактам.
Это различие становится фундаментальным для архитектуры.
15.2 Исходный факт описывает состояние системы
Исходный факт — это утверждение, которое непосредственно фиксирует состояние или отношение в предметной модели.
Например:
Alice → member of → Group A
или:
Document A → belongs to → Project P
или:
Alice → owner of → Document A
Такие факты могут быть созданы, изменены или удалены в результате операций системы.
Они являются исходными данными для дальнейших рассуждений.
15.3 Производный факт получается из других фактов
Теперь рассмотрим:
Alice → READ → Document A
Если такого отношения непосредственно не существует, но оно следует из нескольких других фактов и правил, это производный факт.
Например:
Alice → member → Group A
Group A → access → Project P
Document A → belongs → Project P
и правило:
member + access + belongs
↓
READ
Тогда:
Alice → READ → Document A
получается вычислением.
Его значение не в том, что он был непосредственно записан пользователем.
Его значение в том, что он следует из модели.
15.4 Факт и вывод находятся на разных уровнях
Можно представить два уровня:
ИСХОДНЫЕ ФАКТЫ
Alice → member → Group A
Group A → access → Project P
Document A → belongs → Project P
ПРАВИЛО
member + access + belongs → READ
ПРОИЗВОДНЫЙ ФАКТ
Alice → READ → Document A
Это три разных элемента.
Не следует смешивать:
факт
и:
правило
а также:
правило
и:
результат применения правила
15.5 Почему это различие важно
Если всё хранить как один набор отношений, становится непонятно:
это произошло в предметной области или было вычислено системой безопасности?
Например:
Alice → member → Group A
может быть реальным членством.
А:
Alice → READ → Document A
может быть результатом этого членства.
Если эти два утверждения имеют одинаковый статус, становится трудно понять:
-
откуда взялось разрешение;
-
почему оно исчезло;
-
какой факт нужно изменить;
-
можно ли изменить его непосредственно;
-
что произойдёт после изменения исходного отношения.
Поэтому происхождение факта является частью архитектурной модели.
15.6 Производный факт не становится исходным оттого, что его сохранили
Это особенно важное различие.
Предположим, система вычислила:
Alice → READ → Document A
и сохранила этот результат в отдельной таблице.
Физически он теперь существует в базе данных.
Но семантически он всё ещё остаётся производным.
Его источник:
Alice → member → Group A
Group A → access → Project P
...
не исчезает.
Поэтому:
физическое хранение результата не превращает результат в исходный факт.
Это принципиально важно для любой системы, которая материализует вычисления.
15.7 Производный факт имеет источник
Если существует:
Alice → READ → Document A
полезно понимать:
почему?
Например:
Причина:
Alice member of Group A
Group A has access to Project P
Document A belongs to Project P
Или:
Причина:
Alice is owner of Document A
Производный факт поэтому можно рассматривать как результат функции:
DerivedFact = F(SourceFacts, Rules)
Если исходные факты или правила изменились, результат потенциально тоже должен измениться.
15.8 Производный факт может исчезнуть без изменения объекта
Рассмотрим:
Document A
Сам объект не менялся.
Но Alice покинула группу:
Alice → member → Group A
больше не существует.
Если именно это членство было частью основания доступа, результат:
Alice → READ → Document A
становится недействительным.
Таким образом:
Object unchanged
Source relation changed
↓
Derived permission changed
Это важный архитектурный эффект.
Доступ может измениться без изменения самого защищаемого объекта.
15.9 Производный факт может измениться без изменения исходного объекта
Та же ситуация может возникнуть при изменении другого отношения.
Например:
Document A → published in → Project P
была отозвана публикация.
Сам документ остался тем же.
Но если публикация была необходима для доступа, производный результат меняется:
READ → no longer applicable
Получается:
Изменение отношения
↓
Изменение контекста
↓
Изменение производного результата
И снова сам объект может оставаться неизменным.
15.10 Один исходный факт может влиять на множество результатов
Предположим:
Alice → member → Group A
Group A имеет доступ к большому числу объектов.
Тогда изменение одного членства может повлиять сразу на:
Document A
Document B
Document C
...
То есть:
1 source fact
↓
many derived facts
Это уже не локальное изменение одного объекта.
Изменение находится в одном месте модели, а последствия распространяются по связанным отношениям.
15.11 Один производный факт может зависеть от нескольких источников
Обратная ситуация:
Alice → member → Group A
Group A → access → Project P
Document A → belongs → Project P
Результат:
Alice → READ → Document A
зависит сразу от нескольких фактов.
Если удалить любой необходимый элемент цепочки, результат может исчезнуть.
Поэтому производный факт имеет не только значение, но и зависимости.
15.12 Зависимости образуют отдельную структуру
Можно представить:
Alice ──member──> Group A
│
access
↓
Project P
↑
belongs
│
Document A
и вывод:
Alice → READ → Document A
Зависит от этой структуры.
Если изменить:
Alice → member → Group A
необходимо определить все производные результаты, которые от него зависят.
Если изменить:
Document A → belongs → Project P
картина будет другой.
Следовательно, изменение исходного факта имеет область последствий.
15.13 Производные факты не должны становиться вторым источником истины
Предположим, система хранит:
Source:
Alice → member → Group A
Derived:
Alice → READ → Document A
Если затем удалить членство, но оставить производный факт, система окажется в противоречивом состоянии.
Получится:
Source says: no membership
Derived says: READ
Возникает вопрос:
чему верить?
Поэтому необходимо определить источник истины.
Производный результат не должен независимо утверждать состояние, противоречащее его источникам.
15.14 Производный факт можно пересчитать
Если производный факт является функцией исходных фактов и правил:
D = F(S, R)
то при необходимости его можно получить заново:
S + R
↓
F
↓
D
Это важное свойство.
Если производное состояние повреждено, его в принципе можно восстановить из источников.
Именно поэтому разделение исходных и производных данных имеет не только логический, но и практический смысл.
15.15 Но пересчитывать всё не всегда разумно
Пусть система содержит:
10 000 000 users
1 000 000 objects
100 000 000 relations
Изменился один факт:
Alice → member → Group A
Теоретически можно заново вычислить всю модель.
Но это означает обработку огромного количества данных, большая часть которых не связана с изменением.
Гораздо разумнее определить:
что изменилось
↓
от чего это зависит
↓
какие результаты затронуты
и пересчитать только их.
Пока это ещё не вопрос конкретной технологии.
Это следствие самой структуры зависимости.
15.16 Изменение факта и изменение результата — разные события
Пусть произошло:
Alice left Group A
Это изменение исходного состояния.
А затем:
Alice lost READ on Document A
Alice lost UPDATE on Document B
Alice lost APPROVE on Document C
Это уже последствия изменения.
Важно различать:
Source Change
и:
Derived Changes
Потому что они имеют разную семантику.
Первое изменяет модель предметной области.
Второе изменяет её вычисленное представление.
15.17 Не каждый производный факт относится к безопасности
Производные данные встречаются далеко не только в авторизации.
Например:
Order total
может быть вычислен из строк заказа.
Account balance
может быть вычислен из операций.
Search index
может быть построен из документов.
Effective permission
может быть вычислен из отношений доступа.
Во всех случаях действует одна и та же идея:
Исходное состояние
↓
Правила вычисления
↓
Производное состояние
Поэтому разделение source и derived — общий архитектурный принцип, а не специальный механизм безопасности.
15.18 В безопасности производные факты особенно чувствительны
В обычном вычислении ошибка в производном значении может привести к неправильному отображению данных.
В модели доступа ошибка может привести к другому результату:
Derived = ALLOW
когда исходные факты уже не дают основания для доступа.
Или наоборот:
Derived = DENY
когда основание существует.
Поэтому согласованность производного состояния с исходными фактами становится частью безопасности системы.
15.19 Контекст строится из исходных фактов
Вернёмся к определению контекста.
В главе 3 мы определили его как совокупность фактов и отношений, значимых для конкретного решения.
Теперь можно уточнить происхождение этих фактов.
Контекст может быть построен из:
исходных фактов
+
релевантных отношений
+
производных фактов
Но они не имеют одинакового статуса.
Исходные факты являются основанием модели.
Производные факты являются результатом её применения.
15.20 Производный факт может стать входом следующего вычисления
Это ещё один важный случай.
Например:
Alice → member → Group A
из этого можно получить:
Alice → member of Project P
а затем:
Alice → READ → Document A
Получается цепочка вычислений:
Source Facts
↓
Derived Fact 1
↓
Derived Fact 2
↓
Access Result
Но это не означает, что все уровни становятся исходными.
Каждый уровень имеет собственный источник.
15.21 Поэтому полезно различать слои фактов
Условно модель можно представить так:
┌─────────────────────────────┐
│ Исходные факты │
│ отношения, состояния, роли │
└──────────────┬──────────────┘
↓
┌─────────────────────────────┐
│ Производные факты │
│ результаты правил модели │
└──────────────┬──────────────┘
↓
┌─────────────────────────────┐
│ Эффективные разрешения │
└──────────────┬──────────────┘
↓
┌─────────────────────────────┐
│ Решение по запросу │
└─────────────────────────────┘
Границы между этими слоями помогают понимать, что именно нужно изменять и что необходимо пересчитывать.
15.22 Решение о доступе тоже не обязательно является фактом
Есть ещё одно различие.
Пусть система получила:
Effective Permission = READ
Это производный факт.
Но конкретный запрос:
Alice requests READ on Document A
даёт решение:
ALLOW
Решение зависит от конкретного действия.
Поэтому:
Effective Permission ≠ Access Decision
Даже если эффективное разрешение уже вычислено, конкретный запрос ещё должен быть сопоставлен с ним.
15.23 Производный факт должен иметь определённую семантику
Если в системе появляется таблица или представление:
Alice → READ → Document A
необходимо понимать, что именно означает эта запись.
Она может означать:
-
Alice имеет право читать объект;
-
Alice потенциально может читать объект;
-
Alice прошла последнюю проверку;
-
Alice была связана с объектом через определённый путь;
-
Alice получила разрешение из конкретной роли.
Это разные утверждения.
Поэтому название и семантика производного факта должны соответствовать тому, что действительно было вычислено.
15.24 Нельзя подменять исходную модель её проекцией
Представим, что исходные факты:
Alice → member → Group A
Group A → access → Project P
были преобразованы в:
Alice → READ → Document A
Alice → READ → Document B
Alice → READ → Document C
Если смотреть только на последние записи, мы видим результат, но не видим причины.
Это опасно не потому, что производная модель плоха.
Наоборот, она может быть очень полезна.
Проблема возникает, если забыть, что это представление исходной модели, а не сама модель.
15.25 Производные данные могут быть оптимизацией
Иногда один и тот же вывод приходится получать очень часто.
Тогда имеет смысл сохранить результат:
F(Source Facts)
↓
Materialized Result
вместо повторного вычисления:
Source Facts
↓
F
↓
Result
при каждом запросе.
Так производный слой становится способом оптимизации.
Но он остаётся корректным только при условии, что его состояние соответствует источнику.
15.26 Чем быстрее меняются источники, тем важнее согласованность
Предположим, отношения доступа меняются постоянно.
Тогда возникает проблема:
Source state:
new
Derived state:
old
В этот момент система располагает двумя версиями реальности.
Для обычной аналитики это может быть приемлемо.
Для безопасности нужно точно определить, какой уровень задержки допустим и где проходит граница между старым и новым состоянием.
Это уже инженерная проблема.
Но она появляется непосредственно из разделения:
source
и:
derived
15.27 Изменение модели должно распространяться дальше
Если изменился исходный факт:
Alice → member → Group A
недостаточно просто записать новое значение.
Необходимо обеспечить, чтобы связанные производные факты больше не противоречили новому состоянию.
Концептуально:
Source changed
↓
Affected derived facts identified
↓
Derived state updated
↓
Model becomes consistent
Способ реализации здесь пока не важен.
Важно само требование.
15.28 Без этого эффективное разрешение становится устаревшим
Предположим:
10:00
Alice is member of Group A
→ READ granted
10:05
Alice leaves Group A
10:06
Derived state still says:
Alice → READ → Document A
На уровне исходной модели доступ уже должен быть другим.
Но производное состояние ещё этого не отражает.
Получается:
Source Model ≠ Security Model
Именно здесь появляется понятие согласованности модели безопасности.
Оно станет центральным в следующих главах.
15.29 Исходные факты и производные факты нельзя смешивать в одну ответственность
Для исходного факта вопрос:
что произошло в системе?
Для производного:
что следует из того, что произошло?
Для эффективного разрешения:
какие действия допустимы в текущей модели?
Для решения:
разрешён ли этот конкретный запрос?
Это разные вопросы.
И если они смешаны в одном уровне системы, становится трудно определить:
-
кто изменяет данные;
-
что нужно пересчитать;
-
где искать причину результата;
-
как восстанавливать состояние;
-
что считать источником истины.
15.30 Что это меняет в архитектуре
До этого момента можно было рассматривать модель доступа как набор отношений и правил.
Теперь появляется временная и причинная структура:
Исходный факт
↓
изменение
↓
зависимые производные факты
↓
эффективные разрешения
↓
решение о доступе
Архитектура должна учитывать не только:
что хранить?
но и:
что является источником?
что является результатом?
что от чего зависит?
что нужно изменить после изменения источника?
Это уже вопрос устройства системы, а не только её модели.
15.31 Главный вывод
В модели доступа необходимо различать исходные факты и производные факты.
Исходный факт непосредственно описывает состояние системы:
Alice → member → Group A
Производный факт следует из исходных фактов и правил:
Alice → READ → Document A
Производный факт может быть сохранён физически, но от этого он не становится источником истины.
Его существование определяется зависимостями:
Source Facts
+
Rules
↓
Derived Facts
Поэтому изменение доступа может начинаться далеко от защищаемого объекта.
Изменение одного отношения способно изменить множество разрешений, а одно разрешение может зависеть сразу от нескольких исходных фактов.
Именно здесь появляется следующая архитектурная проблема:
если производные факты нужно поддерживать в соответствии с исходными, их недостаточно просто вычислить один раз — необходимо заранее определить, как модель будет восстанавливаться и изменяться при каждом изменении исходного факта.
Этому посвящена следующая глава.
Глава 16. Почему модель нужно строить заранее
16.1 Когда решение о доступе становится вычислением
До этого момента мы рассматривали доступ как результат применения правил к конкретной ситуации.
Есть субъект. Есть действие. Есть объект. Между ними существует набор отношений и фактов. Часть этих фактов является непосредственной, часть — производной. Из них формируется контекст, затем определяется эффективное разрешение, и только после этого принимается решение о доступе.
Для небольшого числа объектов и отношений такую модель можно вычислять непосредственно в момент запроса.
Допустим, пользователь обращается к объекту. Система может последовательно проверить:
-
является ли пользователь владельцем;
-
состоит ли он в нужной группе;
-
связана ли группа с проектом;
-
опубликован ли объект в этой области;
-
назначена ли пользователю соответствующая роль;
-
какие разрешения даёт эта роль;
-
какие ограничения действуют для данного объекта;
-
какие из найденных отношений действительно применимы.
Пока таких проверок немного, это выглядит естественно.
Но сама модель уже содержит важное свойство: отношения образуют зависимости.
Изменение одного отношения может повлиять не на один объект и не на одного пользователя.
Если пользователь вступил в группу, это может изменить доступ к множеству объектов. Если группа была включена в другую группу, изменяется положение всех её участников. Если роль назначена на проект, измениться может доступ к целому набору объектов внутри этого проекта. Если объект был опубликован в новой области, для него появляется новый путь видимости и действия.
Следовательно, вопрос постепенно меняется.
Нужно вычислить не просто:
имеет ли пользователь право на этот объект?
Нужно вычислить:
какие отношения и факты должны быть учтены, чтобы ответить на этот вопрос?
А это уже может быть нетривиальным вычислением.
Причём сложность определяется не только количеством объектов. Она определяется количеством зависимостей между фактами.
Пусть есть пользователь U, группа G, проект P и объект O.
Связь пользователя с объектом может выглядеть так:
U → G → P → O
Но эта цепочка сама по себе ещё ничего не разрешает. На каждом переходе существует собственная семантика.
Например, членство пользователя в группе может определять принадлежность к области. Роль может действовать в проекте. Публикация может связывать объект с проектом. Правило доступа должно определить, как эти отношения соединяются и какое разрешение из них получается.
Если такую цепочку строить заново для каждого запроса, запрос к данным постепенно превращается в запрос к самой модели безопасности.
И это важное архитектурное изменение.
Система начинает выполнять одну и ту же работу снова и снова.
При этом исходные факты, из которых строится результат, обычно меняются гораздо реже, чем читается результат.
Пользователь может оставаться членом одной группы часами или месяцами. Роль может быть назначена на проект на длительный срок. Публикация объекта также может оставаться неизменной долгое время.
Но за это время система может выполнить тысячи или миллионы проверок доступа.
Получается асимметрия:
изменения модели происходят относительно редко, а чтение результата происходит очень часто.
Именно здесь возникает необходимость строить модель заранее.
16.2 Что значит «заранее»
«Заранее» не означает, что система должна заранее принять все решения о доступе.
Это было бы слишком сильным утверждением.
Нельзя заранее знать все будущие запросы. Субъект, действие и объект конкретного запроса могут быть неизвестны до самого момента обращения.
Заранее нужно построить не само решение, а ту часть модели, которая нужна для его быстрого получения.
Это принципиальное различие.
Можно заранее определить производные факты:
-
какие группы связаны с пользователем;
-
какие разрешения получает пользователь;
-
в каких областях эти разрешения действуют;
-
какие области связаны с объектом;
-
какие отношения между субъектом и объектом могут участвовать в проверке.
А уже в момент запроса сопоставить эти факты с конкретным действием и объектом.
То есть архитектура разделяется на два разных момента.
Построение модели:
Исходные факты → Производные факты
Проверка конкретного запроса:
Субъект + Действие + Объект + Производные факты → Решение
Такое разделение позволяет не выполнять одну и ту же работу при каждом обращении.
Важно и другое: заранее построенная модель не становится новым источником истины.
Исходные отношения по-прежнему остаются исходными фактами. Производные данные лишь представляют результат их обработки в форме, удобной для последующего использования.
Это особенно важно для безопасности.
Если производная модель перестала соответствовать исходным фактам, проблема заключается не в том, что производный факт «стал неправильным источником истины». Проблема заключается в том, что производное состояние устарело.
Следовательно, архитектура должна решать две разные задачи:
-
построить производную модель;
-
поддерживать её в соответствии с исходными фактами.
Первая задача отвечает на вопрос:
как получить модель?
Вторая:
как не дать ей устареть?
Именно вторая задача становится особенно важной при изменении отношений.
16.3 Почему нельзя просто пересчитывать всё
На первый взгляд можно предложить простое решение.
Каждый раз, когда меняется любой факт безопасности, полностью пересчитывать модель.
Такой подход действительно имеет одно очевидное преимущество: его проще рассуждать.
Изменился факт — построили всё заново.
Но стоимость такого решения растёт вместе с размером системы.
Предположим, изменение одного членства пользователя в группе потенциально влияет на несколько тысяч объектов. Нет необходимости пересчитывать права остальных пользователей, если их отношения не изменились.
Если роль была изменена для одного проекта, нет необходимости заново строить модель для всей системы.
Если объект был опубликован в одной области, не требуется пересчитывать отношения, не связанные с этим объектом.
Значит, у изменения есть область влияния.
Это одна из самых важных характеристик производной модели.
Изменился исходный факт F.
Необходимо определить:
F → затронутые производные факты
а затем обновить только их.
Получается уже не полный пересчёт, а распространение изменения по зависимостям.
Такой подход требует знания того, от каких исходных фактов зависит каждый производный результат.
Например, эффективное разрешение пользователя может зависеть одновременно от:
-
назначения роли;
-
области этой роли;
-
членства пользователя;
-
иерархии групп;
-
публикации объекта;
-
владельца объекта;
-
других ограничений модели.
Если изменился один из этих фактов, система должна понимать, какие производные результаты могли измениться.
Поэтому производная модель — это не просто набор удобных таблиц или кешей.
За ней стоит структура зависимостей.
И чем сложнее модель доступа, тем важнее становится эта структура.
16.4 Почему модель должна быть готова до проверки данных
Есть ещё одна причина строить модель заранее.
Проверка доступа часто выполняется уже на уровне данных.
Система должна не просто сказать:
пользователю разрешено читать объект.
Она должна обеспечить, чтобы пользователь действительно получил только те данные, которые соответствуют этому разрешению.
Значит, решение об авторизации должно быть доступно в момент выполнения запроса к данным.
Если для каждой строки таблицы необходимо сначала пройти по исходным отношениям, построить цепочки, вычислить области, определить эффективное разрешение и только после этого решить, можно ли вернуть строку, то сама проверка данных становится зависимой от сложного графа отношений.
Это плохо масштабируется.
Чем глубже модель отношений, тем больше работы приходится выполнять непосредственно во время чтения.
Поэтому полезно перенести сложную часть вычисления из момента проверки в момент построения модели.
Тогда проверка становится значительно проще.
Вместо:
найти отношения → пройти цепочки → вычислить разрешения → определить применимость → проверить объект
можно приблизиться к:
найти готовые производные факты → сопоставить их с объектом → проверить действие
Это не означает, что запрос перестаёт быть частью модели безопасности.
Наоборот.
Просто сложное вычисление выполняется раньше, а запрос использует уже подготовленный результат.
Так появляется принцип:
дорогие вычисления, зависящие от отношений, по возможности выполняются при изменении модели, а не при каждом чтении данных.
16.5 Что именно нужно строить заранее
Теперь можно точнее определить, что означает «построить модель безопасности».
Не нужно заранее создавать запись для каждого возможного запроса.
Количество комбинаций:
Subject × Action × Object
может быть огромным.
Большая часть таких комбинаций никогда не будет использована.
Поэтому заранее строятся не все возможные решения, а устойчивые производные факты, из которых эти решения можно получать.
Например, вместо хранения ответа:
пользователь
Uможет выполнитьREADнад объектомO
можно хранить более фундаментальные производные факты:
пользователь
Uимеет такое-то разрешение в такой-то области;
и отдельно:
объект
Oсвязан с такой-то областью.
Тогда конкретный запрос соединяет уже подготовленные части модели.
Это позволяет избежать двух крайностей.
Первая — вычислять всю модель с нуля при каждом запросе.
Вторая — заранее материализовать все возможные решения для всех пользователей, действий и объектов.
Первая крайность слишком дорога во время чтения.
Вторая создаёт огромный объём производных данных и делает каждое изменение чрезвычайно дорогим.
Между ними находится более устойчивый архитектурный вариант:
материализовать те производные факты, которые являются стабильными результатами модели и могут многократно использоваться при проверках.
Это и есть смысл проекционного подхода.
Проекция представляет исходную модель в другой форме — не для изменения её смысла, а для эффективного использования.
16.6 Изменение становится частью модели
После этого становится очевидно, почему изменение исходного факта нельзя рассматривать как локальную операцию.
Если пользователь был добавлен в группу, изменился не только один факт членства.
Могли измениться:
-
его эффективные отношения с областями;
-
его эффективные разрешения;
-
доступ к объектам, связанным с этими областями.
Если изменилась публикация объекта, изменился не только факт публикации.
Могла измениться видимость объекта для множества субъектов.
Если изменилась роль, результат может распространиться на все области, в которых эта роль действует.
Поэтому изменение должно рассматриваться как начало цепочки:
Исходный факт изменён
→ определена область влияния
→ пересчитаны затронутые производные факты
→ новое состояние модели готово к чтению
Это означает, что модель безопасности должна иметь не только структуру данных, но и механизм её построения и обновления.
В этот момент архитектура начинает отличаться от обычной проверки ролей.
Мы уже не просто проверяем разрешение.
Мы поддерживаем отдельное состояние, которое является производным от отношений системы.
И это состояние должно быть достаточно полным для быстрых проверок, достаточно точным для безопасного доступа и достаточно локальным, чтобы его можно было обновлять без полного пересчёта всей системы.
16.7 От вычисления к проекции
Итак, к этому моменту в модели появилась новая необходимость.
Исходные факты нельзя просто оставить в исходном виде и каждый раз заново проходить все отношения.
Но и хранить готовый ответ на каждый возможный запрос тоже не требуется.
Нужен промежуточный слой.
Он должен отвечать на вопросы, которые повторяются при большом количестве запросов:
-
какие отношения пользователя уже известны системе;
-
какие эффективные разрешения из них следуют;
-
какие области связаны с объектом;
-
какие производные факты изменились после изменения исходного состояния.
Такой слой можно рассматривать как проекцию модели безопасности.
Проекция не заменяет исходные факты.
Она представляет их в форме, оптимизированной для конкретного класса запросов.
Отсюда следует важное архитектурное разделение:
Исходная модель
→ Построение производных фактов
→ Проекции безопасности
→ Проверка доступа
На этом уровне нам пока не важно, будут ли проекции таблицами базы данных, материализованными представлениями, индексами, отдельным хранилищем или другой структурой.
Это уже вопрос реализации.
Но сам принцип появляется именно здесь:
если модель доступа содержит отношения и производные факты, которые используются многократно, разумно построить их заранее и использовать при последующих проверках, а не вычислять заново весь путь отношений для каждого запроса.
Следующая глава будет посвящена тому, как выглядит такой проекционный слой и какие разные проекции нужны для разных частей модели.
16.8 Главный вывод
Сложная модель доступа создаёт две разные нагрузки.
Первая возникает тогда, когда меняются исходные факты.
Вторая — когда множество запросов читает результат этой модели.
Если всю сложность отношений переносить в каждый запрос, система платит за одно и то же вычисление снова и снова.
Поэтому часть модели строится заранее.
При этом заранее строится не окончательное решение для всех возможных запросов, а производные факты, из которых решение можно получить быстро.
Получается следующий цикл:
исходные факты → построение производной модели → проверка запросов → изменение исходных фактов → обновление производной модели.
С этого момента безопасность становится не только алгоритмом проверки.
Она становится состоянием системы, которое необходимо построить и поддерживать.
А следующий архитектурный вопрос уже вполне конкретен:
как представить это состояние так, чтобы оно было одновременно производным, обновляемым и пригодным для быстрого контроля доступа?
Глава 17. Проекции безопасности
17.1. Почему появляется проекция
К этому моменту модель уже содержит несколько уровней:
Source facts
↓
Relationships
↓
Context
↓
Rules
↓
Effective permissions
Если все эти вычисления выполнять непосредственно во время каждого запроса, проверка доступа может превратиться в обход сложного графа отношений.
Поэтому часть результата можно вычислить заранее.
Так появляется проекция безопасности.
Проекция — это производное представление исходной модели, подготовленное для определённого класса проверок.
17.2. Проекция не является второй моделью
Проекция не должна становиться независимым источником истины.
Например:
UserGroupLink
является исходным фактом.
А:
effective_user_group
является производным представлением.
Если удалить исходную связь, проекция должна измениться.
Поэтому:
Source facts → Projection
а не:
Source facts ↔ Projection
Проекция существует ради вычисления и проверки.
17.3. Какие результаты имеет смысл материализовать
В сложной системе обычно не требуется заранее создать запись для каждой комбинации:
User × Action × Object
Это может быть слишком дорого.
Гораздо полезнее материализовать устойчивые промежуточные результаты.
Например:
effective_user_group
effective_user_permission
object_scope
Они представляют разные части модели.
Первая описывает производные отношения субъекта и групп.
Вторая — эффективные разрешения субъекта в определённых областях.
Третья — области и основания, относящиеся к объекту.
17.4. Проекция строится из фактов и правил
Концептуально построение можно представить так:
function rebuildProjection(sourceFacts):
relevantFacts = selectRelevantFacts(sourceFacts)
derivedFacts = deriveSecurityFacts(
relevantFacts,
securityRules
)
replaceProjection(derivedFacts)
Это архитектурный псевдокод.
Он не предполагает конкретного механизма обновления.
Важна причинная цепочка:
Source facts
↓
Security rules
↓
Derived security facts
↓
Projection
17.5. Проекции Guardian
В Guardian значительная часть оснований доступа материализуется заранее.
Например:
KUserGroupLink
↓
effective_user_group
↓
Scope Barrier
↓
effective_user_permission
Отдельно:
KPublish
↓
object_scope
А для владельца существует отдельный OWNER-scope в object_scope.
Таким образом, при обычной проверке объекта система не должна заново обходить всю модель отношений.
Она использует уже подготовленные результаты.
17.6. Почему effective_user_group не проверяется непосредственно
Это важное архитектурное различие.
Можно было бы предположить:
has_object_permission()
↓
effective_user_group
↓
effective_user_permission
Но текущая модель работает иначе.
effective_user_group используется на этапе построения производных разрешений и обработки Scope Barrier.
После этого результат материализуется в:
effective_user_permission
Поэтому проверка объекта непосредственно использует:
effective_user_permission
object_scope
а не заново вычисляет групповую модель.
17.7. Проекция не является окончательным решением
Даже наличие записи в проекции не означает автоматически:
ALLOW
Например:
effective_user_permission = READ
object_scope = UPDATE
required = UPDATE
результат будет отрицательным.
Проекция предоставляет данные для проверки.
Она не заменяет правило.
Поэтому нужно различать:
Projection
и
Authorization decision
17.8. Проекция — часть архитектуры вычисления доступа
Проекции меняют место вычисления.
Без них:
Request
↓
Graph traversal
↓
Rules
↓
Decision
С ними:
Source changes
↓
Projection building
↓
Prepared security facts
Request
↓
Prepared security facts
↓
Decision
Стоимость сложной модели переносится с каждого запроса на этап изменения исходных фактов.
Это не устраняет стоимость.
Оно делает её управляемой.
17.9. Главный принцип
Проекция безопасности должна отвечать на конкретный класс вопросов.
Она не обязана содержать всю модель.
Хорошая проекция:
-
имеет понятный источник;
-
имеет определённые правила построения;
-
имеет понятную область применимости;
-
может быть пересоздана;
-
не становится самостоятельным источником истины.
Поэтому безопасность можно рассматривать как отдельный производный слой над моделью предметной области:
Domain facts
↓
Security-relevant facts
↓
Security projections
↓
Authorization
Следующий вопрос возникает естественно:
Что произойдёт, если изменится один из исходных фактов, от которого зависят несколько проекций?
Это уже проблема распространения изменений.
Глава 18. Изменение одного факта
18.1. Изменение отношения — это изменение модели безопасности
Рассмотрим простой факт:
User A is member of Group G
На первый взгляд изменяется одна связь.
Но если группа участвует в модели доступа, изменение может затронуть:
Group
↓
Project relations
↓
Effective permissions
↓
Object access
Поэтому изменение отношения является одновременно изменением состояния предметной области и изменением производной модели безопасности.
Это особенно важно для удаления связи.
Добавление доступа обычно заметно.
Отзыв доступа должен гарантированно удалить все результаты, которые больше не имеют основания.
18.2. Радиус влияния
Для каждого исходного факта существует область его влияния.
Например:
UserGroupLink
↓
effective_user_group
↓
effective_user_permission
↓
object authorization
Изменение одной связи может повлиять на множество эффективных разрешений.
Поэтому количество изменённых исходных строк не показывает реальную стоимость изменения.
Важнее знать:
какие производные факты зависят от изменённого факта?
Это можно назвать радиусом влияния.
18.3. Изменение может проходить через несколько проекций
Допустим, пользователь удалён из группы.
Тогда последовательность может выглядеть так:
KUserGroupLink
↓
effective_user_group
↓
Scope Barrier
↓
effective_user_permission
↓
object authorization
Другой тип изменения:
KPublish
↓
object_scope
↓
object authorization
Поэтому разные исходные факты могут иметь разные графы зависимостей.
Это ещё одна причина не пытаться представить всю безопасность одной таблицей.
18.4. Добавление и отзыв должны быть симметричны
Если система умеет построить результат:
Grant → Permission
она должна уметь удалить этот результат после:
Revoke → no longer valid
Нельзя считать отзыв второстепенной операцией.
Для безопасности он не менее важен, чем выдача.
Если выдача обработана, а отзыв потерян, система продолжает считать право действующим.
Это уже не обычная ошибка синхронизации.
Это изменение фактического уровня доступа.
18.5. Инкрементальное изменение
Один подход — пересчитывать только затронутую часть модели.
Например:
Changed fact
↓
Affected subjects
↓
Affected permissions
↓
Affected projections
Это эффективно, если зависимости хорошо известны.
Но оно требует точного определения радиуса влияния.
Ошибка в таком алгоритме может оставить старое производное разрешение.
18.6. Полное перестроение
Другой подход — периодически или по необходимости перестраивать проекцию из исходных фактов:
Source facts
↓
Rules
↓
Rebuild
↓
Projection
Полное перестроение дороже.
Зато оно позволяет проверить саму модель вычисления независимо от истории отдельных изменений.
Поэтому инкрементальное обновление и полное перестроение не являются взаимоисключающими.
Обычно они решают разные задачи:
incremental → normal operation
rebuild → recovery / validation / rule change
18.7. Асинхронное обновление
Если проекция обновляется не в той же транзакции, в которой изменился исходный факт, между ними возникает промежуточное состояние.
Например:
Source fact changed
↓
Projection not updated yet
В этот момент исходная модель и производная модель описывают разные состояния системы.
Это называется задержкой согласования.
Она не обязательно является ошибкой.
Но архитектура должна явно определить её семантику.
18.8. Изменение отношения → изменение разрешения
Для модели доступа цепочка должна быть явной:
Source fact changed
↓
Affected projection
↓
Effective permission changed
↓
Object authorization changed
↓
Data access changed
Это позволяет анализировать безопасность не как набор отдельных таблиц, а как граф зависимостей.
При проектировании необходимо понимать, какие изменения могут привести к изменению доступа.
18.9. Идемпотентность не заменяет согласованность
Операция обновления проекции должна по возможности быть идемпотентной.
То есть повторная обработка одного изменения не должна создавать другой результат.
Но:
Idempotence ≠ Consistency
Идемпотентная операция всё равно может быть применена не к тому набору исходных фактов.
Поэтому нужны оба свойства:
correct dependency propagation
+
idempotent processing
18.10. Что должна гарантировать модель
Для каждого класса производных данных должно быть понятно:
-
из каких исходных фактов он строится;
-
какими правилами;
-
какие изменения его затрагивают;
-
как обрабатывается отзыв;
-
как выполняется восстановление;
-
как определяется завершённость обновления.
Только тогда проекция становится управляемой частью архитектуры.
18.11. Модель должна учитывать переходные состояния
В реальной системе существуют состояния:
Source updated
Projection updating
Projection current
Projection rebuilding
Projection unavailable
Нельзя делать вид, что существует только:
correct
и
incorrect
Для каждого переходного состояния должна существовать определённая политика безопасности.
Например, система может временно запрещать операцию, пока критичная проекция не готова.
Или использовать предыдущую согласованную версию.
Главное — не получать это поведение случайно.
18.12. Главный вывод
Изменение отношения нельзя рассматривать как локальную запись в таблице.
Если отношение участвует в модели доступа, оно запускает цепочку:
Domain change
↓
Security model change
↓
Projection update
↓
Effective permission update
↓
Access change
Поэтому распространение изменений является частью самой модели безопасности.
Глава 19. Согласованность модели
19.1. Что означает согласованность
Пусть существуют:
F
— исходные факты,
R
— правила,
и:
P
— производная проекция.
Тогда согласованной можно считать модель, в которой:
P = F(Facts, Rules)
для определённого состояния исходных данных.
Иными словами, проекция должна соответствовать тем фактам и правилам, на которых она должна быть построена.
19.2. Источник истины остаётся исходным
Если:
User → Group
является исходным фактом, а:
effective_user_group
его производным представлением, то именно исходная связь является источником истины.
Если удалить запись из проекции вручную, это не изменяет предметную модель.
При следующем перестроении запись снова появится, если исходный факт всё ещё существует.
Поэтому:
Source facts = source of truth
Projection = derived state
19.3. Eventual consistency
В распределённых системах производная модель может обновляться с задержкой.
Это означает:
t0: source changed
t1: projection still old
t2: projection updated
Такой режим часто называют eventual consistency (согласованность в конечном итоге).
Сам по себе он не определяет, что должна делать система между t0 и t2.
Это архитектурное решение.
19.4. Почему для безопасности задержка особенно важна
Если проекция используется для авторизации, устаревшее состояние может привести к разным последствиям.
Например:
Grant
произошёл, но право ещё не появилось в проекции.
Тогда пользователь временно не получает доступ.
В другом случае:
Revoke
произошёл, но старое разрешение ещё осталось.
Тогда пользователь временно сохраняет доступ.
Эти ситуации не обязательно имеют одинаковую приемлемость для конкретной системы.
Поэтому семантика задержки должна быть частью архитектуры.
19.5. Граница состояния запроса
Важно определить, какое состояние считается актуальным для одного запроса.
Возможны разные модели:
read current projection
или:
read projection at version N
или:
wait until projection reaches required version
Последний вариант особенно полезен, когда действие должно быть разрешено только после применения конкретного изменения.
Таким образом, согласованность — это не только вопрос обновления таблицы.
Это вопрос того, какое состояние модели имеет право увидеть запрос.
19.6. Несколько проекций должны согласовываться по смыслу
Рассмотрим:
effective_user_permission
object_scope
Если одно представление уже обновлено, а второе ещё нет, результат может быть промежуточным.
Например:
new permission
+
old object scope
или наоборот.
Поэтому для связанных проекций необходимо определить границу согласованности.
Она может быть транзакционной, версионной или иной.
Но она должна быть явной.
19.7. Полное перестроение как часть нормальной архитектуры
Проекция должна быть восстанавливаемой из:
Source facts + Rules
Это означает, что полное перестроение не является аварийным костылём.
Оно необходимо как минимум для:
-
восстановления после сбоя;
-
проверки корректности;
-
изменения правил;
-
миграции;
-
устранения накопленного расхождения.
Если проекцию невозможно восстановить, она слишком сильно зависит от своей собственной истории изменений.
19.8. Изменение правил также изменяет производные данные
Не только факты могут измениться.
Могут измениться сами правила.
Например:
Role R → READ
превращается в:
Role R → READ + UPDATE
Исходные отношения пользователей не изменились.
Но эффективные разрешения изменились.
Следовательно:
Projection = F(Source facts, Rules)
а не только:
Projection = F(Source facts)
При изменении правил может потребоваться перестроение соответствующей части модели.
19.9. Согласованность должна быть проверяема
Нельзя ограничиться утверждением:
Проекция обычно обновляется.
Нужно иметь возможность проверить:
Source facts
↓
Rebuild
↓
Expected projection
и сравнить результат с текущим состоянием.
Это превращает согласованность из предположения в проверяемое свойство.
19.10. Guardian как пример
В Guardian исходные отношения и публикации являются источниками изменений.
Из них строятся производные представления:
KUserGroupLink
↓
effective_user_group
↓
Scope Barrier
↓
effective_user_permission
и:
KPublish / ownership / other security facts
↓
object_scope
Проверка объекта затем использует подготовленные:
effective_user_permission
object_scope
Это позволяет отделить построение модели от проверки запроса.
19.11. Согласованность не означает отсутствие задержки
Это важное различие.
Можно иметь модель с задержкой обновления и при этом иметь строго определённую согласованность:
Projection version N
может быть согласована с:
Source version N
хотя исходная модель уже находится на версии N+1.
Поэтому вопрос:
Согласована ли проекция?
не равен вопросу:
Самая ли это последняя версия?
19.12. Безопасность требует определённой деградации
Если проекция недоступна, система должна знать, что делать.
Недопустимо случайно выбирать поведение:
projection unavailable → ALLOW
или:
projection unavailable → DENY
только потому, что это оказалось самым простым техническим вариантом.
Это должно быть архитектурным решением.
Особенно важно не создавать параллельный «аварийный» механизм авторизации, который со временем станет второй моделью безопасности.
19.13. Главный вывод
Согласованность модели безопасности — это соответствие производных фактов исходным фактам и правилам.
Полная цепочка выглядит так:
Source facts
+
Security rules
↓
Security projections
↓
Effective permissions
↓
Access decision
Если исходный факт изменился, должна измениться и зависимая производная модель.
Если изменилось правило, производная модель также может потребовать перестроения.
Поэтому проекции безопасности должны быть:
-
производными;
-
объяснимыми;
-
восстанавливаемыми;
-
проверяемыми;
-
согласованными с определённой версией исходной модели.
Именно это позволяет перейти от простой проверки прав к устойчивой архитектуре безопасности.
Глава 20. Проверка доступа к объекту
20.1. От модели к конкретному запросу
До этого момента мы рассматривали модель доступа как систему фактов, отношений и правил.
Теперь появляется конкретный запрос:
User A
↓
READ
↓
Object X
Система должна ответить:
ALLOW
или:
DENY
Но этот ответ не возникает непосредственно из одной записи о пользователе.
К моменту проверки уже должны существовать необходимые производные факты.
В общем случае цепочка выглядит так:
Subject
↓
Relevant facts
↓
Context
↓
Access grounds
↓
Rules
↓
Effective permission
↓
Object authorization
И только после этого можно перейти к данным объекта.
20.2. Эффективного разрешения недостаточно
Предположим, пользователь имеет:
READ
Это ещё не означает, что он может читать любой объект.
Нужно знать область действия этого права.
Например:
READ in Project A
не означает:
READ everywhere
Поэтому проверка должна сопоставлять две стороны:
permission side
и:
object side
Одна сторона говорит:
Какие права есть у субъекта в данной области?
Другая:
В каких областях и с какими правами объект доступен?
Только их пересечение позволяет принять решение.
20.3. Публикация находится на стороне объекта
Публикация объекта — это не разрешение пользователя.
Она сообщает:
В какой области объект опубликован и какие права допускает эта публикация?
Например:
Object X
↓ published in
Project P
Это ещё не означает:
User A → READ → Object X
Нужно дополнительно установить, имеет ли пользователь эффективное разрешение в Project P.
Таким образом:
Object publication
+
Subject effective permission
↓
Object authorization
20.4. Владелец — отдельное основание
Владелец объекта представляет особый случай.
В текущей модели Guardian право владельца проверяется независимо от effective_user_permission.
Концептуально:
OWNER scope
↓
owner_id = current user
↓
permission mask
↓
required action
Это важно.
Владелец не обязан иметь отдельную роль только для того, чтобы получить собственные права на объект.
Тем самым сохраняется различие:
Creator ≠ Owner ≠ Effective permission
Создатель может передать владение.
После этого право должно следовать за владельцем, а не за created_by.
20.5. Обычная ветка проверки
Для обычного доступа текущая модель Guardian использует две проекции:
object_scope
+
effective_user_permission
Связь выполняется по:
company_id
user_id
scope_type
scope_id
а затем проверяется пересечение масок.
Упрощённо:
User permission
&
Object publication ceiling
&
Required permission
!= 0
То есть:
(eup.permission_mask
&
os.permission_mask
&
required_mask) != 0
Это конкретное правило Guardian, а не универсальная формула всех систем доступа.
20.6. Упрощённый Guardian-псевдокод
Текущую проверку можно представить так:
function hasObjectPermission(objectId, requiredMask):
identity = currentIdentity()
if exists OWNER scope where
scope.objectId == objectId
and scope.companyId == identity.companyId
and scope.scopeType == OWNER
and scope.scopeId == identity.userId
and (scope.permissionMask & requiredMask) != 0:
return ALLOW
if exists objectScope where
objectScope.objectId == objectId
and objectScope.scopeType != OWNER
and exists effectiveUserPermission where
effectiveUserPermission.companyId
== objectScope.companyId
and effectiveUserPermission.userId
== identity.userId
and effectiveUserPermission.scopeType
== objectScope.scopeType
and effectiveUserPermission.scopeId
== objectScope.scopeId
and (
effectiveUserPermission.permissionMask
&
objectScope.permissionMask
&
requiredMask
) != 0:
return ALLOW
return DENY
Это не буквальная копия SQL.
Это сокращённое представление его архитектурной логики.
20.7. Где здесь группа
На этом месте легко сделать неправильный вывод.
Можно предположить:
hasObjectPermission
↓
effective_user_group
↓
check membership
Но текущая модель Guardian устроена иначе.
effective_user_group используется раньше.
Он участвует в построении эффективных разрешений, в частности в обработке Scope Barrier и расширении прав по иерархии.
После построения:
effective_user_permission
runtime-проверка объекта использует уже этот результат.
Поэтому:
effective_user_group
не является непосредственным runtime-фильтром в has_object_permission().
20.8. Почему это разделение важно
Если выполнять всю логику отношений во время каждого запроса, система должна каждый раз заново определять:
к каким группам относится пользователь;
какие проекты связаны с группами;
какие роли действуют;
какие публикации существуют;
какие ограничения применимы.
Это дорого и усложняет проверку.
В Guardian большая часть этой работы происходит заранее.
Поэтому запрос видит уже подготовленные:
effective_user_permission
object_scope
а не весь граф исходных отношений.
20.9. Объектная авторизация ещё не является доступом к данным
Даже если:
has_object_permission(...) = ALLOW
это не означает:
можно читать любые физические строки,
относящиеся к объекту
Логический объект и физические данные могут иметь разные границы.
Один объект может быть представлен:
table A
table B
table C
или несколькими строками одной таблицы.
Поэтому нужна ещё одна граница:
Object authorization
↓
Data enforcement
20.10. Главный вывод
Проверка доступа к объекту — это не поиск пользователя в ACL.
Это сопоставление:
Subject effective permission
×
Object security scope
×
Requested action
В Guardian:
effective_user_permission
+
object_scope
↓
has_object_permission()
При этом владелец проверяется отдельной веткой.
Но даже положительное решение на уровне объекта не отменяет последующую защиту физических данных.
Глава 21. Почему ALLOW ещё не означает доступ к данным
21.1. Логический объект и физические данные
Пусть система разрешила:
User A → READ → Document X
Это решение относится к логическому объекту.
Но где находится Document X физически?
Он может быть представлен:
documents
document_versions
document_attributes
document_permissions
document_content
и другими структурами.
Поэтому между:
ALLOW(Object)
и:
SELECT rows
существует архитектурная граница.
21.2. Один объект может соответствовать нескольким строкам
Например:
Document X
│
├── metadata row
├── version row
├── attribute rows
└── content row
Разрешение на объект не объясняет автоматически, какие из этих строк должны быть доступны.
Для каждой физической таблицы может существовать собственное правило.
Получается:
Object authorization
↓
Data-specific enforcement
↓
Allowed physical rows
21.3. Почему проверки только в приложении недостаточно
Представим код:
if hasObjectPermission(user, document):
return repository.findAllRows(document)
Если репозиторий или другой путь к базе данных способен вернуть лишние строки, сама проверка объекта не защищает данные.
Особенно опасны массовые запросы:
SELECT *
FROM documents
Здесь приложение может вообще не проверять каждый объект отдельно.
Нужен механизм, который ограничивает физический результат самого запроса.
21.4. Row-Level Security
Для этого в реляционных базах данных может использоваться RLS (Row-Level Security, безопасность на уровне строк).
RLS позволяет определить, какие строки таблицы доступны конкретному запросу.
Это принципиально другой уровень модели.
Авторизация отвечает:
Может ли субъект выполнить действие над объектом?
RLS отвечает:
Какие физические строки должен увидеть этот запрос?
Поэтому:
Authorization model
↓
Effective authorization
↓
RLS
↓
Physical rows
RLS не заменяет модель авторизации.
21.5. RLS не является ReBAC
Это различие особенно важно.
ReBAC определяет доступ через отношения:
Subject
↓
Relationships
↓
Object
RLS ограничивает физические строки:
Query
↓
Database policy
↓
Rows
Поэтому нельзя сказать:
RLS — это реализация ReBAC.
Это разные уровни.
ReBAC может быть частью модели авторизации.
RLS может быть механизмом физического применения результата этой модели.
21.6. Почему ALLOW не должен отключать RLS
Даже если приложение уже получило:
ALLOW
это не должно автоматически означать:
RLS bypass
Иначе один ошибочный запрос способен получить больше данных, чем разрешено моделью.
Поэтому безопаснее рассматривать границы последовательно:
Object authorization
↓
RLS / data enforcement
↓
Physical rows
Каждый уровень решает свою задачу.
21.7. Разные таблицы — разные границы
Не каждая таблица обязана иметь одинаковую политику.
Например:
objects
→ object-level policy
object_attributes
→ attribute-level policy
object_content
→ content-level policy
audit_log
→ audit-specific policy
Физическая структура данных может иметь собственные ограничения.
Это ещё одна причина не считать:
ALLOW(object)
универсальным разрешением на всё, что технически связано с объектом.
21.8. В Guardian
Архитектура выглядит так:
Security source facts
↓
Security projections
↓
effective_user_permission
object_scope
↓
has_object_permission
↓
RLS
↓
Physical rows
has_object_permission() отвечает за логическую авторизацию объекта.
RLS остаётся отдельной границей применения к физическим данным.
21.9. Главный вывод
Безопасность данных требует двух разных вопросов:
1. Можно ли субъекту работать с объектом?
2. Какие физические данные должен увидеть этот запрос?
Первый вопрос относится к авторизации.
Второй — к применению этой авторизации к данным.
Поэтому:
ALLOW(Object) ≠ unrestricted data access
Это различие становится особенно важным, когда система выполняет массовые запросы или когда один логический объект представлен несколькими физическими структурами.
Глава 22. Row-Level Security
22.1. RLS как последняя граница
RLS полезен там, где модель безопасности должна быть применена непосредственно к физическим строкам.
Архитектурная цепочка:
Security model
↓
Effective authorization
↓
RLS policy
↓
Physical rows
Здесь RLS находится в самом низу.
Он не должен заново строить всю модель отношений.
22.2. Почему RLS не должен вычислять весь граф
Предположим, доступ зависит от:
User
→ Group
→ Parent Group
→ Project
→ Role
→ Publication
→ Object
Можно попытаться построить весь этот граф внутри каждой RLS policy.
Но тогда база данных становится одновременно:
-
хранилищем;
-
движком отношений;
-
движком проекций;
-
движком авторизации.
Это резко усложняет систему.
Гораздо устойчивее подготовить необходимые security facts заранее:
Relationships
↓
Security projections
↓
RLS
22.3. RLS использует подготовленное состояние
RLS должен получать достаточно информации, чтобы ответить на физический вопрос.
Например:
current_company_id
current_user_id
и связанные с ними производные данные.
Но вычисление сложных отношений желательно вынести выше.
Таким образом:
Projection layer
подготавливает модель,
а:
RLS
применяет её к данным.
22.4. Контекст запроса должен быть доверенным
Если RLS использует:
current_user_id
current_company_id
то возникает вопрос:
Кто устанавливает эти значения?
Они не должны зависеть от произвольного пользовательского SQL.
Контекст должен формироваться доверенным приложением или другим контролируемым механизмом.
В противном случае RLS лишь формально существует, но не является надёжной границей.
22.5. Транзакция и соединение
Контекст безопасности должен относиться к конкретному SQL-сеансу или транзакции.
Иначе connection pool может привести к опасному сценарию:
Request A
↓
connection
↓
security context A
connection returned to pool
Request B
↓
same connection
↓
old context A
Поэтому безопасность контекста соединения является частью архитектуры RLS.
22.6. Как это реализовано в Guardian
В текущей реализации Guardian используется RlsDataSourceProxy.
Архитектура разделяет:
physicalDataSource
и:
RLS-aware application DataSource
Физический источник используется инфраструктурными операциями, в частности Flyway.
Приложение получает прокси над физическим источником.
Прокси применяет RLS-контекст лениво перед первым бизнес-SQL на том же физическом соединении.
То есть принципиальная схема:
Hikari physical connection
↓
RlsDataSourceProxy
↓
business SQL
↓
PostgreSQL RLS
22.7. Почему RLS применяется лениво
Установка security context непосредственно в getConnection() может оказаться слишком ранней.
Соединение могло быть получено, но бизнес-запрос ещё не начался.
Поэтому текущая реализация откладывает применение RLS-контекста до момента, когда действительно выполняется бизнес-SQL.
Важно, что SET LOCAL выполняется на том же физическом соединении, на котором будет выполняться запрос.
Это связывает security context с конкретной транзакцией.
22.8. RLS и миграции
Инфраструктурные операции не должны случайно оказаться внутри обычной пользовательской модели доступа.
Поэтому в Guardian:
Flyway
↓
physicalDataSource
а:
Hibernate / repositories
↓
RlsDataSourceProxy
Это отдельные пути.
Так сохраняется различие между:
infrastructure context
и:
business request context
22.9. Разные операции требуют разных политик
Чтение и изменение данных не обязаны иметь одинаковую политику.
Например:
SELECT
может требовать READ.
А:
UPDATE
может требовать:
UPDATE
+
additional state condition
Поэтому RLS-политики должны соответствовать конкретной операции.
22.10. RLS не заменяет объектную авторизацию
Можно построить систему, где RLS полностью ограничивает строки.
Но это не означает, что RLS должен отвечать на все вопросы бизнес-авторизации.
Например:
Может ли пользователь согласовать объект?
Это может зависеть от роли, состояния, проекта и других отношений.
А вопрос:
Какие строки таблицы доступны этому SQL-запросу?
естественно решается на уровне RLS.
Разделение ответственности остаётся:
Authorization model
↓
Object authorization
↓
Data enforcement
22.11. Главный вывод
RLS — это не модель доступа.
Это механизм, который превращает решение модели безопасности в физическое ограничение данных.
Полная схема:
Source facts
↓
Security projections
↓
Effective authorization
↓
Object authorization
↓
RLS
↓
Physical rows
Чем сложнее отношения в модели, тем важнее не пытаться перенести их целиком в RLS.
База данных должна применять подготовленное правило к данным, а не заново строить всю модель мира.
Глава 23. Один объект — разные физические данные
В предыдущей главе мы дошли до базы данных как последней границы безопасности.
Но здесь возникает новая проблема.
Модель авторизации работает с логическими объектами:
Subject → Action → Object
А база данных работает со строками, таблицами и связями между ними.
Между этими двумя представлениями нет обязательного соответствия «один объект — одна строка».
Один объект может быть представлен множеством физических записей.
И наоборот, одна физическая таблица может содержать данные множества логических объектов.
Поэтому для надёжной защиты недостаточно знать, разрешён ли доступ к объекту.
Нужно ещё понимать, какие физические данные принадлежат этому объекту и как его контекст переносится на эти данные.
23.1 Логический объект и физическое представление — разные уровни
Логический объект существует на уровне предметной модели.
Например:
Document-123
может быть самостоятельным объектом с собственной идентичностью, жизненным циклом, владельцем и отношениями доступа.
Но физически он может выглядеть так:
object
id = 123
object_attribute
object_id = 123
...
object_revision
object_id = 123
...
object_file
object_id = 123
...
Для модели безопасности это один объект.
Для базы данных — несколько наборов строк.
Поэтому проверка:
ALLOW(Document-123)
не является сама по себе проверкой:
ALLOW(all rows related to Document-123)
Связь между этими утверждениями должна быть частью архитектуры.
Именно она определяет, как решение на уровне объекта превращается в ограничение на уровне данных.
23.2 Один объект может иметь несколько физических частей
Разделение объекта на несколько таблиц возникает по самым разным причинам.
Это может быть:
-
нормализация данных;
-
разделение редко используемых атрибутов;
-
хранение версий;
-
отдельные таблицы связей;
-
специализированные индексы;
-
хранение больших значений;
-
исторические записи;
-
технические данные.
При этом не все физические части объекта обязательно имеют одинаковую семантику доступа.
Например:
Document
├── metadata
├── attributes
├── revisions
└── files
Для пользователя всё это может восприниматься как один документ.
Но с точки зрения безопасности доступ к каждой части может определяться отдельно.
Это приводит к важному принципу:
Логическая принадлежность данных объекту ещё не означает одинаковую политику доступа ко всем этим данным.
Иногда доступ действительно должен распространяться на весь объект.
Иногда отдельная часть должна иметь более жёсткое ограничение.
Иногда часть данных вообще не должна быть доступна через пользовательский интерфейс.
Следовательно, физическая модель не должна заставлять нас делать необоснованные выводы о модели доступа.
23.3 Одна таблица может содержать множество объектов
Обратная ситуация встречается ещё чаще.
Например:
object_attribute
----------------
object_id | name | value
101 | ...
102 | ...
103 | ...
Все эти строки принадлежат одной таблице.
Но относятся к разным логическим объектам.
Если субъект имеет доступ только к объекту 101, запрос:
SELECT *
FROM object_attribute;
не должен возвращать ему строки объектов 102 и 103.
Значит, физическая таблица должна иметь возможность связать каждую строку с тем объектом или контекстом, который определяет её допустимость.
Эта связь может быть прямой:
row.object_id → object.id
или опосредованной:
row → revision → object
или:
row → project → object
Но она должна быть определена.
Иначе невозможно надёжно перенести решение об авторизации на физический уровень.
23.4 Контекст объекта не должен теряться при переходе к строке
До этого мы рассматривали контекст как совокупность фактов и отношений, необходимых для конкретного решения.
Теперь появляется ещё один вопрос:
Что происходит с этим контекстом, когда мы переходим от объекта к физической строке?
Допустим:
Object A
↓
Project P
↓
Subject S
Именно эта цепочка отношений позволила получить эффективное разрешение.
Но SQL уже работает с конкретной строкой:
row #8472
Чтобы защита сохранилась, должна существовать связь:
row #8472
↓
Object A
↓
security context
↓
Subject S
Иначе контекст закончится на уровне объектной проверки.
Это одна из наиболее важных архитектурных связей всей модели:
Logical Object
│
│ identity / relation
▼
Physical Data
│
│ security boundary
▼
RLS / enforcement
Физическая модель должна позволять определить, к какому логическому объекту относится защищаемая информация.
23.5 Не всякая связь с объектом означает одинаковый доступ
Здесь легко сделать ещё одну ошибку.
Если строка связана с объектом, можно попытаться сказать:
если есть доступ к объекту,
то есть доступ ко всем связанным строкам.
Но это не универсальное правило.
Связь может иметь разный смысл.
Например, строка может быть:
-
основной частью объекта;
-
исторической записью;
-
техническим журналом;
-
связью между объектами;
-
производным представлением;
-
служебной информацией.
Поэтому нужно различать:
Object relationship
и
Data access relationship
Первая отвечает на вопрос:
как эта запись связана с объектом?
Вторая:
должен ли доступ к объекту распространяться на эту запись?
Иногда ответы совпадают.
Но совпадение должно быть следствием модели, а не предположением, сделанным только по внешнему ключу.
23.6 Один объект может иметь разные уровни данных
Рассмотрим более сложный объект:
Object
├── public metadata
├── internal metadata
├── confidential attributes
└── binary content
Все эти данные относятся к одному объекту.
Но доступ к ним может различаться.
Например, субъект может иметь право:
READ(Object)
но конкретная модель может разрешать ему только часть представления объекта.
Тогда понятие «доступ к объекту» оказывается недостаточно точным.
Нужно различать как минимум:
доступ к объекту
доступ к данным объекта
доступ к отдельной части данных
Это не означает, что каждая система должна вводить отдельные права для каждого поля.
Такой уровень детализации нужен только там, где он соответствует предметной модели.
Главное — не предполагать, что физическое представление автоматически наследует все свойства логического объекта.
23.7 Связь объекта и данных особенно важна для массовых запросов
Для единичного запроса проблему иногда можно скрыть.
Например:
GET /objects/123
Приложение знает конкретный объект и может проверить его доступ.
Но представим:
GET /objects
или:
SELECT *
FROM object_attribute
WHERE name = 'status';
Теперь заранее неизвестно, какие объекты попадут в результат.
Запрос должен сам ограничить множество данных:
All rows
│
▼
rows belonging to accessible objects
│
▼
actual result
Именно здесь становится особенно важной связь:
Physical Row → Logical Object
Если она хорошо определена, политика доступа может работать независимо от того, был ли запрос единичным или массовым.
Если её нет, разработчику приходится вручную восстанавливать эту связь в каждом запросе.
А это возвращает нас к проблеме, которую мы уже видели: безопасность становится свойством отдельных программных путей.
23.8 Как это выглядит в архитектуре Guardian
В Guardian KObject является логическим объектом, для которого существует модель владельца, публикаций, отношений и эффективных разрешений.
Физические данные при этом не обязаны быть одной строкой KObject.
Архитектурно важно другое: конкретная таблица должна иметь определённую связь с объектом или с тем security context, который определяет допустимость строки.
Поэтому путь выглядит примерно так:
KObject
│
├── object identity
│
├── KContext
│
├── publications / grants / relations
│
▼
security projections
│
▼
effective permission
│
▼
table-specific RLS
│
▼
physical rows
При этом нельзя делать обратный вывод:
если строка находится в таблице, связанной с
KObject, значит правила доступа кKObjectавтоматически одинаковы для этой таблицы.
В Guardian разные классы данных имеют разные RLS-политики.
Именно поэтому has_object_permission и RLS остаются отдельными уровнями.
Объектная авторизация определяет допустимость действия.
RLS определяет фактическую границу данных для конкретной таблицы.
23.9 Физическая модель становится частью модели безопасности
На этом этапе становится видно, что безопасность нельзя проектировать полностью отдельно от структуры данных.
Если система должна гарантировать:
Subject S
может читать
Object O
то физическая модель должна позволять однозначно определить:
какие строки представляют O
и:
какие связанные строки должны быть доступны вместе с O
или, наоборот:
какие связанные строки не должны становиться доступными автоматически.
Поэтому при проектировании защищённой системы нужно рассматривать как минимум три слоя:
Domain model
↓
Security model
↓
Physical data model
Первый определяет, какие объекты и отношения существуют.
Второй определяет, какие из них имеют значение для доступа.
Третий определяет, как эти объекты и отношения представлены физически.
Если между слоями нет явного соответствия, безопасность начинает зависеть от случайных особенностей SQL и структуры отдельных таблиц.
А значит, архитектура безопасности должна учитывать не только вопрос:
«Кому разрешён доступ к объекту?»
но и вопрос:
«Где именно в физических данных находится граница этого объекта?»
23.10 Главный вывод
Логический объект и физические данные — разные представления одной системы.
Один объект может состоять из множества строк.
Одна таблица может содержать данные множества объектов.
Связанные с объектом данные могут иметь разную семантику доступа.
Поэтому:
ALLOW(Object)
не означает автоматически:
ALLOW(All related rows)
Между ними должна существовать явная архитектурная связь.
В полной цепочке она выглядит так:
Object
↓
Security context
↓
Effective permission
↓
Object authorization
↓
Object ↔ Physical Data mapping
↓
Data enforcement
↓
Physical rows
Именно эта связь позволяет сохранить смысл модели доступа после перехода от абстрактного объекта к реальной базе данных.
Но остаётся ещё одна проблема.
Что происходит, когда система только запускается, а производные security projections ещё не построены?
Источник истины уже содержит объект и отношения, но слой производных данных ещё пуст.
Следующая глава — «Холодный старт» — посвящена именно этому состоянию.
Глава 24. Холодный старт
До этого момента мы предполагали, что модель безопасности уже построена.
Есть исходные факты.
Есть отношения.
Есть проекции безопасности.
Есть эффективные разрешения.
Есть механизм, который ограничивает физические данные.
Но в реальной системе существует состояние, когда всего этого ещё нет.
Система только запускается.
База данных содержит исходные данные, но производные представления безопасности пусты или ещё не актуальны.
Это состояние можно назвать холодным стартом.
И оно показывает важную особенность всей архитектуры:
Производная модель безопасности не может считаться существующей только потому, что существуют её исходные данные.
Между ними есть процесс построения.
24.1 Исходные факты могут существовать раньше проекций
Представим минимальную систему.
В ней уже существуют:
User A
Project P
Object O
и отношения:
A → member of P
P → related to O
Но таблицы или представления, используемые для быстрых проверок, ещё пусты:
effective_user_permission = ∅
object_scope = ∅
Получается противоречивая на первый взгляд ситуация.
Предметная модель говорит:
отношения существуют.
Производная модель безопасности говорит:
необходимых фактов пока нет.
Это не обязательно означает ошибку.
Проекция является производным представлением.
Если она строится из исходных фактов, то в момент её построения естественно существует переходное состояние:
Source facts
│
│ build
▼
Security projections
Проблема начинается тогда, когда система пытается обслужить запрос так, будто производная модель уже полностью готова.
24.2 Пустая проекция не означает отсутствие права
Это принципиальное различие.
Пусть:
effective_user_permission = ∅
Можно ли из этого заключить:
User has no permission
Не всегда.
Это может означать два совершенно разных состояния:
A. Для пользователя действительно нет разрешения.
B. Разрешение существует в исходной модели,
но проекция ещё не построена.
На уровне одной таблицы оба состояния выглядят одинаково.
Но с точки зрения безопасности это совершенно разные ситуации.
Поэтому производные данные должны иметь определённую семантику состояния.
Нельзя молча интерпретировать:
projection is empty
как:
authorization is denied
если пустота может означать ещё и то, что вычисление не завершено.
Иначе система смешивает отсутствие права и отсутствие результата вычисления.
24.3 Почему нельзя просто разрешить доступ до построения проекций
Возникает естественное решение:
Если проекция ещё не готова, временно доверимся исходным данным.
Но это создаёт другую проблему.
Допустим, обычная проверка должна использовать:
effective_user_permission
+
object_scope
А во время холодного старта система начинает обходить исходные отношения непосредственно из приложения.
Получается уже две реализации одной модели:
normal path
→ projections
cold-start path
→ direct model traversal
Если они реализованы независимо, со временем они могут начать давать разные результаты.
Тогда холодный старт перестаёт быть временным состоянием и превращается во второй движок авторизации.
Поэтому fallback должен быть очень осторожным.
В идеале система должна иметь понятное правило:
проекция готова → используется обычный путь
проекция не готова → система не делает
необоснованный вывод о разрешении
Безопасное поведение при неопределённости обычно должно быть определено явно.
Но конкретное решение — отказ, ограниченный bootstrap-доступ или специальный доверенный путь — является уже свойством конкретной архитектуры.
Универсального варианта здесь нет.
24.4 Создание объекта особенно чувствительно к холодному старту
Проблема проявляется не только после запуска всей системы.
Она возникает при создании каждого нового объекта.
Последовательность может выглядеть так:
CREATE Object
│
▼
Object exists
│
▼
Security facts created
│
▼
Projection updated
│
▼
Object becomes normally accessible
Но между этими шагами существует промежуток.
Если объект уже физически создан, а его security projection ещё не появилась, возникает вопрос:
кто должен иметь доступ к объекту прямо сейчас?
Это особенно важно для создателя.
Новая запись не должна случайно оказаться:
-
недоступной самому создателю;
-
доступной всем;
-
доступной только потому, что сработал общий fallback;
-
доступной через устаревшую проекцию.
Следовательно, создание объекта и создание его первоначальных security facts нельзя рассматривать как полностью независимые операции.
На архитектурном уровне нужно определить bootstrap-состояние доступа.
24.5 Bootstrap не должен менять смысл модели
При холодном старте или создании объекта иногда требуется временный механизм, позволяющий завершить построение модели.
Например:
create object
↓
create initial security state
↓
build projection
↓
normal authorization
Но здесь существует опасность.
Если bootstrap получает особые права, легко сделать так, что эти права случайно станут частью обычной модели.
Тогда появляется скрытое правило:
"кто создал объект, тот всегда имеет право"
даже если предметная модель такого правила не содержит.
Именно поэтому нужно различать:
bootstrap authority
и:
domain authorization
Первое необходимо для технического перехода между состояниями.
Второе определяет обычный доступ системы.
Они могут использовать одни и те же идентификаторы и даже физические механизмы, но семантически это разные вещи.
24.6 Холодный старт требует определённого состояния готовности
Если безопасность зависит от производных данных, система должна понимать не только что построено, но и насколько модель готова.
Минимально существуют состояния:
SOURCE READY
│
▼
PROJECTION BUILDING
│
▼
PROJECTION READY
Но в распределённой системе этого может быть недостаточно.
Например, одна проекция уже обновлена:
а другая ещё нет:
или объектная проекция уже содержит новый объект, но не содержит новую публикацию.
Тогда система находится в частично построенном состоянии.
Это означает, что готовность должна рассматриваться не только для отдельных записей, но и для согласованного набора security facts, необходимого конкретной операции.
Для одного запроса может быть достаточно одной проекции.
Для другого потребуется несколько:
effective_user_group
+
effective_user_permission
+
object_scope
Следовательно, понятие «проекция готова» всегда имеет контекст.
Нужно понимать:
готова ли модель настолько, чтобы безопасно принять конкретное решение?
24.7 Холодный старт должен быть восстанавливаемым
Проекции — производные данные.
Значит, в правильно построенной архитектуре их можно уничтожить и построить заново из исходных фактов.
Это очень сильное свойство.
Оно означает:
Source facts
│
▼
Rebuild
│
▼
Security projections
Если после полного пересчёта получается другая модель безопасности, значит:
-
часть исходных фактов потеряна;
-
правила изменились;
-
вычисление неполно;
-
или производная модель содержит скрытую информацию, которой нет в источнике.
Последний вариант особенно опасен.
Проекция не должна становиться единственным местом, где существует критически важное знание о доступе.
Поэтому холодный старт является одновременно и механизмом запуска, и способом проверки архитектуры.
Если систему можно восстановить из исходных фактов, значит производный слой действительно остаётся производным.
24.8 Холодный старт в Guardian
В Guardian эта проблема проявляется непосредственно в момент создания объектов и построения security projections.
Сначала существуют исходные данные объекта и связанные с ним security facts.
Затем projection layer должен получить производное состояние:
source facts
│
▼
effective_user_group
effective_user_permission
object_scope
│
▼
object authorization
│
▼
RLS
Для нового объекта особенно важен переход от состояния создания к обычному состоянию авторизации.
Создатель должен иметь определённую моделью возможность завершить этот переход, после чего объект должен участвовать в обычном механизме проверки.
При этом bootstrap-состояние не должно превращаться в универсальное право доступа.
В Guardian это также объясняет, почему первоначальная безопасность объекта и последующее обычное разрешение — связанные, но не идентичные этапы.
После построения проекций система возвращается к нормальному пути:
security facts
↓
projections
↓
effective permission
↓
object authorization
↓
RLS
Таким образом, холодный старт не является отдельной моделью доступа.
Это переходное состояние той же модели.
24.9 Что происходит при полном восстановлении
Теперь можно рассмотреть более жёсткий сценарий.
Предположим, все производные security projections потеряны.
Исходные факты остались.
Система должна иметь возможность выполнить:
DROP / CLEAR derived state
↓
rebuild from source facts
↓
validate
↓
enable normal access
Важна не конкретная последовательность SQL-операций.
Важен принцип:
производная модель безопасности должна быть восстанавливаемой без ручного восстановления каждого разрешения.
Это влияет на требования к архитектуре с самого начала.
Нужно знать:
-
какие факты являются источниками;
-
какие проекции из них строятся;
-
какие зависимости существуют между проекциями;
-
как определить завершённость пересчёта;
-
как проверить результат;
-
что происходит с запросами во время восстановления.
Таким образом, восстановление перестаёт быть аварийной процедурой, придуманной после сбоя.
Оно становится нормальным свойством модели.
24.10 Главный вывод
Холодный старт показывает фундаментальное свойство проекционной модели безопасности.
Исходные факты и производные факты не появляются одновременно.
Между ними существует переход:
Source
↓
Build
↓
Projection
↓
Authorization
Поэтому система должна различать как минимум три состояния:
есть факт
есть производный результат
есть подтверждённая готовность модели
Особенно важно не путать:
нет разрешения
с:
разрешение ещё не вычислено
И не превращать bootstrap-механизм во второй источник авторизации.
Холодный старт также даёт критерий качества архитектуры:
если производную модель безопасности можно полностью восстановить из исходных фактов и правил, значит границы между источником истины и проекцией сохранены.
На этом заканчивается часть о физическом обеспечении доступа к данным.
Мы прошли путь от объекта и отношений до фактической строки:
Object
↓
Facts
↓
Context
↓
Access grounds
↓
Effective permission
↓
Authorization
↓
Data enforcement
↓
Physical data
Но до сих пор мы рассматривали отдельные элементы этой цепочки.
Следующая часть соберёт их в один процесс.
Мы посмотрим, что именно происходит с одним запросом — от момента его поступления до получения или изменения конкретных данных.
Глава 25. Один запрос от начала до конца
До этого момента модель доступа рассматривалась по частям.
Мы отдельно говорили об объекте, субъекте, отношениях, областях, публикациях, разрешениях, проекциях и физической защите данных.
Теперь соберём их в одну последовательность.
Рассмотрим один обычный запрос:
Subject S
хочет выполнить
Action A
над
Object O
На первый взгляд достаточно ответить:
ALLOW
Но реальная система должна пройти значительно больший путь:
Request
↓
Subject
↓
Object
↓
Relevant facts
↓
Context
↓
Access grounds
↓
Effective permission
↓
Object authorization
↓
Data enforcement
↓
Physical data
Каждый этап отвечает на свой вопрос.
Именно разделение этих вопросов позволяет сложной модели оставаться объяснимой.
25.1 Сначала определяется сам запрос
Любое решение о доступе начинается не с роли и не с таблицы разрешений.
Оно начинается с конкретного действия.
Есть:
Subject = S
Action = READ
Object = O
То есть система должна понять:
кто действует, что он хочет сделать и над чем именно.
Это кажется очевидным, но именно здесь исчезает слишком простая модель:
User → Permission
Пользователь не получает абстрактное право «читать».
Он выполняет конкретное действие над конкретным объектом.
Поэтому два запроса одного пользователя могут иметь разные результаты:
READ(Object A)
READ(Object B)
даже если субъект один и тот же.
Точно так же один объект может давать разные результаты для разных действий:
READ(Object A)
UPDATE(Object A)
CLOSE(Object A)
Следовательно, запрос является точкой, относительно которой строится весь дальнейший контекст.
25.2 Затем определяется субъект и объект
После фиксации действия система должна определить две стороны отношения:
Subject
Object
Субъект — тот, от чьего имени принимается решение.
Объект — предмет действия.
Важно не смешивать субъекта с механизмом аутентификации.
Например, HTTP-запрос может пройти через:
client
↓
API
↓
service
↓
database
Но это не означает, что каждый из этих компонентов является субъектом бизнес-действия.
Система должна сохранить идентичность того субъекта, от имени которого выполняется операция.
После этого определяется объект.
В простом случае он известен непосредственно из запроса:
GET /objects/123
В более сложном запросе объект может определяться через набор условий или вообще отсутствовать как единственный экземпляр.
Например:
SELECT *
FROM objects
WHERE project_id = :project;
Тогда запрос потенциально затрагивает множество объектов.
Это уже влияет на дальнейшую модель проверки.
25.3 Из исходных данных выбираются релевантные факты
После определения Subject, Action и Object система должна понять, какие факты имеют отношение к этому решению.
Например:
Subject S
├── role R
├── member of Group G
└── assigned to Project P
Object O
├── owned by S2
├── published in Project P
└── related to Group G2
Но весь набор фактов системы здесь не нужен.
Нас интересуют только те, которые могут изменить результат конкретного решения.
Это и есть переход к контексту:
All system facts
│
▼
Relevant facts
│
▼
Context(S, A, O)
Контекст не обязан существовать как одна физическая сущность.
Это логическое множество фактов и отношений, необходимых для данного решения.
25.4 Из контекста определяются основания доступа
Следующий вопрос:
почему этот субъект вообще может получить доступ к этому объекту?
Например, основанием может быть:
ownership
membership
role
publication
delegation
Но наличие отношения ещё не означает наличие права.
Например:
S → member of P
говорит, что субъект связан с проектом.
Но из этого самого по себе ещё не следует:
S → READ → O
Нужны правила, которые определяют значение этого отношения.
Поэтому следующий переход выглядит так:
Relevant facts
↓
Access grounds
↓
Rules
Именно здесь модель превращает факты в основания, а основания — в потенциальные разрешения.
25.5 Формируется эффективное разрешение
Теперь система может определить, какие разрешения действительно применимы.
Пусть существуют два основания:
Role R
→ READ, UPDATE
Publication P
→ makes Object visible
Они выполняют разные функции.
Роль может дать разрешение на действие.
Публикация может сделать объект доступным в соответствующей области.
Поэтому нельзя просто сложить все отношения в один список.
Система должна применить правила композиции.
Результатом становится:
EffectivePermission(S, O)
Причём результат может зависеть от области:
READ @ Project P
UPDATE @ Project P
а не просто:
READ
UPDATE
Это принципиально.
Право без области может быть недостаточно определённым для объектной модели.
Поэтому эффективное разрешение является производным фактом:
Source facts
+
Rules
↓
Effective permission
25.6 Выполняется проверка объектного действия
Теперь можно задать конкретный вопрос:
Can(S, READ, O)?
Проверка использует подготовленную модель.
В ней могут участвовать:
-
эффективные разрешения субъекта;
-
область их действия;
-
публикация объекта;
-
другие объектные ограничения;
-
специальное основание, например владение.
Результатом становится:
ALLOW
или:
DENY
Но здесь важно не остановиться.
ALLOW означает:
модель авторизации разрешила субъекту выполнить это действие над этим логическим объектом.
Он ещё не означает:
база данных обязана вернуть субъекту любые строки, связанные с этим объектом.
Это различие мы установили в предыдущих главах.
Поэтому после объектной авторизации путь продолжается.
25.7 Запрос переходит к физическим данным
Теперь приложение должно получить или изменить реальные данные.
Например:
SELECT *
FROM document
WHERE id = :id;
Или:
SELECT *
FROM document_attribute
WHERE object_id = :id;
Здесь вступает в действие физическая модель безопасности.
База данных должна понимать, какие строки допустимы для текущего субъекта.
Поэтому появляется ещё одна проверка:
Object authorization
+
Data enforcement
↓
Actual rows
Если используется RLS, политика базы данных применяется непосредственно к обращению к таблице.
Это позволяет защитить данные даже тогда, когда запрос был сформирован другим программным путём.
В результате конечный ответ системы определяется не только тем, что модель сказала ALLOW, но и тем, какие физические строки реально прошли ограничения данных.
25.8 Один запрос в собранной модели
Теперь можно посмотреть на весь путь целиком.
REQUEST
│
▼
Subject / Action / Object
│
▼
Relevant facts
│
▼
Context
│
▼
Access grounds
│
▼
Rules
│
▼
Effective permission
│
▼
Object authorization
│
ALLOW
│
▼
Data enforcement
│
▼
Physical rows
Это не обязательно означает, что в системе существует столько последовательных вызовов.
Некоторые этапы могут быть заранее вычислены.
Некоторые — объединены.
Некоторые — реализованы непосредственно в базе данных.
Но логически эти вопросы должны быть различимы.
Это позволяет понять, где искать ошибку.
Если пользователь неожиданно не видит объект, проблема может находиться в:
facts
relations
publication
projection
effective permission
Если объектная проверка возвращает ALLOW, но SQL не возвращает строку, искать проблему нужно уже ниже:
object → physical data mapping
RLS
database security context
Разделение уровней делает такую диагностику возможной.
25.9 Почему нельзя выполнять весь путь заново для каждого запроса
Сквозной алгоритм может показаться дорогим.
Если каждый запрос должен заново искать все отношения, строить контекст, обходить иерархии и вычислять разрешения, то сложная модель быстро становится узким местом.
Поэтому мы ранее разделили два времени.
Время изменения модели:
Source fact changes
↓
Security projections update
Время запроса:
Request
↓
read prepared security state
↓
check
↓
query data
Это одно из главных архитектурных следствий всей модели.
Мы не пытаемся заранее вычислить все возможные:
Subject × Action × Object
Но заранее строим те производные факты, которые можно переиспользовать.
Таким образом, сложность переносится из каждого запроса в процесс изменения модели безопасности.
25.10 Что происходит при изменении одного отношения
Предположим, субъект больше не является членом группы.
На уровне предметной модели изменился один факт:
S → Group G
Но запросы, которые раньше разрешались через эту связь, могут быть многочисленными.
Поэтому изменение проходит обратный путь:
Source fact changed
↓
Affected security facts
↓
Projections
↓
Effective permissions
↓
Future authorization decisions
↓
Data access
Важно, что сам объект при этом может вообще не измениться.
Меняется отношение вокруг него.
Но результат запроса:
Can(S, READ, O)
может измениться.
Это ещё раз показывает, почему доступ нельзя считать простым свойством объекта.
25.11 Что происходит при изменении объекта
Теперь рассмотрим обратный случай.
Объект меняет публикацию:
Object O
was published in P1
becomes published in P2
Сам пользователь может не измениться.
Его роль тоже может не измениться.
Но изменяется объектная сторона отношения.
Поэтому меняется контекст:
Object facts
↓
Object security projection
↓
Applicable permissions
↓
Authorization result
Следовательно, изменение доступа может быть вызвано изменением:
-
субъекта;
-
отношения;
-
роли;
-
области;
-
публикации;
-
объекта;
-
правила.
Это и есть причина, по которой security model должна рассматриваться как отдельная производная модель системы.
25.12 Один запрос — несколько уровней ответственности
Теперь можно сформулировать границы ответственности.
Доменная модель отвечает за то, какие объекты и отношения существуют.
Модель безопасности определяет, какие из этих фактов имеют значение для доступа и как они интерпретируются.
Проекционный слой подготавливает производные security facts.
Авторизация определяет допустимость конкретного действия.
Механизм защиты данных ограничивает фактически доступные физические данные.
Это можно представить так:
Domain
↓
Security model
↓
Projection
↓
Authorization
↓
Data enforcement
Ни один уровень не должен незаметно брать на себя ответственность всех остальных.
Иначе система снова превращается в один большой механизм с неясными границами.
25.13 Главный вывод
Один запрос к защищённым данным — это не одна проверка.
Это прохождение через несколько логически различных вопросов:
Кто действует?
↓
Что он хочет сделать?
↓
Над каким объектом?
↓
Какие факты здесь релевантны?
↓
Какие отношения являются основаниями доступа?
↓
Как из них получается эффективное разрешение?
↓
Разрешено ли конкретное действие?
↓
Какие физические данные действительно доступны?
В компактном виде:
Subject
+
Action
+
Object
↓
Context
↓
Access grounds
↓
Effective permission
↓
Authorization
↓
Data enforcement
↓
Physical data
Главное здесь не последовательность вызовов.
Главное — разделение смыслов.
Контекст не является разрешением.
Разрешение не является решением.
Решение не является доступом к физическим данным.
А физическая строка не является самим логическим объектом.
Когда эти уровни разделены, сложная модель перестаёт выглядеть как набор исключений.
Она становится последовательным преобразованием:
из фактов и отношений — в контекст, из контекста — в разрешение, из разрешения — в ограниченный доступ к данным.
Но до сих пор мы рассматривали в основном чтение уже построенной модели.
Остаётся понять, что происходит, когда сама модель отношений меняется: пользователь вступает в группу, получает роль, объект публикуется, публикация отзывается или меняется область действия.
Именно изменение отношений и является предметом следующей главы.
Глава 26. Что происходит при изменении отношений
26.1. Изменение отношения меняет безопасность
До этого мы рассматривали отношение как один из источников контекста.
Теперь важно увидеть обратную сторону.
Если отношение изменяется, может измениться доступ.
Например:
User A
↓ member
Group G
Удаление этой связи может изменить:
effective_user_group
что затем изменит:
effective_user_permission
и в результате:
object authorization
Поэтому изменение отношения является изменением модели безопасности.
26.2. Добавление связи
Рассмотрим обратный случай:
User A joins Group G
Если группа участвует в модели доступа, изменение может пройти через несколько этапов:
KUserGroupLink
↓
effective_user_group
↓
Scope Barrier
↓
effective_user_permission
↓
object authorization
Не каждая связь должна пройти именно такую цепочку.
Но архитектура должна знать, какие проекции от неё зависят.
26.3. Изменение публикации
Другой пример:
Object X
↓
KPublish
↓
Project P
Изменение публикации затрагивает уже объектную сторону:
KPublish
↓
object_scope
↓
object authorization
Поэтому изменения отношений субъекта и изменения отношений объекта имеют разные радиусы влияния.
26.4. Отзыв важнее выдачи не меньше, чем выдача важнее отзыва
Предположим:
Grant
создал право.
Затем:
Revoke
отменил основание.
Если производная модель не обновилась, старое право остаётся.
Поэтому механизм распространения изменений должен одинаково хорошо обрабатывать:
ADD
CHANGE
REMOVE
Нельзя проектировать только путь выдачи.
26.5. Инкрементальная обработка
Если изменение локальное, можно обновить только затронутую часть проекции.
Например:
Changed group
↓
Affected users
↓
Affected permissions
Это позволяет не перестраивать всю компанию после каждого изменения.
Но инкрементальный алгоритм должен точно знать область влияния.
26.6. Полное перестроение
Иногда проще и безопаснее перестроить модель заново:
Source facts
↓
Security rules
↓
Full rebuild
↓
Projection
Такой режим нужен для:
-
восстановления;
-
проверки;
-
миграции;
-
изменения правил;
-
исправления накопленного расхождения.
Поэтому полное перестроение не является признаком неудачной архитектуры.
Оно является частью архитектуры производных данных.
26.7. Два времени авторизации
На этом уровне можно различить два момента.
Время построения модели:
Fact changed
↓
Projection updated
Время проверки:
Request
↓
Prepared security state
↓
Authorization
Это важное свойство сложной модели.
Авторизация не обязательно вычисляется целиком в момент запроса.
Часть её вычисления происходит раньше.
26.8. Временное расхождение
Если обновление асинхронное:
Source updated
↓
Projection still old
то некоторое время система видит старое состояние безопасности.
Поэтому необходимо определить:
-
допустима ли задержка;
-
сколько она может продолжаться;
-
когда запрос должен ждать;
-
что делать при ошибке обновления;
-
как обнаружить потерю изменения.
Это уже часть семантики безопасности.
26.9. Что должно быть наблюдаемым
Для изменения отношения желательно иметь возможность ответить:
Какой факт изменился?
Какие проекции затронуты?
Какие пользователи затронуты?
Какие разрешения изменились?
Когда новое состояние стало доступно?
Без такой трассировки ошибка авторизации превращается в поиск причины по нескольким независимым таблицам.
26.10. Главный вывод
Связь — это не просто строка отношения.
Если она входит в модель безопасности, изменение связи имеет последствия:
Relationship change
↓
Security projection change
↓
Effective permission change
↓
Access change
Поэтому граф зависимостей между фактами и проекциями должен быть частью архитектуры.
Глава 27. Где именно принимается решение
27.1. Один запрос — несколько уровней
Наивная схема выглядит так:
Request
↓
ALLOW / DENY
Но в реальной системе существуют как минимум два разных решения.
Первое:
Можно ли работать с объектом?
Второе:
Какие физические данные должен вернуть запрос?
Поэтому:
Object authorization
и:
Data enforcement
не следует смешивать.
27.2. Три разные ответственности
Удобно различать:
BUILD
DECIDE
ENFORCE
Build — построить производные security facts.
Decide — определить, разрешено ли действие над объектом.
Enforce — физически ограничить данные.
Схема:
Source facts
↓
BUILD
↓
Security projections
↓
DECIDE
↓
Object authorization
↓
ENFORCE
↓
RLS / physical data
27.3. Где находится логическое решение
Логическое решение имеет форму:
Can(Subject, Action, Object)?
Оно может приниматься приложением, базой данных или специализированным механизмом.
Важно не место само по себе.
Важно, чтобы решение использовало определённую модель и чтобы последующие уровни не могли случайно расширить доступ.
27.4. Где находится физическое ограничение
Физическое ограничение действует ближе к данным.
Например:
Application
↓
SQL
↓
PostgreSQL RLS
↓
Rows
Здесь база данных уже не решает весь вопрос:
Почему этот пользователь имеет отношение к этому объекту?
Она применяет заданную модель к физическим строкам.
27.5. Два уровня Guardian
В Guardian это можно представить так:
Security projections
↓
has_object_permission()
↓
RlsDataSourceProxy
↓
PostgreSQL RLS
↓
Physical rows
Первый уровень работает с логическим объектом.
Второй — с физическими данными.
27.6. Концептуальный псевдокод
В общем виде:
function access(request):
authorization = authorizeObject(request)
if authorization != ALLOW:
return DENY
return queryData(request)
Далее:
queryData(request)
↓
database
↓
RLS
↓
allowed rows
Это не означает, что каждый конкретный сервис обязан реализовывать именно такой вызов.
Это разделение архитектурных обязанностей.
27.7. Почему нельзя считать RLS второстепенным
Если приложение разрешило:
READ Object X
но SQL способен получить строки, относящиеся к:
Object Y
то объектная авторизация сама по себе недостаточна.
RLS может выступить последней защитной границей.
Это особенно важно для:
-
массовых запросов;
-
сложных репозиториев;
-
отчётов;
-
фоновых операций;
-
новых API, которые появились позже основной логики авторизации.
27.8. Но и RLS не должен быть единственным механизмом
Обратная ошибка — перенести всю бизнес-авторизацию в RLS.
Тогда SQL policy начинает знать:
roles
groups
projects
publications
delegations
lifecycle
и другие понятия предметной области.
Это превращает физический слой данных в полный authorization engine.
Поэтому границы должны оставаться явными:
Authorization model
↓
Object decision
↓
Data enforcement
27.9. Место решения и источник истины
Нужно различать три понятия:
Source of truth
Decision point
Enforcement point
Например:
Source facts
↓
Security projections
↓
Object decision
↓
RLS enforcement
Проекция не является источником истины.
Точка принятия решения не обязана быть местом хранения фактов.
RLS не является источником модели доступа.
27.10. Главный вывод
У сложной системы доступа нет необходимости иметь одну магическую функцию:
checkAccess()
Вместо этого существует цепочка ответственности:
BUILD
↓
prepared security state
DECIDE
↓
object authorization
ENFORCE
↓
physical data boundary
Это позволяет разделить сложность модели и сложность её применения к данным.
Глава 28. Событие как источник изменения
В предыдущих главах мы рассматривали систему в момент проверки доступа.
Запрос приходит в систему.
Для него определяется субъект, действие и объект.
На основании исходных фактов и производных security-фактов вычисляется разрешение.
После этого результат применяется к данным.
Но модель безопасности не существует в неизменном состоянии.
Пользователь вступает в группу.
Пользователь выходит из группы.
Меняется роль.
Объект публикуется.
Публикация отзывается.
Меняется область действия отношения.
Удаляется или закрывается объект.
Каждое такое изменение может изменить множество будущих решений о доступе.
Значит, архитектуре нужен механизм, который связывает:
Изменение исходного факта
↓
Изменение производных фактов
↓
Новое состояние модели безопасности
Таким механизмом может быть событие.
Но здесь важно сразу провести границу.
Событие не является самой моделью безопасности.
Оно сообщает, что в системе произошло изменение, которое должно быть отражено в зависимых представлениях.
28.1 Изменение факта должно куда-то распространяться
Рассмотрим простое изменение:
User A → member of Group G
Само по себе это исходный факт.
Но если группа участвует в модели доступа, изменение может повлечь:
User A
↓
Group G
↓
Parent Group P
↓
Effective relation
↓
Effective permission
↓
Object access
Одной записи о членстве недостаточно.
Необходимо изменить все производные данные, которые зависят от этого факта.
Поэтому после изменения возникает задача:
какие части модели безопасности теперь стали недействительными?
Это уже задача распространения изменения.
28.2 Событие фиксирует изменение, а не результат авторизации
Очень легко смешать два разных понятия.
Событие:
UserGroupLinked
говорит:
произошло изменение отношения между пользователем и группой.
Оно не говорит:
User A can READ Object X
Это уже производный вывод.
Разница принципиальна.
Источник события находится на уровне исходного факта.
Результат обработки события находится на уровне производной модели.
Поэтому правильная цепочка выглядит так:
Source fact
↓
Event
↓
Projection update
↓
Derived security fact
А не:
Event
↓
ALLOW
Событие не должно содержать готовое решение авторизации, если это решение можно получить из модели.
28.3 Событие должно иметь понятную семантику
Событие имеет смысл только тогда, когда понятно, что именно изменилось.
Например:
UserGroupLinked
UserGroupUnlinked
RoleAssigned
RoleRevoked
ObjectPublished
PublicationRevoked
ObjectClosed
Названия здесь важны не сами по себе.
Важно, что каждое событие соответствует определённому изменению исходного состояния.
Например:
UserGroupLinked
не означает:
пользователю выданы все права группы.
Оно означает:
создано отношение членства пользователя в группе.
А уже правила модели определяют, какие производные факты должны измениться вслед за этим.
Это позволяет не зашивать правила авторизации внутрь механизма доставки событий.
28.4 Одно событие может иметь большой радиус воздействия
Изменение может быть маленьким физически и большим логически.
Например, изменение одной связи:
A ∈ G
может изменить эффективные отношения пользователя со множеством объектов.
Если группа является частью иерархии:
G
↑
P
↑
R
то изменение связи с G может повлиять на производные отношения, связанные с несколькими уровнями этой структуры.
А если эти отношения участвуют в формировании разрешений, изменяется ещё один слой:
Membership
↓
Effective groups
↓
Effective permissions
↓
Object access
Поэтому нельзя определять область изменения только количеством изменённых строк.
Одна строка может иметь очень большой радиус влияния.
28.5 Обработчик события не должен придумывать новую модель
Предположим, обработчик получил:
RoleAssigned
У него есть два возможных подхода.
Первый:
прочитать событие
→ самостоятельно решить,
какие права выдать
→ записать их
Второй:
прочитать изменение
→ применить существующие правила модели
→ обновить соответствующие производные факты
Второй подход принципиально безопаснее с архитектурной точки зрения.
Иначе обработчик постепенно превращается в ещё один authorization engine.
Через некоторое время получится:
Application authorization
+
Projection authorization
+
Event-handler authorization
+
Database authorization
И каждый слой начнёт трактовать отношения немного по-разному.
Событие должно запускать существующую модель, а не создавать новую.
28.6 Событие связывает время изменения с новым состоянием модели
Проекция всегда относится к некоторому состоянию исходных данных.
Пусть было:
State N
происходит изменение:
Event E
и после обработки должна появиться:
State N+1
Поэтому событие связывает два состояния:
Source state N
↓
Event
↓
Source state N+1
↓
Projection state N+1
Если событие потеряно, проекция может остаться в состоянии N, хотя исходные данные уже находятся в N+1.
Это и есть одна из причин, почему безопасность производных данных требует особого внимания.
Для обычной аналитической проекции устаревшее значение может быть неприятным.
Для security projection устаревшее разрешение может изменить фактический доступ.
28.7 Событие не обязательно означает немедленную обработку
Изменение и обработка его производных последствий могут происходить в разное время.
Например:
T1 Source fact changed
T2 Event recorded
T3 Event processed
T4 Projection updated
Между T1 и T4 существует переходное состояние.
Это не обязательно ошибка.
Архитектура может сознательно использовать асинхронное обновление.
Но тогда необходимо определить:
какое состояние безопасности считается допустимым в этот промежуток?
Особенно важно различать два случая.
Если доступ должен быть отозван немедленно, задержка обновления проекции может быть недопустимой.
Если небольшая задержка допустима, это должно быть частью явно определённой семантики системы.
Нельзя считать eventual consistency свойством, которое автоматически подходит безопасности.
28.8 Отзыв доступа требует такой же надёжности, как выдача
Добавление разрешения обычно легко заметить:
+ permission
Но архитектурно гораздо опаснее другое:
- permission
Пользователь вышел из группы.
Роль отозвана.
Публикация закрыта.
Объект больше не находится в области действия отношения.
Если событие об изменении потеряно, производная модель может продолжать утверждать, что доступ существует.
Поэтому поток должен поддерживать оба направления:
Grant → projection update
Revoke → projection update
Система, которая надёжно добавляет права, но ненадёжно их удаляет, не является надёжной моделью безопасности.
28.9 Надёжность события и надёжность проекции — разные задачи
Даже если событие сохранено, остаётся другая проблема.
Обработчик может:
-
не запуститься;
-
завершиться с ошибкой;
-
обработать событие частично;
-
получить событие повторно;
-
обработать события не в том порядке;
-
остановиться между двумя операциями.
Поэтому архитектура должна отвечать как минимум на несколько вопросов:
Событие потеряно?
Событие обработано?
Обработка завершилась?
Проекция соответствует источнику?
Можно ли повторить обработку?
Можно ли восстановить проекцию?
Это уже не свойства самого события.
Это свойства всей цепочки распространения изменения.
28.10 Идемпотентность становится обязательным свойством
Повторная доставка события вполне возможна.
Например:
Event E
↓
Projection updated
↓
ack lost
↓
Event E again
Если повторная обработка создаёт дополнительный эффект, состояние может стать неверным.
Поэтому обработка должна быть идемпотентной по результату.
Это означает:
Apply(E)
Apply(E)
должно приводить к тому же состоянию, что и:
Apply(E)
Идемпотентность не решает проблему согласованности полностью.
Но без неё надёжная повторная обработка становится значительно сложнее.
28.11 Очередность имеет значение не всегда, но должна быть определена
Рассмотрим два события:
E1: UserGroupLinked
E2: UserGroupUnlinked
Очевидно, что итоговое состояние зависит от их порядка, если события описывают последовательные изменения одного отношения.
Поэтому система должна понимать, какие изменения требуют упорядоченной обработки.
Но это не означает, что все события всей системы должны обрабатываться глобально последовательно.
Глобальный порядок может оказаться слишком дорогим и ненужным.
Вместо этого порядок обычно определяется в пределах той части модели, где он действительно влияет на результат.
Это ещё один пример того, почему архитектура должна начинаться с семантики данных, а не с выбранного механизма доставки.
28.12 Событие и транзакция должны иметь определённую связь
Есть важная граница.
Предположим, система изменила исходный факт:
INSERT membership
а событие записала отдельно:
INSERT event
Если первая операция завершилась успешно, а вторая нет, система получила изменение без сигнала для проекций.
Если наоборот событие появилось без фактического изменения, обработчик может попытаться построить производное состояние из того, чего нет.
Поэтому нужно определить связь между:
Source change
и:
Change notification
В ряде архитектур событие фиксируется в той же транзакционной границе, что и исходное изменение.
В других используется специальный механизм надёжной публикации изменений.
Конкретная технология здесь вторична.
Главное требование одно:
система не должна молча терять связь между изменением исходного факта и необходимостью обновить производные факты.
28.13 Событие не заменяет возможность полного пересчёта
Даже идеальная обработка событий не устраняет необходимость восстановления.
Проекция может быть:
-
повреждена;
-
удалена;
-
построена по ошибочной версии правила;
-
несовместима с новой схемой;
-
остановлена на середине обработки;
-
создана впервые после появления исходных данных.
Поэтому должна существовать возможность:
Source facts
↓
Rebuild
↓
Security projections
События обеспечивают инкрементальное распространение изменений.
Полный пересчёт обеспечивает восстановление и проверку модели.
Это не конкурирующие подходы.
Они решают разные задачи.
28.14 Событие должно быть частью наблюдаемого пути
Если изменение доступа прошло через несколько этапов:
Source
→ Event
→ Projection
→ Authorization
→ Data
то каждый этап должен быть наблюдаемым.
Иначе невозможно ответить на простой вопрос:
почему пользователь получил или потерял доступ?
Например:
Source fact changed ✓
Event recorded ✓
Event processed ✓
Projection updated ✗
Authorization ?
Такой путь гораздо легче диагностировать, чем ситуацию, когда все изменения скрыты внутри нескольких независимых SQL-запросов.
Наблюдаемость здесь становится не только эксплуатационным удобством.
Она является способом проверить, действительно ли модель безопасности распространяется так, как задумано.
28.15 В Guardian событие соединяет исходные факты и проекции
В Guardian изменение отношений проходит именно через этот принцип.
Например:
KUserGroupLink
↓
security change
↓
effective_user_group
↓
effective_user_permission
↓
object authorization
А изменение публикации имеет другой путь:
KPublish
↓
object_scope
↓
object authorization
То есть событие не должно содержать готовое:
ALLOW user X object Y
Оно сообщает об изменении исходного факта.
Соответствующая проекция обновляется по правилам своей модели.
Именно поэтому разные исходные отношения могут иметь разные маршруты распространения.
28.16 Событие — не источник истины
Есть ещё одна принципиальная граница.
Можно построить систему, в которой события хранятся очень долго и содержат всю историю изменений.
Это полезно для аудита и восстановления.
Но даже тогда нужно различать:
Source state
и:
History of changes
Событие отвечает на вопрос:
что произошло?
Исходное состояние отвечает на вопрос:
что существует сейчас?
Для построения текущей security model обычно нужны именно текущие исходные факты и правила.
История событий может помочь восстановить их, но не должна незаметно становиться второй, противоречащей источнику истины.
28.17 Главный вывод
Событие нужно не потому, что «современная архитектура должна быть событийной».
Оно нужно потому, что изменение исходного факта должно надёжно распространиться на производные части модели.
Полная цепочка выглядит так:
Source fact changes
↓
Event records the change
↓
Affected projections are identified
↓
Derived security facts are recalculated
↓
New security state becomes available
↓
Future authorization uses the new state
При этом необходимо сохранить несколько принципов.
Событие описывает изменение, а не готовое разрешение.
Обработчик применяет существующую модель, а не создаёт вторую.
Выдача и отзыв доступа должны распространяться одинаково надёжно.
Повторная обработка не должна искажать результат.
Порядок событий должен учитываться там, где он влияет на состояние.
Проекция должна иметь возможность полного восстановления.
И главное:
событие — это механизм переноса изменения из мира исходных фактов в мир производных фактов.
Теперь возникает следующий вопрос.
Если событие сообщает об изменении, кто именно превращает его в новое состояние security model?
Где находятся правила построения проекций?
И как сделать так, чтобы разные проекции не начали независимо интерпретировать одни и те же факты?
Этим занимается следующий слой архитектуры — проекционный слой.
Глава 29. Проекционный слой
29.1. Где находится проекция
Проекция возникает между исходной моделью и проверкой запроса:
Source facts
↓
Security projections
↓
Authorization
Она нужна не для хранения копии предметной области.
Её задача — представить производные факты в форме, удобной для конкретного класса проверок.
29.2. Не вся модель должна вычисляться во время запроса
В универсальном виде можно представить авторизацию так:
Request
↓
findAccessGrounds()
↓
Rules
↓
Decision
Но это не обязательно означает, что findAccessGrounds() физически обходит весь граф отношений во время запроса.
В зрелой системе значительная часть результата может быть построена заранее:
Source relations
↓
Projection building
↓
Prepared access facts
Поэтому универсальный псевдокод описывает логическую функцию, а не обязательно её момент исполнения.
29.3. Что происходит в Guardian
В Guardian часть access grounds материализуется заранее.
Например:
KUserGroupLink
↓
effective_user_group
затем:
effective_user_group
+
role assignments
↓
effective_user_permission
А объектная сторона:
KPublish
↓
object_scope
Владелец также представлен в object_scope отдельным OWNER-scope.
Таким образом, к моменту проверки уже существует подготовленное security state.
29.4. effective_user_group
Эта проекция представляет производные отношения пользователя и групп.
Она может учитывать иерархию.
Но её назначение не в том, чтобы каждый раз отвечать:
Может ли пользователь читать объект?
Она используется при построении эффективных разрешений, в том числе при обработке Scope Barrier.
Это важное разделение:
effective_user_group
↓
build effective permissions
а не:
effective_user_group
↓
direct runtime authorization
29.5. effective_user_permission
Эта проекция уже ближе к проверке.
Она представляет производное разрешение пользователя в конкретной области:
company
user
scope_type
scope_id
permission_mask
Именно поэтому has_object_permission() может сопоставить её с:
object_scope
без повторного обхода групповой и проектной структуры.
29.6. object_scope
object_scope описывает области безопасности объекта.
Например:
OWNER
COMPANY
PROJECT
GROUP
в зависимости от конкретного основания.
Важно не смешивать область публикации с физическим размещением.
Например, owning_group_id может быть полезен как placement/storage metadata, но это не означает, что он становится входом в ACL-проверку.
Текущая объектная проверка использует:
scope_type
scope_id
permission_mask
для сопоставления с эффективным разрешением пользователя.
29.7. Почему проекции не являются ACL
Можно было бы сказать:
effective_user_permission = ACL
Но это слишком узко.
Проекция представляет производный факт.
Окончательное решение появляется только после сопоставления:
effective_user_permission
+
object_scope
+
required action
Поэтому:
Projection ≠ Decision
29.8. Две фазы работы
В Guardian удобно различать:
Построение security model:
Domain/security fact
↓
Projection update
↓
Prepared security state
и:
Использование security model:
Request
↓
has_object_permission()
↓
RLS
↓
Data
Это позволяет заранее выполнять дорогую работу и оставлять запросу короткую проверку.
29.9. Главный вывод
Проекционный слой является не оптимизацией поверх уже готовой авторизации.
Он является частью архитектуры вычисления авторизации.
Сложные отношения превращаются в производные факты заранее:
Relations
↓
Security projections
↓
Effective authorization
А запрос использует уже подготовленное состояние.
Глава 30. PostgreSQL как часть модели безопасности
30.1. База данных как граница применения
База данных обычно рассматривается как место хранения.
В модели с RLS она становится ещё и местом принудительного применения ограничений.
Это не означает, что база данных становится владельцем всей модели авторизации.
Она получает дополнительную ответственность:
не вернуть физические строки, которые не должны быть доступны текущему запросу.
30.2. Авторизация и RLS — разные уровни
Полезно разделять:
Authorization
и:
Data enforcement
Авторизация отвечает:
Can(Subject, Action, Object)?
RLS отвечает:
Which rows can this SQL operation see or modify?
Поэтому архитектурно:
Relationship-based authorization
↓
Effective permission
↓
Object authorization
↓
PostgreSQL RLS
↓
Physical data
30.3. PostgreSQL не должен строить весь контекст
Если доступ зависит от длинной цепочки:
User
→ Group
→ Parent Group
→ Project
→ Role
→ Publication
→ Object
нет необходимости заставлять RLS каждый раз вычислять весь этот граф.
Это уже было сделано на этапе построения security projections.
База получает более компактное условие.
Так разделяются:
model construction
и:
data enforcement
30.4. Что именно проверяется в Guardian
Для объектной авторизации текущая схема использует:
object_scope
и:
effective_user_permission
Обычная ветка сопоставляет:
scope_type
scope_id
и проверяет:
permissionMask
&
publicationMask
&
requiredMask
Владелец проверяется отдельно через OWNER-scope.
Таким образом, PostgreSQL участвует в принятии решения, но получает уже материализованную модель.
30.5. Почему это не «Zanzibar внутри PostgreSQL»
Relationship-based подход и PostgreSQL RLS решают разные задачи.
Relationship-based authorization определяет, какие отношения и правила приводят к разрешению.
RLS ограничивает физические строки.
Поэтому архитектура может выглядеть так:
Relationship facts
↓
Security projections
↓
Authorization
↓
PostgreSQL RLS
Это не означает, что PostgreSQL является authorization engine целиком.
30.6. Контекст пользователя в базе
Чтобы RLS мог применять пользовательские ограничения, база должна знать контекст текущего запроса.
В Guardian используется:
current_company_id
current_user_id
Эти значения устанавливаются в рамках управляемого соединения.
Важно, что они не должны сохраняться как неконтролируемое состояние connection pool.
30.7. RlsDataSourceProxy
В текущей архитектуре приложение получает:
@Primary DataSource
↓
RlsDataSourceProxy
↓
physicalDataSource
Прокси не выполняет побочные SQL-операции просто при получении соединения.
RLS-контекст устанавливается лениво перед первым бизнес-SQL.
Это позволяет связать security context с тем же физическим соединением, на котором выполняется запрос.
30.8. Почему Flyway использует отдельный источник
Миграции базы данных — инфраструктурная операция.
Они не должны зависеть от пользовательского:
company_id
user_id
Поэтому:
Flyway
↓
physicalDataSource
а бизнес-приложение:
Hibernate / repositories
↓
RlsDataSourceProxy
Это два разных контекста доверия.
30.9. RLS как защита от ошибки приложения
Допустим, разработчик написал новый запрос:
SELECT *
FROM documents
и забыл добавить фильтр доступа.
Если единственной защитой была логика приложения, запрос может вернуть лишние данные.
При наличии корректного RLS запрос всё равно проходит через физическую границу:
SQL
↓
RLS policy
↓
allowed rows
Это не отменяет необходимость правильной авторизации.
Но создаёт дополнительный уровень защиты от ошибок конкретного пути доступа.
30.10. Почему RLS не должен быть универсальным
Не вся информация обязана защищаться одинаковым способом.
Для одних данных естественной границей являются строки.
Для других:
-
бизнес-операция;
-
состояние процесса;
-
отдельное поле;
-
внешний сервис;
-
документ;
-
результат вычисления.
Поэтому решение:
Всё защитим RLS
так же неправильно, как:
Ничего не будем защищать на уровне базы.
RLS должен соответствовать физической структуре данных и модели угроз.
30.11. Главный вывод
PostgreSQL может быть частью security architecture, не становясь всей моделью безопасности.
В рассматриваемой архитектуре его роль можно выразить так:
Source security facts
↓
Security projections
↓
Effective authorization
↓
Object authorization
↓
PostgreSQL RLS
↓
Physical data
Проекции строят производное состояние.
Авторизация принимает логическое решение.
RLS физически ограничивает данные.
Так три разных задачи остаются различимыми.
Глава 31. Надёжность и восстановление
31.1. Надёжность — это свойство всей цепочки
Модель доступа может быть логически правильной и при этом работать ненадёжно.
Например, исходный факт изменился:
User A removed from Group G
но производная модель осталась прежней:
effective_user_group
effective_user_permission
В результате система продолжает видеть старое право.
Поэтому надёжность модели доступа нельзя свести к надёжности базы данных.
Нужно обеспечить всю цепочку:
Source fact
↓
Change propagation
↓
Projection
↓
Authorization
↓
Data enforcement
Ошибка на любом этапе может изменить фактический результат проверки.
31.2. Источник истины должен сохраняться
Проекция является производным состоянием.
Поэтому при восстановлении нельзя полагаться только на неё.
Если произошло повреждение:
effective_user_permission
система должна иметь возможность восстановить его из исходных фактов.
Принцип:
Source facts + Rules
↓
Rebuild
↓
Security projections
Если такую операцию невозможно выполнить, проекция фактически превратилась во второй источник истины.
Это опасно.
31.3. Событие не заменяет исходный факт
События помогают распространять изменения:
UserGroupLinked
UserGroupUnlinked
ObjectPublished
ObjectUnpublished
Но событие не должно становиться единственным источником состояния.
Система должна уметь ответить:
В каком состоянии находится модель сейчас?
а не только:
Какие события когда-то были обработаны?
Поэтому устойчивее иметь:
Source state
+
Change propagation
+
Rebuild capability
Событие ускоряет изменение производного состояния.
Оно не заменяет исходную модель.
31.4. Выдача и отзыв должны быть симметричны
Для безопасности особенно опасен путь отзыва.
Выдача может выглядеть так:
Grant
↓
Projection updated
↓
ALLOW
Но отзыв должен обеспечить:
Revoke
↓
Projection updated
↓
DENY
Если выдача задержалась, пользователь некоторое время может не получить новое право.
Если отзыв задержался, пользователь может сохранить уже отозванный доступ.
Поэтому семантика задержки должна быть определена отдельно.
31.5. Инкрементальное обновление и полная перестройка
Обычно есть два механизма.
Первый — инкрементальный:
One fact changed
↓
Find affected projection rows
↓
Update them
Второй — полный:
All source facts
↓
Apply rules
↓
Rebuild projection
Инкрементальный механизм нужен для обычной работы.
Полный — для восстановления, проверки и изменения модели.
Наличие полного rebuild особенно важно тогда, когда производные данные строятся сложными алгоритмами.
31.6. Восстановление должно быть частью архитектуры
Восстановление нельзя проектировать после появления первой аварии.
Должны быть заранее определены:
1. источник исходных фактов;
2. правила построения;
3. порядок восстановления;
4. способ проверки результата;
5. момент, когда восстановленная модель считается готовой.
Последний пункт особенно важен.
Заполненная таблица ещё не означает готовую security model.
31.7. Нельзя путать отсутствие проекции с отсутствием права
Пусть пользователь существует:
User A
но запись в производной проекции ещё не появилась.
Тогда возможны как минимум два состояния:
projection says DENY
и:
projection is not ready
Это разные состояния.
Если система автоматически превращает второе в первое, она получает безопасное, но потенциально некорректное поведение.
Если автоматически превращает его в разрешение, возникает очевидная проблема безопасности.
Поэтому состояние готовности модели должно быть определено явно.
31.8. Правила тоже являются частью состояния
Проекция зависит не только от фактов.
Она зависит от:
Source facts
+
Security rules
Если правило изменилось, старые производные данные могут больше не соответствовать модели.
Например:
Old rule
↓
Projection P
после изменения:
New rule
↓
Projection P'
Поэтому изменение самой модели правил может требовать перестроения проекций.
31.9. Проверка после восстановления
После rebuild недостаточно проверить:
row count > 0
Нужно проверять свойства модели.
Например:
Source facts
↓
Expected derived state
и затем проверять:
Actual projection
=
Expected projection
Для критичных систем полезны также инварианты:
-
нет производного права без основания;
-
отзыв действительно удаляет право;
-
область разрешения не расширилась;
-
владелец соответствует объекту;
-
производные записи согласованы между собой.
31.10. Восстановление не должно зависеть от сломанной авторизации
Если обычная authorization model повреждена, нельзя делать восстановление зависимым от неё самой.
Иначе возникает цикл:
Need security projection
↓
Need projection to rebuild projection
Восстановление должно использовать более низкий уровень доверия:
Source facts
+
Controlled rebuild process
и после этого возвращать систему в обычный режим.
31.11. Главный вывод
Надёжная модель доступа должна иметь не только путь:
Fact → Projection → Authorization
но и обратный эксплуатационный путь:
Source facts
↓
Rebuild
↓
Projection
↓
Validation
↓
Normal authorization
Проекция может быть потеряна.
Проекция может устареть.
Алгоритм её построения может измениться.
Архитектура должна быть готова ко всем трём случаям.
Глава 32. Репликация модели безопасности
32.1. Когда одной системы уже недостаточно
В распределённой системе данные одного логического объекта могут использовать несколько сервисов.
Например:
Service A
↓
owns object
Service B
↓
searches object
Service C
↓
builds report
Возникает вопрос:
Как сервис B должен узнать, какие пользователи имеют доступ к объектам сервиса A?
Простой ответ:
Каждый запрос отправлять обратно в сервис A.
Но тогда доступ к данным становится зависимым от удалённого вызова.
32.2. Локальная модель доступа
Более устойчивый вариант — иметь локальное представление необходимой части security model:
Central source facts
↓
Security change
↓
Service B projection
↓
Local authorization
Сервис получает не всю модель, а только её необходимую часть.
Это важное ограничение.
Не нужно реплицировать весь граф отношений во все сервисы.
32.3. Данные и модель безопасности — разные вещи
В распределённой системе могут отдельно распространяться:
Domain data
и:
Security facts
Например, сервис может получить информацию:
User A can access Project P
не получая сам проект.
И наоборот, сервис может владеть проектом, но не хранить все организационные отношения компании.
Поэтому репликация security model — самостоятельная архитектурная задача.
32.4. Что именно реплицировать
Есть принципиальная разница между:
Security facts
и:
Final ALLOW/DENY
Например, можно передать:
User A
Role = Reviewer
Scope = Project P
а локальная система сама построит производное состояние.
Так правила остаются частью локальной модели.
Передача готового:
ALLOW(User A, READ, Object X)
сильнее связывает сервисы.
Это может быть оправдано в отдельных системах, но не должно автоматически считаться универсальным решением.
32.5. Локальная проекция должна быть ограниченной
Сервису не обязательно знать:
все пользователи;
все группы;
все проекты;
все объекты;
все роли.
Ему нужна только часть модели, которая влияет на его данные.
Например:
Service A
↓
Objects A1..An
Security projection
↓
only facts relevant to A1..An
Так уменьшаются:
-
объём данных;
-
стоимость обновления;
-
радиус ошибки;
-
зависимость между сервисами.
32.6. Задержка становится частью модели
После изменения отношения может существовать состояние:
Source model = new
Local projection = old
Поэтому распределённая authorization model требует явного ответа на вопрос:
Что происходит в этот промежуток времени?
Особенно важен отзыв.
Если локальная проекция отстаёт, пользователь может временно видеть уже закрытый ресурс.
Поэтому допустимая задержка — не просто технический параметр.
Это часть security semantics.
32.7. Порядок и повторная обработка
Изменения могут:
-
прийти повторно;
-
прийти не по порядку;
-
потеряться;
-
быть обработаны частично.
Поэтому механизм распространения должен обеспечивать свойства вроде:
idempotence
и возможность обнаружить пропуск.
При этом сама security model не должна зависеть от конкретного транспорта сообщений.
Транспорт — инфраструктурный механизм.
Модель отношений и разрешений — самостоятельный слой.
32.8. Снимок и последующие изменения
Для восстановления локальной проекции удобна комбинация:
Snapshot
+
Incremental changes
Например:
Security snapshot at T0
↓
changes T1
changes T2
changes T3
Если локальная проекция потеряна, её можно восстановить из снимка и изменений.
Но и здесь остаётся важным наличие исходной модели или возможности полного rebuild.
32.9. Локальное применение важнее удалённого решения
Запрос к данным желательно обслуживать локально:
Request
↓
Local security projection
↓
Local authorization
↓
Local data
а не:
Request
↓
Remote authorization service
↓
Remote response
↓
Local data
Второй вариант создаёт дополнительную зависимость от сети, задержки и доступности другого сервиса.
Это не делает централизованную авторизацию невозможной.
Но при большом количестве запросов локальная проекция часто позволяет лучше отделить runtime-доступ к данным от доступности внешнего сервиса.
32.10. B2B добавляет ещё одну границу
В межорганизационном доступе появляется отношение:
Company A
↓ grants
Company B
Но оно само по себе не означает:
Company B can read everything from Company A
Остаются:
-
конкретный объект;
-
конкретная область;
-
конкретное действие;
-
уровень разрешения;
-
локальные ограничения.
Поэтому B2B-отношение является одним из оснований доступа, а не универсальным разрешением.
32.11. Главный вывод
В распределённой системе security model может иметь:
Central source facts
↓
Security changes
↓
Local security projection
↓
Local authorization
↓
Local data enforcement
Главный принцип:
сервис должен получать достаточно security state для локального принятия решения, но не обязан владеть всей моделью безопасности системы.
Это позволяет разделить владение данными, моделью безопасности и физическое применение доступа.
Глава 33. Цена сложной модели доступа
33.1. Сложная модель не бывает бесплатной
Чем больше отношений учитывает доступ, тем больше работы требуется от системы.
Появляются:
relations
hierarchies
publications
delegations
scopes
projections
synchronization
rebuilds
Поэтому нельзя рассматривать сложную authorization model только как способ получить более точный результат.
Она меняет архитектуру всей системы.
33.2. Проекции переносят стоимость
Если система вычисляет всё во время запроса:
Request
↓
Graph traversal
↓
Decision
стоимость возникает при чтении.
Если система строит проекции заранее:
Change
↓
Projection update
стоимость переносится на изменение.
Получается:
Runtime read
← cheaper
Data change
← more expensive
Это не устранение стоимости.
Это изменение её места.
33.3. Появляется дополнительное хранилище
Каждая производная проекция требует:
-
места;
-
индексов;
-
обновления;
-
очистки;
-
восстановления;
-
мониторинга.
Поэтому нужно спрашивать:
Действительно ли эта проекция сокращает необходимую стоимость системы?
Если проверка выполняется один раз в сутки, сложная materialized security model может не иметь смысла.
Если проверка выполняется миллионы раз в секунду, ситуация будет другой.
33.4. Меняется стоимость изменения
Представим пользователя, состоящего в группе:
User A → Group G
Если группа влияет на тысячи проектов, изменение одного отношения может затронуть множество производных записей.
Поэтому стоимость операции:
UPDATE one relation
не обязательно равна стоимости изменения одной строки.
Её реальная цена определяется радиусом влияния.
33.5. Сложность появляется и в тестировании
Нужно проверять не только:
User can read Object
но и:
why?
и:
what happens after revoke?
и:
what happens after intermediate relation changes?
и:
what happens after rebuild?
Чем больше путей к одному праву, тем больше комбинаций для проверки.
33.6. Сложность появляется при восстановлении
Проекция должна быть:
rebuildable
Если её невозможно воспроизвести из исходных фактов и правил, эксплуатационная стоимость резко возрастает.
Поэтому при проектировании нужно учитывать не только:
How is permission computed?
но и:
How is permission reconstructed?
33.7. Распределённая система увеличивает цену
Если security model пересекает сервисные границы, добавляются:
replication
ordering
lag
failure recovery
version compatibility
Теперь изменение отношения должно не только изменить локальную проекцию.
Оно должно добраться до других владельцев производного состояния.
33.8. Когда сложность оправдана
Сложная модель имеет смысл, когда система действительно содержит несколько независимых измерений доступа:
Subjects
+
Objects
+
Relationships
+
Areas
+
Hierarchies
+
Publication
+
Delegation
+
Mass data access
Особенно если эти отношения одновременно используются большим количеством запросов.
33.9. Когда можно остаться проще
Если система имеет:
few users
few objects
one organization
simple roles
no hierarchy
no delegation
обычного ролевого контроля доступа может быть достаточно.
Не следует вводить граф отношений только потому, что он архитектурно интереснее.
Сложность должна следовать из предметной области.
33.10. Главный вывод
Сложная модель доступа покупает выразительность ценой:
Storage
Computation
Synchronization
Testing
Recovery
Observability
Поэтому правильный вопрос не:
Какая модель доступа самая мощная?
а:
Какая модель точно описывает отношения предметной области при приемлемой эксплуатационной стоимости?
Авторизация является частью архитектуры системы.
И её сложность должна быть соразмерна сложности самого мира, который система моделирует.
Глава 34. Где RLS подходит, а где нет
34.1. Два разных вопроса
К этому моменту можно сформулировать важное различие.
Первый вопрос:
Как определить, имеет ли субъект право действовать над объектом?
Второй:
Как физически ограничить данные после того, как такое право определено?
Для первого могут использоваться разные модели авторизации.
Для второго — разные механизмы enforcement.
RLS относится прежде всего ко второму вопросу.
34.2. Relationship-Based Access Control
Один из важных классов моделей — ReBAC (Relationship-Based Access Control, управление доступом на основе отношений).
В ReBAC отношения между субъектами, объектами и другими сущностями становятся частью основания доступа.
Упрощённо:
User
↓ member
Group
↓ has access
Project
↓ contains
Object
Такой подход хорошо соответствует системам, где доступ нельзя описать только свойствами пользователя.
Именно эту проблему мы рассматривали в предыдущих главах.
34.3. Zanzibar
Одним из наиболее известных опубликованных примеров relationship-based подхода является Zanzibar — архитектура системы авторизации, опубликованная Google.
Её важность для рассматриваемой темы не в конкретном API или формате хранения.
Важен сам архитектурный принцип:
authorization
↓
relationships
↓
objects
↓
permissions
То есть авторизация рассматривается как вычисление на модели отношений.
Это очень близко к центральной идее книги:
Object
↓
Relationships
↓
Context
↓
Effective permission
Но это не означает, что описываемая здесь модель является реализацией Zanzibar.
34.4. OpenFGA, SpiceDB и Ory Keto
На практике существуют системы, реализующие близкий класс relationship-based подходов, например:
-
OpenFGA;
-
SpiceDB;
-
Ory Keto.
Они предлагают отдельные механизмы моделирования отношений и вычисления авторизации.
Их конкретные модели, API и эксплуатационные свойства различаются.
Но архитектурная идея пересекается:
Relationships
↓
Authorization decision
Это полезно сравнивать с другими архитектурами не на уровне названий продуктов, а на уровне ответственности.
34.5. Где находится наша модель
Рассмотренная в книге модель также исходит из отношений:
Subject
Object
Relationship
Area
Context
Rule
Effective permission
Но из этого ещё не следует конкретный продукт или конкретный authorization engine.
Один и тот же концептуальный уровень может быть реализован:
-
отдельным authorization service;
-
библиотекой;
-
SQL-проекциями;
-
частью приложения;
-
комбинацией этих механизмов.
Поэтому модель и реализация должны оставаться различимыми.
34.6. Где здесь PostgreSQL RLS
Теперь можно поставить RLS в эту картину.
Relationship model
↓
Authorization
↓
Effective permission
↓
RLS
↓
Physical rows
RLS находится ниже модели отношений.
Он не отвечает на вопрос:
Почему пользователь связан с объектом?
Он отвечает:
Какие строки должен пропустить текущий SQL-запрос?
Поэтому:
ReBAC ≠ RLS
И:
RLS ≠ authorization model
34.7. Почему эти подходы могут использоваться вместе
Нет необходимости выбирать между:
ReBAC
и:
RLS
как между взаимоисключающими технологиями.
Они могут находиться на разных уровнях:
Relationship-based model
↓
Security projections
↓
Object authorization
↓
RLS
Именно это позволяет использовать выразительную модель отношений, сохраняя физическую защиту данных на уровне базы.
34.8. Guardian как конкретный пример
Guardian реализует не «Zanzibar внутри PostgreSQL».
Его архитектура другая.
В ней security state заранее материализуется в проекциях:
effective_user_group
effective_user_permission
object_scope
После этого объектная авторизация сопоставляет:
effective_user_permission
+
object_scope
а физические данные дополнительно защищаются RLS.
Поэтому Guardian интересен здесь не как реализация какого-либо одного промышленного стандарта.
Он является примером другой точки сборки тех же архитектурных идей:
Relations
↓
Projections
↓
Authorization
↓
Database enforcement
34.9. Когда RLS особенно естественен
RLS хорошо подходит, когда граница безопасности совпадает с физическими строками.
Например:
company_id
tenant_id
owner_id
object_id
могут непосредственно участвовать в ограничении строк.
Особенно полезен RLS для массовых запросов:
SELECT ...
FROM objects
поскольку физическая выборка сама получает ограничение.
34.10. Когда RLS недостаточен
RLS плохо подходит в качестве единственного механизма для правил вроде:
Пользователь может согласовать документ, если он является ответственным за проект, проект находится в состоянии Review, документ опубликован в области, к которой относится пользователь, и действие выполняется в рамках допустимой делегации.
Такое правило относится к authorization model.
Попытка целиком перенести его в RLS смешивает:
Business semantics
Authorization
Data enforcement
и усложняет каждую из них.
34.11. Главный вывод
Промышленные relationship-based системы показывают, что сложный доступ действительно можно моделировать через отношения.
RLS показывает другую сторону задачи: даже правильно вычисленное разрешение должно быть применено к физическим данным.
Поэтому полезно разделять:
Relationship model
↓
Authorization
↓
Data enforcement
Zanzibar, OpenFGA, SpiceDB и Ory Keto относятся прежде всего к первому и второму уровням.
PostgreSQL RLS — к третьему.
Эти механизмы могут дополнять друг друга, потому что решают разные задачи.
Глава 35. Безопасность данных — не только авторизация
35.1. Авторизация — только один слой
До сих пор основным вопросом был:
Может ли субъект выполнить действие над объектом?
Но безопасность данных шире.
Она включает как минимум:
Authentication
Authorization
Integrity
Confidentiality
Audit
Lifecycle
Operational security
Авторизация определяет допустимость действия.
Она не отвечает на все остальные вопросы.
35.2. Разрешение не определяет форму данных
Пусть:
User A → READ → Customer X
Это ещё не отвечает на вопросы:
Какие поля можно показать?
Можно ли экспортировать данные?
Можно ли передать их другому пользователю?
Можно ли положить их в кэш?
Можно ли записать их в лог?
Можно ли использовать их для аналитики?
Следовательно:
READ permission
не является универсальным разрешением на любое использование результата.
35.3. Конфиденциальность может продолжиться после SELECT
Предположим, база данных вернула разрешённую строку.
Дальше данные могут попасть в:
API response
cache
search index
report
log
message
analytics store
backup
Каждый такой путь становится потенциальной поверхностью раскрытия.
Поэтому:
Database authorization
не заканчивает security architecture.
35.4. Производные данные тоже требуют защиты
Мы уже видели, что security projections являются производными данными.
Но то же относится к бизнес-данным.
Например:
Document
↓
Search index
↓
Search result
Если исходный документ стал недоступен, индекс не должен продолжать его выдавать.
То же относится к:
-
кэшам;
-
полнотекстовым индексам;
-
embeddings;
-
отчётам;
-
агрегатам;
-
экспортам.
Изменение права может требовать изменения не только ACL.
35.5. Авторизация и целостность
Авторизация отвечает:
Можно ли выполнить действие?
Целостность отвечает:
Что произойдёт с данными, если действие выполнено?
Например, пользователь может иметь:
UPDATE
но это ещё не означает, что любое изменение допустимо с точки зрения предметной области.
Могут существовать правила:
document.state = DRAFT
или:
revision is editable
Это уже не обязательно authorization rule.
Это может быть бизнес-инвариант.
35.6. Аудит не заменяет авторизацию
Аудит отвечает:
Что произошло?
Авторизация отвечает:
Можно ли было это сделать?
Логирование операции после её выполнения не предотвращает несанкционированный доступ.
Поэтому:
Audit ≠ Authorization
Но аудит остаётся частью общей security architecture.
35.7. Технический компонент и субъект
В распределённой системе запрос может выглядеть так:
User A
↓
Service B
↓
Service C
↓
Database
Физический SQL выполняет Service C.
Но бизнес-субъектом действия может оставаться User A.
Поэтому нужно различать:
Authentication source
Technical caller
Business subject
Database connection
Смешение этих понятий может привести к ошибочному разрешению.
35.8. Администратор и доверенные компоненты
Система может иметь операции, выполняемые доверенным компонентом:
rebuild
migration
maintenance
bootstrap
Это не обязательно обычные пользовательские действия.
Поэтому модель доверия к инфраструктурному компоненту должна быть определена отдельно.
Нельзя просто предположить:
service = admin
если архитектура этого явно не утверждает.
35.9. Шифрование и авторизация
Шифрование решает другую задачу.
Оно защищает данные:
at rest
in transit
но не отвечает само по себе на вопрос:
Может ли User A прочитать Object X?
Поэтому:
Encryption ≠ Authorization
Они дополняют друг друга.
35.10. Резервные копии и срок хранения
Обычная authorization model работает с текущими данными.
Но резервная копия может содержать состояние, существовавшее месяц назад.
Удаление или отзыв доступа в основной системе не обязательно означает мгновенное физическое исчезновение информации из backup.
Поэтому:
Retention
Backup
Recovery
имеют собственную security semantics.
35.11. Полная цепочка
В результате архитектуру безопасности данных полезно представлять шире:
Authentication
↓
Subject
↓
Authorization model
↓
Effective permission
↓
Object authorization
↓
Data enforcement
↓
Physical data
↓
Response
↓
Cache / Log / Index / Export
Каждый переход может создать новую поверхность риска.
35.12. Главный вывод
Авторизация отвечает на важнейший вопрос:
Can(Subject, Action, Object)?
Но безопасность данных требует большего.
Нужно понимать:
кто действует;
что разрешено;
какие данные физически доступны;
что можно изменить;
что записывается в аудит;
куда попадают результаты;
как данные восстанавливаются;
как долго они существуют.
Поэтому authorization model должна быть частью security architecture, но не должна подменять её целиком.
Глава 36. Границы модели
36.1. Модель доступа не равна всей безопасности
В предыдущих главах модель стала достаточно сложной:
Subject
Object
Relationship
Scope
Context
Rule
Effective permission
Projection
RLS
Возникает естественный вопрос:
Может ли всё это стать единой моделью безопасности системы?
Нет.
Модель доступа решает конкретный класс задач.
Она определяет допустимость действия субъекта над объектом в определённом контексте.
36.2. Что относится к модели доступа
Если правило отвечает на вопрос:
Может ли субъект выполнить действие над объектом?
оно относится к access model.
Например:
User A
↓
member of Group G
↓
Group G has access to Project P
↓
Object X published in Project P
Если эти факты определяют:
Can(User A, READ, Object X)?
они входят в модель доступа.
36.3. Что находится за её пределами
Но не вся информация о пользователе является частью контекста доступа.
Например:
name
language
avatar
могут вообще не влиять на authorization.
То же самое относится к многим техническим свойствам объекта.
Следовательно:
System state ≠ Access context
Контекст выбирает только релевантные факты.
36.4. Аутентификация находится раньше
Сначала нужно установить:
Кто выполняет запрос?
Это задача аутентификации (authentication, проверка подлинности субъекта).
Затем:
Что этот субъект может сделать?
Это задача авторизации.
Поэтому:
Authentication
↓
Subject
↓
Authorization
Нельзя заменить одно другим.
Установленный user_id ещё не является разрешением.
36.5. Бизнес-правило не всегда является правилом доступа
Предположим:
Order cannot be closed after shipment.
Это ограничение бизнес-состояния.
Оно может применяться даже к пользователю, который имеет:
CLOSE
Следовательно:
Permission
и:
Business invariant
могут оба влиять на выполнение операции, но решают разные задачи.
Упрощённо:
Authorization
+
Business validity
↓
Operation allowed
36.6. RLS находится ещё ниже
RLS также не является всей моделью.
Он отвечает за физическое применение ограничений к данным.
Получается несколько уровней:
Authentication
↓
Authorization model
↓
Business rules
↓
Data enforcement
В конкретной системе порядок некоторых проверок может отличаться.
Но ответственность уровней должна оставаться различимой.
36.7. Контекст — не снимок всей системы
Контекст также имеет границы.
Если в системе существуют:
10 million users
1 million objects
100 million relationships
это не означает, что контекст конкретного запроса содержит всё это состояние.
Для:
Can(User A, READ, Object X)?
нужна только релевантная часть модели.
Поэтому:
Context(S, A, O)
зависит от конкретного запроса.
36.8. Не вся связь является связью безопасности
Объект может иметь десятки отношений:
created by
modified by
linked to
located in
referenced by
owned by
published in
Но только часть из них может участвовать в авторизации.
Например:
Object X references Object Y
не означает автоматически:
User who can read X can read Y
Смысл отношения определяется правилами модели.
36.9. Не вся техническая система является субъектом
Сервис может выполнять SQL.
База данных может выполнять функцию.
Фоновый worker может запускать операцию.
Но техническая возможность выполнить запрос ещё не означает, что каждый компонент является самостоятельным бизнес-субъектом.
Нужно явно определить:
Who is acting?
и:
Who is trusted to perform enforcement?
36.10. Почему границы важны
Если модель слишком узкая, она может пропустить значимое отношение.
Если слишком широкая, она начинает описывать:
everything about everything
и становится практически неуправляемой.
Поэтому хороший критерий прост:
Если факт влияет на решение о допустимости конкретного действия над конкретным объектом, он потенциально относится к модели доступа.
Если не влияет — нет причины включать его только ради полноты.
36.11. Минимальное ядро модели
После всех рассмотренных расширений можно вернуть модель к нескольким элементам:
Subject
Action
Object
Relationships
Areas
Context
Access grounds
Rules
Effective permissions
Вокруг них уже могут существовать:
Projections
RLS
Audit
Encryption
Replication
Caching
Business invariants
Но они не должны автоматически превращаться в одну сущность.
36.12. Граница между моделью и реализацией
Одна и та же модель может быть реализована по-разному.
Например:
Relationship model
может использовать:
authorization service
database projections
application code
или их комбинацию.
А физическое применение может использовать:
RLS
views
procedures
repository filters
Следовательно:
Conceptual model ≠ Implementation mechanism
Это различие позволяет менять реализацию, не меняя смысл модели.
36.13. Главный вывод
Модель доступа имеет чёткую границу.
Она начинается там, где возникает вопрос:
Can(Subject, Action, Object, Context)?
и заканчивается там, где начинаются другие задачи:
Authentication
Business integrity
Encryption
Audit
Retention
Backup
Operational trust
Они связаны между собой, но не являются одной моделью.
Именно поэтому архитектура безопасности становится устойчивой тогда, когда разные вопросы имеют разные уровни ответственности.
36.14. Перед следующей частью
К этому моменту можно собрать весь путь в одну цепочку:
Object
↓
Relationships
↓
Relevant facts
↓
Context
↓
Access grounds
↓
Rules
↓
Effective permission
↓
Object authorization
↓
Data enforcement
↓
Physical data
Дальше возникает уже другой вопрос.
Если эта модель применима к корпоративным системам, PLM, поиску знаний, SaaS и межсервисному доступу, то насколько универсален сам принцип?
Именно это будет предметом следующей части.
Глава 37. Корпоративные системы
Корпоративная система — один из самых понятных примеров того, почему модель доступа постепенно перестаёт укладываться в схему:
User → Role → Permission
В небольшой системе этого действительно может быть достаточно.
Есть сотрудники.
Есть несколько ролей.
Есть набор операций.
Но по мере роста организации появляются подразделения, проекты, группы, документы, временные назначения, делегирование и разные уровни ответственности.
Тогда возникает уже другой вопрос:
не просто что может делать пользователь, а в каком отношении он находится к конкретному объекту и в каком контексте это отношение действует.
37.1 Корпоративный пользователь не является самостоятельным контекстом
Рассмотрим простую организацию:
Company
├── Engineering
│ ├── Team A
│ └── Team B
├── Sales
└── Finance
Есть пользователь:
User A
Он может одновременно:
-
работать в Engineering;
-
участвовать в Team A;
-
быть владельцем нескольких документов;
-
иметь роль менеджера проекта;
-
временно замещать другого сотрудника.
Поэтому вопрос:
"What can User A do?"
не имеет одного ответа.
Более точный вопрос:
What can User A do
to Object O
in Context C?
Один и тот же пользователь может иметь разные права над разными объектами.
37.2 Организационная принадлежность — это отношение
Пусть:
User A
↓
member of
↓
Engineering
Само это отношение ещё не является permission.
Оно является основанием, из которого правила могут вывести определённые права.
Например:
Engineering member
↓
may READ
↓
Engineering documents
Здесь участвуют как минимум два факта:
User A → Engineering
Document D → Engineering
И правило связывает их:
same organizational context
→ READ
Это уже существенно отличается от:
User A → READ
Право существует не само по себе.
Оно возникает из отношения пользователя к определённой части организационной структуры.
37.3 Один пользователь может иметь несколько независимых отношений
Пусть сотрудник:
User A
одновременно:
member of Engineering
member of Team A
manager of Project P
owner of Document D
reviewer of Document E
Эти отношения имеют разный смысл.
Нельзя автоматически объединить их в одно:
User A → Employee
потому что такая запись потеряет информацию о контексте.
Например:
manager of Project P
не означает:
manager of every project
А:
member of Engineering
не означает:
member of every team in Engineering
Отношение имеет собственную область действия.
37.4 Роль становится отношением, а не просто свойством пользователя
В классической модели можно записать:
User A → Manager
Но для корпоративной системы часто точнее:
User A
↓
Manager
↓
Project P
или:
User A
↓
Manager
↓
Department D
В первом случае роль действует внутри проекта.
Во втором — внутри подразделения.
Поэтому сама роль недостаточна.
Нужно знать:
кто
какая роль
в каком отношении
к какой области
Это один из самых важных переходов от простой RBAC-модели к более общей модели отношений.
37.5 Проект может быть самостоятельным контекстом
Корпоративный проект часто пересекает организационную структуру.
Например:
Project P
├── Engineering
├── Design
└── Sales
В него входят сотрудники из разных подразделений.
Получается:
User A → Engineering
User B → Design
User C → Sales
и одновременно:
User A → Project P
User B → Project P
User C → Project P
Если право зависит от участия в проекте, организационная принадлежность уже не определяет его полностью.
Появляется ещё одно измерение:
Organization
+
Project
Это и есть пример контекста с несколькими независимыми отношениями.
37.6 Один объект может иметь несколько оснований доступа
Пусть документ:
Document D
одновременно:
owned by User A
и:
published in Project P
и:
belongs to Engineering
Пользователь User B может получить доступ через проект.
User A — как владелец.
Другой пользователь — через организационную роль.
Получается:
Document D
├── ownership
├── project publication
└── organizational relation
И эффективное разрешение может иметь несколько оснований.
Это ровно тот случай, когда нельзя сказать:
"у документа есть одно право доступа"
Право является результатом применения правил к отношениям.
37.7 Иерархия организации не означает автоматического наследования всех прав
Предположим:
Engineering
↓
Team A
Если пользователь является членом Engineering, это ещё не означает автоматически, что он имеет все права Team A.
И наоборот.
Иерархия показывает отношение:
Team A
↓
part of
↓
Engineering
Но правила должны определить, какое значение имеет эта связь для доступа.
Например:
Engineering member
→ READ all Engineering documents
может быть допустимым правилом.
Но:
Engineering member
→ MODIFY every Team A document
не следует из самой иерархии.
Поэтому:
иерархия организации создаёт отношения, но не определяет автоматически семантику разрешений.
37.8 Делегирование показывает временную природу контекста
Корпоративные системы часто содержат временные отношения.
Например:
Manager A
↓
delegates approval
↓
Manager B
сроком:
01.09 → 15.09
Теперь право пользователя B зависит не только от отношений, но и от времени.
В один день:
Can(B, APPROVE, Document)?
→ YES
в другой:
→ NO
Хотя сам объект не изменился.
Изменился контекст.
Это хорошо показывает, почему контекст нельзя свести к постоянному свойству пользователя.
37.9 Корпоративный доступ часто имеет несколько независимых границ
Один и тот же пользователь может одновременно находиться внутри:
Company
Department
Team
Project
Object
Но эти границы не обязательно означают одно и то же.
Например:
Company
может определять общую изоляцию данных.
Department
может определять область ответственности.
Project
может определять рабочую область.
Object
может иметь собственного владельца.
Получается:
Company
+
Department
+
Project
+
Object relation
Это не четыре разных пользователя.
Это четыре независимых измерения контекста.
37.10 Публикация особенно важна для корпоративных документов
Не каждый существующий объект должен быть видим всем участникам организации.
Например, документ может быть:
created
но оставаться:
draft
для автора.
Затем его публикуют:
Document
↓
published to Project P
Теперь другие участники проекта могут получить к нему доступ.
Публикация здесь не является permission.
Она меняет отношение объекта к области видимости.
Право пользователя определяется уже комбинацией:
User relation to Project
+
Document publication
+
Permission
37.11 Группы дают ещё один уровень отношений
В организации могут существовать группы:
Engineering
├── Backend
├── Frontend
└── QA
Пользователь может быть членом Backend.
Некоторые права могут распространяться на всю Engineering.
Другие — только на Backend.
Тогда недостаточно знать:
User A ∈ Backend
Нужно понимать, как отношение:
Backend
↓
Engineering
участвует в конкретном правиле.
В одном случае связь может расширять область видимости.
В другом — ничего не менять.
В третьем — ограничивать доступ.
Сама иерархия не отвечает на эти вопросы.
37.12 Массовые запросы делают физическое enforcement особенно важным
Предположим, пользователь открывает список документов:
GET /documents
Список может содержать тысячи записей.
Проверять каждый документ отдельным запросом к authorization service было бы дорого.
Поэтому корпоративная система часто выигрывает от предварительно построенных security facts:
User
↓
effective relations
↓
effective permissions
↓
object scopes
После этого физический запрос может дополнительно ограничиваться на уровне данных.
Здесь хорошо проявляется вся архитектура книги:
Relationships
↓
Context
↓
Effective permission
↓
Authorization
↓
Data enforcement
37.13 Корпоративная система хорошо показывает разницу между субъектом и организационной структурой
Важно не превратить организацию в одного большого субъекта.
Если:
User A ∈ Engineering
это не означает:
User A = Engineering
Группа или подразделение является отдельным объектом отношения.
Это позволяет одному пользователю участвовать одновременно в нескольких структурах:
User A
├── Engineering
├── Project P
├── Team A
└── Review Board
Каждая связь может иметь собственную семантику.
Именно это делает модель отношений более выразительной, чем простое присвоение набора ролей пользователю.
37.14 Что происходит при изменении организационной структуры
Предположим, пользователь переводится:
Engineering
↓
Sales
Объекты при этом не изменяются.
Роли объектов тоже не обязательно изменяются.
Но доступ пользователя может измениться сразу к большому количеству данных.
То есть:
Organization change
↓
Relationship change
↓
Security model change
↓
Effective permissions change
↓
Future access changes
Это тот же механизм, который мы рассматривали ранее.
Изменение доступа может происходить без изменения самого объекта.
37.15 Корпоративная модель может быть проще или сложнее
Важно не считать корпоративную систему автоматически сложной.
Если требования таковы:
Company
+
Role
+
Permission
простая RBAC-модель может быть вполне достаточной.
Если появляются:
Department
+
Project
+
Group
+
Publication
+
Delegation
+
Object ownership
возникает потребность в более общей модели отношений.
Следовательно, предметная область определяет необходимую глубину модели.
Архитектура не должна добавлять отношения, которых нет в реальной системе.
37.16 Корпоративный пример показывает общий принцип
Корпоративная система хорошо демонстрирует весь путь:
User
↓
relationships
↓
organizational context
↓
project/object relations
↓
access grounds
↓
effective permission
↓
authorization
↓
data enforcement
При этом ни один отдельный элемент не является всей моделью.
Не является ею:
Role
Не является:
Group
Не является:
Project
Не является:
Publication
И даже:
Permission
не является готовым решением.
Решение возникает из их отношения друг к другу в конкретном контексте.
37.17 Главный вывод
Корпоративная система показывает, почему модель:
User → Role → Permission
остаётся полезной, но перестаёт быть полной.
Пользователь одновременно участвует в нескольких отношениях.
Роль может действовать в определённой области.
Группа и подразделение создают организационные отношения.
Проект формирует отдельную рабочую область.
Публикация определяет, с какой областью связан объект.
Владелец может иметь отдельное основание доступа.
Делегирование добавляет временное отношение.
А изменение любого из этих фактов может изменить будущий результат проверки.
Получается:
Corporate access
=
Subjects
+
Relationships
+
Areas
+
Objects
+
Rules
+
Context
Но это не специальная модель именно для корпоративных систем.
Те же принципы проявляются в других предметных областях — только сами объекты и отношения будут другими.
В системе управления жизненным циклом изделия объектом может быть изделие, документ, версия или спецификация.
В поиске по знаниям — документ, фрагмент, коллекция или источник.
В SaaS — ресурс конкретного клиента и его пользователей.
В B2B — отношения между организациями.
Модель остаётся той же:
Object
↓
Relationships
↓
Context
↓
Access
Меняется предметная семантика этих отношений.
Следующая глава покажет это на другой стороне корпоративного мира — в PLM, где доступ определяется уже не только организационной принадлежностью, но и структурой изделия, версиями, документами и жизненным циклом инженерных данных.
Глава 38. PLM
PLM (Product Lifecycle Management, управление жизненным циклом изделия) хорошо показывает, что контекст доступа может определяться не только организационной структурой.
В корпоративной системе достаточно естественно рассуждать так:
User → Department → Project → Document
В PLM к этой цепочке добавляется сама структура изделия.
Изделие состоит из составных частей.
Документы относятся к изделиям или их компонентам.
Версии существуют в определённых состояниях.
Одни объекты являются исходными для других.
Спецификации связывают изделия, узлы, детали и материалы.
Поэтому вопрос доступа становится не только вопросом:
в каком подразделении работает пользователь?
Он становится вопросом:
какое отношение имеет пользователь к конкретному инженерному объекту, в какой версии и в каком состоянии этого объекта?
38.1 В PLM объект имеет собственную структуру отношений
Рассмотрим простое изделие:
Product
├── Assembly A
│ ├── Part A1
│ └── Part A2
└── Assembly B
├── Part B1
└── Part B2
Каждый элемент этой структуры является самостоятельным объектом.
При этом между объектами существуют отношения:
Product → contains → Assembly A
Assembly A → contains → Part A1
Assembly A → contains → Part A2
Эти отношения не являются разрешениями.
Они описывают предметную область.
Но они могут участвовать в формировании контекста доступа.
Например, пользователь может иметь право работать с изделием, а доступ к его составным частям определяется правилами системы.
Получается уже знакомая нам конструкция:
Subject
↓
relationship
↓
Object
но сам Object теперь также находится внутри множества отношений.
38.2 Структура изделия может становиться частью контекста
Пусть пользователь имеет право работать с изделием P.
У него есть:
User A → Engineer → Project P
А:
Product P
↓
Assembly A
↓
Part A1
Возникает вопрос:
Can(User A, READ, Part A1)?
Одного отношения:
User A → Project P
может быть недостаточно.
Нужно ещё определить, как правила системы трактуют связь:
Part A1
↓
belongs to
↓
Product P
Если правило разрешает доступ к инженерным объектам внутри доступного изделия, структура изделия становится частью контекста.
Тогда путь может выглядеть так:
User
↓
Project relation
↓
Product
↓
Assembly
↓
Part
Но сам факт существования такого пути ещё не означает разрешение.
Как и раньше, путь становится основанием доступа только тогда, когда это предусмотрено правилами модели.
38.3 Спецификация изделия — это не просто папка с файлами
В PLM особенно важно не сводить структуру изделия к физическому размещению документов.
Например, изделие может иметь спецификацию:
Product P
├── Assembly A × 1
├── Part B × 4
└── Part C × 2
Эта структура описывает состав изделия.
Она не обязательно означает:
"все эти объекты находятся в одной папке"
Тем более что один и тот же компонент может использоваться в нескольких изделиях.
Например:
Product P1
↓
Part X
Product P2
↓
Part X
Теперь у Part X есть как минимум два предметных отношения.
Доступ к нему нельзя корректно определить только по его физическому расположению.
Именно здесь особенно хорошо видно различие между:
Object
и:
Object placement
Физическое размещение — лишь один из возможных фактов.
38.4 Версия и состояние изменяют контекст
Инженерный объект обычно существует не только как идентичность.
У него есть жизненный цикл.
Например:
Part X
├── Revision 1 — Draft
├── Revision 2 — Review
└── Revision 3 — Released
Один и тот же пользователь может иметь разные возможности в разных состояниях.
Например:
Draft
→ MODIFY
Review
→ READ
→ APPROVE
Released
→ READ
Конкретные правила, конечно, определяются системой.
Но сама структура показывает важную вещь:
право зависит не только от субъекта и объекта, но и от состояния объекта.
Поэтому:
Can(User, UPDATE, Part X)
не обязательно является постоянным свойством пользователя или детали.
Более точный вопрос:
Can(User, UPDATE, Part X, Revision 2, Review)?
Состояние становится частью контекста, если правила доступа используют его.
38.5 Один инженерный объект может иметь несколько независимых оснований доступа
Предположим, инженер A работает над проектом P1.
Деталь X используется одновременно:
Product P1
Product P2
При этом:
User A → Engineer → P1
и:
User A → Reviewer → P2
Получаются разные отношения к одному и тому же объекту.
Одно из них может давать право изменения.
Другое — только чтения или согласования.
Поэтому для Part X недостаточно определить:
User A has access
Нужно определить:
какое отношение действует
в какой области
для какого действия
Например:
User A
├── Engineer → P1 → MODIFY → X
└── Reviewer → P2 → APPROVE → X
Здесь один объект имеет несколько путей к эффективным разрешениям.
Это тот же принцип, который мы рассматривали в общей модели, но в PLM он становится особенно наглядным.
38.6 Документ, изделие и спецификация не обязаны иметь одинаковую модель доступа
В PLM рядом могут существовать разные типы объектов:
Product
Part
Assembly
Drawing
Specification
Requirement
Change
Revision
У них могут быть разные отношения и разные правила.
Например, право на изменение детали не обязательно означает право на изменение чертежа этой детали.
Связь:
Drawing → describes → Part
не означает:
permission(Drawing) = permission(Part)
А связь:
Specification → describes → Product
не превращает спецификацию в часть самого изделия.
Поэтому модель доступа должна различать:
предметное отношение
и:
отношение доступа
Это особенно важно в инженерных системах, где один объект может быть связан с большим количеством других объектов.
38.7 Иерархия изделия не означает автоматического наследования прав
Структура:
Product
└── Assembly
└── Part
выглядит как естественная иерархия.
Но из неё не следует универсальное правило:
permission(Product)
→ permission(Assembly)
→ permission(Part)
Иногда такое наследование действительно является частью модели.
Иногда доступ к дочернему объекту должен определяться отдельно.
Иногда разные ветви изделия имеют разные ограничения.
Например:
Product P
├── Public Assembly
│ └── Part A
│
└── Restricted Assembly
└── Part B
Пользователь может иметь доступ к изделию в целом, но не иметь права видеть отдельную закрытую ветвь.
Следовательно, иерархия сама по себе не является правилом доступа.
Она является исходным фактом.
Правило определяет, какое значение этот факт имеет для конкретного решения.
38.8 Жизненный цикл добавляет ещё одно измерение
В PLM отношения между объектами могут оставаться неизменными, пока меняется состояние объекта.
Например:
Part X
↓
used in
↓
Product P
остаётся тем же отношением.
Но сама деталь проходит состояния:
Draft
↓
Review
↓
Approved
↓
Released
В результате один и тот же пользователь может получить разные результаты:
READ → YES
UPDATE → NO
APPROVE → YES
а после выпуска:
READ → YES
UPDATE → NO
APPROVE → NO
Здесь изменение доступа произошло без изменения организационной принадлежности пользователя.
Изменился предметный факт — состояние объекта.
Это ещё раз показывает, почему эффективное разрешение нельзя считать постоянным свойством пользователя.
38.9 Изменение структуры изделия может иметь большой радиус влияния
Предположим, деталь X удаляется из спецификации:
Product P
↓
Assembly A
↓
Part X
Само изменение выглядит локальным.
Но если доступ к объектам зависит от структуры изделия, изменение может повлиять на множество решений:
Structure change
↓
Relationship change
↓
Security context changes
↓
Effective permissions change
↓
Future access decisions change
То же самое происходит при:
-
изменении владельца;
-
изменении проекта;
-
публикации документа;
-
изменении состояния;
-
отзыве доступа;
-
изменении состава изделия.
Поэтому в сложной PLM-системе изменение предметной модели может одновременно быть изменением модели безопасности.
38.10 150%-ная структура особенно хорошо показывает различие объекта и контекста
В инженерных системах часто полезно различать фактический и расширенный состав изделия.
Например, рабочая спецификация может содержать не только то, что непосредственно входит в конкретную поставку, но и дополнительные элементы, варианты, заменяемые компоненты или элементы, необходимые для исполнения определённого процесса.
Такая структура может быть существенно шире конкретного экземпляра исполнения.
Это ещё один пример того, почему нельзя считать:
"находится внутри структуры"
синонимом:
"имеет одинаковый доступ"
Структура описывает предметную область.
Контекст доступа определяет, какие именно отношения этой структуры релевантны конкретному субъекту, действию и объекту.
Один и тот же инженерный объект может участвовать в нескольких спецификациях и сценариях.
Следовательно, физическая или логическая принадлежность к одной структуре не должна автоматически становиться универсальным правилом доступа.
38.11 В PLM объект может быть доступен через несколько независимых путей
Рассмотрим:
User A
├── Engineer → Project P1
├── Reviewer → Change C1
└── Owner → Document D
и:
Document D
├── describes → Part X
├── belongs to → Product P1
└── related to → Change C1
Для одного запроса может оказаться релевантным только один путь.
Для другого — несколько.
Например:
READ Document D
может быть разрешён через участие в проекте.
А:
APPROVE Change C1
может требовать отдельного отношения reviewer.
Поэтому контекст не должен представлять собой весь граф PLM.
Он должен содержать те факты и отношения, которые релевантны конкретному решению.
Это тот же принцип, который мы сформулировали раньше:
Context(Subject, Action, Object)
а не:
Context = complete state of PLM
38.12 Физические данные в PLM также требуют отдельного enforcement
Инженерный объект редко представлен одной строкой.
Один Part может иметь:
identity
revision
attributes
documents
specification links
lifecycle state
change history
Физически эти данные могут находиться в разных таблицах или даже в разных хранилищах.
Поэтому:
ALLOW(Part X)
ещё не означает:
ALLOW(all physical data related to Part X)
Для каждого физического представления должна существовать понятная граница доступа.
Особенно это важно для массовых операций:
получить все доступные детали
получить все документы проекта
получить спецификацию изделия
получить историю изменений
В таких запросах недостаточно выполнить одну проверку на уровне интерфейса.
Физический путь к данным тоже должен быть ограничен.
Это возвращает нас к различию между:
Authorization
и:
Data enforcement
которое мы рассматривали ранее.
38.13 Что PLM добавляет к общей модели
Корпоративная система показала:
User
→ Organization
→ Project
→ Object
PLM добавляет ещё один слой:
User
↓
Organizational relations
↓
Project relations
↓
Product structure
↓
Object relations
↓
Lifecycle state
↓
Access decision
Но это не новая модель доступа.
Это тот же принцип, применённый к другой предметной области.
В PLM особенно хорошо видны три важных различия.
Первое:
предметная структура ≠ модель доступа
Второе:
отношение между объектами ≠ разрешение
Третье:
иерархия ≠ автоматическое наследование прав
Эти различия позволяют не превращать всю структуру изделия в огромную ACL.
38.14 Главный вывод
PLM показывает ещё одну важную сторону контекста.
В корпоративной системе контекст в значительной степени формируется отношениями между субъектом и организацией.
В PLM в него могут дополнительно входить отношения между самими объектами:
User
↓
Project
↓
Product
↓
Assembly
↓
Part
а также:
Part
↓
Revision
↓
Lifecycle state
При этом ни один из этих элементов сам по себе не является готовым разрешением.
Объект изделия не является правом.
Спецификация не является правом.
Проект не является правом.
Состояние объекта не является правом.
Даже роль пользователя не является готовым решением.
Они становятся частью решения только в рамках определённых правил.
Поэтому для PLM можно записать ту же общую конструкцию:
Subject
+
Object
+
Relationships
+
Areas
+
State
+
Rules
↓
Context
↓
Effective permission
↓
Access decision
Главное здесь не сама структура изделия.
Главное — то, что предметная модель начинает непосредственно участвовать в формировании контекста доступа.
Именно поэтому в сложной системе безопасности нельзя полностью отделить вопрос «что это за объект?» от вопроса «в каком отношении он находится с другими объектами?».
Следующая область показывает ещё более характерный случай: когда объектом доступа становится не только сам документ или запись, а информация, извлекаемая из множества источников. Это приводит нас к RAG и поиску по знаниям.
Глава 39. RAG и поиск по знаниям
RAG (Retrieval-Augmented Generation, генерация с дополнением найденным контекстом) часто воспринимают как задачу поиска.
Есть документы.
Из них строятся фрагменты.
Фрагменты индексируются.
По пользовательскому запросу система находит наиболее подходящие материалы и передаёт их модели генерации.
На первый взгляд задача доступа кажется простой:
User
↓
Search
↓
Documents
↓
Answer
Но в корпоративной системе этого недостаточно.
Пользователь может иметь право увидеть один документ и не иметь права увидеть другой, хотя оба документа одинаково хорошо соответствуют запросу.
Более того, модель может не должна знать даже о существовании закрытого документа.
Поэтому в поиске по знаниям появляется важное различие:
релевантность информации и допустимость её использования — разные свойства.
39.1 Поиск отвечает не на тот же вопрос, что авторизация
Поисковая система пытается ответить:
какие материалы наиболее релевантны запросу?
Модель доступа отвечает:
какие материалы этот субъект имеет право использовать?
Это разные функции.
Пусть есть три документа:
D1 — архитектура продукта
D2 — внутренний финансовый отчёт
D3 — инструкция для клиентов
Пользователь задаёт:
"Как устроен продукт?"
Поисковая система может считать D1 и D2 наиболее релевантными.
Но это ещё не означает:
User → READ → D2
Если доступ к D2 запрещён, его нельзя передавать в последующую обработку только потому, что он оказался хорошим поисковым результатом.
Получается:
Relevance
≠
Authorization
39.2 Поиск должен работать только с допустимым множеством
Для конкретного пользователя можно представить два множества.
Первое:
Relevant(Query)
— всё, что соответствует запросу.
Второе:
Allowed(User)
— всё, что пользователь имеет право использовать.
Результат должен находиться в их пересечении:
SearchResult
=
Relevant(Query)
∩
Allowed(User)
Это не обязательно означает, что система буквально выполняет две операции над множествами.
Это архитектурный принцип.
Поиск не должен рассматривать запрещённые данные как допустимый источник ответа.
39.3 Документ и его фрагменты — не одно и то же
RAG обычно работает не с целыми документами, а с фрагментами.
Например:
Document D
├── Chunk 1
├── Chunk 2
├── Chunk 3
└── Chunk 4
Возникает вопрос:
если пользователь имеет доступ к документу, получает ли он доступ ко всем его фрагментам?
Во многих системах — да.
Но это уже правило модели.
Нельзя считать это свойство самого технического разбиения.
В другой системе разные части документа могут иметь разные ограничения.
Например:
Document D
├── public section
├── internal section
└── restricted section
Тогда физический chunk становится объектом, участвующим в модели доступа.
Это показывает, почему нельзя автоматически считать:
Document = security boundary
Граница доступа определяется моделью.
39.4 Метаданные поиска становятся частью безопасности
Чтобы найти нужный фрагмент, индекс обычно хранит не только текст.
У фрагмента могут быть метаданные:
document_id
project_id
department_id
owner_id
classification
lifecycle_state
Эти данные используются для фильтрации и поиска.
Но если они участвуют в ограничении доступа, они становятся частью security path.
Например:
chunk
↓
project_id = P1
и:
User A
↓
member of
↓
P1
могут вместе стать основанием для допуска фрагмента к поиску.
Тогда поисковый индекс уже не является нейтральным техническим хранилищем.
Он содержит производное представление данных, которое должно сохранять необходимые границы безопасности.
39.5 Доступ должен ограничивать поиск, а не только готовый ответ
Самая очевидная ошибка — разрешить поиск по всему индексу, а затем проверить доступ к найденным документам перед возвратом ответа.
Проблема в том, что запрещённый объект уже участвовал в вычислении.
Например:
Query
↓
Search all documents
↓
D1
D2 — restricted
D3
↓
Filter D2
↓
Generate answer
На первый взгляд всё выглядит безопасно.
Но архитектурно здесь уже возник вопрос:
мог ли закрытый материал повлиять на вычисление до того, как был отброшен?
Если поисковый движок, reranker или генератор использовал закрытый фрагмент, простого удаления его из конечного ответа может быть недостаточно.
Поэтому граница доступа должна проходить как можно раньше.
Более надёжная схема:
Query
↓
Security context
↓
Allowed search space
↓
Retrieval
↓
Reranking
↓
Generation
↓
Answer
Авторизация становится ограничением пространства поиска.
39.6 Контекст пользователя и контекст генерации — разные вещи
В RAG слово «контекст» используется особенно часто.
Но это два разных понятия.
Первое:
Access Context
— факты и отношения, определяющие допустимость доступа.
Второе:
Generation Context
— найденные фрагменты, переданные модели для формирования ответа.
Их нельзя смешивать.
Например:
Access Context
=
User
+
Project
+
Role
+
Object relations
+
State
а:
Generation Context
=
Chunk A
+
Chunk B
+
Chunk C
Первый определяет, какие данные могут попасть во второй.
Поэтому:
Access Context
↓
determines allowed sources
↓
Generation Context
Это особенно важно потому, что оба понятия обычно называются одним словом — context.
39.7 Один запрос может затрагивать множество объектов
В обычной CRUD-операции часто можно явно определить:
Subject
Action
Object
В поиске вопрос сложнее.
Пользователь задаёт один запрос:
"Как мы реализовали механизм публикации?"
Система может найти десятки фрагментов:
Document A
Document B
Document C
...
Document N
Каждый из них может иметь собственный контекст доступа.
Поэтому запрос пользователя не означает:
один объект → одно решение
На этапе retrieval фактически выполняется множество проверок:
Can(User, READ, Chunk1)?
Can(User, READ, Chunk2)?
Can(User, READ, Chunk3)?
...
При этом сама модель поиска должна быть построена так, чтобы эти проверки выполнялись эффективно.
39.8 Один документ может быть доступен через несколько оснований
Пусть документ относится к проекту P1.
Пользователь может получить к нему доступ потому что он:
member of P1
или:
owner of Document D
или:
explicitly granted access
Поисковая система не должна обязательно знать, почему документ разрешён, если для неё достаточно итогового security fact.
Но архитектура авторизации должна уметь построить этот результат.
Получается знакомое разделение:
Source relationships
↓
Effective permissions
↓
Allowed search space
Поисковый индекс может использовать уже подготовленный результат.
Он не обязан каждый раз самостоятельно обходить весь граф отношений.
39.9 Изменение отношения должно менять поисковую видимость
Предположим, пользователь покинул проект:
User A
↓
removed from
↓
Project P1
Документы проекта не изменились.
Индекс тоже может физически не измениться.
Но поисковая доступность пользователя должна измениться.
То есть:
Relationship change
↓
Security projection change
↓
Search visibility change
Это особенно важно для больших индексов.
Необязательно перестраивать весь индекс документа только потому, что изменилось членство одного пользователя.
Можно изменить производный security layer, через который определяется допустимость поиска.
39.10 Отзыв доступа важнее простого добавления
Допустим, пользователь имел доступ к проекту:
User A → Project P1
Документы проекта уже могли попасть:
-
в поисковый индекс;
-
в кэш;
-
в промежуточные результаты;
-
в историю поисковых запросов;
-
в другие производные представления.
После отзыва доступа:
User A → Project P1
↓
revoked
недостаточно изменить только основную запись членства.
Нужно понимать, какие производные состояния используют это отношение.
Именно поэтому ранее мы различали:
Source fact
и:
Derived security fact
Для RAG это различие становится особенно важным.
39.11 Индекс сам становится частью поверхности безопасности
Поисковый индекс часто воспринимается как техническая оптимизация.
Но если он содержит исходный текст или достаточно информации для его восстановления, то он является ещё одним местом хранения данных.
Например:
Database
↓
Search index
↓
Cache
↓
LLM context
Доступ к базе может быть ограничен, но если индекс доступен шире, модель безопасности фактически обходится через другой путь.
Поэтому для каждого производного представления нужно задать вопрос:
какие исходные данные можно восстановить из него и какие ограничения должны сохраняться?
Это относится не только к RAG.
Тот же принцип действует для:
-
кэшей;
-
материализованных представлений;
-
поисковых индексов;
-
отчётов;
-
экспортов;
-
аналитических витрин.
RAG просто делает эту проблему особенно заметной.
39.12 Генерация ответа не отменяет исходные права
Допустим, пользователь имеет доступ к документу только для чтения.
Модель генерации может на основе этого документа сформировать:
summary
Это не означает автоматически, что пользователь получил новый уровень доступа.
Но возникает другой вопрос:
не создаёт ли производный ответ новый способ раскрытия данных?
Например, документ может быть доступен для чтения, но отдельные сведения могут иметь ограничения на распространение или экспорт.
Поэтому authorization исходного объекта и правила использования производного результата могут быть связаны, но не обязаны быть идентичными.
Это снова показывает границу между:
Authorization
и более широкой:
Data security
которую мы рассматривали раньше.
39.13 RAG особенно хорошо показывает разницу между релевантностью и допустимостью
Пусть система нашла:
D1 — relevance 0.94 — allowed
D2 — relevance 0.92 — forbidden
D3 — relevance 0.88 — allowed
Нельзя рассуждать:
D2 is highly relevant
→ use D2
→ hide it later
Правильная последовательность должна сохранять ограничение доступа:
Allowed
↓
Relevant
↓
Ranked
↓
Generated
То есть безопасность задаёт допустимое пространство, внутри которого работает поиск.
Это не означает, что authorization всегда должен выполняться отдельным запросом перед retrieval.
Напротив, эффективная архитектура может объединять фильтрацию и поиск.
Но семантически эти функции остаются разными.
39.14 В RAG особенно полезны производные security facts
Предположим, исходные отношения таковы:
User A → member of Project P1
User A → member of Department D1
Документы:
Document X → Project P1
Document Y → Department D1
Document Z → Project P2
Из исходных отношений можно заранее получить производное представление:
User A
├── allowed Project P1
└── allowed Department D1
А индекс может использовать эти данные для ограничения поиска.
Такой подход переносит вычисление сложных отношений с момента запроса на момент изменения модели.
Это тот же архитектурный принцип, который мы рассматривали для security projections:
Source facts
↓
Security projections
↓
Allowed search space
↓
Retrieval
При этом индекс и security projection остаются разными понятиями.
Индекс отвечает за поиск.
Security projection — за производное представление отношений доступа.
39.15 Но нельзя превратить поисковый индекс в источник истины
Если пользователь изменил принадлежность к проекту, источником истины остаётся исходная модель:
User
Project
Membership
а не:
SearchIndex.allowed = true
Индекс должен быть перестроен или обновлён так, чтобы соответствовать актуальной модели.
Иначе производное состояние начнёт определять безопасность самостоятельно.
Получится опасная конструкция:
Domain model
↓
Search index
↓
Security decision
в которой индекс фактически становится вторым источником истины.
Правильнее:
Domain facts
↓
Security model
↓
Security projection
↓
Search filtering
39.16 Один запрос может иметь разный контекст доступа для разных пользователей
Это особенно хорошо видно на одном и том же вопросе.
Пользователи:
User A
User B
задают:
"Как устроена система?"
Поисковый запрос одинаков.
Но множества разрешённых источников различаются:
Allowed(A) ≠ Allowed(B)
Поэтому:
Search(Query, A)
и:
Search(Query, B)
могут вернуть разные результаты.
Это не ошибка поисковой системы.
Это естественное следствие того, что поиск выполняется внутри контекста субъекта.
39.17 Здесь особенно важно различать объект, фрагмент и источник
В RAG могут существовать сразу несколько уровней:
Source
↓
Document
↓
Chunk
↓
Embedding
Каждый следующий уровень является производным от предыдущего.
Но security semantics не обязана полностью совпадать с этой структурой.
Например, право может определяться на уровне документа, а фильтрация выполняться на уровне chunk.
Тогда нужно явно определить соответствие:
Chunk
↓
belongs to
↓
Document
↓
has access scope
Если такого соответствия нет или оно устарело, поисковый слой может начать показывать данные, которые исходная модель доступа не разрешает.
Следовательно, техническая декомпозиция данных должна иметь понятное отношение к security boundary.
39.18 Что RAG добавляет к общей модели
В предыдущих главах мы рассматривали доступ к одному конкретному объекту.
RAG показывает ситуацию, когда один пользовательский запрос приводит к работе сразу с множеством потенциальных объектов.
При этом:
Search relevance
определяет, что полезно найти, а:
Access context
определяет, что допустимо использовать.
Поэтому общая схема приобретает ещё один уровень:
Subject
↓
Access context
↓
Allowed objects
↓
Relevant objects
↓
Retrieved context
↓
Generation
Самое важное здесь — порядок смыслов.
Поиск не должен расширять множество разрешённых объектов.
Он только выбирает наиболее релевантные объекты внутри допустимого множества.
39.19 Главный вывод
RAG показывает, что контекст доступа становится особенно важным там, где система работает не с одним явно указанным объектом, а с большим множеством потенциальных источников информации.
Поисковая релевантность не заменяет авторизацию.
Индекс не является источником истины о правах.
Фрагмент документа не автоматически наследует все свойства документа — это должно быть определено моделью.
Изменение отношения пользователя к проекту, организации или объекту может изменить множество результатов поиска, даже если сами документы не изменились.
А производные данные — индекс, кэш, найденные фрагменты и сгенерированный ответ — становятся частью общей поверхности безопасности.
Поэтому для поиска по знаниям полезно разделять:
Что соответствует запросу?
↓
Что разрешено субъекту?
↓
Что из разрешённого наиболее релевантно?
↓
Что можно передать в дальнейшую обработку?
И снова мы приходим к той же конструкции:
Relationships
↓
Context
↓
Effective access
↓
Allowed data
↓
Search / Retrieval
В PLM контекст усложняется структурой изделия.
В RAG — множеством потенциальных источников и производных представлений данных.
Но принцип остаётся тем же: доступ определяется не отдельным свойством пользователя или объекта, а отношениями между ними в конкретном контексте.
Следующая глава переносит этот принцип в SaaS, где особенно ясно проявляется другая граница: один и тот же пользователь может работать с множеством организаций и ресурсов, а изоляция клиента становится самостоятельным измерением контекста.
Глава 40. SaaS
SaaS (Software as a Service, программное обеспечение как услуга) особенно хорошо показывает, почему один и тот же пользователь не обязательно принадлежит только одному контексту.
В простой многопользовательской системе можно представить модель:
Company
↓
Users
↓
Resources
Кажется естественным считать организацию основной областью доступа.
Пользователь принадлежит компании.
Ресурс принадлежит компании.
Значит, пользователь может работать с ресурсом.
Но реальная SaaS-система быстро добавляет другие отношения:
-
пользователь участвует в нескольких организациях;
-
внутри организации у него разные роли;
-
один ресурс может быть связан с проектом;
-
доступ может быть выдан непосредственно;
-
существуют внешние пользователи;
-
есть делегирование;
-
разные действия требуют разных прав;
-
некоторые ресурсы являются общими между организациями;
-
физические данные должны быть изолированы.
Поэтому tenant является важным измерением контекста, но не всей моделью доступа.
40.1 Tenant задаёт границу, но не готовое разрешение
Рассмотрим SaaS-систему с двумя организациями:
Company A
Company B
Пользователь:
User U
может состоять в обеих:
User U → Company A
User U → Company B
При этом его роль может отличаться:
Company A → Admin
Company B → Viewer
Поэтому вопрос:
"What can User U do?"
снова недостаточно точен.
Нужно спросить:
What can User U do
to Object O
within Company C?
Компания определяет область, в которой должно интерпретироваться отношение пользователя.
Но сама по себе принадлежность к компании ещё не является разрешением.
40.2 Один пользователь — несколько организаций
Это один из принципиальных случаев SaaS.
Пользователь может работать:
User U
├── Company A
├── Company B
└── Company C
При этом:
Company A → Admin
Company B → Editor
Company C → Viewer
Одна и та же операция:
UPDATE Object O
может иметь разные результаты в зависимости от того, к какой организации относится объект.
Поэтому роль нельзя хранить только как свойство пользователя:
User.role = EDITOR
Такое представление потеряло бы организационный контекст.
Точнее:
User
↓
Role
↓
Company
Роль становится отношением пользователя к определённой области.
40.3 Tenant и объект должны рассматриваться вместе
Пусть:
Object O1 → Company A
Object O2 → Company B
и:
User U ∈ Company A
User U ∈ Company B
Сам факт, что пользователь состоит в обеих организациях, не означает:
User U → same permissions → O1 and O2
Для каждого объекта нужно определить соответствующее отношение.
Например:
Company A:
User U → Admin → O1
Company B:
User U → Viewer → O2
Таким образом, tenant является частью контекста конкретного решения:
Context(User U, UPDATE, O1)
и:
Context(User U, UPDATE, O2)
могут содержать разные отношения.
40.4 Изоляция tenant — отдельный уровень безопасности
В SaaS есть важное требование, которого может не быть в обычной корпоративной системе:
данные одной организации не должны случайно стать доступны другой.
Это уже не только вопрос роли.
Даже если пользователь является администратором внутри:
Company A
это не должно автоматически означать доступ к:
Company B
Получается независимая граница:
Tenant boundary
Она ограничивает пространство, внутри которого вообще может рассматриваться обычная модель доступа.
Можно представить это так:
Tenant isolation
↓
Access model
↓
Effective permission
Изоляция клиента и authorization внутри клиента связаны, но это не одно и то же.
40.5 Одна организация — несколько уровней доступа
Даже внутри одного tenant простого правила:
User ∈ Company
→ access
обычно недостаточно.
Пусть есть:
Company A
├── Project P1
├── Project P2
└── Finance
Пользователь:
User U
может быть:
Admin → Company A
или:
Manager → Project P1
или:
Viewer → Project P2
Каждая роль действует в собственной области.
Поэтому даже внутри одного tenant контекст может иметь несколько измерений:
Tenant
+
Project
+
Role
+
Object
+
Action
40.6 Ресурс может быть связан с несколькими областями
Предположим, SaaS-система управляет документами.
Документ:
Document D
связан с:
Company A
Project P1
Но пользователь может получить доступ к нему через разные основания:
Owner
Project membership
Direct grant
Company role
Это не означает, что все основания одинаковы.
Например:
Company role → READ
Project role → UPDATE
Owner → MANAGE
Эффективное разрешение становится результатом их совместного применения.
SaaS здесь ничем принципиально не отличается от предыдущих примеров.
Меняется только предметная семантика отношений.
40.7 Общие ресурсы нарушают простую модель tenant
Теперь рассмотрим более сложный случай.
Некоторые данные могут быть общими для нескольких организаций:
Company A ─┐
├── Shared Resource
Company B ─┘
Например, это может быть:
-
общий каталог;
-
шаблон;
-
библиотека;
-
публичный внутри платформы справочник;
-
совместный проект.
Тогда утверждение:
Object → exactly one Company
может быть неверным для конкретного класса объектов.
Появляется другой вопрос:
как именно объект связан с организациями и какая семантика у этой связи?
Это снова возвращает нас к различию:
Tenant
≠
universal object scope
Для одних объектов tenant является жёсткой границей.
Для других — одной из областей публикации или совместного использования.
40.8 Прямой доступ не должен ломать tenant isolation
Пусть пользователь получил прямое разрешение:
User U
↓
READ
↓
Object O
Если O принадлежит другой организации, нельзя автоматически считать прямое разрешение достаточным.
Сначала должна быть определена семантика межорганизационного доступа.
Например:
Company A
↓
B2B relationship
↓
Company B
и только затем:
User U
↓
delegated access
↓
Object O
Такой доступ уже содержит несколько отношений.
Получается:
User
↓
Company A
↓
B2B relationship
↓
Company B
↓
Object
Сам по себе прямой grant не должен молча отменять организационную границу.
40.9 B2B показывает, что субъект тоже может быть составным
В межорганизационном SaaS действие может выполняться не просто:
User → Object
а:
User
↓
Company A
↓
B2B relationship
↓
Company B
↓
Object
При этом важно различать:
-
пользователя;
-
организацию, от имени которой он действует;
-
технический сервис;
-
конкретный объект;
-
отношение между организациями.
Например, сотрудник компании A может иметь право работать с данными компании B только в рамках определённого договора или проекта.
Тогда организация становится не просто tenant boundary, а участником отношения.
Это ещё один случай, когда:
Subject
нельзя свести к одному идентификатору пользователя.
40.10 В SaaS особенно важен уровень действия
Пусть пользователь имеет доступ к объекту:
Document D
Это ещё не означает одинаковое право на все операции.
Например:
READ
UPDATE
SHARE
EXPORT
DELETE
могут иметь разные правила.
Пользователь может:
READ → YES
UPDATE → YES
SHARE → NO
EXPORT → NO
Поэтому проверка:
User has access to Document D
слишком груба.
Нужно проверять:
Can(User, Action, Object, Context)?
Это особенно важно в SaaS, где операции вроде SHARE или EXPORT могут создавать новый поток данных за пределы исходной организации.
40.11 Изменение роли может изменить доступ сразу к множеству объектов
Предположим, пользователь:
User U
был:
Viewer → Company A
и стал:
Editor → Company A
Объекты при этом не изменились.
Но эффективные права пользователя изменились сразу для большого количества объектов.
То есть:
Role change
↓
Security model change
↓
Effective permissions change
↓
Access changes
Это тот же механизм распространения изменения, который мы рассматривали раньше.
SaaS просто делает его особенно заметным из-за большого количества объектов и пользователей.
40.12 Физическая изоляция должна соответствовать логической
Tenant isolation имеет ещё один уровень.
Допустим, приложение проверило:
User U belongs to Company A
Но затем выполнило SQL:
SELECT *
FROM documents;
без ограничения по tenant.
Логическая проверка ничего не спасает.
Физический путь к данным должен сохранять ту же границу.
Концептуально:
Tenant context
↓
Authorization
↓
Data enforcement
↓
Rows of Company A
Поэтому в SaaS особенно естественно использовать механизмы ограничения данных на уровне базы или другого физического хранилища.
Но это всё равно не превращает механизм физической изоляции в полную модель authorization.
40.13 Один запрос может работать сразу с несколькими tenant
Иногда пользовательский интерфейс позволяет администратору или оператору работать сразу с несколькими организациями.
Например:
Company A
Company B
Company C
Тогда запрос:
"Show all open incidents"
может охватывать несколько tenant.
Но это не означает, что tenant boundary исчезает.
Наоборот, результат должен сохранять происхождение каждой записи:
Incident 1 → Company A
Incident 2 → Company B
Incident 3 → Company C
И для каждой организации должны применяться соответствующие правила.
Это важный пример того, почему:
tenant_id
не всегда достаточно как единственного security attribute.
Нужен контекст конкретного действия.
40.14 Массовые запросы снова делают физическое enforcement критичным
SaaS-система постоянно выполняет запросы вида:
all documents
all projects
all users
all invoices
all tasks
Нельзя полагаться только на то, что каждый отдельный объект будет проверен после извлечения.
Например:
SELECT *
FROM invoices
WHERE ...
должен физически возвращать только те строки, которые находятся в допустимом пространстве данных.
Именно здесь хорошо разделяются два уровня:
Authorization
→ может ли субъект выполнять действие?
Data enforcement
→ какие физические данные могут участвовать в операции?
Для SaaS это различие особенно важно, потому что ошибка в одном фильтре может привести к смешению данных разных организаций.
40.15 Кэш и фоновые процессы тоже должны знать о границе tenant
SaaS редко ограничивается синхронным HTTP-запросом.
Есть:
cache
background jobs
exports
reports
notifications
search indexes
integrations
Каждый из этих компонентов может получить данные.
Поэтому tenant isolation должна сохраняться не только в основном API.
Например, кэшировать:
"all invoices"
без учёта tenant нельзя.
Нужна семантика вроде:
CacheKey
=
Tenant
+
Resource
+
Query
Но и это лишь техническое следствие более общего принципа:
производное представление данных не должно терять security boundary исходной модели.
40.16 SaaS показывает разницу между tenant и context особенно хорошо
Если бы контекст был равен tenant, достаточно было бы написать:
Context = Company A
Но для конкретного запроса этого мало.
Например:
User U
Company A
Project P
Role Manager
Object O
Action APPROVE
State Review
Все эти факты могут быть релевантны одновременно.
Поэтому:
Tenant ⊂ Context
если tenant вообще является частью конкретного решения.
Но:
Tenant ≠ Context
Tenant задаёт одну границу.
Контекст объединяет все релевантные для данного решения факты и отношения.
40.17 Что происходит при удалении пользователя из tenant
Предположим:
User U ∈ Company A
отношение удаляется.
Документы компании не меняются.
Проекты не меняются.
Но пользователь должен потерять соответствующие права.
Это снова показывает:
Relationship change
↓
Security projection change
↓
Access change
Причём если пользователь состоит в нескольких организациях, удаление из Company A не должно автоматически менять его доступ к Company B.
Изменение имеет область влияния.
Это одна из причин, по которой security facts полезно строить с явной областью применимости.
40.18 SaaS не требует одной универсальной модели доступа
Как и в предыдущих системах, не всякий SaaS требует сложной модели.
Для простого продукта может быть достаточно:
Tenant
+
Role
+
Permission
Если требования ограничиваются изоляцией клиентов и несколькими ролями, этого может быть вполне достаточно.
Но если появляются:
multiple organizations per user
project roles
groups
direct grants
B2B access
delegation
shared resources
object ownership
fine-grained actions
модель отношений становится необходимой.
Поэтому архитектура должна следовать требованиям предметной области, а не наоборот.
40.19 Главный вывод
SaaS показывает особенно ясно, что организационная граница и модель доступа — разные уровни.
Tenant отвечает на важный вопрос:
к какой области изоляции относится этот запрос или данные?
Но он не отвечает сам по себе:
что именно может делать этот пользователь с конкретным объектом?
Для этого нужны другие отношения:
User
↓
Tenant
↓
Role
↓
Project / Group
↓
Object
↓
Action
В межорганизационном доступе цепочка может стать ещё длиннее:
User
↓
Company A
↓
B2B relationship
↓
Company B
↓
Object
А физическая модель должна сохранить те же границы:
Access context
↓
Authorization
↓
Data enforcement
↓
Tenant-safe data
Таким образом, SaaS добавляет к нашей модели особенно важное измерение — границу организации.
Но эта граница не заменяет контекст.
Она является одним из его возможных элементов.
Именно поэтому даже в многопользовательской SaaS-системе недостаточно сказать:
User belongs to tenant
Нужно определить:
what relationship
to which object
for which action
within which tenant
under which conditions
И только после этого возникает эффективное разрешение.
Следующая глава переносит ту же модель на межсервисный и B2B-доступ. Там субъектом действия может быть уже не только пользователь, а организация, сервис или делегированный участник, а границы доверия проходят между несколькими системами.
Глава 41. Межсервисный и B2B-доступ
До сих пор мы в основном рассматривали доступ внутри одной системы.
Пользователь обращается к объекту.
Система знает его отношения, роли, области и ограничения.
Но в распределённой архитектуре возникает другой случай:
Service A
↓
Service B
Или:
Company A
↓
B2B relationship
↓
Company B
Теперь субъектом действия может быть не только человек.
Запрос может исходить от:
-
другого сервиса;
-
организации;
-
интеграционного компонента;
-
технического клиента;
-
пользователя, действующего через сервис;
-
сервиса, выполняющего действие от имени пользователя.
Это добавляет ещё один слой отношений.
Но основной принцип не меняется:
аутентификация определяет, кто обращается к системе, а модель доступа определяет, что этому субъекту разрешено делать с конкретным объектом в конкретном контексте.
41.1 Сервис тоже может быть субъектом
В простой архитектуре можно представить:
User
↓
API
↓
Database
В распределённой системе путь может быть:
User
↓
Service A
↓
Service B
↓
Database
При этом Service B должен понимать, от чьего имени выполняется операция.
Есть как минимум два разных субъекта:
Technical caller = Service A
Business subject = User U
Иногда они совпадают.
Иногда нет.
Например, фоновый процесс может самостоятельно обрабатывать данные без конкретного пользователя.
Тогда:
Subject = Service A
а не:
Subject = User U
Смешивать эти понятия опасно.
41.2 Аутентификация сервиса не является разрешением
Пусть Service A успешно аутентифицировался в Service B.
Это означает:
Service B knows:
"this request came from Service A"
Но из этого не следует:
Service A may read every object
Нужно отдельно определить:
Can(Service A, READ, Object O, Context)?
Получается та же последовательность:
Authentication
↓
Subject
↓
Authorization model
↓
Access decision
Техническая идентичность вызывающей стороны — только один из входов.
41.3 Сервис может действовать от имени пользователя
Более сложный случай:
User U
↓
Service A
↓
Service B
Service A передаёт запрос дальше.
Теперь Service B должен отличать:
"Service A is allowed to call me"
от:
"User U is allowed to access this object"
Это разные утверждения.
Например, Service A может иметь право вызывать API Service B для всех пользователей своего продукта.
Но конкретный пользователь может не иметь права читать конкретный объект.
Тогда:
Service A → CALL → Service B
не означает:
User U → READ → Object O
Второе решение требует собственной модели доступа.
41.4 Передача идентичности не должна превращаться в передачу всех прав
Если Service A передаёт Service B сведения о пользователе, это не означает, что Service B должен доверять всем утверждениям Service A без ограничений.
Например, запрос может содержать:
user_id
company_id
roles
permissions
Но возникает вопрос:
кто является источником истины для этих данных?
Если Service B принимает произвольное:
role = ADMIN
от другого сервиса, граница доверия фактически становится неограниченной.
Поэтому между сервисами важно различать:
Identity
и:
Authorization facts
Они могут передаваться вместе, но имеют разную семантику и разный уровень доверия.
41.5 У межсервисного доступа есть собственный контекст
Рассмотрим:
Service A
↓
Service B
↓
Object O
Контекст может включать:
calling service
business subject
organization
requested action
object
delegation
purpose
Например:
Service A
acts for User U
in Company C
to READ Object O
Это уже полноценный контекст.
Если Service A вызывает Service B самостоятельно:
Service A
acts as itself
to UPDATE Object O
контекст будет другим.
Поэтому нельзя считать:
"request came from Service A"
полным описанием доступа.
41.6 B2B добавляет отношения между организациями
В SaaS пользователь мог иметь доступ внутри одного tenant.
В B2B появляется связь:
Company A
↓
partner of
↓
Company B
Но само партнёрство не обязательно означает доступ ко всем данным.
Может существовать более точное отношение:
Company A
↓
supplier for
↓
Company B
или:
Company A
↓
service provider for
↓
Company B
или:
Company A
↓
participant in Project P
↓
Company B
Каждое отношение имеет собственную семантику.
Поэтому:
Company A is partner of Company B
не является готовым permission.
Это основание, которое правила могут использовать для конкретного доступа.
41.7 Межорганизационный доступ часто ограничен объектом
Пусть компании связаны:
Company A
↓
partner
↓
Company B
Это ещё не означает:
Company A → READ → all Company B data
В реальной системе доступ может быть ограничен:
Project P
Document D
Order O
API resource R
Например:
Company A
↓
supplier
↓
Company B
↓
Project P
и только данные проекта P доступны партнёру.
Контекст становится:
Company relationship
+
Project relationship
+
Object
+
Action
То есть B2B не устраняет объектную модель доступа.
Он добавляет к ней ещё один уровень отношений.
41.8 Делегирование соединяет пользователя, сервис и организацию
Рассмотрим цепочку:
User U
↓
Company A
↓
Service A
↓
Company B
↓
Object O
Она может означать:
пользователь компании A через сервис A выполняет разрешённую операцию с объектом компании B.
Но такой путь не должен автоматически считаться правом.
Нужно определить, какие отношения являются допустимыми.
Например:
User U
↓
authorized to use
↓
Service A
и:
Service A
↓
authorized by
↓
Company B
Только совместное применение этих правил может дать доступ.
Это хорошо показывает принцип из главы 14:
цепочка отношений становится основанием доступа только тогда, когда это предусмотрено правилами.
41.9 Не каждый сервис должен видеть весь security graph
В распределённой системе возникает соблазн передать каждому сервису всю информацию:
all users
all groups
all roles
all projects
all permissions
all relationships
Но это быстро превращается в отдельную проблему.
Каждому сервису нужен только тот набор security facts, который необходим для его собственных решений.
Например:
Service B
может знать:
User U
Company C
Project P
effective permission
и не знать всю организационную структуру платформы.
Это тот же принцип проекций:
Source security model
↓
Relevant projection
↓
Local authorization
Распределение модели не означает копирование всей модели в каждый сервис.
41.10 Локальная проверка особенно важна для физического доступа
Предположим:
Service A
проверил:
User U may READ Object O
и затем передал запрос Service B.
Service B всё равно должен обеспечить собственную границу данных, если он является владельцем этих данных.
Иначе получится:
Service A
↓
ALLOW
↓
Service B
↓
all rows
Так нельзя.
Сервис, владеющий данными, должен иметь собственную модель enforcement.
Поэтому в распределённой системе появляются как минимум две ответственности:
Service A
→ may initiate the operation
Service B
→ may expose these data
Они могут использовать общую модель отношений, но ответственность за физические данные остаётся у владельца данных.
41.11 Межсервисная модель не должна превращаться в сетевую ACL
Можно пойти по простому пути:
Service A → allowed to call Service B
Это полезное правило, но оно отвечает только на один вопрос:
может ли Service A вызвать Service B?
Оно не отвечает:
какие данные Service A может получить?
Тем более оно не отвечает:
какие данные конкретный пользователь может получить через Service A?
Поэтому сетевой доступ и объектный доступ находятся на разных уровнях.
Условно:
Network / service trust
↓
Service authorization
↓
Business subject
↓
Object authorization
↓
Data enforcement
Один уровень не заменяет другой.
41.12 Изменение B2B-отношения может изменить множество доступов
Пусть две организации больше не сотрудничают:
Company A
↓
partner
↓
Company B
отношение удаляется.
Объекты не меняются.
Пользователи не меняются.
Но множество допустимых действий может измениться сразу:
B2B relation revoked
↓
Security facts change
↓
Effective permissions change
↓
Access changes
Если отношения использовались в нескольких сервисах, изменение должно распространиться на соответствующие локальные security projections.
Это тот же принцип согласованности, который мы уже рассматривали внутри одной системы.
Только теперь область распространения проходит через границу сервисов.
41.13 Границы сервисов не отменяют необходимость единой семантики
Несколько сервисов могут иметь собственные базы:
Service A → DB A
Service B → DB B
Service C → DB C
Но если они используют общую модель доступа, определения отношений должны оставаться согласованными.
Например, если:
"member of project"
в одном сервисе означает одно, а в другом — другое, распределённая модель быстро становится непредсказуемой.
Поэтому нужно разделять:
semantic model
и:
local implementation
Каждый сервис может хранить свою проекцию.
Но смысл отношения должен оставаться определённым.
41.14 Синхронизация модели — отдельная задача
Если security facts распространяются между сервисами, возникает вопрос:
что происходит между изменением исходного отношения
и обновлением локальной проекции?
Например:
Company A
↓
removed from Project P
а Service B ещё некоторое время содержит старое производное состояние.
Это переходное состояние.
Для него нужна явная семантика.
Нужно понимать:
-
допускается ли временная задержка;
-
какие операции должны быть заблокированы;
-
как обрабатывается отзыв;
-
как обнаруживается потеря изменения;
-
как выполняется повторная синхронизация;
-
как восстанавливается локальная проекция.
То есть межсервисный доступ наследует все проблемы согласованности производной модели, которые мы уже рассматривали.
41.15 Snapshot и изменения решают разные задачи
При передаче security model между сервисами можно использовать два типа информации.
Первый:
Snapshot
— текущее состояние модели.
Второй:
Change
— изменение этого состояния.
Snapshot позволяет восстановить состояние.
Изменения позволяют поддерживать его актуальным.
Поэтому надёжная архитектура обычно должна учитывать оба сценария:
Known snapshot
+
Subsequent changes
Если локальная проекция потеряла часть изменений, она должна иметь путь к восстановлению.
Это не специфично для B2B.
Это общий принцип для любой производной security model.
41.16 Сервис не должен доверять удалённой авторизации без собственной границы данных
Предположим, Service A отвечает:
ALLOW
Service B не должен автоматически воспринимать это как:
return any data
Если Service B владеет данными, он должен проверить собственные ограничения.
В зависимости от архитектуры это может быть:
-
собственная authorization projection;
-
локальный policy check;
-
ограничение запроса;
-
database policy;
-
RLS;
-
комбинация этих механизмов.
Главное — сохраняется принцип:
Authorization
≠
Data enforcement
Даже если authorization распределена между сервисами.
41.17 Один сервис может быть одновременно субъектом и владельцем данных
Роли сервисов не всегда фиксированы.
Например:
Service A
↓
calls
↓
Service B
но:
Service B
↓
calls
↓
Service C
При этом Service B одновременно:
-
субъект для Service C;
-
владелец данных для собственного домена;
-
посредник для Service A.
Поэтому архитектурная модель должна рассматривать эти роли независимо.
Нельзя сказать:
"Service B is trusted"
и считать вопрос закрытым.
Нужно определить:
trusted for what?
within which boundary?
for which action?
on which objects?
41.18 Межсервисный доступ особенно хорошо показывает ограниченность простого RBAC
Можно назначить:
Service A → role = CLIENT
Но этого недостаточно, если Service A работает:
-
с несколькими организациями;
-
с несколькими проектами;
-
от имени разных пользователей;
-
с разными типами объектов;
-
с разными операциями.
Тогда возникает знакомая конструкция:
Subject
+
Action
+
Object
+
Relationships
+
Context
Просто субъектом теперь может быть сервис.
Или пользователь через сервис.
Или организация.
Или комбинация этих сущностей.
41.19 Что B2B и межсервисный доступ добавляют к общей модели
В корпоративной системе основными отношениями были:
User
→ Department
→ Project
→ Object
В PLM добавилась структура самих объектов.
В RAG — множество источников и производных данных.
В SaaS — tenant и изоляция организаций.
B2B и межсервисный доступ добавляют ещё одну границу:
Subject
↓
Organization
↓
Relationship between organizations
↓
Service
↓
Object
Но принцип остаётся тем же.
Каждое отношение является фактом.
Правило определяет его значение.
Контекст объединяет релевантные факты.
Эффективное разрешение является результатом.
Физический слой обеспечивает enforcement.
41.20 Главный вывод
Межсервисный и B2B-доступ показывают, что субъектом действия не обязательно является человек.
Им может быть:
User
Service
Organization
или пользователь, действующий через сервис.
Но изменение субъекта не меняет саму архитектурную задачу.
По-прежнему нужно определить:
кто действует
какое действие выполняется
над каким объектом
в каком контексте
и:
какие отношения являются основанием доступа
А затем:
Source facts
↓
Context
↓
Effective permission
↓
Authorization
↓
Data enforcement
В распределённой системе появляется ещё одна важная граница: доверие между участниками не должно автоматически превращаться в право на все данные.
Аутентифицированный сервис — ещё не всесильный сервис.
Партнёрская организация — ещё не владелец всех данных другой организации.
Пользователь, переданный через доверенный сервис, — ещё не пользователь с неограниченными правами.
Каждая граница должна иметь собственную семантику.
Именно поэтому межсервисный и B2B-доступ не требуют другого принципа.
Они показывают, насколько далеко этот принцип может быть распространён:
Object
↓
Relationships
↓
Context
↓
Access
Отдельные системы различаются не самим принципом, а тем, какие объекты, субъекты и отношения существуют в их предметной области.
На этом заканчивается часть, в которой мы проверяли модель на разных типах систем.
Следующая часть возвращает нас к исходной идее книги и собирает всё в одну цепочку: объект → отношения → контекст → доступ.
Глава 42. Объект определяет возможные отношения
В предыдущих главах мы рассматривали одну и ту же модель в разных системах.
В корпоративной системе объектом мог быть документ.
В PLM — изделие, деталь, спецификация или версия.
В RAG — документ или его фрагмент.
В SaaS — ресурс конкретной организации.
В B2B — объект, принадлежащий одной стороне взаимодействия или доступный нескольким сторонам.
Предметные области различаются.
Но во всех них остаётся одна общая закономерность:
модель доступа начинается не с набора разрешений, а с понимания того, какие отношения вообще могут существовать вокруг объекта.
Это важный переход.
Объект не определяет, кто получит доступ.
Но объект определяет, какие виды отношений с ним имеют смысл.
42.1 Нельзя определить доступ, не определив объект
Начнём с простого вопроса:
Что является предметом действия?
Пока на него нет ответа, невозможно точно определить разрешение.
Например:
READ
само по себе ничего не говорит.
READ чего?
READ Document
READ Project
READ User
READ Part
READ Invoice
Это разные действия над разными объектами.
Поэтому модель начинается с:
Subject
Action
Object
Но и этого недостаточно.
Нужно понять, какие отношения связывают этот объект с остальной моделью.
42.2 У объекта есть предметная семантика
Объект существует не только как идентификатор.
У него есть смысл в предметной области.
Например:
Document
может:
-
принадлежать проекту;
-
иметь владельца;
-
быть опубликованным;
-
иметь статус;
-
быть связанным с другой версией.
Для:
Project
могут существовать:
-
участники;
-
руководители;
-
документы;
-
задачи;
-
внешние организации.
Для:
Part
могут существовать:
-
состав изделия;
-
версии;
-
спецификации;
-
документы;
-
изменения.
Таким образом, тип объекта задаёт пространство возможных отношений.
42.3 Не каждое отношение можно применить к любому объекту
Предположим, в системе существует отношение:
member of
Для пользователя оно естественно:
User → member of → Project
Но:
Document → member of → Project
уже означает что-то другое.
А:
Part → member of → Project
может быть вообще недопустимой семантикой конкретной системы.
Для объектов могут использоваться другие отношения:
Document → belongs to → Project
Part → component of → Assembly
Revision → revision of → Part
User → member of → Project
Все эти связи имеют разные значения.
Поэтому нельзя строить универсальную модель отношений, в которой любая сущность может быть связана с любой другой сущностью одинаковым образом.
42.4 Тип объекта ограничивает пространство отношений
Можно представить объект как точку, вокруг которой существует определённое множество допустимых отношений.
Например:
Project
├── member
├── manager
├── document
├── task
└── external participant
Для документа:
Document
├── owner
├── project
├── publication
├── revision
└── related change
Для пользователя:
User
├── member of
├── role in
├── owner of
├── delegate of
└── authenticated by
Это не список разрешений.
Это список отношений, которые могут участвовать в построении контекста.
Именно поэтому объект является отправной точкой модели.
42.5 Отношение не возникает только потому, что его можно технически записать
В базе данных можно создать таблицу:
relation(subject_id, object_id)
Но техническая возможность записать пару идентификаторов ещё не означает существование предметного отношения.
Например:
Document → manages → User
можно представить в универсальной таблице.
Но если в предметной модели такого отношения нет, запись не имеет корректной семантики.
Следовательно, модель отношений должна исходить из предметной области, а не из удобства хранения.
Это особенно важно для безопасности.
Если система позволяет строить произвольные связи, она может случайно превратить техническую структуру данных в неявный механизм выдачи прав.
42.6 Объект не определяет право, но определяет возможные основания
Это различие принципиально.
Пусть:
Document D
и известно, что документ:
owned by User A
belongs to Project P
published to Group G
Из этого ещё не следует:
User B → READ → D
Но мы уже знаем, какие отношения могут участвовать в принятии решения.
Например:
ownership
project membership
group membership
publication
Дальше правила определят, какое значение имеет каждое из них.
Получается:
Object
↓
possible relationships
↓
relevant relationships
↓
access grounds
↓
effective permission
Объект задаёт пространство.
Правила выбирают значение.
42.7 Один объект может иметь несколько независимых отношений
Рассмотрим документ:
Document D
Он может одновременно быть:
owned by User A
belongs to Project P
published to Group G
related to Change C
Эти отношения не являются разными способами хранения одного и того же факта.
Они описывают разные стороны объекта.
Одно отношение может использоваться для определения владельца.
Другое — для видимости.
Третье — для участия в проекте.
Четвёртое — для жизненного цикла.
И только часть из них может быть релевантна конкретному действию.
Поэтому контекст не обязан включать все отношения объекта.
42.8 Релевантность отношения определяется действием
Для одного действия отношение может иметь значение.
Для другого — нет.
Например:
Document D
owned by User A
может быть важно для:
CLOSE
но не иметь значения для:
READ
если система разрешает чтение всем участникам проекта.
А отношение:
Document D → Project P
может быть ключевым для READ.
Получается:
RelevantRelations
=
f(Object, Action, Subject)
Это ещё одна причина, по которой контекст нельзя считать постоянным свойством объекта.
42.9 Объект может быть связан с другими объектами
В сложных системах отношения возникают не только между пользователем и объектом.
Например:
User
↓
Project
↓
Document
или:
User
↓
Company
↓
Project
↓
Product
↓
Part
Здесь сам объект является частью цепочки отношений.
Это означает, что доступ может зависеть от промежуточных объектов.
Но снова действует важное ограничение:
path ≠ permission
Путь становится основанием доступа только тогда, когда модель определяет его как допустимый путь.
42.10 Один объект может иметь разные security boundaries
Не всегда вся информация об объекте имеет одну границу доступа.
Например, документ может содержать:
metadata
content
attachments
comments
audit
Эти данные физически или логически могут иметь разные ограничения.
Тогда:
Document
остаётся одним логическим объектом, но вокруг него существует несколько security boundaries.
Поэтому нельзя автоматически считать:
access to object
=
access to every representation of object
Нужно определить, какие отношения и правила относятся к каждой части данных.
Это связывает модель объекта с тем, что мы раньше называли data enforcement.
42.11 Физическое представление не определяет предметную идентичность
Объект может быть представлен:
в одной таблице;
в нескольких таблицах;
в документном хранилище;
в поисковом индексе;
в кэше;
в нескольких сервисах.
Но физические представления не становятся разными объектами только потому, что они хранятся отдельно.
И наоборот, несколько физических строк могут представлять один логический объект.
Поэтому сначала определяется:
Object identity
а уже затем:
physical representation
Это важно и для безопасности.
Иначе можно случайно построить разные правила доступа для разных частей одного объекта, не понимая, что они относятся к одной предметной сущности.
42.12 Объект может изменять набор релевантных отношений
Жизненный цикл объекта может менять не его идентичность, а значение окружающих отношений.
Например:
Document D
Draft
затем:
Document D
Review
затем:
Document D
Released
Сам объект остаётся тем же.
Но для него могут измениться:
-
допустимые действия;
-
роль согласующего;
-
область публикации;
-
возможность изменения;
-
возможность удаления;
-
доступность внешним пользователям.
То есть состояние объекта может менять контекст доступа.
42.13 Изменение одного отношения может менять доступ к объекту
Пусть:
User A
↓
member of
↓
Project P
и:
Document D
↓
belongs to
↓
Project P
Если пользователь покидает проект:
User A
↓
removed from
↓
Project P
Документ не изменился.
Но результат:
Can(User A, READ, Document D)
может измениться.
Это ещё раз показывает, почему доступ нельзя хранить только внутри пользователя или объекта.
Он зависит от отношения между ними и другими объектами.
42.14 Изменение самого объекта тоже может менять доступ
Обратная ситуация:
User A
↓
member of
↓
Project P
остаётся неизменной.
Но документ:
Document D
переносится из:
Project P
в:
Project Q
Тогда доступ пользователя может измениться без изменения его собственных отношений.
Изменился объектный факт.
Получается симметрия:
Subject relation changes
↓
Access may change
и:
Object relation changes
↓
Access may change
Поэтому модель доступа нельзя строить как одностороннюю функцию только от пользователя.
42.15 Некоторые отношения создают видимость, другие — возможность действия
Это различие мы уже встречали, но здесь оно становится частью общей модели объекта.
Например:
Publication
может определять, что объект становится видимым в определённой области.
А:
Role
может определять допустимые действия.
При этом:
Owner
может быть отдельным основанием.
То есть вокруг одного объекта могут существовать отношения разного назначения:
visibility
ownership
membership
responsibility
delegation
lifecycle
Нельзя объединять их в одну категорию только потому, что все они используются при принятии решения.
Их смысл должен сохраняться.
42.16 Объект определяет не только отношения, но и допустимые действия
Для разных объектов набор действий также может различаться.
Например:
Document
→ READ
→ UPDATE
→ APPROVE
→ PUBLISH
а:
User
→ READ
→ UPDATE
→ CLOSE
а:
Project
→ READ
→ UPDATE
→ ARCHIVE
Поэтому объект определяет не только возможные отношения, но и пространство осмысленных действий.
Это не означает, что сам объект содержит permissions.
Он задаёт предметную семантику, внутри которой permission имеет смысл.
42.17 Тип объекта и тип отношения образуют семантическое ограничение
Можно представить это как множество допустимых связей:
Relation(ObjectType A, RelationType, ObjectType B)
Например:
User
└── member of → Project
Document
└── belongs to → Project
Part
└── component of → Assembly
Такая модель полезна не только для предметной области.
Она ограничивает пространство возможных security facts.
Если отношение не имеет смысла для данного типа объекта, оно не должно становиться основанием доступа.
Таким образом, корректная модель доступа начинается ещё раньше, чем появляется permission.
Она начинается с корректной модели объектов и отношений.
42.18 Почему нельзя начинать проектирование с ACL
Если начать сразу с таблицы:
user_id
object_id
permission
можно быстро получить работающий механизм.
Но такая таблица ничего не объясняет о происхождении права.
Почему пользователь получил permission?
Потому что:
-
владелец;
-
участник проекта;
-
член группы;
-
представитель другой организации;
-
делегат;
-
согласующий?
Если источник отношения не представлен отдельно, его невозможно корректно изменить без изменения самой ACL.
Поэтому сначала нужно определить:
Objects
↓
Relationships
↓
Rules
↓
Permissions
а не наоборот.
42.19 Объект является точкой входа, но не центром всей модели
Можно легко впасть в другую крайность и считать, что вся модель строится вокруг объекта.
Но объект — только один из элементов:
Subject
Action
Object
Relationships
Context
Rules
Объект определяет:
-
свою идентичность;
-
предметную семантику;
-
возможные отношения;
-
допустимые действия;
-
связи с другими объектами.
Но субъект определяет, кто выполняет действие.
Отношения определяют, как субъект связан с объектом.
Контекст определяет, какие из этих связей релевантны сейчас.
Правила определяют, какое значение имеют эти связи.
И только после этого появляется эффективное разрешение.
42.20 Главный вывод
Мы начали книгу с вопроса:
почему недостаточно знать роль пользователя?
Теперь можно сформулировать следующий уровень ответа.
Проблема не только в том, что у пользователя может быть несколько ролей.
Проблема в том, что вокруг объекта существует множество отношений, и разные объекты допускают разные отношения.
Поэтому модель начинается с предметной сущности:
Object
Затем определяются:
какие отношения вокруг него возможны
затем:
какие отношения релевантны конкретному субъекту и действию
затем:
какие правила применяются
и только после этого:
какое эффективное разрешение возникает
Получается цепочка:
Object
↓
Possible relationships
↓
Relevant relationships
↓
Context
↓
Rules
↓
Effective permission
↓
Access decision
Объект поэтому не является разрешением.
Но именно объект задаёт пространство предметных отношений, внутри которого вообще может быть построено решение о доступе.
Это возвращает нас к главному принципу книги:
прежде чем спрашивать, кому разрешён доступ, нужно понять, что за объект существует и какие отношения с ним имеют смысл.
Следующая глава сделает следующий шаг: если объект определяет возможные отношения, то именно отношения начинают формировать контекст, в котором принимается решение о доступе.
Глава 43. Отношения формируют контекст
В предыдущей главе мы пришли к тому, что объект определяет пространство возможных отношений.
Но сами по себе эти отношения ещё не дают ответа на вопрос о доступе.
Пусть известно:
User A
↓
member of
↓
Project P
Document D
↓
belongs to
↓
Project P
Мы уже знаем больше, чем в простой модели User → Role → Permission.
Но всё ещё не знаем, может ли User A выполнить над Document D конкретное действие.
Для этого нужно определить, какие из известных отношений имеют значение в данном запросе.
Именно здесь появляется контекст.
Контекст формируется из тех отношений и фактов, которые связывают конкретного субъекта, действие и объект и имеют значение для принятия решения.
Поэтому отношения являются одним из основных строительных материалов контекста.
43.1 Отношение связывает объекты, контекст связывает факты с решением
Само отношение отвечает на вопрос:
Кто с чем связан?
Например:
User A → member of → Project P
или:
Document D → belongs to → Project P
Но решение о доступе задаёт другой вопрос:
Can(User A, READ, Document D)?
Чтобы перейти от первого вопроса ко второму, нужно соединить несколько отношений.
User A
↓
member of
↓
Project P
↑
belongs to
↑
Document D
Теперь появился путь, который может быть значимым для доступа.
Но даже этот путь ещё не является разрешением.
Он становится частью контекста, который затем интерпретируется правилами.
Получается:
Relations
↓
Relevant relations
↓
Context
↓
Rules
↓
Effective permission
43.2 Контекст не равен одному отношению
Для простого объекта контекст иногда действительно может состоять почти из одного отношения.
Например:
User A → owner of → Document D
Если правило системы говорит, что владелец может читать документ, этого отношения достаточно как основания.
Но в более сложном случае может потребоваться несколько фактов:
User A → member of → Project P
Document D → belongs to → Project P
Document D → state = Released
Action = APPROVE
Здесь решение зависит уже от совокупности фактов.
Поэтому:
Context ≠ Relation
Отношение является элементом контекста.
Контекст — это совокупность релевантных отношений и других фактов, необходимых для применения правил.
43.3 В контекст попадают не все отношения объекта
У объекта может быть десятки отношений.
Но конкретному запросу может быть нужен только небольшой набор.
Допустим, документ имеет:
owner
project
publication
revision
change
audit
attachments
Для проверки:
READ
может быть достаточно:
project
publication
owner
Для:
APPROVE
могут потребоваться:
project
role
lifecycle state
delegation
Для:
UPDATE
может иметь значение ещё другой набор.
Следовательно, контекст нельзя строить как полный снимок объекта.
Он определяется вопросом, который система сейчас задаёт.
Context = f(Subject, Action, Object, relevant facts)
43.4 Контекст связывает несколько независимых отношений
Одна из причин сложности модели доступа заключается в том, что необходимые факты часто находятся в разных частях предметной модели.
Например:
User A
↓
member of
↓
Project P
и:
Document D
↓
belongs to
↓
Project P
и:
User A
↓
has role
↓
Reviewer
и:
Document D
↓
state
↓
Review
Ни один из этих фактов сам по себе не отвечает на вопрос:
Can(User A, APPROVE, Document D)?
Но вместе они могут сформировать необходимый контекст.
Например, правило может требовать:
User is Reviewer
AND
User participates in Document's Project
AND
Document is in Review state
Тогда контекст содержит несколько независимых измерений.
43.5 Отношения могут соединяться через промежуточные объекты
Контекст часто формируется не прямой связью:
User → Object
а через несколько объектов:
User
↓
Company
↓
Project
↓
Document
или:
User
↓
Group
↓
Project
↓
Document
Каждое звено является отдельным отношением.
Но для конкретного решения важен не весь граф, а только тот путь или набор путей, который определён правилами как значимый.
Поэтому можно представить:
Subject
↓
Relation
↓
Intermediate object
↓
Relation
↓
Object
как часть контекста.
Это особенно важно в системах, где доступ определяется организационной структурой, проектами, делегированием или связями между объектами.
43.6 Путь отношений не становится правилом автоматически
Наличие пути ещё ничего не говорит о его допустимости.
Предположим:
User A
↓
member of
↓
Group G
↓
related to
↓
Project P
↓
contains
↓
Document D
Технически такой путь существует.
Но можно ли на его основании читать документ?
Это уже вопрос правил.
Одна система может считать такой путь достаточным.
Другая — потребовать ещё публикацию.
Третья — учитывать группу только для части объектов.
Четвёртая — вообще не использовать эту связь для авторизации.
Поэтому:
Path ≠ Access
Путь становится частью контекста только в той мере, в которой его семантика определена моделью доступа.
43.7 Одни отношения дают основание, другие ограничивают его применение
Не все отношения выполняют одинаковую функцию.
Например:
User A → member of → Project P
может быть основанием доступа.
А:
Document D → state = Draft
может ограничивать допустимые действия.
В другом случае:
Document D → published to → Group G
может определять область видимости.
А:
User A → delegated by → User B
может давать дополнительное основание.
Таким образом, контекст может содержать отношения разного назначения:
Основание
Область
Ограничение
Состояние
Делегирование
Связь объектов
Их нельзя смешивать только потому, что все они участвуют в одном решении.
43.8 Область становится частью контекста через отношение
Ранее мы различили контекст и область действия.
Теперь это различие можно уточнить.
Если существует:
User A
↓
Role R
↓
Project P
то Project P может быть областью действия отношения пользователя и роли.
Но это не означает, что:
Project P = Context
Контекст может одновременно включать:
User A
Role R
Project P
Document D
Publication
Document state
Requested action
Поэтому область — только одно из измерений контекста.
Она появляется внутри контекста потому, что определённое отношение действует в некоторой области.
43.9 Контекст зависит от субъекта
Рассмотрим один объект:
Document D
Для:
User A
контекст может содержать:
member of Project P
role Reviewer
Для:
User B
тот же объект может иметь другой контекст:
owner of Document D
Для:
User C
может существовать:
published to Group G
Объект один.
Но контексты различаются.
Поэтому контекст нельзя хранить как постоянное свойство объекта.
43.10 Контекст зависит от действия
Даже для одного субъекта и одного объекта контекст может различаться.
Например:
User A
Document D
Для READ релевантны:
project membership
publication
ownership
Для APPROVE могут добавиться:
reviewer role
lifecycle state
delegation
Для UPDATE может использоваться:
ownership
editor role
document state
То есть:
Context(User A, READ, D)
и:
Context(User A, APPROVE, D)
могут быть разными.
Это фундаментальное свойство модели.
43.11 Контекст зависит от объекта
Обратная ситуация также возможна.
Пусть один пользователь имеет:
Role = Reviewer
Для одного документа эта роль может быть релевантной.
Для другого — нет.
Например:
Document D1 → Project P1
Document D2 → Project P2
а пользователь является reviewer только в P1.
Тогда:
Context(User A, APPROVE, D1)
и:
Context(User A, APPROVE, D2)
будут различаться.
Следовательно, контекст определяется не только субъектом.
Он является результатом отношения:
Subject × Action × Object
к которому добавляются релевантные факты.
43.12 Контекст может содержать отношения между субъектами
Иногда путь к объекту проходит не через область или проект, а через другого субъекта.
Например:
User A
↓
delegated by
↓
User B
и:
User B
↓
owner of
↓
Document D
Если модель допускает делегирование, эта цепочка может стать частью контекста.
Но снова не следует делать вывод:
User A can access D
только из наличия такой цепочки.
Нужно знать:
-
разрешено ли делегирование;
-
какие действия делегируются;
-
в какой области;
-
на какой срок;
-
можно ли передавать право дальше.
Контекст содержит отношения.
Правила определяют их значение.
43.13 Временные отношения тоже могут входить в контекст
Некоторые отношения действуют не постоянно.
Например:
User A
↓
delegated access
↓
Document D
может быть действителен:
с 1 сентября
по 15 сентября
Тогда время становится частью контекста.
В один момент:
Can(User A, READ, D) = true
а в другой:
Can(User A, READ, D) = false
при неизменных объекте и субъекте.
Это ещё раз показывает, почему контекст нельзя представить только как структуру:
User → Scope → Permission
В него могут входить дополнительные измерения.
43.14 Контекст может содержать состояние объекта
Состояние также может быть релевантным отношением или фактом.
Например:
Document D
state = Draft
может означать:
UPDATE allowed
APPROVE forbidden
После изменения:
Document D
state = Review
правила могут измениться.
При этом пользовательские отношения не изменились.
Изменился объектный факт.
Поэтому контекст строится не только из отношений между субъектами.
В него входят любые факты, которые правила используют для данного решения.
43.15 Один и тот же факт может участвовать в разных контекстах
Допустим:
User A → member of → Project P
Этот факт может использоваться при проверке:
READ Document
UPDATE Document
APPROVE Change
Но его значение в каждом случае может быть разным.
Для READ членство может быть достаточным основанием.
Для UPDATE может потребоваться дополнительная роль.
Для APPROVE — ещё и соответствующее состояние объекта.
Таким образом, факт не имеет одного заранее определённого значения для всех решений.
Его значение определяется правилами конкретного запроса.
43.16 Несколько отношений могут образовывать одно основание
Иногда основанием доступа является не отдельное отношение, а их комбинация.
Например:
User A → member of → Project P
User A → has role → Reviewer
Document D → belongs to → Project P
Document D → state = Review
Правило может выглядеть концептуально так:
member(Project)
AND
role(Reviewer)
AND
state(Review)
Здесь ни один факт не даёт право самостоятельно.
Право появляется из их совместного применения.
Это показывает, почему полезно отделять:
facts
от:
rules
Факты формируют контекст.
Правило определяет, как из этого контекста получить результат.
43.17 Контекст не должен превращаться в огромный снимок системы
Есть опасная архитектурная крайность.
Если контекст зависит от множества фактов, может возникнуть желание включить в него вообще всё:
User
all roles
all groups
all projects
all objects
all publications
all states
all delegations
...
Но это уже не контекст конкретного решения.
Это почти снимок всей системы.
Такой подход делает модель тяжёлой и плохо объяснимой.
Правильный вопрос другой:
Какие факты действительно нужны для применения правил к этому субъекту, действию и объекту?
Только они и должны становиться частью релевантного контекста.
43.18 Контекст может быть вычислен, но не обязан храниться как объект
Контекст — концептуальная модель.
Это не означает, что в базе обязательно должна существовать таблица:
context
Контекст может быть:
-
вычислен во время запроса;
-
собран из нескольких проекций;
-
частично материализован;
-
представлен набором производных фактов;
-
сформирован несколькими слоями системы.
Важно не физическое представление, а семантика.
Если система хранит:
effective_user_permission
object_scope
effective_user_group
это не означает, что одна из этих таблиц является «контекстом».
Они могут представлять отдельные части модели, из которых для конкретного решения собирается необходимая информация.
43.19 Проекции ускоряют работу с отношениями, но не меняют их смысл
Когда отношения становятся сложными, повторно проходить весь граф при каждом запросе дорого.
Поэтому можно заранее получить производные факты:
User A
↓
effective relation
↓
Project P
или:
User A
↓
effective permission
↓
Project P
Но производный факт не становится новым предметным отношением только потому, что он сохранён.
Он остаётся результатом вычисления.
Источник истины:
source facts
а проекция:
derived representation
Это различие особенно важно для безопасности.
Если исходное отношение изменилось, производные данные должны быть пересчитаны.
43.20 Изменение отношения означает изменение контекста
Предположим:
User A → member of → Project P
было истинно.
Затем отношение удалено.
Сам документ:
Document D
не изменился.
Но для пользователя изменился контекст:
Context(User A, READ, D)
Следовательно, измениться может и эффективное разрешение.
Это означает, что контекст не является статическим объектом.
Он является результатом текущего состояния релевантных отношений.
43.21 Отношения образуют граф, но решение не обязано обходить весь граф
В сложной системе можно представить все отношения как граф:
Subject
│
├── Group
│ └── Project
│
├── Company
│ └── Project
│
└── Delegation
└── Subject
Объекты также связаны между собой:
Project
├── Document
├── Task
└── Product
Но конкретный запрос использует только часть этого графа.
Поэтому задача модели безопасности не в том, чтобы каждый раз обходить весь граф.
Она должна уметь определить:
какие связи релевантны этому решению
и подготовить их в форме, пригодной для проверки.
Именно здесь возникает связь между предметной моделью и проекционным слоем.
43.22 Контекст — это не ещё один уровень разрешений
Важно не создать новую путаницу.
Мы уже различаем:
Relation
Context
Permission
Decision
Они находятся на разных уровнях.
Relation:
User A → member of → Project P
Context:
User A
Project P
Document D
READ
relevant membership
relevant publication
relevant object state
Effective permission:
READ
Decision:
ALLOW
Нельзя заменить эту цепочку одной сущностью.
Если контекст начинает хранить готовые permissions, он перестаёт быть контекстом и превращается в ещё один слой разрешений.
43.23 Контекст является связующим слоем
Теперь можно увидеть роль контекста во всей модели.
С одной стороны находятся предметные факты:
Objects
Relationships
States
Areas
Publications
Roles
Delegations
С другой:
Rules
Effective permissions
Access decisions
Контекст соединяет их:
Source facts
↓
Relevant relationships and facts
↓
Context
↓
Rules
↓
Effective permission
↓
Decision
Он не создаёт отношения.
Не создаёт permissions.
Не заменяет правила.
Он определяет, какая часть состояния системы имеет значение для данного решения.
43.24 Главный вывод
Мы начали эту часть книги с объекта.
Объект определяет пространство возможных отношений.
Теперь следующий шаг становится очевидным:
отношения определяют, какие факты связывают субъект, действие и объект в конкретной ситуации.
Но сами отношения ещё не являются контекстом.
Контекст появляется тогда, когда из множества существующих фактов выбирается их релевантная для конкретного решения часть.
Получается цепочка:
Object
↓
Possible relationships
↓
Actual relationships
↓
Relevant facts
↓
Context
↓
Rules
↓
Effective permission
↓
Access decision
При этом:
Relation ≠ Permission
Context ≠ Permission
Context ≠ Scope
Effective permission ≠ Decision
Это разделение позволяет сохранить смысл каждого уровня.
Объект говорит, что существует.
Отношения говорят, как объекты связаны.
Контекст говорит, какие из этих связей имеют значение сейчас.
Правила говорят, как их интерпретировать.
Эффективное разрешение показывает, какое право получилось.
Решение отвечает на конкретный вопрос:
Can(Subject, Action, Object)?
Именно поэтому контекст нельзя добавить в архитектуру как ещё одно поле или ещё одну таблицу.
Он возникает из самой структуры отношений.
Контекст — это не контейнер для отношений. Это релевантная структура отношений и фактов, рассмотренная с точки зрения конкретного решения о доступе.
Следующая глава завершит логическую цепочку: если контекст сформирован, именно он становится входом для определения того, какое действие над объектом действительно допустимо.
Глава 44. Контекст определяет доступ
В предыдущих главах мы последовательно прошли несколько уровней.
Сначала появился объект.
Затем отношения вокруг объекта.
Затем из множества отношений и других фактов мы выделили те, которые имеют значение для конкретного запроса.
Так появился контекст.
Но теперь возникает главный вопрос:
Что происходит с контекстом дальше?
Сам по себе контекст ещё ничего не разрешает.
Он только содержит информацию, необходимую для применения правил.
Поэтому следующая часть цепочки выглядит так:
Объект
↓
Отношения
↓
Релевантные факты
↓
Контекст
↓
Правила
↓
Эффективное разрешение
↓
Решение о доступе
Именно здесь становится окончательно понятно, зачем вообще нужен контекст.
Он связывает предметную модель с решением о допустимости действия.
44.1 Контекст не принимает решение сам
Предположим, у нас есть:
User A
Document D
Action = READ
И контекст сообщает:
User A
→ member of
→ Project P
Document D
→ belongs to
→ Project P
Можно ли на этом основании разрешить чтение?
Контекст сам этого не знает.
Он содержит факты.
Правило может сказать:
если пользователь является участником проекта,
к которому относится документ,
то участник может читать документ.
Тогда возникает:
Context
↓
Rule
↓
READ
Но другое правило могло бы сказать:
участник проекта может изменять документ
только если ему назначена роль Editor.
Тогда тот же контекст дал бы другой результат.
Следовательно:
контекст не определяет разрешение без правил.
Он предоставляет входные данные для правил.
44.2 Одни и те же отношения могут давать разные права
Это важное свойство.
Пусть:
User A → member of → Project P
Document D → belongs to → Project P
Эти факты не меняются.
Но действия различаются:
READ
UPDATE
APPROVE
CLOSE
Правила могут определить:
READ → разрешено
UPDATE → разрешено
APPROVE → запрещено
CLOSE → запрещено
Поэтому нельзя считать, что наличие отношения означает наличие одного универсального права.
Отношение даёт основание.
Правило определяет, для каких действий это основание действительно.
Получается:
Relation
+
Rule
+
Action
↓
Effective permission
44.3 Одно отношение может участвовать в нескольких правилах
Например:
User A → owner of → Document D
Это отношение может использоваться для разных действий.
Одно правило может разрешать владельцу:
READ
другое:
UPDATE
третье:
CLOSE
Но это не означает, что отношение owner само содержит эти разрешения.
Оно остаётся одним фактом:
Owner(User A, Document D)
А набор разрешений возникает из правил.
Это позволяет изменить модель без изменения самого предметного факта.
Например, если бизнес-правило изменилось и владелец больше не может самостоятельно закрывать объект, отношение владельца остаётся тем же.
Меняется правило.
44.4 Контекст может приводить к разным результатам
Один и тот же объект может находиться в разных контекстах для разных субъектов.
Например:
Document D
Для пользователя A:
owner
Для пользователя B:
project member
Для пользователя C:
published to group
Для пользователя D:
delegated access
Поэтому вопрос:
Какие права имеет Document D?
не является полным.
Нужно спрашивать:
Какие права имеет Subject S
над Object O
для Action A
в Context C?
Именно контекст делает это решение конкретным.
44.5 Контекст может ограничивать уже найденное основание
Не все правила работают по принципу:
если факт существует → разрешить.
Иногда отношение является необходимым, но недостаточным условием.
Например:
User A → member of → Project P
может быть необходимо для чтения.
Но документ:
Document D → state = Confidential
может требовать дополнительного основания.
Тогда правило имеет форму:
Project membership
AND
Confidential access role
В другом случае:
Project membership
AND
Document state = Released
Таким образом, контекст может содержать одновременно:
-
основания доступа;
-
ограничения;
-
состояние объекта;
-
область действия;
-
дополнительные условия.
Правило определяет, как они комбинируются.
44.6 Контекст не обязан содержать готовый ответ
Это принципиально для архитектуры.
Можно представить контекст:
Subject = A
Action = APPROVE
Object = D
Relations:
member(Project P)
role(Reviewer)
Object state:
Review
Publication:
Project P
Это ещё не:
ALLOW
и не:
DENY
Это вход модели.
После применения правил может получиться:
Effective permission = APPROVE
а затем:
ALLOW
Или при другом состоянии объекта:
Effective permission = none
и:
DENY
Так сохраняется различие между фактами и выводами.
44.7 Эффективное разрешение — промежуточный результат
В предыдущих главах мы уже различили назначенное и эффективное разрешение.
Теперь видно, где появляется второе.
Например, исходные факты:
User A → role Reviewer → Project P
User A → member of Project P
Document D → belongs to Project P
могут привести к:
Effective permission:
APPROVE
Но это ещё не окончательное решение.
Нужно проверить конкретное действие:
Requested action = APPROVE
Если оно входит в эффективное разрешение:
APPROVE ∈ EffectivePermissions
получаем:
ALLOW
Если нет:
APPROVE ∉ EffectivePermissions
получаем:
DENY
Поэтому цепочка остаётся:
Facts
↓
Context
↓
Rules
↓
Effective permission
↓
Decision
44.8 Почему полезно отделять разрешение от решения
На первый взгляд можно было бы сразу вычислять:
ALLOW / DENY
Но промежуточный слой имеет важное значение.
Он позволяет понять:
почему право возникло;
и:
какие права вообще действуют в данном контексте.
Например:
User A
Project P
Role Reviewer
Effective permissions:
READ
APPROVE
Тогда проверка:
READ
и:
APPROVE
использует один и тот же подготовленный результат.
Но:
CLOSE
может отсутствовать.
Такой подход особенно полезен, когда один субъект выполняет много действий над одним объектом.
44.9 Несколько оснований могут привести к одному разрешению
Предположим:
User A → owner of → Document D
и одновременно:
User A → member of → Project P
Document D → belongs to → Project P
Оба пути могут давать:
READ
Тогда эффективный результат всё равно:
READ
Не нужно создавать два разных READ только потому, что у него две причины.
Причины остаются частью модели.
Результат может быть одним.
Это позволяет отделить:
why
от:
what
То есть:
Основания → почему право возможно
Permission → какое действие разрешено
Decision → разрешено ли конкретное действие
44.10 Разные основания могут давать разные права
В другом случае:
owner
может давать:
READ
UPDATE
CLOSE
а:
project membership
только:
READ
Тогда объединение результатов зависит от правил модели.
Получаем:
Owner
└── READ, UPDATE, CLOSE
Project member
└── READ
И эффективный результат может быть:
READ, UPDATE, CLOSE
Но это не универсальный закон.
В другой модели одно отношение может ограничивать другое.
Например:
Project member → UPDATE
но:
Document state = Locked
может запрещать UPDATE.
Поэтому нельзя заранее предполагать, что все права просто складываются.
Способ объединения и ограничения определяется правилами.
44.11 Контекст определяет применимость правила
Правило существует не в вакууме.
Например:
Reviewer may approve a document
нельзя применить только потому, что пользователь где-то является Reviewer.
Нужно определить:
Reviewer where?
Reviewer for what?
Reviewer over which object?
Reviewer for which action?
Именно здесь появляется область отношения.
Например:
User A
→ Reviewer
→ Project P
может применяться к:
Document D
→ belongs to Project P
но не к:
Document E
→ belongs to Project Q
Таким образом, контекст определяет не только наличие отношения, но и его применимость к конкретному объекту.
44.12 Правила могут требовать пересечения нескольких условий
Рассмотрим:
User A
→ Reviewer
→ Project P
и:
Document D
→ Project P
→ state = Review
Правило может требовать:
Reviewer
AND
same Project
AND
state = Review
Только после выполнения всех условий возникает:
APPROVE
Здесь хорошо видно различие между объединением и пересечением.
Разные независимые основания могут дать дополнительные права.
Но отдельное правило может требовать одновременного выполнения нескольких условий.
Поэтому модель доступа нельзя свести к одной операции над наборами permission.
Сначала нужно сохранить семантику отношений.
44.13 Ограничение может действовать после получения основания
Рассмотрим:
User A → owner of → Document D
и:
Document D → state = Archived
Правило может говорить:
owner → UPDATE
но другое правило:
Archived → UPDATE forbidden
Тогда одного основания недостаточно.
Важно, что это не означает:
owner relation was false
Отношение остаётся истинным.
Просто в текущем контексте его действие ограничено состоянием объекта.
Это ещё одна причина не смешивать:
fact
relation
permission
constraint
decision
44.14 Запрет и отсутствие разрешения — разные вещи
Если пользователь не может выполнить действие, это может означать две разные ситуации.
Первая:
Permission отсутствует.
Вторая:
Permission существует,
но конкретное условие запрещает действие.
Например:
User A → Reviewer
даёт:
APPROVE
но:
Document D → state = Archived
делает APPROVE недопустимым.
Это отличается от ситуации, когда пользователь вообще не является Reviewer.
Поэтому модель должна явно определять семантику ограничений и запретов.
Не существует универсального правила, согласно которому любой DENY автоматически сильнее любого ALLOW.
Это часть конкретной модели доступа.
44.15 Контекст может быть различным при одинаковом результате
Интересна и обратная ситуация.
Для пользователя A:
owner
Для пользователя B:
project member
Для пользователя C:
publication
Все трое могут получить:
READ
Результат одинаков.
Но контексты различны.
Это означает, что:
same permission
≠
same reason
Именно поэтому полезно сохранять отношения и основания отдельно от эффективного разрешения.
Если сохранить только:
User A → READ → Document D
то исчезает информация о том, почему это право возникло.
44.16 Изменение контекста меняет результат без изменения permission-модели
Предположим, правила не менялись.
Роль не менялась.
Но документ перешёл:
Draft → Released
или пользователь:
removed from Project P
Контекст изменился.
Значит, измениться может и эффективное разрешение.
Это показывает важную особенность модели:
правила могут оставаться неизменными, пока результат доступа меняется вслед за изменением исходных фактов.
Именно поэтому security projections должны обновляться при изменении отношений и состояния, если они используются для вычисления эффективных разрешений.
44.17 Изменение правил — другой тип изменения
Теперь рассмотрим другой случай.
Исходные факты те же:
User A → Reviewer
Document D → Review
Но правило изменилось.
Было:
Reviewer may APPROVE.
Стало:
Reviewer may APPROVE
only if assigned as lead reviewer.
Теперь исходные факты могут оказаться недостаточными.
Изменился не контекст.
Изменились правила его интерпретации.
Поэтому модель безопасности должна различать:
изменение фактов
и:
изменение правил.
Оба события могут изменить эффективные разрешения, но механизм их распространения может быть различным.
44.18 Эффективное разрешение является производным фактом
Теперь можно точно определить его место.
Исходными фактами могут быть:
User A → member of Project P
User A → Reviewer in Project P
Document D → belongs to Project P
Document D → state = Review
Производным фактом становится:
User A
→ effective permission APPROVE
→ in context of Project P
Он получен из других данных.
Поэтому его можно:
-
вычислять;
-
материализовать;
-
обновлять;
-
пересчитывать;
-
проверять относительно исходных фактов.
Но он не должен становиться независимым источником истины.
44.19 Правило превращает контекст в эффективное разрешение
Теперь можно записать основную функцию:
EffectivePermission =
Rules(Context(Subject, Action, Object))
В более общем виде:
Context =
RelevantFacts(Subject, Action, Object)
EffectivePermission =
Rules(Context)
А затем:
Decision =
Check(EffectivePermission, Action)
Это не означает, что реальная система обязана реализовывать именно такие функции буквально.
Это концептуальная модель.
Она показывает разделение ответственности:
RelevantFacts
определяет, что важно.
Context
собирает это состояние.
Rules
интерпретируют его.
EffectivePermission
является результатом.
Decision
отвечает на конкретный запрос.
44.20 В реальной системе контекст и разрешение могут быть распределены по слоям
В физической реализации эти элементы необязательно находятся рядом.
Например:
Источник фактов
↓
Проекции
↓
Эффективные отношения
↓
Эффективные разрешения
↓
Проверка объекта
↓
Ограничение физических данных
При этом никакая одна таблица не обязана называться Context.
Контекст существует как смысловая конструкция, собранная из нескольких представлений модели.
Это позволяет разделить предметную модель и техническую реализацию.
44.21 Доступ определяется контекстом, но не исчерпывается им
Здесь важно сделать ещё одно различие.
Даже если:
Can(User A, READ, Document D)
получает значение true, это ещё не означает, что пользователь получит любые физические данные, связанные с документом.
Ранее мы уже различили:
Object authorization
и:
Data enforcement
Контекст участвует в авторизации объекта.
Но физические данные могут иметь дополнительные границы.
Поэтому цепочка продолжается:
Context
↓
Effective permission
↓
Object authorization
↓
Data enforcement
↓
Physical data
Контекст не отменяет остальные уровни безопасности.
44.22 Решение о доступе всегда относится к конкретному действию
В конечном счёте система отвечает не на вопрос:
Does User A have access to Document D?
Это слишком расплывчатый вопрос.
Нужно спросить:
Can(User A, READ, Document D)?
или:
Can(User A, UPDATE, Document D)?
или:
Can(User A, APPROVE, Document D)?
Один и тот же контекст может привести к разным результатам для разных действий.
Поэтому универсальное свойство:
User A has access to Document D
часто скрывает слишком много информации.
Более точная модель:
Subject
+
Action
+
Object
+
Context
→
Decision
44.23 Доступ становится функцией контекста
Теперь всю модель можно выразить компактно:
Can(S, A, O) =
Rules(Context(S, A, O))
Где:
Context(S, A, O)
строится из релевантных фактов и отношений.
Получается:
Facts
↓
Context(S, A, O)
↓
Rules
↓
Can(S, A, O)
Это и есть центральный переход от старой модели:
User → Role → Permission
к более общей:
Subject
+
Action
+
Object
+
Context
↓
Decision
Роль и permission никуда не исчезают.
Они становятся частью более широкой модели.
44.24 Контекст не усложняет модель ради самой сложности
На этом этапе может показаться, что мы просто добавили ещё один термин.
Но контекст нужен не для усложнения.
Он позволяет не смешивать разные вопросы.
Без него легко сказать:
User A has READ.
Но непонятно:
READ чего?
Где?
На каком основании?
В каком состоянии?
На какой срок?
Через какое отношение?
При каких условиях?
Контекст собирает именно те ответы, которые нужны для конкретного решения.
Он не добавляет произвольную сложность.
Он делает уже существующую сложность явной.
44.25 Главный вывод
Теперь основная цепочка книги практически замкнулась.
Мы начали с объекта:
Object
У объекта есть предметная семантика и возможные отношения:
Object
↓
Possible relationships
Фактические отношения соединяют объект с субъектами и другими объектами:
Actual relationships
Из них для конкретного запроса выбираются релевантные факты:
Relevant facts
Они формируют контекст:
Context
Правила интерпретируют контекст:
Context
↓
Rules
и дают эффективное разрешение:
Effective permission
После чего проверяется конкретное действие:
Can(Subject, Action, Object)?
И только после этого результат должен быть физически обеспечен на уровне данных.
Полная цепочка:
Object
↓
Relationships
↓
Relevant facts
↓
Context
↓
Rules
↓
Effective permission
↓
Access decision
↓
Data enforcement
Каждый уровень отвечает на свой вопрос.
Объект — что является предметом действия?
Отношение — как этот объект связан с другими сущностями?
Факт — что сейчас известно о системе?
Контекст — какие из этих фактов имеют значение для данного решения?
Правило — как интерпретировать эти факты?
Эффективное разрешение — какое право получилось?
Решение — разрешено ли конкретное действие?
Data enforcement — какие физические данные действительно могут быть затронуты?
Главный принцип можно сформулировать коротко:
доступ определяется не свойством пользователя и не свойством объекта. Он является результатом применения правил к контексту конкретного действия над конкретным объектом.
И именно поэтому контекст становится не дополнительным атрибутом системы безопасности, а связующим звеном между предметной моделью и механизмом доступа.
В следующей, последней главе книги останется сделать последний шаг: показать, что после этого доступ перестаёт быть внешней функцией над данными и становится частью самой архитектуры данных.
Глава 45. Доступ становится частью архитектуры данных
Мы начали эту книгу с простой модели:
User → Role → Permission
Она полезна.
Но по мере усложнения системы оказалось, что одного пользователя, роли и разрешения недостаточно.
Появился объект.
Вокруг объекта — отношения.
Отношения связали объект с другими объектами и субъектами.
Из множества фактов пришлось выделить те, которые имеют значение для конкретного действия.
Так появился контекст.
Затем правила превратили контекст в эффективное разрешение.
И наконец, решение о доступе пришлось связать с физическими данными.
Получилась цепочка:
Object
↓
Relationships
↓
Relevant facts
↓
Context
↓
Rules
↓
Effective permission
↓
Access decision
↓
Data enforcement
На первый взгляд это просто более подробная модель авторизации.
Но следствие гораздо шире.
Если доступ зависит от структуры объектов, отношений и физических данных, то модель безопасности уже нельзя проектировать независимо от модели данных.
Безопасность становится частью архитектуры данных тогда, когда изменение предметных фактов способно изменить то, какие данные доступны субъекту.
Это не означает, что вся предметная модель превращается в модель безопасности.
Это означает другое: граница безопасности проходит через архитектуру данных, а не поверх неё.
45.1 Данные и доступ больше нельзя проектировать независимо
Представим систему, в которой сначала проектируется модель данных:
User
Project
Document
Group
А затем, после завершения разработки, добавляется безопасность:
Кто может читать Document?
На простом уровне это может сработать.
Но если доступ определяется отношениями:
User → member of → Project
Document → belongs to → Project
User → role in → Project
Document → published to → Group
то эти отношения уже являются частью самой структуры данных.
Нельзя сначала сделать вид, что их не существует, а затем построить безопасность поверх готовых таблиц.
Они имеют предметный смысл.
И этот смысл одновременно может иметь значение для доступа.
Поэтому проектирование постепенно меняет форму:
Data model
+
Security model
вместо:
Data model
→
Security as an afterthought
45.2 Но не каждый факт предметной модели становится security fact
Здесь важно не сделать обратную ошибку.
Если безопасность является частью архитектуры данных, это не означает, что каждый атрибут должен участвовать в авторизации.
Например, документ может иметь:
title
description
created_at
format
language
author
project
state
Из них для конкретного решения могут иметь значение только:
author
project
state
А format и language могут вообще не участвовать.
Следовательно:
Domain fact
не равно:
Security fact
Security fact — это факт, который используется моделью доступа.
Такое различие позволяет не превратить безопасность в копию всей предметной модели.
45.3 Безопасность начинается с правильной идентичности объекта
Одна из самых важных архитектурных границ проходит между логическим объектом и его физическим представлением.
Пусть существует:
Document D
Физически он может быть представлен:
documents
document_versions
document_files
document_comments
document_index
Но для предметной модели это один объект или одна связанная совокупность объектов.
Если модель безопасности говорит:
User A can READ Document D
это ещё не отвечает на вопрос:
Какие физические строки можно вернуть?
Поэтому между логической авторизацией и физическими данными появляется ещё одна связь:
Logical object
↓
Physical representation
Эта связь должна быть известна архитектуре.
Иначе система может корректно принять решение на уровне объекта и всё равно вернуть лишние данные.
45.4 Доступ должен проходить до физической границы данных
На уровне приложения можно получить:
ALLOW
Но запрос к базе данных может затронуть множество строк.
Особенно это важно для массовых операций:
SELECT ...
UPDATE ...
DELETE ...
Если проверка выполнена только один раз для некоторого объекта, это ещё не означает, что каждая физическая строка находится в разрешённой области.
Поэтому архитектура должна иметь физическую границу:
Authorization
↓
Data enforcement
↓
Physical rows
Одним из механизмов такой границы может быть Row-Level Security (RLS, безопасность на уровне строк).
Но RLS — не сама модель авторизации.
Его задача — физически ограничить данные в соответствии с уже определённой моделью безопасности.
45.5 Проекции становятся частью архитектуры данных
Если отношения сложные, проверять весь граф при каждом запросе становится дорого.
Поэтому модель может заранее строить производные представления:
Source facts
↓
Security projections
Например, отдельно можно представить:
effective relationships
effective permissions
object scopes
Эти данные не заменяют исходные факты.
Они существуют для того, чтобы быстро отвечать на вопросы безопасности.
Получается ещё один важный принцип:
архитектура данных должна учитывать не только исходные предметные факты, но и производные представления, необходимые для безопасного выполнения запросов.
Это уже не обычный кэш.
Если производное представление используется для авторизации, его корректность становится частью безопасности системы.
45.6 Изменение данных становится изменением модели безопасности
До этого момента можно было рассматривать изменение данных как обычную операцию:
INSERT
UPDATE
DELETE
Но теперь видно, что некоторые изменения имеют второе измерение.
Например:
User A joins Project P
изменяет не только членство пользователя.
Оно может изменить доступ к сотням документов.
А:
User A leaves Project P
может отозвать доступ к тем же документам.
Другой пример:
Document D
Project P → Project Q
не изменяет пользователя вообще.
Но меняет его контекст доступа к документу.
Поэтому некоторые изменения данных являются одновременно:
Domain change
+
Security model change
Это необходимо учитывать при проектировании транзакций, событий, проекций и восстановления.
45.7 Отзыв доступа становится таким же важным, как выдача
В простой модели можно думать в терминах:
дать право
Но в реальной системе не менее важна операция:
забрать право
Если пользователь покинул проект, недостаточно удалить членство.
Нужно обеспечить, чтобы производные данные перестали предоставлять ему доступ.
То есть изменение должно пройти путь:
Source fact changed
↓
Affected security facts
↓
Projection updated
↓
Authorization changed
↓
Data enforcement reflects change
Если один из этапов остался старым, система может находиться в переходном состоянии.
Поэтому согласованность security projections — это не только вопрос производительности.
Это вопрос корректности доступа.
45.8 Архитектура данных должна учитывать радиус изменения
Небольшая запись может иметь большой security impact.
Например:
User A → member of → Company C
может косвенно влиять на:
-
проекты;
-
группы;
-
документы;
-
задачи;
-
публикации;
-
разрешения;
-
поисковые результаты.
Количество изменяемых строк исходной модели невелико.
Но радиус изменения производных данных велик.
Поэтому архитектура должна знать не только:
что изменилось
но и:
что зависит от этого изменения
Это приводит к графу зависимостей:
Source fact
↓
Derived fact
↓
Derived fact
↓
Authorization
↓
Data
Именно этот граф определяет, насколько локальным или широким является изменение.
45.9 Изменение правил тоже является изменением архитектуры безопасности
Есть ещё один тип изменения.
Можно не менять ни пользователя, ни объект, ни отношения.
Можно изменить правило.
Например, раньше:
Project member → READ
а затем:
Project member → READ
only for published documents
Исходные данные не изменились.
Но изменился результат для множества объектов.
Следовательно, правила безопасности также имеют зависимости от производных данных.
При существенном изменении правил может потребоваться:
rebuild
а не только обновление отдельных записей.
Это означает, что версия и семантика правил должны рассматриваться как часть состояния модели безопасности.
45.10 Восстановление должно начинаться с исходных фактов
Производные данные могут быть потеряны.
Например:
effective permissions
оказались повреждены.
Если они являются производными, система не должна считать их единственным источником истины.
Должен существовать путь:
Source facts
+
Rules
↓
Rebuild
↓
Security projections
Именно поэтому производная модель должна быть воспроизводимой.
Если её невозможно восстановить из исходных фактов и правил, она постепенно превращается во второй независимый источник истины.
А два источника истины для одной модели доступа почти неизбежно приводят к расхождениям.
45.11 Безопасность должна быть видна на границах сервисов
В распределённой системе модель усложняется ещё сильнее.
Пусть один сервис владеет объектом:
Service A → Document
а другой хранит отношения:
Service B → User membership
Тогда решение о доступе зависит от данных нескольких сервисов.
Нельзя просто считать, что сетевой вызов:
Service A → Service B
сам по себе означает разрешение пользователю.
Нужно различать:
Service authorization
и:
Business subject authorization
и:
Object authorization
и:
Data enforcement
Получается:
Network/service trust
↓
Service authorization
↓
Business subject
↓
Object authorization
↓
Data enforcement
Каждый уровень имеет собственный смысл.
45.12 Локальное исполнение не отменяет общей модели
Если безопасность распределена между несколькими сервисами, не обязательно заставлять каждый запрос обращаться к центральному сервису авторизации.
Наоборот, физическое исполнение доступа часто должно происходить там, где находятся данные.
Поэтому сервис может иметь локальную проекцию необходимых security facts:
Global source facts
↓
Local security projection
↓
Local authorization
↓
Local data enforcement
Локальная проекция не становится новым источником истины.
Она является представлением общей модели в пределах конкретного сервиса.
Это позволяет сочетать:
-
автономность сервиса;
-
локальное выполнение запросов;
-
физическую защиту данных;
-
единую семантику отношений.
45.13 Поиск, кэш и производные данные тоже входят в security path
Если объект разрешён пользователю, это ещё не означает, что все его производные представления можно отдавать без дополнительных ограничений.
Например:
Object
↓
Search index
↓
Cache
↓
Report
↓
Export
Если доступ к объекту изменился, старые производные данные могут остаться доступными.
Особенно опасна ситуация с поиском.
Если запрещённый документ уже попал в поисковый индекс и участвует в ранжировании или генерации ответа, простое удаление его из финального списка может оказаться недостаточным.
Поэтому security path должен учитывать не только основную таблицу.
Он может проходить через:
database
cache
search index
materialized view
export
integration
Безопасность данных заканчивается не в момент чтения исходной строки.
Она заканчивается там, где данные перестают быть под контролем системы доступа.
45.14 Авторизация не заменяет целостность данных
Есть ещё одна важная граница.
Пусть пользователь имеет право:
UPDATE Document
Это означает, что он может выполнять действие.
Но это не означает, что любое изменение допустимо с точки зрения предметной модели.
Например, документ может находиться в состоянии:
Released
и переход:
Released → Draft
может быть запрещён бизнес-правилом.
Получается:
Authorization
≠
Business validity
Авторизация отвечает:
имеет ли субъект право выполнять действие?
Целостность отвечает:
допустимо ли такое изменение состояния системы?
Эти модели связаны, но не должны смешиваться.
45.15 Аудит тоже не заменяет авторизацию
Можно записывать:
User A
updated Document D
at 12:30
Но аудит не делает действие разрешённым.
Он фиксирует произошедшее.
Поэтому:
Authorization
отвечает:
можно ли?
а:
Audit
отвечает:
что произошло?
Эти функции не должны подменять друг друга.
Но аудит становится частью общей security architecture, потому что позволяет проверить, как модель реально использовалась.
45.16 Простая система не обязана строить сложную модель
Из всей книги не следует, что каждая система должна иметь:
-
граф отношений;
-
десятки проекций;
-
сложный контекст;
-
RLS;
-
распределённую репликацию;
-
отдельный проекционный слой.
Если система действительно имеет простую модель:
User
→ Role
→ Permission
этого может быть достаточно.
Если все данные принадлежат одному tenant, роли стабильны, объектов немного, нет делегирования и сложных отношений, более сложная архитектура может оказаться неоправданной.
Сложность должна соответствовать предметной модели.
Но если отношения уже существуют, их нельзя сделать проще только потому, что система безопасности хочется оставить простой.
Тогда сложность никуда не исчезает.
Она просто оказывается скрыта:
-
в SQL;
-
в контроллерах;
-
в исключениях;
-
в специальных проверках;
-
в ручных фильтрах;
-
в интеграциях;
-
в фоновых задачах.
И становится труднее доказать, что система действительно безопасна.
45.17 Главный критерий архитектуры — объяснимость
Сложная модель доступа оправдана не количеством таблиц и не количеством permission.
Её ценность в том, что для конкретного решения можно объяснить:
Почему User A получил доступ к Object D?
Хорошая модель позволяет пройти путь:
User A
↓
relevant relationship
↓
Project P
↓
Object D
↓
Context
↓
Rule
↓
Effective permission
↓
ALLOW
И наоборот:
Почему доступ был отозван?
можно показать:
Membership removed
↓
Security projection changed
↓
Effective permission removed
↓
Authorization denied
↓
Data enforcement blocks rows
Такая объяснимость — не удобство отладки.
Она позволяет проверить саму архитектуру.
45.18 От объекта к системе
Теперь можно вернуться к названию книги и пройти весь путь ещё раз.
Всё начинается с объекта.
Объект имеет идентичность и предметную семантику.
Из его семантики возникают возможные отношения.
Фактические отношения соединяют объект с другими объектами и субъектами.
Из множества фактов выбираются релевантные для конкретного действия.
Так формируется контекст.
Правила интерпретируют контекст.
Возникает эффективное разрешение.
По нему принимается решение о доступе.
Затем это решение должно быть обеспечено на физическом уровне данных.
Получается:
Object
↓
Relationships
↓
Context
↓
Access
↓
Data
Но это не линейная цепочка только для одного запроса.
Изменение объекта или отношения запускает обратное распространение:
Object / Relationship changes
↓
Security facts change
↓
Projections change
↓
Effective permissions change
↓
Access decisions change
↓
Data visibility changes
Таким образом, доступ становится частью жизненного цикла данных.
45.19 От системы к архитектуре
На этом уровне уже недостаточно говорить:
у системы есть авторизация.
Авторизация становится одной из функций архитектуры данных.
Архитектура должна понимать:
какие существуют объекты;
какие отношения между ними возможны;
какие отношения являются security-relevant;
какие правила превращают их в разрешения;
какие производные факты нужны для быстрых проверок;
как изменения распространяются;
где физически ограничиваются данные;
как модель восстанавливается;
как она изменяется вместе с предметной областью.
То есть безопасность перестаёт быть отдельным модулем, который можно полностью изолировать от модели данных.
Она становится поперечным свойством архитектуры.
При этом её границы должны оставаться ясными.
Модель доступа не должна поглощать:
-
всю предметную область;
-
аутентификацию;
-
бизнес-валидацию;
-
аудит;
-
резервное копирование;
-
операционную безопасность.
Но она должна быть связана с каждым слоем, который влияет на возможность получить или изменить данные.
45.20 Финальная модель
В начале книги мы спрашивали:
Почему модель
user → role → permissionперестаёт работать?
Теперь ответ можно дать полностью.
Она перестаёт быть достаточной не потому, что роли плохи.
И не потому, что permissions больше не нужны.
Она становится недостаточной, когда разрешение начинает зависеть от конкретного объекта и отношений вокруг него.
Тогда возникает более полная модель:
Subject
+
Action
+
Object
+
Relationships
+
Relevant facts
+
Context
+
Rules
↓
Effective permission
↓
Access decision
↓
Data enforcement
А исходные факты и производные представления образуют уже не отдельный механизм авторизации, а часть архитектуры данных:
Domain facts
↓
Security facts
↓
Security projections
↓
Authorization
↓
Data enforcement
↓
Physical data
При этом источник истины остаётся в предметных фактах.
Проекции остаются производными.
Правила остаются правилами.
Разрешение остаётся результатом.
Решение остаётся конкретным ответом на конкретное действие.
Именно разделение этих уровней позволяет построить сложную модель, не превращая её в неуправляемый набор исключений.
45.21 Главный вывод
Вся книга сводится к одному переходу.
Не:
User
↓
Role
↓
Permission
а:
Object
↓
Relationships
↓
Context
↓
Access
А затем:
Access
↓
Data enforcement
↓
Data
Объект определяет, какие отношения имеют смысл.
Отношения формируют релевантные факты.
Релевантные факты образуют контекст.
Контекст становится входом правил.
Правила формируют эффективное разрешение.
Разрешение превращается в решение о конкретном действии.
Решение должно быть обеспечено там, где находятся реальные данные.
И изменения исходных фактов должны распространяться через всю эту цепочку.
Поэтому главный архитектурный принцип можно сформулировать так:
Доступ нельзя надёжно отделить от данных, если право доступа определяется отношениями вокруг самих данных.
Но из этого не следует, что безопасность должна поглотить предметную модель.
Наоборот.
Хорошая архитектура сохраняет границы:
Предметная модель
↓
Security-relevant facts
↓
Модель доступа
↓
Производные security facts
↓
Enforcement
Каждый слой знает только то, что ему необходимо.
Именно поэтому путь:
от объекта
к отношениям,
от отношений
к контексту,
от контекста
к доступу,
от доступа
к данным
является не просто способом описать авторизацию.
Это способ проектировать систему так, чтобы безопасность следовала из её структуры, а не была приклеена к ней после того, как данные уже спроектированы.
Объект определяет возможные отношения.
Отношения формируют контекст.
Контекст определяет доступ.
Доступ становится частью архитектуры данных.
На этом книга заканчивается.
Не ответом на вопрос «какую систему авторизации выбрать», а более ранним вопросом:
какие отношения существуют вокруг объекта и какие из них действительно определяют, кому, что, когда и в каком контексте разрешено?
Именно с этого вопроса начинается архитектура доступа.
Эпилог
В начале книги доступ можно было представить простой цепочкой:
User → Role → Permission
В конце эта цепочка никуда не исчезла.
Она просто оказалась частью большей модели.
Object
↓
Relationships
↓
Relevant facts
↓
Context
↓
Rules
↓
Effective permission
↓
Authorization
↓
Data enforcement
Это не означает, что простая модель была неправильной.
Она была неполной.
Для простой системы этого различия может быть достаточно.
Для сложной системы приходится увидеть то, что находится между пользователем и разрешением.
Сначала существует объект
Любая модель начинается не с разрешения.
Сначала существует что-то, над чем вообще можно совершить действие.
Документ.
Проект.
Заказ.
Изделие.
Запись.
Файл.
Знание.
Сервисный ресурс.
Объект имеет идентичность и смысл в предметной области.
И только после этого возникает вопрос:
Кто и что может с ним сделать?
Вокруг объекта существуют отношения
Объект никогда не существует изолированно.
Он может принадлежать субъекту.
Находиться в проекте.
Быть частью группы.
Быть опубликованным в области.
Иметь состояние.
Быть связанным с другими объектами.
Эти отношения существуют независимо от того, проверяет ли их система прямо сейчас.
Некоторые из них имеют значение для безопасности.
Некоторые — нет.
Поэтому следующий шаг состоит не в том, чтобы взять все факты системы.
Нужно определить релевантные.
Из отношений возникает контекст
Контекст не является ещё одной сущностью, которую обязательно нужно сохранить в таблице.
Это способ посмотреть на релевантные факты с точки зрения конкретного решения.
Для одного запроса важен проект.
Для другого — владелец.
Для третьего — состояние объекта.
Для четвёртого — делегирование.
Контекст зависит от:
Subject
Action
Object
Поэтому один и тот же объект не имеет единственного контекста доступа.
Контекст возникает заново для конкретного вопроса.
Контекст становится входом для правил
Правила определяют смысл отношений.
Одна и та же связь может давать разные результаты для разных действий.
Участник проекта может читать документ, но не закрывать его.
Владелец может изменять объект, но не иметь права изменить его состояние.
Пользователь может иметь роль, но только в определённой области.
Поэтому нельзя заменить правила самим фактом существования отношения.
Отношение говорит, что связь существует.
Правило говорит, что эта связь означает.
Разрешение становится производным фактом
После применения правил появляется эффективное разрешение.
Оно уже не является исходным фактом.
Оно является результатом вычисления:
Context
↓
Rules
↓
Effective permission
Это различие важно не только теоретически.
Производное состояние можно материализовать.
Его можно индексировать.
Его можно реплицировать.
Его можно перестраивать.
Но источником истины остаются исходные факты и правила.
Доступ становится частью архитектуры данных
На этом месте заканчивается привычное разделение:
Data model
с одной стороны и:
Security
с другой.
Если безопасность зависит от отношений между объектами, эти отношения уже являются частью архитектуры данных.
Если изменение отношения меняет доступ, изменение отношения становится одновременно изменением security model.
Если разрешение материализуется в проекции, эта проекция становится частью инфраструктуры данных.
Если физические строки должны быть защищены, граница данных становится частью модели безопасности.
Поэтому безопасность нельзя полностью добавить после того, как модель данных уже построена.
Но границы всё равно существуют
Это не означает, что вся система должна превратиться в одну большую security model.
Аутентификация остаётся отдельной задачей.
Бизнес-инварианты остаются отдельной задачей.
Шифрование, аудит, резервное копирование и эксплуатационное доверие имеют собственные уровни ответственности.
Даже контекст не является снимком всей системы.
Хорошая модель не стремится описать всё.
Она описывает именно те отношения и факты, которые необходимы для решения конкретного класса задач.
От объекта к контексту
В итоге путь книги можно свести к четырём предложениям.
Объект определяет возможные отношения.
Отношения формируют контекст.
Контекст определяет доступ.
Доступ становится частью архитектуры данных.
Это не алгоритм конкретной системы.
Это способ смотреть на архитектуру.
Когда появляется новый объект, нужно спросить не только, где он будет храниться.
Нужно спросить:
Какие отношения могут существовать вокруг него?
Когда появляется новое отношение, нужно спросить:
Меняет ли оно контекст доступа?
Когда появляется новое правило, нужно спросить:
Как оно изменяет эффективное разрешение?
Когда появляется новая таблица, нужно спросить:
Как логический объект связан с физическими данными?
И когда меняется отношение, нужно помнить:
Возможно, изменилось не только состояние предметной области. Изменилась сама модель безопасности.
В первой книге речь шла о способности увидеть замысел и создать из него систему.
Эта книга начинается с другого навыка.
Увидеть то, что связывает созданные объекты.
Потому что система начинается не там, где появляются данные.
Она начинается там, где между ними появляются отношения.
И именно в этих отношениях часто находится настоящий контекст происходящего.