Всем привет! На связи снова Никита Таскин и Анастасия Ильина. Продолжаем тему о контроле доступа применительно к системным интерфейсам, и на этот раз речь пойдёт об API. Напомним, что первая часть была про авторизацию UI, и ознакомиться с ней можно здесь. У нас всё так же разбор контекста ABAC-авторизации для управления задачами команд разработки в Сфера.Задачи. В этот раз мы глубже окунёмся в архитектуру и спроектируем решения для разных по сложности вариантов атрибутного контроля. Также посмотрим на другой вариант применения нашего экспериментального подхода «постанализ юзкейсов».
Программный Интерфейс API и Авторизация
Наш таск-трекер на уровне С2 представляет собой типичное макросервисное приложение: клиентская часть веб-приложения, сервис задач и дополнительные сервисы, каждый со своей базой данных. Обращение идёт через API-шлюз.

Поскольку нас интересует только управление задачами, сосредоточимся на их сервисе и базовом tasks API с методами POST, GET и PATCH.
Снова воспользуемся результатами анализа вариантов использования, и на этот раз построим из них рабочий процесс в соответствии с жизненным циклом задачи. Как вы помните, процесс предполагает создание и архивирование задачи лидером команды, а редактирование — любым участником. Зафиксируем события на входе и выходе:
Лидер создал и назначил исполнителю задачу, и она перешла в фазу редактирования.
Участник может актуализировать задачу, только если она не закрыта.
Если задача завершена, она может перейти в фазу архивирования.
Добавим сюда, какими эндпоинтами мы воспользуемся для реализации юзкейсов. Создание предполагает только POST-запрос. Когда участникам нужно будет увидеть задачу в табличном или досочном представлении и открыть её, отправляются GET-запросы. Редактирование будет выполняться через PATCH-запросы.

Решение для достижения старой модели авторизации (as-is)
Для первой версии авторизации таск-трекера задача выглядит просто: проверь роль и команду пользователя для всех запросов, а для PATCH ещё и статус задачи, и что запрос на архивирование. Для GET-запроса массового чтения достаточно фильтрации ответа по доступным командам.
Теперь нужно определиться, где нам делать авторизацию запросов при таких вводных? Будем считать, что данные о пользовательских доступах лежат в базе нашего сервиса. Самый сложный с точки зрения достижения безопасности вариант — PATCH-запрос.
С API-запросами у нас работают два компонента: API-шлюз и API-сервис. Со вторым всё понятно, он обрабатывает запросы, и на практике обычно ещё авторизует. Так дело было и в нашем случае. Однако, тут хочется отойти от реальности и задаться вопросом: можно ли сделать полноценную авторизацию PATCH-запроса на уровне API-шлюза?
Чтобы ответить на этот вопрос, надо понять, откуда же API-шлюз возьмёт атрибуты субъекта и объекта для реализации логики проверки? Первое очевидное решение: использовать то, что уже есть для работы с аутентификацией — веб-токен (JWT). Можно зашить доступы в payload JWT, однако надо ли нам получать вариант утечки информации? Ведь токен легко декодировать для извлечения данных, и возможны варианты его подделки. Так откуда же API-шлюз возьмёт атрибуты субъекта и объекта? Вариантов два: эту информацию можно получить от сервиса отдельным запросом или от веб-клиента, например, в заголовках. Давайте рассмотрим оба решения.

При запросе к сервису мы получаем актуальную информацию, но при этом создаём дополнительные сетевые запросы и теряем в производительности. Если просим веб-клиент отправлять нам в заголовках, то кастомный заголовок может потеряться, или данные могут вообще подделать. Первый вариант выглядит предпочтительнее.
Но почему бы не пойти третьим путём? Например, использовать сразу оба варианта, только для разных атрибутов. Для данных субъекта будем заранее запрашивать и кешировать права. Время на инвалидацию задаём в соответствии с требованиям к безопасности: сколько отпущено на отключение доступов у уволенного сотрудника.
А вот со вторым подходом придётся действовать нестандартно. Лучше перейти в обязательные query-параметры: team и status. Это позволит шлюзу принимать решения при расхождениях в данных, от подделывания спасёт только сервис: будет вылавливать случаи расхождения между рельной информацией и из запросов.
В общем, авторизация возможна до попадания в сервис, например, на уровне API-шлюза. На него ложится обязанность по соблюдению безопасности, а главному сервису остаётся только учесть авторизацию при массовом чтении.
Решение для желаемой модели авторизации (to-be)
Во второй версии таск-трекера всё сложнее: мы хотим отделить заинтересованных лиц и дать им доступ на редактирование данных. Фиксируем в событиях: участнику для актуализации задачи нужно учитывать контекст заинтересованного лица, потому что какие-то атрибуты он сможет редактировать, какие-то — нет, а конфиденциальные данные и вовсе не сможет увидеть. Само заинтересованное лицо в праве редактировать атрибуты только в том случае, когда задача находится непосредственно в его контексте. И помним, что завершённую задачу отправляем в архив.

