Batteries-included Authorization with Oso.

01.12.2021 Raffael Schneider
Tech security auth access-control policy-engine Rust

When it comes to redesigning applications, security in is often a black box for many Fullstack \ developers, which is often left to someone else. However, Authorization is one of the core tasks of every user story. There are different ways of integrating Authorization in an application, but there, too, an in-house Customization is often chosen, as one is of the opinion that better control over critical access vectors can be achieved.

However, the requirements for Authorization are not rocket science, and the vectors are known in advance. For this reason, Graham Neray and Sam Scott have committed themselves to the development of a universal solution which should streamline the process and its implementation. The two are founders of the New York City-based Oso HQ and offer with the identically named product, stylized as oso, a basic technology that allows Authorization to be implemented specifically for application development.

Oso - Batteries-Included Authorization

Oso's slogan is “Ship RBAC fast - this is your forever framework” and, in its self-image, is a so-called “_ batteries-included framework for building authorization in your application_”.

Policies on Polar

One of the important features of Oso is its in-house declarative policy language, called Polar (the polar bear inside the oso logo is a direct allusion onto the language Polar). With this Policy-Language, policies - or, more colloquially, simply called rules - are declared in a text file and thus has the option of recording application-specific authorization in precisely this file, which in turn has further advantages such as traceability when used as a single source of truth is used in a GitOps pattern.

Polar also provides built-in primitives with which roles, relationships and hierarchies can be defined. Of course, you can use it to write your own, more specific rules that can map more complex relationships.

Allow anything

Let's start with our first polar rule. Here it is simply declared via allow() that all access types, users _actor, actions _action, and resources _resource are allowed.

# This rule matches all inputs, allowing
# any actor to perform any action on any
# resource. Not very useful...
allow(_actor, _action, _resource);

Empty

An empty policy means that the entire access system does not allow anything, as nothing is explicitly declared.

# Oso is deny-by-default, so an empty policy
# means nobody can do anything. This system is
# locked down.

Only public repos

With the next policy it will be declared that all users can execute the action "read" on all Repository \ resources, provided that this resource repository is considered public.

# Allow any user to perform the "read" action
# on a repository if it is public. Note that
# the delete button is disabled, because no
# one has permission to delete it.
allow(_actor, "read", repository: Repository) if
  repository.isPublic;

Basic RBAC

The next policy is a little more complex because it implements a full but simple RBAC. Take a look at the declarations yourself and try to understand what exactly is happening here.

actor User {}

# Now roles are involved -- users have roles
# on repositories, granting them permissions
# specific to each repository.
resource Repository {
  permissions = ["read", "delete"];
  roles = ["reader", "admin"];

  "delete" if "admin";
  "read" if "reader";

  "reader" if "admin";
}

has_role(actor: User, role_name: String, resource: Repository) if
  role in actor.roles and
  role.name = role_name and
  role.resource = resource;

has_permission(_actor: User, "read", repository: Repository) if
  repository.isPublic;

allow(actor, action, resource) if
  has_permission(actor, action, resource);

Advanced RBAC

The next and last policy that we are looking at is a little more complex than the previous one, because a somewhat more extensive RBAC is implemented here.

actor User {}

resource Repository {
  permissions = ["read", "delete"];
  roles = ["reader", "admin"];
  relations = { parent: Organization };

  "delete" if "admin";
  "read" if "reader";

  "reader" if "admin";
  "reader" if "member" on "parent";
  "admin" if "owner" on "parent";
}

resource Organization {
  roles = ["member", "owner"];
  "member" if "owner";
}

has_role(actor: User, role_name: String, resource: Resource) if
  role in actor.roles and
  role.name = role_name and
  role.resource = resource;

has_relation(organization: Organization,
             "parent", repository: Repository) if
  repository.organization = organization;

has_permission(_actor: User, "read", repository: Repository) if
  repository.isPublic;

allow(actor, action, resource) if
  has_permission(actor, action, resource);

Oso is a library

Oso is primarily a library that is integrated into your application. Oso is also a Cross-Platform \ library, which supports a variety of programming languages. These include:

  • Python
  • Node.js
  • Go
  • Java
  • Ruby
  • Rust

It is also worth mentioning that Oso himself is written in Rust, but via a clever release process automated with 87 GitHub-Actions libraries for the target languages listed above on the 3 major operating systems, Linux, MacOS and Windows generates and tests automatically.

Oso is free and open source. There is a paid support service, but you can simply integrate Oso into your application and try it out without obligation.

The Authorization Academy

As mentioned at the beginning, the idea of access rights and security often creates a bitter aftertaste for many developers. Oso provides a little remedy here by providing a so-called Authorization Academy which rolls out the whole topic in 5 chapters and tries to put it into concrete words as simply as possible. In these 5 chapters, basic concepts of authorization, practical architecture models and best practices are shown, which should make the use and implementation of Oso, but also authorization, more accessible.

I can say here that I am only too happy to recommend these 5 chapters to you, because there the mystical veil of authorization is really taken away in a few words and its added value in an overall architectural consideration brings to the point.

In order to provide a basic understanding of authorization, I will simply quote freely from these Academy courses without necessarily looking at the order of the individual chapters. I also recommend that you look it up at the Academy again if you have any questions about it. Well, here I am going to list a few points and explain them:

Authentication vs. Authorization

In the context of security, the abbreviation ‘auth’ is often used. The problem here is obvious; what is meant by that: Authentication or perhaps Authorization after all? The assumption that it can be both is not entirely wrong, because both topics share an overlap, which does not make it easier to keep apart.

