Batteries-included Authorization mit Oso.

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

Security in ist bei der Neukonzeption von Applikationen für viele Fullstack-Entwickler nicht selten eine Blackbox, welche gerne jemand Drittem überlassen wird. Jedoch gehört gerade die Authorization zu einer der Kernaufgaben jeder User-Story. Es gibt unterschiedliche Möglichkeiten eine Authorization in einer Applikation einzubinden, aber auch dort wird oft eine hauseigene Customization gewählt, da man der Auffassung ist eine bessere Kontrolle über kritische Zugriffsvektoren zu erzielen.

Jedoch sind die Anforderungen an Authorization keine Raketenwissenschaft, und die Vektoren im voraus bekannt. Aus diesem Grund haben sich Graham Neray und Sam Scott sich der Entwicklung einer universellen Lösung verschrieben, welche den Prozess und dessen Implementation streamlinen soll. Die zwei sind Founder der in New York City ansässigen Oso HQ und bieten mit dem gleichnamigen Produkt, stilisiert als oso, eine Grundtechnologie, welche es erlaubt Authorization gezielt für die Applikationsentwicklung zu implementieren.

Oso – Batteries-Included Authorization

Der Slogan von Oso heisst “Ship RBAC fast – this is your forever framework” und ist im Selbstverständnis ein sogenanntes “batteries-included framework for building authorization in your application”.

Policies auf Polar

Eines der wichtigen Features von Oso ist dessen hauseigene deklarative Policy-Language, genannt Polar (der Polarbär im Oso-Logo ist eine direkte Anspielung auf Polar). Mit dieser Policy-Language werden Policen –oder umgangssprachlicher einfach Regeln genannt– in einer Textdatei deklariert und hat somit die Möglichkeit Applikationsspezifische Authorization in genau dieser Datei festzuhalten, was wiederrum weitere Vorteile wie Rückverfolgbarkeit, wenn als Single-Source-of-Truth in einem GitOps-Pattern Verwendung findet, hat.

Polar liefert auch built-in Primitives womit Roles, Relationships und Hierarchies definiert werden können. Selbstverständlich kann man damit eigene, spezifischere Regeln schreiben, welche komplexere Beziehungen abbilden können.

Allow anything

Starten wir gleich mit unserer ersten Polar-Rule. Hier wird ganz einfach per allow() deklariert, dass alle Zugriffstypen, Users _actor, Aktionen _action, und Ressourcen _resource erlaubt sind.

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

Empty

Eine leere Police bedeutet, dass das komplette Zugriffssystem nichts zulässt, da nichts explizit deklariert wird.

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

Only public repos

Mit der nächsten Police wird deklariert, dass alle Users die Aktion "read" auf allen Repository-Ressourcen ausführen können, sofern diese Ressource repository als public gilt.

# 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

Die nächste Police ist ein wenig komplexer, weil diese ein vollständigen, aber simplen RBAC implementiert. Schaut die Deklarationen selber an und versucht zu verstehen was hier genau passiert.

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

Die nächste und letzte Police, die wir uns anschauen ist noch ein wenig komplexer als vorige, weil hier ein etwas extensiverer RBAC implementiert wird.

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 ist eine Library

Oso ist in erster Linie eine Library die in Ihrer Applikation eingebunden wird. Oso ist zudem eine Cross-Platform-Bibliothek, welche eine Vielzahl von Programmiersprachen unterstützt. Darunter zählen:

  • Python

  • Node.js

  • Go

  • Java

  • Ruby

  • Rust

Erwähnenswert ist sicherlich auch der Umstand, dass Oso selber in Rust geschrieben ist, aber über einen geschickten Release-Prozess automatisiert mit 87 GitHub-Actions Bibliotheken für die oben aufgeführten Zielsprachen auf den 3 grossen Betriebssystemen, Linux, MacOS und Windows generiert und automatisch testet.

Oso ist frei und quelloffen. Es gibt eine entgeltliche Support-Dienststelle, aber man kann Oso einfach gleich in Ihrer Applikation einbinden und unverbindlich ausprobieren.

Die Authorization Academy

