Механизмы авторизации в web-приложениях на Rust
Для обеспечения безопасности приложений мы используем такие механизмы как аутентификация и авторизация. Думаю, многие из вас знакомы с этими концепциями и в этой статье мы сфокусируемся на понятие авторизации и связанных с ней моделях контроля доступом.
Определения терминов, которые используются в статьеВажно понимать отличия авторизации от аутентификации:
Аутентификация — процесс подтверждения вашей личности и доказательства того, что вы являетесь непосредственным клиентом системы (по средствам пароля, токена или любой другой формы учетных данных).
Авторизация в свою очередь — это механизм, в результате которого запрос к определенному ресурсы системы должен быть разрешен или отклонен.
Субъект доступа — пользователь или процесс, который запрашивает доступ к ресурсу.
Объект доступа — напротив, является ресурсом, к которому запрашивается доступ со стороны субъекта.
Крейт (Crate) –библиотека или исполняемая программа в Rust.
К процессу авторизации относится понятие политики контроля доступа, в соответствии с которой и определяется набор допустимых действий конкретного пользователя (субъекта доступа) над ресурсами системы (объект доступа).
А также модель контроля доступа — общая схема для разграничения доступа по средствам пользовательской политики, которую мы выбираем в зависимости от различных факторов и требований к системе.
Давайте рассмотрим основные модели контроля доступа:
DAC (Discretionary access-control) — избирательное (дискреционное) управление доступом
Данная парадигма позволяет пользователям самостоятельно передавать право на какие-либо действия над его данными другим участникам системы, для чего используются списки контроля доступа (ACL).
Наиболее распространено применение в случаях, когда пользователи непосредственно владеют некими ресурсами и могут самостоятельно решать кому позволять взаимодействие с ними.
Примером могут служить операционные системы или социальные сети, где люди самостоятельно меняют видимость их контента.
MAC (Mandatory access-control) — мандатное управление доступом
Была разработана в государственных целях с акцентом на применение в чрезвычайно защищенных системах (например, военных), где и получила наибольшее распространение.
Защита данных основана на метках конфиденциальности (уровень секретности или важности), с помощью которых происходит проверка наличия уровня доступа у субъектов. Характерным также является централизованная выдача прав управляющим органом.
Пожалуй, MAC одна из самых строгих и безопасных моделей, но с этим связана сложность и высокую стоимость реализации и поддержания инфраструктуры вокруг этого решения (есть множество способов, требующих тщательного планирования).
RBAC (Role-Based access-control) — управление доступом на основе ролей
Наиболее распространенная и многим известная модель, которая хорошо накладывается на предметные бизнес-области и коррелирует с должностными функциями. Является неким развитием DAC, где привилегии группируются в соответствующие им роли.
Каждый субъект может обладать перечнем ролей, где роль в свою очередь может предоставлять доступ к некому перечню объектов.
Следует отметить, что в рамках RBAC иногда выделяют PBAC (Permission-Based access-control) модель контроля доступа на основе разрешений, когда для каждого ресурса системы выделяется набор действий (например: READ_DOCUMENT
, WRITE_DOCUMENT
, DELETE_DOCUMENT
) и связывают с субъектом через соотношение с ролями, напрямую с пользователем или гибридным подходом — где субъект может обладать ролью и отдельными привилегиями.
ABAC (Attribute-Based access-control) — управление доступом на основе атрибутов
В данном подходе необходимо ведение специальных политик, которые объединяют атрибуты субъектов и объектов, а решение о допуске предоставляется на основе анализа и сравнительной оценки этих атрибутов.
Это наиболее гибкий из описанных подходов с огромным количеством возможных комбинаций, который позволяет принимать решения на основе таких параметров, как время запроса, местоположение, должность сотрудника и т.п., но требует более детального планирования политик для предотвращения несанкционированного доступа.
Для применения ABAC требуется некий механизм интерпретации политик и некого синтаксического подмножества, что может влечь за собой затраты времени исполнения (в случае динамической реализации) или компиляции (при генерации кода).
Подробнее о некоторых из них можно почитать в материалах OWASP (Open Web Application Security Project) и в документации IBM.
Контроль доступа составляет очень важную часть веб приложений, поскольку необходимо строго соблюдать разграничение доступа к ресурсам и данным в зависимости от привилегий пользователей и в особенности персональным данным, защита которых предусмотрена законодательными аспектами.
Что мы имеем в веб-фреймворках на Rust?
Как правило, для реализации механизмов защиты от несанкционированного доступа в популярных веб-фреймворках (таких, как actix-web, Rocket или tide), используются реализации Middleware, FromRequest или Guard (Filter в случае warp).
То есть в неком промежуточном ПО, где из запросов можно извлечь данные о субъекте и объекте доступа. Такой подход довольно удобен, поскольку позволят разграничить зоны ответственности.
Это могут быть как библиотечные реализации в виде крейтов, так и пользовательские. Но на текущий момент, предпочтения отдают собственным реализациям, что вероятно связано с небольшим количеством готовых реализаций и спецификой применяемых политик в рамках различных проектов.
casbin-rs
Casbin — наиболее обширное production-ready решение с открытым исходным кодом, которое мне удалось найти, с внушительным количеством поддерживаемых моделей доступа (заявлены ACL, RBAC, ABAC) и возможностью гибкого изменения политики по средствам изменения только лишь конфигурационного файла.
В casbin используется своя мета-модель PERM (Policy, Effect, Request, Matchers) для построения модели доступа, что дает большую гибкость, но привносит затраты на ее интерпретацию и валидацию.
# Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))
# Matchers
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
При ее описании можно легко допустить ошибку, в связи с чем был разработан веб-редактор моделей для удобной и корректной модификации
Администрирование привилегий для вашей системы происходит через описание политики (в файле или базе данных), соответствующей формату PERM модели.
p, alice, data1, read
p, bob, data2, write
К сожалению, это вызывает определенное дублирование идентификаторов объектов и субъектов и неочевидность на уровне вызывающего кода.
use casbin::prelude::*;
#[tokio::main]
async fn main() -> () {
let mut e = Enforcer::new("examples/acl_model.conf", "examples/acl_policy.csv").await?;
e.enable_log(true);
let sub = "alice"; // the user that wants to access a resource.
let obj = "data1"; // the resource that is going to be accessed.
let act = "read"; // the operation that the user performs on the resource.
if let Ok(authorized) = e.enforce((sub, obj, act)) {
if authorized {
// permit alice to read data1
} else {
// deny the request
}
} else {
// error occurs
}
}
Такой инструмент определенно заслуживает уважения. Огромное спасибо сообществу, которое вносит свой вклад в его развитие!
Но, как мы можем наблюдать, разработчики учитывают определенные нюансы и отсюда вытекает стремление писать собственные решения из проекта в проект, поскольку требования могут быть детерминированы изначально, а вся предоставляемая гибкость может так и не понадобиться, и следовательно, мы вольны выбирать более узкую и легковесную реализацию, подходящую под наши требования.
Как это было и у меня, когда я взялся за написание backend на Rust. Мне было достаточно модели PBAC и исходя из своего опыта разработки веб-приложений, в большинстве типовых проектов достаточно моделей ACL/RBAC.
В связи с чем я пришел к идее реализации и вынесения собственного решения в качестве отдельного крейта с открытым исходным кодом: actix-web-grants.
actix-web-grants
Основная идея проекта состоит в использовании встроенной Middleware
для получения привилегий пользователей из запроса и указанию необходимых разрешений у пользователей непосредственно на ваших эндпоинтах.
Это довольно легковесный крейт с простым подключением, с использованием которого можно, как минимум, применять следующие модели: списки доступа (ACL), управление доступом на основе ролей или разрешений (RBAC/PBAC).
Таким образом, нам достаточно реализовать функцию получения привилегий:
// Sample application with grant protection based on extracting by your custom function
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let auth = GrantsMiddleware::with_extractor(extract);
App::new()
.wrap(auth)
.service(index)
})
.bind("localhost:8081")?
.run()
.await
}
async fn extract(_req: &ServiceRequest) -> Result, Error> {
// Here is a place for your code to get user permissions/grants/permissions from a request
// For example from a token or database
// Stub example
Ok(vec![ROLE_ADMIN.to_string()])
}
Данный подход добавляет гибкости и позволяет нам реализовывать авторизацию вне зависимости от способов аутентификации и хранения привилегий пользователей: это может быть JWT-токен, база данных, промежуточный кэш или любое другое решение.
После чего мы можем расставлять ограничения непосредственно над нашими ресурсами:
use actix_web_grants::proc_macro::{has_roles};
#[get("/secure")]
#[has_roles("ROLE_ADMIN")]
async fn macro_secured() -> HttpResponse {
HttpResponse::Ok().body("ADMIN_RESPONSE")
}
Возможность влиять на политику доступа напрямую в коде является отличительной частью actix-web-grants, снижая дублирование объектов доступа и предоставляя нам наглядную информацию о необходимых привилегиях.
Для полноты картины, написаны минимальные примеры приложений с идентичным профилем использования и проведены замеры производительности процесса авторизации (на базе wrk) для удовлетворения собственного интереса.
Примеры написаны с упрощенной реализацией модели RBAC для двух тест-кейсов авторизации: запрос к ресурсу разрешен и отклонен, в соответствие с наличием необходимых ролей. Для аутентификации использовались заглушки. Весь код опубликован на GitHub: actix-web-authz-benchmark (больше примеров всегда можно найти на страницах самих проектов).
Результаты бенчмарка можете наблюдать в таблице:
| casbin-rs | actix-web-grants | ||
Latency | Req/Sec | Latency | Req/Sec | |
Allowed Endpoint | 6.18 ms | 16.27k | 4.41 ms | 22.69k |
Denied Endpoint | 6.70 ms | 14.98k | 4.94 ms | 20.23k |
rustc: v1.52.0 (stable); CPU: 2,6 GHz 6-Core Intel Core i7; RAM: 16 GB
Таким образом, мы видим, что actix-web-grants позволяет более просто интегрировать и администрировать политики доступа над конечным точками (endpoints), при этом не уступает в производительности по сравнению с casbin-rs.
Post Scriptum
Данная библиотека пока не имеет в своём арсенале интеграций с множеством веб-фреймворков, но у меня есть планы по вынесению некоторых абстракций и написанию модулей под другие фреймворки, внесению некоторых улучшений (например, возможность наследования ролей и поддержки пользовательских типов). Буду рад любым предложениям и вкладу!