DEV Community

Cover image for Unveiling Permify ABAC: A New Dimension to Attribute-Based Access Control
Ege Aytin for Permify

Posted on • Originally published at permify.co

Unveiling Permify ABAC: A New Dimension to Attribute-Based Access Control

Today, we are thrilled to announce the launch of our Attribute-Based Access Control (ABAC) feature. This new addition brings a whole new level of granularity to our authorization system, allowing for more precise and flexible access control.

At Permify, we are always on the lookout for ways to enhance our open-source authorization service, making it more robust and adaptable to the diverse needs of engineering teams.

But first start with explaining what is ABAC: Attribute-Based Access Control (ABAC) is like a security guard that decides who gets to access what based on specific characteristics or "attributes".

These attributes can be associated with users, resources, or the environment, and their values can influence the outcome of an access request.

Let’s make an analogy, it’s the best of way to understanding complex ideas.

Think about an amusement park, and there are 3 different rides. In order to access each ride, you need to obtain different qualities. For the;

  1. first ride you need to be over 6ft tall.
  2. second ride you need to be under 200lbs.
  3. third ride you need to be between 12 - 18 years old.

Similar to this ABAC check certain qualities that you defined on users, resources, or the environment.

Why Would Need ABAC?

It’s obvious but simple answer is “use cases”… Sometimes, using ReBAC and RBAC isn't the best fit for the job. It's like using winter tires on a hot desert road, or summer tires in a snowstorm - they're just not the right tools for the conditions.

  1. Geographically Restricted: Think of ABAC like a bouncer at a club who only lets in people from certain towns. For example, a movie streaming service might only show certain movies in certain countries because of rules about who can watch what and where.
  2. Time-Based: ABAC can also act like a parent setting rules about when you can use the computer. For example, a system might only let you do certain things during office hours.
  3. Compliance with Privacy Regulations: ABAC can help follow rules about privacy. For example, a hospital system might need to limit who can see a patient's data based on the patient's permission, why they want to see it, and who the person is.
  4. Limit Range: ABAC can help you create a rules defining a number limit or range. For instance, a banking system might have limits for wiring or withdrawing money.
  5. Device Information: ABAC can control access based on attributes of the device, such as the device type, operating system version, or whether the device has the latest security patches.

As you can see ABAC has more contextual approach. You can define access rights regarding context around subject and object in an application.

Permify Authorization in a Nutshell?

Before diving deep into the ABAC feature let’s summarize how existing authorization works at Permify.

Permify has a domain specific language for defining your authorization model. And it helps you create hierarchies via establishing relationships between objects and subjects in your application.

For instance, in Google Drive you have document editor that allows a user to edit the given document. Assume user James want to edit document called Blog Post in order to edit James has to have editor relation for Blog Post.

Here’s how you can create the same system in Permify;

Entity user {}

Entity docs {
    relation editor @user
    relation viewer @user

    permission edit = editor
    permission view = editor or viewer  
}
Enter fullscreen mode Exit fullscreen mode

Introducing New Key Elements

To support ABAC in Permify, we've added two main components into our DSL: attributes and rules.

Attributes

Attributes are used to define properties for entities in specific data types. For instance, an attribute could be an IP range associated with an organization, defined as a string array:

attribute ip_range string[]
Enter fullscreen mode Exit fullscreen mode

There are different types of attributes you can use;

Boolean

For attributes that represent a binary choice or state, such as a yes/no question, the Boolean data type is an excellent choice.

entity post {
    attribute is_public boolean

    permission view = is_public
}
Enter fullscreen mode Exit fullscreen mode

⛔ If you don’t create the related attribute data, Permify accounts boolean as FALSE

String

String can be used as attribute data type in a variety of scenarios where text-based information is needed to make access control decisions. Here are a few examples:

  • Location: If you need to control access based on geographical location, you might have a location attribute (e.g., "USA", "EU", "Asia") stored as a string.
  • Device Type: If access control decisions need to consider the type of device being used, a device type attribute (e.g., "mobile", "desktop", "tablet") could be stored as a string.
  • Time Zone: If access needs to be controlled based on time zones, a time zone attribute (e.g., "EST", "PST", "GMT") could be stored as a string.
  • Day of the Week: In a scenario where access to certain resources is determined by the day of the week, the string data type can be used to represent these days (e.g., "Monday", "Tuesday", etc.) as attributes!