Переходим к ручкам API (они же эндпойнты, методы). Схема такая же: POST для создания, PATCH для редактирования, GET для чтения данных. Но сценарии доступа с атрибутами очень неопределённые: проверять нужно не только роль и команду, но и контекст всего объекта, причём постоянно: требования к авторизации PATCH сильно усложняются, дополнительно требуется постфильтрация данных в разрезе атрибутов для всех ответов на любой запрос.
Теперь рассмотрим API-авторизацию в желаемой версии планировщика. При таких вводных возникают неопределённости с необходимым контекстом для авторизации, да и ещё клиент может не обладать всеми данными из-за ограничений пользователя на чтение. Оставаясь в старой парадигме, для проверок точно пришлось бы каждый раз запрашивать сущность у сервиса. Поэтому самым лучшим местом для авторизации запросов остаётся сам сервис.

На выходе не будет усложнения взаимодействия компонентов, ещё больше выиграем в производительности, если закешируем доступы, но уже в виде связки «пользователь-правило». Перед отдачей ответа на любой запрос будем «обнулять» недоступные атрибуты объектов.
Постанализ юзкейсов для проектирования авторизации API

Повторим, что мы сделали:
Определили поток работы из вариантов использования и участников, распределив их в рамках процесса.
Определили события до и после юзкейсов и вынесли на них условия авторизации.
Определили API и оценили риски авторизации на уровне данных запроса в методе. Соединили сценарии с эндпоинтами, оценили достаточность данных в запросах для авторизации.
Сравнили подходы до и после попадания в бизнес-логику. При высокой неопределённости условий и недостаточности данных в запросе лучше использовать подход «авторизация в бизнес-логике».
Дизайн непрерывной авторизации программного интерфейса API

Давайте теперь разберёмся в действиях системного аналитика. При подходе “до бизнес-логики” (на API-шлюзе) мы один раз проектируем ручку для получения прав, а также логику обращения и кеширования атрибутов субъекта. В рамках каждой функциональности определяем доступность запросов для ролей или разрешений, учитывая доставку авторизуемых атрибутов объектов на уровне запросов.
При реально гибкой ABAC-авторизации лучше перейти на подход “в бизнес-логике” в рамках самого сервиса, там мы один раз определяем подходящий Domain Specific Language (DSL) для своих условий, а также принцип работы фильтрации и постфильтрации. Для каждой функциональности также сопоставляем эндпойнты и связки «ресурс-операция», и в своих спецификациях снабжаем разработчиков примерами правил и ожидаемых результатов работы авторизации API.
Заключение
Для реализации авторизации UI нужна совместная работа фронтенда и бэкенда. При этом распределение логики может быть разным:
Только на фронтенде: очень быстрая реализация, но нет реальной безопасности.
Только на бэкенде: быстро, безопасно, но страдает UX.
Параллельный: небыстро в реализации, но безопасно, это классический подход.
Бэкенд-ориентированный: сложнее и дольше в разработке, но покрывает самые сложные кейсы.
В полноценных системах необходимо использовать один из двух последних подходов, возможно даже гибридно!
Для реализации авторизации API нужно определиться с подходом:
На границе бэкенда: быстрая и относительно простая разработка, безопасно для обычных ролёвок.
Внутри основной логики: самая сложная и долгая разработка, но покрывающая самые сложные кейсы атрибутного контроля.
и альтернатива — внешняя авторизация, когда проверки мы вообще делегируем отдельному сервису, реализация простая - в одном месте, сложнее такой покрывать полноценный ABAC и явно будет страдать производительность.
Подробнее про подходы авторизации UI и API смотрите в материалах Telegram-канала «постанализ».
И последнее: дизайнить безопасность нужно всегда, когда занимаетесь проектированием UI и API. Выберите подход в зависимости от интерфейса: сведите их в схему как пути пользователей, или как жизненный цикл ресурса, или как поток работы системы. Этого хватит, чтобы проанализировать общие закономерности и имеющиеся решения, провести изыскания и спроектировать новый дизайн.
«Постанализ юзкейсов» — это отличный инструмент проверки непрерывности авторизации в парадигме Zero Trust (поможет убедится, каждый ли ваш шаг закрыт проверками). Для аналитиков и инженеров это будет удобный инструмент для обсуждения специфики проблем и дизайна интерфейсов.
Нам постанализ помог за месяц погрузиться в новую для нас систему, изучить имеющееся решение и спроектировать новое. И, главное, с помощью получившихся визуальных материалов мы смогли сформировать общее видение проблем и решений у всех разработчиков и заинтересованных лиц. Благодаря этому получилось решить очень сложную задачу - разработать гибко настраиваемую модель доступа, что сейчас стало киллер-фичей нашей системы управления проектами Сфера.Задачи.
Авторы:
Никита Таскин, эксперт-системный аналитик
Анастасия Ильина, системный аналитик