Wie Eingangs erwähnt erzeugt die Idee rund um Zugriffsrechte und Security oft einen bitteren Beigeschmack bei vielen Entwicklern. Oso verschafft hier ein wenig Abhilfe, indem eine sogenannte Authorization Academy bereits gestellt wird, welche in 5 Chapters das ganze Thema ausrollt und dabei versucht so einfach wie möglich in konkrete Worte zu fassen. In diesen 5 Kapiteln werden grundlegende Konzepte von Authorization, praktische Architekturmodelle und Best-Practices aufgezeigt, was die Anwendung und Implementation von Oso, aber auch Authorization zugänglicher gestalten soll.

Ich kann hier anmerken, dass ich nur zu gerne Ihnen diese 5 Kapitel ans Herz lege, da dort wirklich in kurzen Worten den mystischen Schleier von Authorization genommen wird und dessen Mehrwert bei einer gesamtarchitektonischen Betrachtung auf den Punkt bringt.

Um ein Grundverständnis über Authorization zu geben, werde ich einfach aus diesen Academy-Kursen frei zitieren, ohne dabei zwingend auf die Reihenfolge der einzelnen Kapitel zu schauen. Zudem empfehle ich hier nochmals gleich dort in der Academy nachzuschlagen, falls es noch Fragen dazu geben sollte. Nun, hier werde ich ein paar Punkte auflisten und diese erklären:

Authentication vs. Authorization

Im Kontext von Security wird oft das Kürzel ‘auth’ genutzt. Das Problem liegt hier auf der Hand; was ist nun damit gemeint: Authentication oder etwa doch Authorization? Die Annahme, dass es beides sein kann, ist auch nicht ganz falsch, denn beide Themen teilen eine Schnittmenge, was das Auseinanderhalten nicht erleichtert.

Grundsätzlich kann gesagt werden, dass Authentication der Mechanismus ist womit bestimmt wird, wer ein Nutzer ist. Konkret ist eine Authentifizierung gegeben, wenn eine Identität wie etwa einen Nutzernamen in Kombination mit einer Verifizierungsmethode –das Passwort– gefragt ist. Authentifizierungslösungen sind beispielsweise OAuth, OpenID Connect (OIDC) oder SAML. All diese Lösungen haben Sie sicher schonmal in einem Enterprise-Umfeld gehört. Das sind Standardlösungen für Authentifizierung, oder etwa auf Englisch, Authentication.

Authorization hingegen ist der Mechanismus welcher bestimmt was ein gewisser Nutzer tun kann/darf. Authorization baut auf Authentication auf und stehen in einer gegenseitigen Abhängigkeit zueinander. Man kann dies mit einem Bild veranschaulichen: Wenn der Zugang zur Eingangstüre zu einem Haus die Authentication abbildet, so wäre die Authorization die Gesamtheit der Zugänge innerhalb des Hauses. Die gleichen Variablen wie bei der Authentication, spielen auch bei der Authorization eine Rolle, da die Identität –der Nutzername– oft der entscheidende Key ist ob der Zugang –die Permission– erlaubt ist.

Das Authorization Model

Authorization ist stets in einer formalisierten Form gegeben. Diese Form ist definiert durch 3 Grundfragen der Autorisierung und können wie folgt zusammengefasst werden:

  • Wer macht die Anfrage? Das ist der Actor.

  • Was versucht er zu unternehmen? Das ist die Action (oft aber nicht ausschliesslich CRUDs).

  • Wer oder was ist davon betroffen? Das ist die Resource.

Dieses Model ist nicht in Stein gemeisselt und kann dementsprechend beliebig angepasst werden, bietet aber eine Art formalen Standard der als Guideline für Authorization Models genutzt wird.

Die Authorization API

Die Schnittstelle zu einem System welches ein Authorization Model implementiert hat ist zentral, da damit der User oder die App kommuniziert und darüber die Autorisierung freigegeben wird.