entity user {}

entity organization {

    relation admin @user

    attribute location string[]

    permission view = check_location(request.current_location, location) or admin
}

rule check_location(current_location string, location string[]) {
    current_location in location
}
Enter fullscreen mode Exit fullscreen mode

⛔ If you don’t create the related attribute data, Permify accounts string as EMPTY

Integer

Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples:

  • Age: If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access.
  • Security Clearance Level: In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance).
  • Resource Size or Length: If access to resources is controlled based on their size or length (like a document's length or a file's size), these can be stored as integer attributes.
  • Version Number: If access control decisions need to consider the version number of a resource (like a software version or a document revision), these can be stored as integer attributes.
entity user {
    attribute age integer

    permission view = check_age(age)
}

entity content {
    relation user @user
    attribute budget double

    permission view = user.view
}

rule check_age(age integer) {
    age >= 18
}
Enter fullscreen mode Exit fullscreen mode

⛔ If you don’t create the related attribute data, Permify accounts integer as 0

Double

Double can be used as attribute data type in several scenarios where precise numerical information is needed to make access control decisions. Here are a few examples:

  • Usage Limit: If a user has a usage limit (like the amount of storage they can use or the amount of data they can download), and this limit needs to be represented with decimal precision, it can be stored as a double attribute.
  • Transaction Amount: In a financial system, if access control decisions need to consider the amount of a transaction, and this amount needs to be represented with decimal precision (like $100.50), these amounts can be stored as double attributes.
  • User Rating: If access control decisions need to consider a user's rating (like a rating out of 5 with decimal points, such as 4.7), these ratings can be stored as double attributes.
  • Geolocation: If access control decisions need to consider precise geographical coordinates (like latitude and longitude, which are often represented with decimal points), these coordinates can be stored as double attributes.
entity user {}

entity account {
    relation owner @user
    attribute balance double

    permission withdraw = check_balance(request.amount, balance) and owner
}

rule check_balance(amount double, balance double) {
    (balance >= amount) and (amount <= 5000)
}
Enter fullscreen mode Exit fullscreen mode

⛔ If you don’t create the related attribute data, Permify accounts double as 0.0

Rule

Rules are structures that allow you to write specific conditions for the model. They accept parameters and are based on conditions. For example, a rule could be used to check if a given IP address falls within a specified IP range:

rule check_ip_range(ip string, ip_range string[]) {
    ip in ip_range
}
Enter fullscreen mode Exit fullscreen mode

Modeling ABAC

Let's take a look at how ABAC can be implemented in Permify using a practical example. Suppose we have an organization entity with an admin relation to a user entity and an IP range attribute. We can define a permission rule for viewing the organization based on the check_ip_range rule or the admin relation:

entity organization {
    relation admin @user
    attribute ip_range string[]
    permission view = check_ip_range(request.ip_address, ip_range) or admin
}
Enter fullscreen mode Exit fullscreen mode

The 'context' within the request refers to the context of the request. Any type of data can be added from within the request and can be called within the model. For instance:

"context": {
    "ip_address": "187.182.51.206",
    "day_of_week": "monday"
}
Enter fullscreen mode Exit fullscreen mode

Real Life Examples

Let’s discover a couple of real life examples reinforce what we have learned.

Mercury Approvals

For those who don’t know, Mercury is a bank specifically designed for startups, offering both checking and savings accounts, complete with debit and credit card features. Given the delicate nature of financial transactions, Mercury has built-in access control features to ensure security.

But today we’re going to focus on approvals. Mercury allows it’s users to set a number amount for multiple user approval for any action.

For instance, an admin can decide that withdrawals above $1000 by members require approval from two designated approvers. This means, if a member wants to withdraw more than $1000, they need a green light from two admin. And if an admin tries to withdraw they need an approval form another admin.

  • Admin → Withdraw $1000 → needs an approver
  • Member → Withdraw $1000 → needs 2 approvers.

So let’s start with building basics. We need Users, Organization, Accounts both Savings and Deposits as entities in the mercury

entity user {}

entity organization {

}

entity teams {

}

entity accounts {

}
Enter fullscreen mode Exit fullscreen mode

Then inserting relations into these entities.

entity user {}

entity organization {
    relation admin @user
    relation member @user
}

entity accounts {
    relation checkings @accounts
    relation savings @accounts

    relation org @organization
}
Enter fullscreen mode Exit fullscreen mode

