Trusty is a free-to-use web app that provides data and scoring on the supply chain risk for open source packages.
We recently introduced a revamped authorization model and engine in Minder, and we’re excited enough to write a blog post for this! We'll walk you through how we switched from a naive database-backed authorization implementation to a multi-tenant, relationship-based authorization model using OpenFGA.
All projects have to start somewhere. Let’s take a look back in time at how we initially envisioned authorization working in Minder.
The initial iteration of Minder’s authorization model had the concept of users, roles, organizations, and groups.
A user is a user of the Minder system.
An organization allows for grouping together users within Minder.
A group is a bucket of objects in Minder, for example, repositories and artifacts. Groups are owned by an organization.
A role is an assignment and the set of permissions a user can have for a group.
While this worked for the initial implementation, there were some limitations and nuances that we wanted to address:
Having flat groups would make it harder to do support for a multi-tenant environment. We’d need to have special global permissions in order to do this, which can turn into an anti-pattern.
We only had two roles: member and admin. The admin can do everything while the member is a view-only role.
Instead of working with these concepts, we decided to go back to the drawing board and see if we could come up with a better model.
We eventually decided to replace groups with projects. A project is similar to a group in the sense that it’s a grouping of things in Minder (repositories, artifacts, etc.). However, a project is also hierarchical. That is, a project can have sub-projects. This concept is called hierarchical multi-tenancy. And if done right, it allows for having hierarchical permissions as well! This would make it a lot easier to do support by granting someone a role on a parent project.
We also decided to drop the concept of roles as they were, and have a role be the group of permissions you are allowed to do in a project. While the mapping between the role and the permissions is defined as a role grant or assignment. Grants are the relation between a user and a project, which also define the permissions someone has within the project.
Deciding how to implement hierarchical permissions is not an easy task. We evaluated multiple frameworks, and after some discussion, we decided that this was a case where ReBAC could help us out. And OpenFGA seemed like just the right fit!
Off we went to write the model!
Fortunately, writing a hierarchical model in FGA is not particularly difficult. The main thing to figure out is how granular we want our model to be. We could make it quite granular and complete, which would give us quite a bit of functionality and control! However, this could get complex very fast. An example of this is to allow folks to set permissions for individual repositories within projects. Such a setup would require us to replicate all objects in Minder within FGA so it would be able to track all dependencies. However, such replication comes at a cost, as every action in Minder needs to have a transaction persisted in two databases. This is not something we wanted to start with.
To suit our use case, we decided to make all authorization decisions at the project level. This allows us to establish roles within projects and take the hierarchy into account without adding more dependencies or complexity. So now, the only thing we need to replicate into OpenFGA is projects that don’t change that often.
A simplified view of the model is as follows:
model
schema 1.1
type user
type group
relations
define member: [user, group#member] or admin
define admin: [user, group#member]
type project
relations
define parent: [project]
# Defines the `admin` role for this project.
define admin: [user, group#member] or admin from parent
# Defines the `editor` role for this project.
define editor: [user, group#member] or admin or editor from parent
# Defines a `viewer` or `read-only` role for this project.
define viewer: [user, group#member] or editor or viewer from parent
All we need to do then is track relationships between users (or groups of users) and projects!
We can then express roles as relationships between these entities. These permissions can inherit from each other, thus greatly simplifying role definitions.
Project hierarchy was a matter of adding a self-referencing relationship. In our case, called parent.
We can also express explicit permissions as relationships! Here’s how the repository CRUD permissions look as follows:
type project
relations
...
define repo_get: viewer
define repo_create: editor
define repo_update: editor
define repo_delete: editor
Another benefit we get from OpenFGA is the ability to create tests for our model. This helped us reason about it and ensure it’s reliable.
Once we were convinced on the model, we started the integration work. We made sure that for every user with admin permissions in a project, we created the corresponding relationship in OpenFGA. For existing users, this was made part of our migration script.
We also created a new permissions API to allow folks to handle these permissions, more on that later.
Once we had Minder’s view of users and projects replicated in OpenFGA, we added middleware that performed the authorization check before allowing a request to proceed into Minder's core logic.
Finally, we removed the Minder-specific database tables for permissions and project relationships, thus relying fully on OpenFGA for this. This ended up simplifying our code-base greatly.
All of these changes to Minder's internal implementation mean that you can now invite users to join your project.
To view the roles available within your project, you can do the following using the Minder CLI:
$ minder project role list
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------+
| NAME | DESCRIPTION |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------+
| admin | The admin role allows the user to perform all actions on the project and sub-projects. |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------+
| editor | The editor role allows for more write and read actions on the project and sub-projects except for project administration. |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------+
| viewer | The viewer role allows for read actions on the project and sub-projects. |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------+
You can grant a team member access to your project as follows:
minder project role grant -s -r
And view grants in your project as follows:
minder project role grant list
Not quite!
We’ll be exploring more use-cases and potentially add more fine-grained roles. We’ll also explore more usability improvements so folks can take advantage of this functionality in a friendlier way. In the meantime, you can get started with Minder by reading our documentation here.
Make sure to watch our live demo on Tuesday, February 20! We'll show you how Minder can help you manage your open source dependencies through our integrations with Trusty and OSV to flag PRs with unsafe dependencies. We'll also use this demo to show you how Minder's new authorization model helps you control what permissions users can take in Minder.
Visit our YouTube page to subscribe and get notifications about the demo!