Die zwei zentrale Punkte bei der Schnittstelle das Enforcement (zu Deutsch Durchsetzung) und die Decision (der Entscheid). Wenn wir als Schnittstelle aus dem vorherigen Beispiel folgenden Ausdruck zur Hand nehmen: is_allowed(actor, action, resource) und als Input is_allowed(current_user, “read”, Repository(name: “acme/anvil”) eingeben, dann ist das Enforcement das Applizieren der is_allowed()-Methode. Die Decision die Antwort darauf: “is allowed to”, true oder etwa ein HTTP 403 Forbidden.

Role-Based Access Control (RBAC)

Rollen-basierte Authorization heisst Permissions in Rollen zu katalogisieren und Nutzern diese Rolle zuzuweisen. Diese Strukturierung macht ersichtlich welche Ressourcen welchen Nutzern zugänglich sind. Eine RBAC-Implementation erweitert das Authorization Model um die Kategorie Rolle, was auch zur Folge hat, dass die Schnittstelle in Bezug auf das Enforcement und Decision-Making um diese Rolle erweitert wird. Somit ist RBAC eine Art Superset oder Sonderfall von einer konventionellen, simplistischen Authorization Model.

Relationship-Based Access Control (ReBAC)

Nebst dem bekannteren RBAC gibt es auch noch ein weiteres Model für Authorziation. Nämlich ein Model, dass auf Relationship –also Bezugsverhältnisse– anstatt Rollen aufbaut. ReBAC hat zur Folge, dass das Model viel zentraler zur Applikationslogik steht und somit massgeschneidert für jede Applikation implementiert werden muss.

Quickstart Hands-On in Java

1. Quickstart-Repo klonen

Klone folgende Repo aus:

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

2. Server starten

Sobald die Dependencies heruntergeladen und installiert sind, können Sie den Server mit Maven wie folgt starten:

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

3. Policy anpassen

In der Quickstart-Repo gibt es ein main.polar-Datei, welche alle Applikationsspezifischen Regeln deklariert. Darin werden wir jetzt eine neue Regel (Rule) schreiben.

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);

Jetzt speichern und den Server neu starten. http://localhost:5000/repo/react aufrufen und folgender Output sollte im Browser ersichtlich sein.

Anwendungsfälle und übliche Access Patterns

Oso ist klar eine Library die man gut für eine Vielzahl von Anwendungsfällen einsetzen kann. Im Webauftritt ist auch eine kleine Liste von sogenannten Access Patterns –Zugriffsmustern– ersichtlich worin die gängigsten Anwendungsfälle für Oso beschrieben sind. Bevor wir vielleicht diese auflisten und uns selber anschauen, möchten wir gemeinsam uns zu Gemüte führen was Oso definitiv nicht ist.

  • Oso ist kein Authentication & User Management wie AWS Cognito oder Firebase Authentication.

  • Oso ist keine Infrastructure Authorization wie AWS IAM oder VPN-Tunnels.

Access Patterns

  • Organizations

  • Role-Based Access Control (RBAC)

  • Data Filtering

  • Groups

  • Ownership

  • Granular Access

  • Custom Roles

  • Invites and Sharing

  • Custom Roles

Oso schreibt selber, dass die Library in erster Linie zur Implementation von Authorization in B2B SaaS-Apps Verwendung findet und dass dies auch ihre Zielgruppe darstellt. Zentral für die Nutzung von Oso ist der Umstand, dass eine eigene deklarative Policy-Language, Polar, ausgeliefert wird und damit jeden Authorization-Use-Case implementiert werden kann.

Fazit

Oso ist eine kleine, feine Library um effizient Authorization in eine gegebene App zu implementieren. Damit wird die ganze Authoriztation gekonnt abstrahiert und präsentiert dem Endnutzer, dem Entwickler die wichtigsten Werkzeuge um Authorization-zentrierte User-Story zum Erfolg zu bringen. Oso wird als Library in vielen beliebten Programmiersprachen ausgeliefert und bietet sich somit in vielen Projekten an ohne, dass man die Grundkonzepte neu erlernen müsste. Auch die Policy-Language, Polar, die mit ausgeliefert wird ermöglicht es Authorization App- und Domain-übergreifend konsistent zu behalten und erlauben es die Policies als deklarative Datei bereits im Vorfeld zu definieren.

Weiterführende Links und Ressourcen

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

Raffael Schneider – Crafter, Disruptor, Freethinker. Als leidenschaftlicher Software-Crafter schreibt Raffael gerne über Programmiersprachen und Resilienz in modernen verteilten Systemen. Ob DevOps, SRE oder Systemarchitektur, Raffael weiss stets wie man diese Dinge auf eine neue Weise betrachten kann.