Next step is to define actions in our use case.

entity user {}

entity organization {
    relation admin @user
    relation member @user
}

entity account {

    relation checkings @account
    relation savings @account

    relation org @organization

    action withdraw =

}
Enter fullscreen mode Exit fullscreen mode

Now we need to define our attributes which will help us create access rights via Withdraw Limit and Admin Approval of the account.

Every organization has a set withdrawal limit. Additionally, for members and admins of the organization, there are specific approval limits in place when they attempt to withdraw amounts exceeding this limit.

entity user {}

entity organization {
    relation admin @user
    relation member @user
}

entity account {

    relation checkings @account
    relation savings @account

    relation org @organization

    attirbute approval integer
    attribute balance double

    action withdraw =

}
Enter fullscreen mode Exit fullscreen mode

Rules.jpg

Let’s create our rules that defines our attribute-based access rights.

  • Balance of the account must be more than withdraw amount
  • If withdraw amount is less than the withdraw limit we don’t need approval
  • Else; we need approve of two admins if we’re member, and we need approve of single admin if we’re another admin.
entity user {}

entity organization {
    relation admin @user
    relation member @user
}

entity account {

    relation checkings @account
    relation savings @account

    relation org @organization

    attirbute approval integer
    attribute balance double

    action withdraw = check_balance(request.amount, balance) and (check_limit(request.amount, request.withdraw_limit) or (org.admin and check_approval(approval, request.approval_limit)) or (org.member and check_approval(approval, request.approval_limit)))
}

rule check_balance (amount double, balance double) {
    amount =< balance
}

rule check_limit (amount double, withdraw_limit double){
    amount =< withdraw_limit
}

rule check_approval (approval integer, approval_limit integer) {
    approval >= approval_limit
}
Enter fullscreen mode Exit fullscreen mode

At last, as you can see we use the Rules to define access rights to withdraw which basically translates into;

  • Check balance if it’s over the withdraw amount. If not don’t allow the action.
  • Check withdraw limit; if it’s less than the limit allow the action…
  • Else;
    • Check if user is admin, and have approval more than the approval limit for admins.
    • Check if user is member, and have approval more than the approval limit for members.

Instagram Private Profile

Let’s start with our first example to understand Permify ABAC. As you perhaps know, almost every social media app out there have private/public profile settings. For instance, on instagram you can turn your profile into a private profile in which only your followers can view, like or comment on your posts.

Or even we can go further, and setup an authorization rule that only allow people you follow to comment on a certain post but your followers.

Let’s start with the building blocks; we have users, accounts, and post.

entity user {}

entity account {

}

entity post {

}
Enter fullscreen mode Exit fullscreen mode

Now we established the core, let’s fill out the relationships we have on the instagram app.

entity user {}

entity account {
    //users have accounts
    relation owner @user

    //accounts can follow other users/accounts.
    relation following @user

    //other users/accounts can follow account.
    relation follower @user

}

entity post {
    //posts are linked with accounts.
    relation owner @account

}
Enter fullscreen mode Exit fullscreen mode

Instagram Attribute.jpg

Let’s establish our attributes. We want two main functions;

  • Define wether our account is private or public.
  • Only allow people you follow to comment on a certain posts.
entity user {}

entity account {
    //users have accounts
    relation owner @user

    //accounts can follow other users/accounts.
    relation following @user

    //other users/accounts can follow account.
    relation follower @user

    //accounts can be private or public.
    attribute private boolean

}

entity post {
    //posts are linked with accounts.
    relation owner @account

    //comments are limited to people followed by the parent account.
    attribute public_comment boolean

}
Enter fullscreen mode Exit fullscreen mode

Next step is to structure our permissions, or in other words who can take which actions. In Permify this is generally defined by the relations between objects and subjects.

For instance, if user: Ryu is the owner of post:1, Ryu can delete post:1

As we established, with the ABAC update, you can use Attributes or Rules like relations.

entity user {}

entity account {
    //users have accounts
    relation owner @user

    //accounts can follow other users/accounts.
    relation following @user

    //other users/accounts can follow account.
    relation follower @user

    //accounts can be private or public.
    attribute private boolean

    //Users can view an account if they're followers, owners, or if the account is not private.
    action view = (owner or follower) not private

}

