Преамбула

Привет! Меня зовут Максим Газин. Мой опыт в ИТ — 15 лет, из которых больше 10 я сотрудничал с компанией Bercut. В последние годы специализируюсь на разработке архитектур масштабных ИТ‑решений для телекоммуникационных компаний, работающих в областях мобильной и фиксированной связи.

В Bercut всегда изучают, что происходит в отрасли, отслеживают эволюцию технологий и тренды, и постоянно улучшают процессы, инструменты, подходы. И я тоже люблю читать материалы, посвященные информационной безопасности и облачным технологиям. Недавно наткнулся на интересную статью, которую решил перевести и поделиться с сообществом на Хабре.

Эта статья может быть полезна аналитикам, разработчикам и архитекторам информационных систем. В ней обращается внимание на часть связанных с авторизацией вопросов, неизбежно возникающих при разработке корпоративных веб‑приложений. Цель перевода — сделать эту информацию доступной как можно большему числу заинтересованных людей, которые могли пропустить оригинальную публикацию. Надеюсь, мои усилия помогут коллегам лучше понимать и применять современные технологии и подходы к авторизации в своих проектах.

Использование Verified Permissions для реализации точной авторизации в высоконагруженных приложениях

Для аутентификации клиенты часто используют внешних поставщиков идентификации (Identity Provider — IdP), таких как Amazon Cognito или Keycloak. При этом логика авторизации обычно реализуется в коде самого приложения. Этот код имеет тенденцию к усложнению и подверженности ошибкам со временем — из‑за неизбежных изменений модели разрешений, возникающих по мере развития организации. Это затрудняет аудит разрешений и определение кто к чему имеет доступ. В результате в CWE списке 25 самых опасных программных уязвимостей за 2023 год две из них связаны с некорректной авторизацией.

На конференции re:Inforce 2023 AWS запустили Amazon Verified Permissions, сервис управления точными разрешениями доступа (fine grained authorization — FGA) для приложений, разрабатываемых клиентами на тех. стеке AWS. Verified Permissions централизует разрешения в хранилище политик и позволяет разработчикам использовать эти разрешения для авторизации действий пользователей в их приложениях. Разрешения выражаются в виде политик Cedar.

В этом посте мы рассмотрим, как с помощью использования двух техник — пакетной авторизации и кеширования ответов — повысить эффективность и отзывчивость веб-приложений. Мы расскажем, как применять эти техники при отображении разрешенных ресурсов и действий, а также при загрузке нескольких компонентов на веб-страницах.

Сценарии использования

Вы можете использовать Verified Permissions (Cedar engine) для обеспечения соблюдения разрешений, которые определяют, что пользователь может видеть на уровне пользовательского интерфейса (UI) и что ему разрешено делать на уровне API.

  1. Разрешения UI позволяют разработчикам контролировать, что пользователь имеет право видеть в приложении. Разработчики применяют разрешения в UI для контроля списка ресурсов, которые пользователь может видеть, и действий, которые он может выполнять. Например, разрешение на уровне UI в банковском приложении может определять, активна ли кнопка перевода средств для счета Клиента.

  2. Разрешения API позволяют разработчикам контролировать, права доступа пользователя в приложении. Разработчики контролируют доступ к отдельным вызовам API, совершаемым приложением от имени пользователя. Например, разрешение на уровне API в банковском приложении может определять, разрешено ли пользователю выполнять перевод средств со счета Клиента. Cedar обеспечивает единообразные и легко читаемые политики, которые можно использовать как на уровне UI, так и API. Например, одна и та же политика может проверяться на уровне UI для определения, показывать ли кнопку перевода средств, и на уровне API для определения разрешения на инициацию перевода средств.

Проблемы

