Authorization and Cedar: A New Way to Manage Permissions - Part II

pigius

Daniel Aniszkiewicz

Posted on June 26, 2023

Authorization and Cedar: A New Way to Manage Permissions - Part II

Welcome back to our series on authorization and Cedar! In our previous article, we introduced the concept of authorization, the difference between Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC), and the basics of Cedar, an open-source policy language developed by AWS. We also started to explore how we could apply Cedar to an e-commerce platform scenario.

In this article, we're going to dive deeper into our e-commerce scenario, and we'll start to map our resources, actions, and principals to Cedar. We'll also link to a GitHub repository with a Rust server that you can download and use to follow along. So, grab a cup of coffee, and let's get started!

E-commerce Scenario: A Quick Recap

Let's quickly recap our e-commerce scenario. We have an online platform with several types of users (principals) such as customers, sellers, and administrators. These users perform various actions like viewing products, placing orders, managing inventory, and handling customer service issues. The objects they interact with (resources) include product listings, orders, customer profiles, and more.

Here's a simple diagrams to illustrate the hierarchy:

Principals

Actions

Resources

In this scenario, we need to control who can do what to which resources. For example, a customer should be able to view products and place orders, but they shouldn't be able to modify product listings. A seller can manage their own product listings and view orders for their products, but they can't view other sellers' product listings or orders. An administrator can view and manage all resources.

Mapping to Cedar

Now, let's see how we can map these to Cedar. In Cedar, the authorization decision is based on four main components: principal, action, resource, and context.

Principal: The entity that is making the request. In our scenario, the principal could be a customer, a seller, or an administrator.
Action: What the principal is trying to do. This could be viewing a product, placing an order, or managing a product listing.
Resource: The object that the action is being performed on. This could be a product listing, an order, or a customer profile.
Context: Additional information that can be used in the decision. This could include the time of the request, the location of the principal, or other relevant details.
For example, a policy might state that a customer (principal) can view a product (action) if the product is in stock (resource attribute) and the customer is shopping during store hours (context).

Writing Cedar Policies

In Cedar, policies are written in a simple, human-readable language. They consist of a decision (permit or deny), a condition, and an optional when clause that specifies additional conditionsHere's the continuation of the corrected article:

Here's the general structure of a Cedar policy:



decision(condition) when {additional conditions};


Enter fullscreen mode Exit fullscreen mode

Let's start by writing a policy that permits all customers to view products.



permit(principal == "UserType::Customer", action == "ActionType::View", resource == "ResourceType::Product");


Enter fullscreen mode Exit fullscreen mode

This policy permits the action if the principal is a customer and the action is to view a product. Note that we're using the == operator to compare the attributes of the principal, action, and resource to specific values.

Next, let's write a policy that permits customers to place orders:



permit(principal == "UserType::Customer", action == "ActionType::Create", resource == "ResourceType::Order");


Enter fullscreen mode Exit fullscreen mode

This policy permits the action if the principal is a customer and the action is to create an order.

We can also write policies that deny certain actions. For example, let's write a policy that denies customers from editing products.



forbid(principal == "UserType::Customer", action == "ActionType::Edit", resource == "ResourceType::Product");


Enter fullscreen mode Exit fullscreen mode

This policy denies the action if the principal is a customer and the action is to edit a product.

Authorization Decision System

To bring our Cedar policies to life and see them in action, I've created a basic authorization decision service for Cedar policies in Rust. Don't worry if you don't know Rust, we will communicate with server over HTTP protocol. The repository could be found here (along with instructions). Feel free to clone it and run it.

This server is designed to evaluate Cedar policies and make authorization decisions based on them. It's a playground for us to experiment with different policies and see how they affect the authorization decisions. Please do remember that there is an official playground provided by Cedar.

It's worth mentioning, that Cedar itself is written in Rust. Cedar has a Java bindings, and it provides a Foreign Function Interface (FFI) that allows to write bindings for different languages. To be in the loop it's worth to check Github of the Cedar.

There is also open source project called cedar-agent to make life easier when working with Cedar policies.

Playing Around with the Authorization Server

Now that we have our Rust authorization server up and running, let's see it in action. We'll use curl to send HTTP POST requests to the server, and it will respond with authorization decisions based on the Cedar policies we've defined. We just need to send our cedar policies, and cedar components to validate it against defined policies.

Let's start with a simple example. Suppose we have a user of type customer who wants to view a product (based on our allow policy):



permit(principal == "UserType::Customer", action == "Action::View", resource == "Resource::Product");


Enter fullscreen mode Exit fullscreen mode

And here's the corresponding curl command:



curl -X POST -H "Content-Type: application/json" -d '{
    "policies": ["permit(principal == UserType::\"Customer\", action == Action::\"View\", resource == Resource::\"Product\");"],
    "resources": ["Resource::\"Product\""],
    "action": "Action::\"View\"",
    "principal": "UserType::\"Customer\"",
    "context": {}
}' "http://localhost:8080/evaluate"


Enter fullscreen mode Exit fullscreen mode

The server will respond with an authorization decision. If everything is set up correctly, it should allow the action.The output will be:



{"message":"Authorization decision: allow."}


Enter fullscreen mode Exit fullscreen mode

Now let's check if can edit the Product (based on our deny policy):



curl -X POST -H "Content-Type: application/json" -d '{
    "policies": ["forbid(principal == UserType::\"Customer\", action == Action::\"Edit\", resource == Resource::\"Product\");"],
    "resources": ["Resource::\"Product\""],
    "action": "Action::\"Edit\"",
    "principal": "UserType::\"Customer\"",
    "context": {}
}' "http://localhost:8080/evaluate"


Enter fullscreen mode Exit fullscreen mode

The output will be:



{"message":"Authorization decision: deny."}


Enter fullscreen mode Exit fullscreen mode

Context to the Rescue!

Now let's try a more complex example. Suppose we have a policy that only allows customers to purchase a product if they have a premium membership. We can do it easily with context included into the policy.



permit(principal == "UserType::Customer" and context.membership == "Premium", action == "Action::Purchase", resource == "Resource::Product");


Enter fullscreen mode Exit fullscreen mode

The curl to test that:



curl -X POST -H "Content-Type: application/json" -d '{"policies": ["permit(principal == UserType::\"Customer\", action == Action::\"Purchase\", resource == Resource::\"Product\") when {context.membership == \"Premium\"};"],
    "resources":["Resource::\"Product\""],
    "action": "Action::\"Purchase\"",
    "principal": "UserType::\"Customer\"",
    "context": {"membership": "Premium"}
}' "http://localhost:8080/evaluate"


Enter fullscreen mode Exit fullscreen mode

The output will be:



{"message":"Authorization decision: allow."}

Enter fullscreen mode Exit fullscreen mode




Why semi-colon?

If you're wondering why do Cedar policies end with a semi-colon, here is the explanation.

Next steps

I invite you to play-around with this simple server, and try to use different scenarios. It will not embrace every usecase, but for a starting point with Cedar I think it is very good.

In the next article we will make an introduce to Amazon Verified Permissions! Stay tuned!

💖 💪 🙅 🚩
pigius
Daniel Aniszkiewicz

Posted on June 26, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related