entity post {
    //posts are linked with accounts.
    relation owner @account

    //comments are limited to people followed by the parent account.
    attribute restricted boolean

    //users can view the posts, if they have access to view the linked accounts.
    action view = owner.view

    //users can comment on unrestricted posts or posts by owners who follow them.
    action comment = owner.following not restricted

}
Enter fullscreen mode Exit fullscreen mode

Disney+ Locations

Disney's streaming service, Disney+, has many access control features, but we'll focus on filtering content by region and age. Disney+ provides a wide array of content, but some may be off-limits depending on your location and age.

The most straightforward access control feature is age-based.

For instance, if content is rated +18 for violence and your profile age is 16, you can't access it. Even if another profile on the same account is +18.

We can further refine this by adding regions, which can bring different access control scenarios. We can create a rule where the selected region must align with the content's allowed regions, adding complexity to content filtering.

We can also establish an access control layer that requires matching the IP address range with the chosen region, ensuring genuine regional access.

Let’s start with establishing basic block; account, profiles, content, and watch-list.

entity user {}

entity account {

}

entity profile {

}

entity watch-list {

}

entity content {

}
Enter fullscreen mode Exit fullscreen mode

Simply explain; users have accounts, accounts will have multiple or single profile, in each profile you can create a watch-list and add different content into the watch-list.

In order to do these let’s create relations;

entity user {}

entity account {
    //users own an account.
    relation owner @user
}

entity profile {
    //Accounts can own a multiple profiles.
    relation owner @account

}

entity watch_list {
    //each profile can own a multiple watch-list.
    relation owner @profile


}

entity content {
    //contents can be a part of watch_lists
    relation watch-list @watch_list
}
Enter fullscreen mode Exit fullscreen mode

Next step is to define our rules and attributes to create an extra layer of access control we have been describing.

entity user {}

entity account {
    //users own an account.
    relation owner @user

    action create_profile = owner
}

entity profile {
    //Accounts can own a multiple profiles.
    relation owner @account

    action edit = owner 
    action delete = owner
    action create_watchlist = owner 

}

entity watchlist {
    //each profile can own a multiple watch-list.
    relation owner @profile

    action edit = owner
    action delete = owner
    action remove_content = owner

}

entity content {
    //contents can be a part of watch_lists
    relation watchlist @watchlist
    relation user @profile

    attribute age_rating integer
    attribute allowed_region string[]

    action view = check_age(request.age, age_rating) and check_region(request.region, allowed_region) and check_ip (request.ip, user.region_ip)
    action add_watchlist = user 

}

rule check_age (age integer, age_rating integer) {
    age >= age_rating
}

rule check_region (region string, allowed_region string) {
    region in allowed_region
}

rule chekck_ip (ip string, region_ip string) {
    ip in region_ip
}
Enter fullscreen mode Exit fullscreen mode
entity user {}

entity account {
    //users own an account.
    relation owner @user

    attribute region string[]
    attribute region_ip string[]

    action create_profile = owner
}

entity profile {
    //Accounts can own a multiple profiles.
    relation owner @account

    attribute age integer
    attribute region string[]
    attribute region_ip string[]

    action edit = owner 
    action delete = owner
    action create_watchlist = owner 

}

entity watchlist {
    //each profile can own a multiple watch-list.
    relation owner @profile

    action edit = owner
    action delete = owner
    action remove_content = owner

}

entity content {
    //contents can be a part of watch_lists
    relation watchlist @watchlist
    relation user @profile

    attribute age_rating integer
    attribute allowed_region string[]

    action view = check_age(user.age, ager_rating) and check_region(user.region, allowed_region) and check_ip (request.ip, user.region_ip)
    action add_watchlist = user 

}

rule check_age (age integer, age_rating integer) {
    age >= age_rating
}

rule check_region (region string, allowed_region string) {
    region in allowed_region
}

rule chekck_ip (ip string, region_ip string) {
    ip in region_ip
}
Enter fullscreen mode Exit fullscreen mode

The Power of ABAC

The introduction of ABAC in Permify opens up a world of possibilities for platform engineering teams. It allows for more granular control over access rights, enabling teams to create more sophisticated and flexible authorization systems. With ABAC, you can define access control rules based on any attribute associated with a user or resource, providing a high level of customization and precision.

We are excited about the potential of our new ABAC feature and look forward to seeing how it will be utilized to create more robust and flexible access control systems. As always, we are eager to hear your thoughts and feedback. Happy authorizing!

Top comments (0)