Verified Permissions можно использовать для реализации точных разрешений API. Приложения клиентов могут использовать Verified Permissions (Cedar engine) для авторизации API запросов с низкой задержкой. Приложения выполняют авторизацию пользовательских запросов, по средствам выполнения запросов к точке принятия авторизационных решений (Policy Decision Point — PDP) — т. е. вызова API‑метода «IsAuthorized» сервиса Verified Permissions (Cedar engine) в нашем п римере, и ответ содержит информацию о том отклонён или разрешён конкретный запрос пользователя.
Клиенты (AWS) были довольны временем отклика на отдельные запросы авторизации, но просили AWS помочь им улучшить производительность для случаев, требующих отправки множественных запросов авторизации. Обычно упоминались два сценария использования:

  • Составная авторизация: Составная авторизация необходима, когда одно действие на уровне высокоуровневого API (бизнес апи) влечет за собой множество действий на низком уровне (атомарное апи), доступ к каждому из которых регулируется своим разрешением. Это требует от приложения совершения множественных запросов к PDP для авторизации бизнес-действия пользователя. Например, в банковском приложении загрузка выписки по кредитной карте может требовать трех вызовов API: GetCreditCardDetails, GetCurrentStatement и GetCreditLimit. Это в свою очередь потребует трех вызовов к PDP, по одному для каждого вызова атомарного метода API.

  • Разрешения для UI: Разработчики реализуют разграничение доступа к отдельным элементам UI и действиям над ними. Каждый элемент/действие на форме влечет за собой отдельный вызов к PDP, и UI может быть полностью отрисован только после завершения всех из них.

Решение

Рассмотрим две техники для оптимизации отзывчивости приложения на основе разрешений API и UI:

  1. Пакетная авторизация

  2. Кеширование ответов

Решение проблемы точного разграничения доступа с обеспечением превосходного уровня User Experience

Использование разрешений для UI позволяет определить, какие ресурсы и действия доступны пользователю в приложении. Разработчики обычно получают список ресурсов на основе фильтрации данных на уровне запросов БД, после чего сокращают его до тех ресурсов, к которым у пользователя есть доступ. Это достигается проверкой прав через систему Verified Permissions. Например, когда пользователь IT системы банка пытается посмотреть остатки на счетах компании (Клиента), приложение сначала получает из БД список счетов компании. После чего приложение фильтрует полученный список, чтобы оставить в нём только те счета, к которым у текущего пользователя есть доступ, путём отправки запроса к PDP по каждому счёту. С использованием пакетной авторизации приложению для такой фильтрации будет достаточно сделать только один вызов к PDP.

Аналогично можно использовать разрешения UI для определения того, какие компоненты страницы или действия должны быть видны пользователям приложения. Например, в банковском приложении нужно контролировать видимость подпродуктов (таких как, кредитной карты, банковского или брокерского счета) для пользователя или отображать только те действия, которые доступны пользователю (скажем, перевод средств или изменение адреса) на экране сводной страницы лицевого счёта. Клиенты хотят использовать Verified Permissions для определения, какие компоненты страницы отображать, но это может негативно сказаться на пользовательском опыте (User Experience, UX), если приложение будет совершать множество вызовов PDP для построения страницы. С пакетной авторизацией можно сделать один вызов к PDP, чтобы сразу определить разрешения для всех компонентов страницы; таким образом можно существенно снизить задержку загрузки и полного отображения станицы.

Решение задачи контроля доступа к каждому API вызову без ущерба для производительности

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

+Следует учитывать, что в процессе, выполняющем составную операцию, перед началом выполнения нужно наперёд знать всю последовательность дочерних вызовов, из которых эта составная операция состоит. Если же бизнес-приложение не монолитно (разбито на микросервисы) и дочерняя операция (которая в свою очередь тоже может оказаться составной) будет выполняться в другом процессе, то между процессами нужно установить доверительную связь либо выполнять обращение к PDP из каждого дочернего процесса.

Демо-приложение – сценарии использования, пользователи и разрешения

Рассмотрим онлайн-приложение для управления заказами в магазине игрушек, чтобы продемонстрировать, как можно применить пакетную авторизацию и кэширование ответов для улучшения UX и производительности приложения.

Одна из функций приложения — позволить сотрудникам магазина обрабатывать онлайн заказы.

Пользователи

Приложение используется двумя типами пользователей:

  • Упаковщики — ответственны за сбор, упаковку и отправку заказов. Они прикреплены к определённому отделу

  • Управляющие — отвечают за надзор за операциями в магазине

