Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Глава 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

Каждый слой знает только то, что ему необходимо.

Именно поэтому путь:

от объекта
к отношениям,
от отношений
к контексту,
от контекста
к доступу,
от доступа
к данным

является не просто способом описать авторизацию.

Это способ проектировать систему так, чтобы безопасность следовала из её структуры, а не была приклеена к ней после того, как данные уже спроектированы.

Объект определяет возможные отношения.
Отношения формируют контекст.
Контекст определяет доступ.
Доступ становится частью архитектуры данных.

На этом книга заканчивается.

Не ответом на вопрос «какую систему авторизации выбрать», а более ранним вопросом:

какие отношения существуют вокруг объекта и какие из них действительно определяют, кому, что, когда и в каком контексте разрешено?

Именно с этого вопроса начинается архитектура доступа.