Authorize like GitHub: A real-world example of fine-grained authorization

Mar 21st, 2024

Omri Gazitt avatar

Omri Gazitt

ReBAC  |  

Engineering  |  

Topaz

github-topaz-model

GitHub is where the world goes to code. Developers use it every day to manage their code repositories, projects, and issues.

GitHub employs a fine-grained authorization model. It provides different roles - organization members, admins, outside collaborators, and teams - with different permission levels. There are a few authorization targets, and they are organized in hierarchies, where lower-level resources (like repos) inherit their default permissions from their parents (like organizations). Organizations own repos, which can be private or public. Repositories have issues, pull requests, actions, and other resources that need safeguarding.

Permissions can be granted directly to users or indirectly via group membership. While outside contributors can only be granted permissions explicitly, organization members can also inherit permissions. Permissions can be inherited from team membership (including parent teams), organization roles, or organization defaults.

In this post, we dive into some of the ways GitHub provides users with these granular permissions. The platform has built a robust system, so we’ve focused on the most commonly used elements - granular roles, inherited permissions, and defaults across repos. Read on for all the details.

Fine grained roles

GitHub has a hierarchical structure, where organizations own repositories that contain various discrete resources, such as pull requests and issues. Furthermore, users can be members of the organization that owns the repo, or outside collaborators. Organization members can join teams, which can be nested. And permissions are assigned directly to users or via association with teams.

GitHub comes with five fine-grained roles for repositories, and Enterprise customers can create custom roles. Github also provides five granular roles for organizations. Given that most of the activity happens in repositories, we’ll focus on those roles.

Here are the five repo roles, from the most access to the least: Admin Maintainer, Writer, Triager, and Reader. The roles can be thought of as a set of “nested dolls”, in which each element has all the permissions granted to its predecessor plus additional ones granted to it explicitly. For example, an Admin can do anything a Maintainer can, and has additional permissions such as the can_delete permission. Here’s a breakdown of the granular permissions associated with each role.

To model GitHub’s granular repository roles with Topaz, we’ll need to define the permissions associated with each role. Given that these roles extend the permissions of the access level below them (i.e. Admin inherits all of the Maintainer permissions, Maintainer inherits all of the Writer permissions and so forth), we’ll need to build that into the authorization model as well.

Thankfully, the Topaz modeling language allows permissions to be defined in terms of other permissions. It also supports unions, intersections, exclusions, and relation navigations (arrow operators), allowing you to easily define these granular roles in an elegant way.

For example, this is a snippet of the Topaz GitHub manifest, which defines the repo object type, the various roles (relations) on a repo, and the permissions that are granted through each role.

types:
  repo:
    relations:
      owner: organization

      admin:      user | team#member
      maintainer: user | team#member
      writer:     user | team#member
      triager:    user | team#member
      reader:     user | team#member

    permissions:
      can_administer: admin | owner->can_administer
      can_delete:     can_administer
      can_maintain:   maintainer | can_administer
      can_write:      writer  | can_maintain | owner->can_write
      can_triage:     triager | can_write
      can_read:       reader  | can_triage   | owner->can_read

Check out the full manifest here.

Roles aren’t the only elements that grant permissions. Organization members can inherit permissions assigned to the teams they are members of, including those assigned to the parent team. Users can also inherit repo permissions from organizational defaults.

Nested teams

We can reflect the company's hierarchy within GitHub using nested teams. We can organize multiple levels of nested teams to provide varied degrees of access. And we can determine whether these teams will be visible to other users on GitHub. Note that only visible teams can be nested, and inherit the parent's access permissions.

As a graph-based authorization system, Topaz supports hierarchical relationships, making it easy to model hierarchical inheritance, parent-child relationships, containment, and other complex relationships.

The manifest snippet below defines a team object type, whose members can either be users or the members of another team.

  team:
    relations:
      member: user | team#member

Users can inherit permissions associated with teams they are a member of and of the parent team. This helps simplify permission management. But it also means that if we add or remove access to repositories from the parent team, members of all the child teams will be affected, gaining/losing access to those repos. Something to keep in mind.

Default permissions across repositories

Last but not least, organization owners can set defaults across the organization. We can limit access to certain settings and actions across repositories and set defaults that apply to all members. For example, we can determine the default role organization members have in every repository the organization owns, set up pull request review workflows, etc.

Conclusion

GitHub is where the world goes to code. And it does a great job securing access to that code.

Using a combination of fine-grained roles, inherited permissions, and organization defaults the developer platform provides organization owners with fine-grained control over access to their code.

A relationship-based directory makes it easy to model these granular permissions. As a graph, it natively supports inherited permissions, parent-child relationships, containment, and other complex relationships, allowing organization to deliver resource-level authorization.

Topaz is our open-source authorizer. It supports every authorization model (RBAC, ABAC, ReBAC, PBAC), authorizes in milliseconds, and is dead simple to integrate. You can model everything in this post using Topaz and deploy it to your cloud today.

Getting started with the GitHub authorization template is literally two commands away:

brew install aserto-dev/tap/topaz

topaz templates install github

If you have any questions, feel free to ask them in our community slack.

Happy hacking!

Omri Gazitt avatar

Omri Gazitt

CEO, Aserto