Сценарии использования

Приложение поддерживает следующие сценарии использования:

  1. Просмотр заказов: Пользователи могут просматривать заказы. Пользователь должен видеть только те заказы, для которых у него есть разрешения на просмотр.

  • Упаковщики — могут просматривать все заказы своего отдела

  • Управляющие — могут просматривать все заказы своего магазина

Заказы для , который является упаковщиком в отделе мягких игрушек (Рисунок 1):

Рисунок 1: Заказы <UserName> в отделе мягких игрушек
Рисунок 1: Заказы <UserName> в отделе мягких игрушек
  1. Действия с заказом: пользователи могут выполнять определённые действия с заказом. Приложение активирует соответствующие элементы пользовательского интерфейса в зависимости от разрешений пользователя.

  • Упаковщики — могут выполнять действия «Get box size» и «Mark as shipped» (Рисунок 2).

  • Управляющие — могут выполнять действия «Get box size», «Mark as shipped», «Cancel order» и «Route to different warehouse».

Рисунок 2: Действия доступные <UserName>, как упаковщику
Рисунок 2: Действия доступные <UserName>, как упаковщику
  1. Просмотр заказа: пользователи могут просматривать детали конкретного заказа. Когда пользователь просматривает заказ, приложение загружает детали, этикетку и квитанцию. Доступные действия для упаковщика (Рисунок 3)

Рисунок 3: Детали и доступные действия над заказом для <UserName>
Рисунок 3: Детали и доступные действия над заказом для <UserName>

Создание политик

Приложение использует Verified Permissions в качестве централизованного хранилища политик. Эти политики написаны на языке Cedar. Приложение использует подход управления ролями с помощью шаблонов политик для реализации контроля доступа на основе ролей. Мы Рекомендуем вам ознакомиться с лучшими практиками реализации контроля доступа на основе ролей в Cedar, чтобы понять, подходит ли этот подход вашему сценарию использования.

В демо-приложении шаблон политики для роли владельца магазина выглядит следующим образом:

permit (
        principal == ?principal,
        action in [
                avp::sample::toy::store::Action::"OrderActions",
                avp::sample::toy::store::Action::"AddPackAssociate",
                avp::sample::toy::store::Action::"AddStoreManager",
                avp::sample::toy::store::Action::"ListPackAssociates",
                avp::sample::toy::store::Action::"ListStoreManagers"
        ],
        resource in ?resource
);

Когда пользователю назначается роль, приложение создает политику из соответствующего шаблона, подставляя в него пользователя и магазин. Например, созданная для владельца магазина политика выглядит так:

permit (
    principal ==  avp::sample::toy::store::User::"test_user_pool|sub_store_manager_user", 
    action in  [
                avp::sample::toy::store::Action::"OrderActions",
                avp::sample::toy::store::Action::"AddPackAssociate",
                avp::sample::toy::store::Action::"AddStoreManager",
                avp::sample::toy::store::Action::"ListPackAssociates",
                avp::sample::toy::store::Action::"ListStoreManagers"
    ],
    resource in avp::sample::toy::store::Store::"toy store 1"
);

Чтобы узнать больше о дизайне политик этого приложения, см. файл readme демо-приложения.

Сценарии использования — дизайн и реализация

В этом разделе мы обсуждаем общий дизайн, интеграционные моменты и как можно использовать вышеупомянутые техники для сокращения задержек в работе приложения

Просмотр заказов

Рисунок 4: просмотр заказов
Рисунок 4: просмотр заказов

Процесс просмотра заказов следующий:

  1. Пользователь обращается к приложению, размещенному на AWS Amplify.

  2. Затем пользователь аутентифицируется через Amazon Cognito и получает токен идентификации.

  3. Приложение вызывает API метод ListOrders для получения списка заказов, доступных пользователю.

  4. API метод ListOrders реализован с помощью ƛ-функции, которая защищена функцией авторизации и опубликована на API Gateway.

  5. Функция ListOrders собирает информацию о сущностях из хранилища данных для формирования запроса на авторизацию — IsAuthorized.

  6. Затем ƛ-функция вызывает Verified Permissions для авторизации запроса пользователя. Функция последовательно проверяет разрешения для каждого заказа, полученного из хранилища данных. Если Verified Permissions возвращает отказ, то этот заказ не возвращается пользователю. Если Verified Permissions возвращает разрешение, то запрос продвигается дальше.

