Posted on

Table of Contents

Introduction

To ensure application security, we use mechanisms such as authentication and authorization. I think many of you are familiar with these concepts and in this article we will focus on the concept of authorization and related access control models.

security

Definitions of terms used in the article

It's important to understand the difference between authorization and authentication:

Authentication – a process of verifying your identity and proving that you are a user of the system (by means of a password, token or any other form of credentials).

Authorization - a mechanism whose task is to allow or deny a request for a specific system resource.

Access subject – a user or process that is requesting access to the resource.

Access object – on the contrary, it's a resource to which access is requested by the subject.

Crate – a library or executable (binary) program in Rust.


The authorization process includes the concept of access control policy, in accordance with which the set of permissible actions of a particular user (access subject) over the system resources (access objects) is determined.

And also the access control model is a general scheme for delimiting access through a user policy, which we choose depending on various factors and system requirements.

Access Control Models

Let's take a look at the basic access control models:

  • DAC - Discretionary access-control
Discretionary access-control

This paradigm allows users to independently grant the right to any action on their data to other system participants, for which access control lists (ACL) are used.

Most often used in cases where users directly own certain resources and can independently decide who to allow interaction with them.

An example would be operating systems or social networks, where people independently change the visibility of their content.

  • MAC - Mandatory access-control
Discretionary access-control

It was developed for government purposes with a focus on application in extremely secure systems (for example, military), where it was most widespread.

Data protection is based on confidentiality labels (level of secrecy or importance), through which the level of access of subjects is checked. As a rule, the rights are issued centrally by the management body.

MAC is perhaps one of the most rigorous and secure models, but it comes with the complexity and high cost of implementing and maintaining the infrastructure around it (there are many ways that require careful planning).

  • RBAC - Role-Based access-control

The most common and well-known model that fits well with business domains and correlates with job functions. It is a kind of development of DAC, where privileges are grouped into their respective roles.

Each subject can have a list of roles, where the role, in turn, can provide access to a certain list of objects.

It should be noted that in RBAC the PBAC (Permission-Based access-control) model is sometimes allocated when a set of actions is allocated for each resource in the system (for example: READ_DOCUMENT,WRITE_DOCUMENT, DELETE_DOCUMENT) and bind it with the subject through the relationship with roles, directly with the user, or a hybrid approach, when the subject can have a role and separate privileges.

  • ABAC - Attribute-Based access-control

Discretionary access-control

In this approach, it's necessary to maintain special policies that combine the attributes of subjects and objects, and the access decision is provided based on the analysis and comparison of these attributes.

This is the most flexible of the described models with a huge number of possible combinations, which allows making decisions based on such parameters as request time, location, employee position, etc., but requires more detailed planning of policies to prevent unauthorized access.

ABAC requires some mechanism for interpreting policies and some syntactic subset, which can entail execution time (in the case of a dynamic implementation) or compilation (in the case of code generation).

You can read more about some of the models in OWASP materials (Open Web Application Security Project) and in IBM documentation.

Access control is a very important part of web applications, since it is necessary to strictly observe the delimitation of access to resources and data (especially personal ones - the protection of which is provided for by legislative aspects), depending on the privileges of users.


What do we have in Rust web frameworks?

Typically, to implement anti-tampering mechanisms in popular web frameworks (such as actix-web, Rocket, or tide), Middleware, FromRequest, or Guard (Filter in the case of warp) implementations are used.

That is, in some kind of middleware, where data about the subject and object of access can be extracted from requests. This approach is quite convenient, since it will allow you to delimit areas of responsibility.

It can be both library (in the form of crates), and custom implementations. But at the moment, the preference is often given to own implementations, which is probably due to the small number of production-ready solutions and the specifics of the applied policies in various projects.

casbin-rs

casbin-rs

GitHub last commit Crates.io crates.io Docs CI Codecov Gitter forum

The most complete production-ready open source solution that I have been able to find is the adaptation of Casbin (casbin-rs), with an impressive number of supported access models (ACL, RBAC, ABAC declared) and the ability to flexibly change policy by changing only the configuration file ...

Casbin uses its own meta-model PERM (Policy, Effect, Request, Matchers) to build an access model, which gives more flexibility, but introduces the cost of its interpretation and validation.

# 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

When describing it, you can easily make a mistake, and therefore the web editor of models was developed for convenient and correct modification.

The administration of privileges for your system occurs through the description of the policy (in a file or database) corresponding to the PERM model format.

p, alice, data1, read
p, bob, data2, write

Unfortunately, this causes a certain duplication of object and subject identifiers and is not obvious at the level of the calling code.

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
    }
}

Such a tool definitely deserves respect. Many thanks to the community for contributing to its development 👏 !

But, as we can see, developers take into account certain nuances and sometimes want to write their own solutions from project to project, since the requirements can be defined initially, and all the flexibility provided by the library may not be needed, and therefore, we can choose a narrower and lighter solution. that meets our requirements.

As it was with me when I started writing a backend in Rust. The PBAC model was enough for me, and based on my experience in developing web applications, in most typical projects, the ACL/RBAC models are enough.

I came up with the idea of implementing my own solution as a separate open source crate: actix-web-grants.

actix-web-grants

actix-web-grants

CI Crates.io Downloads Badge crates.io Documentation dependency status Apache 2.0 or MIT licensed

The main idea of the project is to use built-in middleware to get user privileges from a request and specify the necessary permissions directly on your endpoints.

This is a fairly lightweight crate with simple integration, using which you can at least apply the following models: access lists (ACL), role-based or permission-based access control (RBAC/PBAC).

Thus, we just need to implement the function of obtaining privileges:

// 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 from a request
    // For example from a token or database
    
    // Stub example
    Ok(vec![ROLE_ADMIN.to_string()])
}

This approach adds flexibility and allows us to implement authorization regardless of the methods of authentication and storage of user privileges: it can be a JWT token, a database, an intermediate cache, or any other solution.

Then we can place restrictions directly on our resources (via macro):

use actix_web_grants::proc_macro::{has_roles};

#[get("/secure")]
#[has_roles("ROLE_ADMIN")]
async fn macro_secured() -> HttpResponse {
    HttpResponse::Ok().body("ADMIN_RESPONSE")
}

The ability to change to access policy directly in the code is a distinctive part of actix-web-grants, reducing duplication of access objects and providing us with visual information about the required privileges.

For the sake of completeness, minimal examples of applications with an identical usage profile were written and the performance of the authorization process was measured (based on wrk) to satisfy our own interest.

The examples are written with a simplified implementation of the RBAC model for two test cases of authorization: a request to a resource is allowed and denied, in accordance with the presence of the necessary roles. Stubs were used for authentication. All code is available on GitHub: actix-web-authz-benchmark (more examples can always be found on the pages of these projects).

The benchmark results can be seen in the table:

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

Thus, we see that actix-web-grants makes it easier to integrate and administer access policies over endpoints (endpoints), while not inferior in performance compared to casbin-rs.

Post Scriptum

This library does not yet have integrations with many web frameworks in its arsenal, but I have plans to introduce some abstractions and write modules for other frameworks, make some improvements (for example, the ability to inherit roles and support custom types). Any suggestions and contributions will be welcome!