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

Определения терминов, которые используются в статье

      Важно понимать отличия авторизации от аутентификации:

Аутентификация – процесс подтверждения вашей личности и доказательства того, что вы являетесь непосредственным клиентом системы (посредством пароля, токена или любой другой формы учетных данных).

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

Субъект доступа – пользователь или процесс, который запрашивает доступ к ресурсу.

Объект доступа – напротив, является ресурсом, к которому запрашивается доступ со стороны субъекта.

Крейт (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 (больше примеров всегда можно найти на страницах самих проектов).

Результаты бенчмарка можете наблюдать в таблице:


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

Данная библиотека пока не имеет в своём арсенале интеграций с множеством веб-фреймворков, но у меня есть планы по вынесению некоторых абстракций и написанию модулей под другие фреймворки, внесению некоторых улучшений (например, возможность наследования ролей и поддержки пользовательских типов). Буду рад любым предложениям и вкладу!