Проблема

График показывающий, что приложение множество раз последовательно вызывает IsAuthorized (Рисунок 5). Множественные последовательные вызовы приводят к медленной загрузке страницы и увеличению накладных затрат на ИТ-инфраструктуру.

Рисунок 5: Повторяющиеся вызовы к IsAuthorized
Рисунок 5: Повторяющиеся вызовы к IsAuthorized

Сокращение задержек с помощью пакетной авторизации

Если перейти к использованию пакетной авторизации, приложение может получить до 30 решений об авторизации за один вызов API Verified Permissions. Время на авторизацию сократилось с почти 800 мс до 79 мс, обеспечивая существенное улучшение общего пользовательского опыта (Рисунок 6).

Рисунок 6: Сокращение задержек с помощью пакетной авторизации
Рисунок 6: Сокращение задержек с помощью пакетной авторизации

Действия с заказом

Рисунок 7: действия над заказом
Рисунок 7: действия над заказом

Процесс получения разрешенных действий с заказом следующий (Рисунок 7):

  1. Пользователь открывает на стартовую страницу приложения (размещённого на Amplify).

  2. Приложение вызывает API метод OrderActions, опубликованный на API Gateway.

  3. Приложение отправляет запрос на проверку действий с заказом, чтобы отобразить только разрешенные действия пользователю.

  4. API метод OrderActions реализован с помощью ƛ-функции, которая собирает информацию о сущностях из хранилища данных для формирования запроса на авторизацию.

  5. Затем Lambda-функция проверяет в Verified Permissions каждое действие над заказом. Если Verified Permissions возвращает отказ, действие отбрасывается. Если Verified Permissions возвращает разрешение, запрос продвигается вперед, и действие добавляется в список действий по заказу, который будет отправлен в последующем запросе к Verified Permissions для представления действий в пользовательском интерфейсе.

Проблема

Как показано на графике (Рисунок 8), приложение многократно последовательно вызывает IsAuthorized. Это значит, что страница будет загружаться медленно и накладные затраты будут велики.

Рисунок 8: Повторяющиеся вызовы к IsAuthorized
Рисунок 8: Повторяющиеся вызовы к IsAuthorized

Если перейти к использованию пакетной авторизации, приложение сможет получить все решения одним вызовом API к Verified Permissions. Время на авторизацию сократилось с почти 500 мс до 150 мс, обеспечив улучшение пользовательского опыта (Рисунок 9).

Рисунок 9: График, демонстрирующий результаты применения пакетной авторизации
Рисунок 9: График, демонстрирующий результаты применения пакетной авторизации

Просмотр заказа

Рисунок 10: просмотр заказа
Рисунок 10: просмотр заказа

Процесс просмотра заказа следующий (Рисунок 10):

  1. Пользователь получает доступ к приложению, размещенному на Amplify.

  2. Пользователь аутентифицируется через Amazon Cognito и получает токен идентификации.

  3. Приложение последовательно вызывает три API метода, опубликованных на API Gateway.

  4. API методы «Get order details», «Get label», и «Get receipt» вызываются последовательно для отрисовки пользовательского интерфейса в приложении.

  5. Каждый из вышеупомянутых API методов защищен «ƛ‑авторизатором», который выполняется при каждом вызове.

  6. ƛ‑функция собирает информацию о сущностях из хранилища данных для формирования запроса на авторизацию.

  7. Для каждого API метода повторяются следующие шаги:

    а) ƛ‑функция вызывает Verified Permissions для авторизации запроса. Если Verified Permissions возвращает отказ, запрос отклоняется, и возвращается HTTP‑ответ с кодом неавторизованного доступа (403). Если Verified Permissions возвращает разрешение, запрос передаётся на выполнение.

    б) Если запрос разрешен ƛ-авторизатором, API Gateway вызывает ƛ-функцию, непосредственно обрабатывающую запрос. Это основная ƛ-функция, которая реализует бизнес-логику приложения.