Basically it can be said that Authentication is the mechanism with which it is determined who a user is. In concrete terms, authentication is given when an identity such as a username in combination with a verification method - the password - is required. Authentication solutions are for example OAuth, OpenID Connect (OIDC) or SAML. You have probably heard of all these solutions in an enterprise environment. These are standard solutions for authentication, or in English, for example, authentication.

Authorization on the other hand is the mechanism which determines what a certain user can / may do. Authorization is based on authentication and is interdependent. This can be illustrated with a picture: If the access to the entrance door to a house depicts the authentication, then the authorization would be the entirety of the entrances within the house. The same variables as in authentication also play a role in authorization, since the identity - the username - is often the key to whether access - the permission - is allowed.

The authorization model

Authorization is always given in a formalized form. This form is defined by 3 basic questions of authorization and can be summarized as follows:

  • Who makes the request? This is the Actor.
  • What is he trying to do? This is the Action (often but not exclusively CRUDs).
  • Who or what is affected by this? This is the resource.

This model is not set in stone and can therefore be adapted as desired, but offers a kind of formal standard that is used as a guideline for authorization models.

The Authorization API

The interface to a system that has implemented an authorization model is central, as the user or the app communicates with it and the authorization is released through it.

The two central points at the interface the enforcement and the decision. If we use the following expression as the interface from the previous example: is \ _allowed (actor, action, resource) and enter is \ _allowed (current_user, “read”, Repository(name: “acme/anvil”) as the input, then the enforcement is the application of the is \ _allowed () method. The decision is the answer: “is allowed to”, true or an HTTP 403 Forbidden.

Role-Based Access Control (RBAC)

Role-based authorization means cataloging permissions in roles and assigning this role to users. This structure makes it clear which resources are accessible to which users. An RBAC implementation extends the authorization model to include the role category, which also means that the interface with regard to enforcement and decision-making is expanded to include this role. Thus, RBAC is a kind of superset or special case of a conventional, simplistic authorization model.

Relationship-Based Access Control (ReBAC)

In addition to the better-known RBAC, there is also another model for authorization. Namely, a model that builds on relationship - that is, reference relationships - instead of roles. ReBAC means that the model is much more central to the application logic and therefore has to be implemented in a tailor-made manner for each application.

Quickstart hands-on in Java

1 . Clone the Quickstart repo

Clone the following repo:

git clone https://github.com/osohq/oso-java-quickstart.git
cd oso-java-quickstart
mvn install

2 . Start server

Once the dependencies are downloaded and installed, you can start the server with Maven as follows:

mvn clean package exec:java -Dexec.mainClass="quickstart.Server"

3 . Adjust policy

In the Quickstart Repo there is a main.polar file which declares all application-specific rules. We will now write a new rule (Rule) in it.

actor User {}

resource Repository {
  permissions = ["read", "push", "delete"];
  roles = ["contributor", "maintainer", "admin"];

  "read" if "contributor";
  "push" if "maintainer";
  "delete" if "admin";

  "maintainer" if "admin";
  "contributor" if "maintainer";
}

# This rule tells Oso how to fetch roles for a repository
has_role(actor: User, role_name: String, repository: Repository) if
  role in actor.roles and
  role_name = role.name and
  repository = role.repository;

has_permission(_actor: User, "read", repository: Repository) if
  repository.isPublic;

allow(actor, action, resource) if
  has_permission(actor, action, resource);

Save now and restart the server. Call http://localhost:5000/repo/react and the following output should be visible in the browser.

Use cases and common access patterns

Oso is clearly a library that can be used well for a variety of applications. On the website you can also see a short list of so-called Access Patterns - which describe the most common use cases for Oso. Before we maybe list them and look at ourselves, we would like to see together what Oso is definitely not.

  • Oso is not an authentication & User Management like AWS Cognito or Firebase Authentication.
  • Oso is not an Infrastructure Authorization like AWS IAM or VPN-Tunnels.

Access Patterns

  • Organizations
  • Role-Based Access Control (RBAC)
  • Data filtering
  • Groups
  • Ownership
  • Granular Access
  • Custom Roles
  • Invites and Sharing
  • Custom Roles

Oso himself writes that the library is primarily used to implement authorization in B2B SaaS apps and that this is also its target group. Central to the use of Oso is the fact that its own declarative policy language, Polar, is delivered and that every authorization use case can be implemented.

Conclusion

Oso is a small, fine library to efficiently implement authorization in a given app. This skilfully abstracts the entire authorization and presents the end user, the developer, with the most important tools for making authorization-centered user stories a success. Oso is supplied as a library in many popular programming languages and is therefore ideal for many projects without having to learn the basic concepts again. The policy language, Polar, which is also supplied, enables authorization to be kept consistent across apps and domains and allows the policies to be defined in advance as a declarative file.

Further links and resources

https://www.osohq.com/ https://docs.osohq.com/ https://www.osohq.com/academy https://www.osohq.com/post/comparison-oso-vs-open-policy-agent-opa https://www.osohq.com/post/cross-platform-rust-libraries


This text was automatically translated with our golang markdown translator.

Raffael Schneider – crafter, disruptor, free spirit. As a fervent software craftsmanship, Raffael likes to write about programming languages and software resilience in modern distributed systems. Be it DevOps, SRE or systems architecture, he always got a new way of approaching things.