Для обеспечения безопасности приложений мы используем такие механизмы как аутентификация и авторизация. Думаю, многие из вас знакомы с этими концепциями и в этой статье мы сфокусируемся на понятие авторизации и связанных с ней моделях контроля доступа.
Определения терминов, которые используются в статье
Важно понимать отличия авторизации от аутентификации:
Аутентификация – процесс подтверждения вашей личности и доказательства того, что вы являетесь непосредственным клиентом системы (посредством пароля, токена или любой другой формы учетных данных).
Авторизация в свою очередь – это механизм, в результате которого запрос к определенному ресурсу системы должен быть разрешен или отклонен.
Субъект доступа – пользователь или процесс, который запрашивает доступ к ресурсу.
Объект доступа – напротив, является ресурсом, к которому запрашивается доступ со стороны субъекта.
Крейт (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<Vec<String>, 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
Данная библиотека пока не имеет в своём арсенале интеграций с множеством веб-фреймворков, но у меня есть планы по вынесению некоторых абстракций и написанию модулей под другие фреймворки, внесению некоторых улучшений (например, возможность наследования ролей и поддержки пользовательских типов). Буду рад любым предложениям и вкладу!
Katrychenko
Каждый год появляется новый супер фреймворк на котором всем срочно нужно научиться писать. А я все ещё успешно пишу на чистом php. Уже 12 лет
drunkmowgli
На мой взгляд, данная статья не несет в себе никакого призыва к использованию того или иного решения, она лишь освещает тему авторизации и одного из самых популярных решений для ее реализации, а так же возможной альтернативы при использовании Rust в качестве ЯПа.
1ntrovert
Сочувствую
Kazikus
Казалось бы при чём здесь rust. Кто-то и на С пишет значительно больше, чем 12 лет. Это не повод ограничить себя одним php, конечно, если программирование чуть больше, чем просто деньги. Для общего развития всегда полезно посмотреть как живут другие люди. Попробовать на реальных задачах применение разных идей и концепций (clojure, rust, ocaml, haskell).
alexmixaylov
Я знаю одного программиста который пишет на чистом PHP и ненавидит фреймворки и не пользуется библиотеками тоже.
Все пишет сам и это ужасно. Просто нет слов. Не посчастливилось согласится на один проект, а потом выяснилось, что он ведущий разработчик и диктует правила. Короче печаль....