Проблема

Используя стандартный паттерн авторизации в этом сценарии, приложение вызывает Verified Permissions три раза. Так происходит потому, что действие пользователя «Посмотреть заказ» требует составной авторизации, поскольку каждый вызов API, совершаемый приложением, должен быть авторизован. Хотя это и согласуется с принципом наименьших привилегий, такая реализация негативно отражается на времени загрузки и перезагрузки страницы приложения.

Сокращение задержки с помощью пакетной авторизации и кэширования ответов

В демо приложении функция кеширования реализуется API Gateway. Совместное применение техник пакетной авторизации и кеширования ответов при разработке приложения приводит к тому, что потребуется выполнить только один вызов к Verified Permissions (Рисунок 11).

Рисунок 11: Пакетная авторизация с кэшированием ответов
Рисунок 11: Пакетная авторизация с кэшированием ответов

Процесс кэширования ответов следующий (Рисунок 11):

  1. Пользователь получает доступ к приложению, размещенному на Amplify.

  2. Пользователь аутентифицируется через Amazon Cognito и получает токен идентификации.

  3. Приложение затем вызывает три метода API, опубликованные на API Gateway.

  4. Когда вызывается ƛ-функция API метода «Get order details», она использует ƛ‑авторизатор для выполнения пакетной авторизации, чтобы получить решения об авторизации для запрашиваемого действия «Get order details» и связанных действий «Get label» и «Get receipt».

  5. Каждый из вышеупомянутых API методов защищен ƛ-авторизатором, но благодаря использованию пакетной авторизации запрос авторизацонных решений выполняется только один раз.

  6. ƛ-функция собирает информацию о бизнес-сущностях, к которым обращается пользователь, из хранилища данных для формирования запроса «IsAuthorized».

  7. ƛ-функция вызывает Verified Permissions для авторизации запроса пользователя. Если Verified Permissions возвращает отказ, запрос отклоняется, и возвращается HTTP-ответ с кодом неавторизованного доступа (403). Если Verified Permissions возвращает разрешение, обработка запроса переходит на следующую стадию:

    а) API Gateway кэширует решения об авторизации для всех действий (запрашиваемого и связанных действий).

    б) Если запрос разрешен ƛ-авторизатором, API Gateway вызывает ƛ-функции управления заказами для обработки запроса. Это ƛ-функции, которые содержат основную бизнес-логику приложения.

    в) При последующих вызовах API, API Gateway использует кэшированные решения об авторизации и не вызывает функцию ƛ-авторизации повторно.

Рекомендации по кэшированию

Рассмотрели, как можно использовать кэширование для реализации точной авторизации в масштабируемых веб-приложениях. Этот подход хорошо работает, когда у вашего приложения высокие показатели попадания в кэш, где результаты авторизации часто загружаются из кэша. В приложениях, где пользователи инициируют одно и то же действие несколько раз или выполняют предсказуемую последовательность действий, будет наблюдаться высокий процент попадания в кэш. С другой стороны, использование кэширования может увеличить время между обновлением политик и их применением. Не рекомендуем использовать кэширование для решений об авторизации, если ваше приложение требует быстрого применения политик и в случае, если ваши политики зависят от времени (например, политика, предоставляющая доступ между 10:00 и 14:00).

Заключение

В этой статье показано, как реализовать точные разрешения в приложениях с использованием Verified Permissions (Cedar engine). Мы рассмотрели, как можно использовать пакетную авторизацию и кэширование авторизацонных решений для улучшения производительности и отзывчивости веб-приложений. Применение этих техник показано на примере демо-приложения — avp‑toy‑store-sample.

Комментарии (1)


  1. gleb_l
    19.07.2024 20:49

    Что делать, если разрешенные действия пользователя по отношению к объекту зависят в частности от состояния и/или набора свойств самого объекта - например, овнер (автор записи) при том же наборе ролей в системе имеет другой набор разрешенных действий по отношению к этой записи, чем по отношению к другим. Как эти принципы прописать в облако, чтобы оно выдавало пермиссионный вектор с учетом этого?