Flexibele autorisatie met Open Policy Agent
Wat een gebruiker met bepaalde software mag doen wordt traditioneel bepaald aan de hand van een rolmodel. Dit is door de vele regels in een bedrijf meestal echter te star. Met de Open Policy Agent kun je flexibeler bepalen wie wat mag doen.
Elke toepassing waar meer dan één werknemer gebruik van moet kunnen maken, moet telkens weer de volgende, allesbepalende vraag beantwoorden: mag deze gebruiker de gewenste actie echt uitvoeren? Bij bedrijfstoepassingen hangt veel af van het juiste antwoord op die vraag. Het is meestal niet de softwareontwikkelaar zelf die beslist wie wat mag doen – in plaats daarvan krijgt hij de regels die in het bedrijf gelden in hele zinnen op zijn tafel en vervolgens heeft hij de taak om die te vertalen naar de applicatie. Dergelijke regels kunnen er als volgt uitzien: - Medewerkers van de inkoopafdeling mogen orders
tot 1000 euro accorderen.
- Het adjunct-hoofd mag tot 10.000 euro
goedkeuren.
- Het hoofd van de inkoopafdeling mag overal
toestemming voor geven.
Om het probleem aan te pakken wordt meestal gebruik gemaakt van een model dat dergelijke autorisaties vereenvoudigt. Veel ontwikkelaars kiezen voor een rollenmodel (Role Based Access Control, RBAC). Bij een dergelijk systeem zijn er gebruikers, groepen en rollen. Een gebruiker kan lid zijn van verschillende groepen. Rollen kunnen worden toegewezen aan gebruikers en groepen – de applicatie beslist dan per geval of de rol beschikbaar is die nodig is om een actie uit te voeren. In het beste geval kun je de autorisatiecontrole zelfs uitbesteden aan een aparte RBAC-component, zodat de applicatiecode en de bedrijfsregels niet door elkaar gaan lopen.
Voor de drie hierboven geformuleerde regels heb je al drie rollen nodig, bijvoorbeeld purchase_employee, purchase_deputy en purchase_head. Je loopt echter tegen de grenzen van RBAC aan als de besluitvormers een andere regel bedacht hebben en die op het bureau van de ontwikkelaar leggen:
- Medewerkers van de inkoopafdeling kunnen bestellingen van elk willekeurig bedrag accorderen als de leverancier op de lijst van vaste leveranciers staat.
Een systeem dat alleen naar de rollen van de gebruiker kijkt, kan een dergelijke beslissing onmogelijk nemen – andere attributen van de opdracht moeten hierbij worden geëvalueerd. Dan wordt ook wel gesproken van Attribute Based Access Control (ABAC). Als het systeem tot dan toe alleen heeft gecontroleerd op het bestaan van een rol, moet de ontwikkelaar daarom hier midden
in de applicatie een trucje uithalen en, naast het controleren op de rol purchase_employee de bestelling ook nader inspecteren en reageren op verschillende leveranciers met een if-statement. Als je eenmaal bent begonnen met het integreren van dergelijke beslissingen in de code van de applicatie, zal dat zeker steeds weer gebeuren. Dan is het gedaan met de scheiding tussen de RBAC-component en de applicatie – als een bedrijfsspecificatie later verandert, moet een ontwikkelaar die de krochten van de applicatie kent, de wijzigingen op de juiste plaats aanbrengen. Het is dan nog moeilijker om uit de code een overzicht te filteren van wie welke rechten heeft. Als je bepaalde bedrijfssoftware aan meerdere klanten wilt verkopen, zijn dergelijke trucs uit den boze. Het scheiden van programmacode en individuele regels is dan een onbetwist vereiste.
OPA IS DE OPLOSSING
Om een oplossing te vinden voor dergelijke problemen, heeft het bedrijf Styra de Open Policy Agent (OPA) in het leven geroepen. De uitvinders komen zowel uit de software-defined networking als uit de cloud- en containerwereld en dus zijn containeromgevingen (met Docker of Kubernetes) een mogelijk, maar zeker niet het enige toepassingsgebied voor OPA. In 2019 heeft Styra het project geschonken aan de Cloud Native Computing Foundation, een stichting onder de paraplu van de Linux Foundation. Sindsdien is de opensource software door programmeurs van veel bedrijven verder ontwikkeld. Tot de gebruikers van OPA behoren zowel energie- en telecommunicatiebedrijven als banken en verzekeringsmaatschappijen – het evalueren van regels maakt daar deel uit van de kernactiviteit.
De Open Policy Agent is een dienst die regels en gegevens ontvangt en op basis daarvan een beslissing terugstuurt. De makers noemen hun uitvinding een universele policy-engine. In het eenvoudigste geval is zo’n beslissing allow: true of allow: false. Maar er kan bijvoorbeeld ook een gefilterde lijst terugkomen. Autorisatiecontroles zijn daarom slechts één van de mogelijke toepassingsgebieden.
Als je OPA in je software wilt gebruiken, heb je verschillende opties. Als je gebruik maakt van een klassieke infrastructuur zonder containers, kun je de OPAbinary downloaden en OPA in servermodus starten (zie de link op de laatste pagina van dit artikel). OPA stelt dan een REST-API beschikbaar waarmee je applicatie via een HTTP-request een beslissing aanvraagt. Je gebruikt de API ook om één of meer tekstdocumenten over te dragen met de regels, die OPA op dat moment voor je toepast. Integratie in bestaande systemen is daardoor mogelijk met elke programmeertaal waarvoor een HTTP-client bestaat – wat voor elke relevante taal zou moeten gelden.
Als je gebruik maakt van een containeromgeving waarbij je applicatie ook in een container draait, kun je OPA evenals als container starten en er met behulp van HTTP mee communiceren. De containerimage bij Docker Hub heet openpolicyagent/opa:latest.
Als je op dit moment een applicatie in Go op de planning hebt staan, heb je een derde optie voor de integratie van OPA. Omdat Open Policy Agent in Go geschreven is, kun je de policy-engine ook als Go-pakket integreren met import ("github.com/open-policy-agent/opa/ rego")
Met de functie query.Eval() bevraag je OPA dan rechtstreeks – in dat geval moet je zelf zorgdragen voor het beheer van de regels in het geheugen of in bestanden. De documentatie geeft daar enkele suggesties voor (zie de link op de laatste pagina).
ER ZIJN REGELS
Voordat je aan de integratie in je applicatie gaat werken, moet je eerst de regels bekijken. De ontwikkelaars hebben besloten om hun eigen taal te creëren voor het schrijven van dergelijke regels – die heet Rego en de syntaxis is gedeeltelijk gebaseerd op Go, maar daar hoef je verder niets van te weten. OPA is niet de eerste poging om flexibele regels in een voor machines leesbaar formaat te gieten – sinds 2003 is er bijvoorbeeld XACML, gebaseerd op XML. Maar die aanpak heeft niet veel volgers gekregen, mede omdat de XML-fragmenten nauwelijks leesbaar zijn voor mensen. Rego-regels zijn daarentegen makkelijk te begrijpen.
Om de flexibiliteit van Rego uit te proberen, hoef je niets te downloaden of een gecompliceerde containerinfrastructuur te starten. Gebruik de online playground op play.openpolicyagent.org om te experimenteren. In het venster links is er ruimte voor de regels, rechts is er een gebied voor de invoer van gegevens (Input) in de vorm van JSON-objecten. In het veld daaronder kun je verdere gegevens (Data) meegeven als een JSONobject, dat moet worden gebruikt voor de besluitvorming. In de rechter benedenhoek wordt het resultaat teruggegeven zodra je op de knop Evaluate drukt. Als je een experiment in de online playground wilt opslaan of wilt delen met collega’s, klik dan op Publish in de rechterbovenhoek.
Als voorbeeld laten we nu stap voor stap zien hoe je de hierboven geformuleerde regels voor de inkoopafdeling kunt vertalen naar Rego. Je leert de belangrijkste onderdelen van de Rego-syntaxis. Eerst moeten de drie invoervelden in de browseromgeving leeggemaakt worden. Vul dan het veld voor de invoerwaarden:
{
"operation": "create_purchase", "user": "bob",
"roles": ["purchase_employee"], "order":{ "company_id": } } "123", "sum":
Als je de voorbeelden niet wilt overtypen, kun je deze playground vinden via de link bij dit artikel. Bij deze invoerwaarden zie je al de gebruikersnaam (bob) en een lijst van zijn rollen (purchase_employee). Het gebruik van rollen uit een klassiek RBAC-systeem en OPA sluiten elkaar dus geenszins uit – als je gewend bent daar mee te werken, kun je rollen gewoon blijven gebruiken en roleigenaren bijvoorbeeld ook beheren in een SQL-database.
Je applicatie, die wacht op een beslissing van OPA, hoeft die informatie alleen maar als JSON-object samen te stellen.
OPA is niet verantwoordelijk voor de authenticatie – als gebruikers bijvoorbeeld inloggen met een gebruikersnaam en wachtwoord, dan moet een ander deel van je applicatie die combinatie controleren en de gebruikersnaam aan OPA doorgeven als onderdeel van de invoergegevens.
In het veld links formuleer je dan je eerste regel: package purchase.example
import input import data default allow = false
allow { input.operation } == "create_purchase"
Elk Rego-document begint met een naam, ingeleid met
package. Dat is belangrijk als je de regels later wilt hergebruiken in andere Rego-documenten. Dan volgen twee import-commando’s. Daarmee zorg je ervoor dat de invoergegevens en de overige informatie gebruikt kunnen worden.
Daarna wordt een standaardwaarde voor de toegestane resultaatwaarde ingesteld. Standaard moet de toegang worden verboden met allow: false. De naam allow voor de returnwaarde staat echter geenszins vast, je zou die anders kunnen noemen – maar je zult allow als voorbeeld tegenkomen in alle documentatie waar het om autorisatie gaat.
Daarna volgt de eerste echte regel. Vertaald betekent die dat als de waarde voor operation bij de invoerwaarden create_purchase is, allow: true wordt teruggeleverd. Die regel is echter nog steeds veel te ruim, tot nu toe ontbreekt de controle op de rol
purchase_employee en het ordertotaal. Een operator voor een EN-aaneenschakeling van voorwaarden is er bij Rego niet. In plaats daarvan levert OPA alleen true op voor een regel als aan alle regels binnen de {} voldaan zijn. Je kunt dus eenvoudigweg meer voorwaarden toevoegen in nieuwe regels:
allow { input.operation == some i input.roles[i] == "purchase_employee" data.departments.purchase.deputy «
» == input.user input.order.sum < 10000
} "create_purchase"
Ook voor OR heb je bij Rego geen syntaxis nodig. Maak gewoon een andere regel voor allow, gevolgd door de voorwaarden in { }. OPA zal ze één voor één controleren en bij de eerste hit meteen allow: true terugleveren. In de online playground kun je eenvoudig zien welke regel gebruikt is en op welke regel het wellicht mis liep. Zet rechtsboven een vinkje bij Coverage. Rijen met een groene kleur zijn met succes afgehandeld. Voordat je verdere regels gaat schrijven voor het afdelingshoofd en de vaste leveranciers, moet je de code een beetje opschonen. De eerste drie regels komen telkens voor. Daar kun je een herbruikbare regel voor aanmaken: is_purchase { input.operation some i input.roles[i]
} == == "create_purchase" "purchase_employee"
Als aan die twee voorwaarden is voldaan, krijgt is_ purchase de waarde true. Je kunt die hulpregel direct gebruiken, waardoor de regel voor het afdelingshoofd er er als volgt uitziet: allow { is_purchase == true data.departments.purchase.head «
» == input.user }
Je kunt die notatie nog verder inkorten en == true weglaten. OPA controleert dan automatisch of een waarde true is.
De laatste regel, die een puur RBAC-systeem aan de grenzen van zijn kunnen zou brengen, is ook snel geformuleerd: allow { is_purchase some j data.trusted_companies[j] «
» == input.order.company_id }
Een gewone werknemer mag dan zoveel geld uitgeven als hij wil, zolang hij bij een vaste leverancier bestelt.
Met die basisfuncties heb je nog lang niet alle mogelijkheden van Open Policy Agent en Rego benut. Een overzicht van de functies staat bij de link rechtsonder. Er zitten bijvoorbeeld functies in voor het tellen van elementen (count()) of toegang tot de huidige tijd en datum (via het object time). Er zijn ook functies om extra gegevens op te halen bij het evalueren van regels.
INTEGREREN
Met deze basiskennis moet je een zinvolle architectuur voor je software kunnen bedenken waarin je OPA kunt integreren. Je hoeft daar je huidige RBAC-systeem niet voor af te schaffen. Je kunt bestaande rollen overbrengen naar OPA als gegevens of invoerwaarden en die opnemen in je regels. De OPA-documentatie biedt inspiratie voor eigen integraties (zie de link op deze pagina). In de voorbeelden zitten onder andere een HTTP-API, een van de meest voorkomende toepassingen voor OPA. Voor sommige toepassingen zijn er ook kant-en-klare integraties. Je kunt OPA bijvoorbeeld in combinatie met Linux-PAM gebruiken om precies te bepalen wie welke bewerkingen op een Linux-machine mag uitvoeren. Je kunt ook een autorisatieconcept toevoegen aan een dockerhost met een kant-en-klare OPA-integratie.
REGELS ZIJN CODE
Een van de voordelen van Open Policy en regels in Rego-formaat is dat je de regels in je organisatie als code kunt behandelen. Rego-regels kunnen worden gedocumenteerd (met een # aan het begin van een regel) en in Git-repositories worden opgeslagen. Eenmaal opgeslagen in Git kun je een revisieproces in je CI-oplossing inbouwen, zodat wijzigingen alleen met dubbele controle kunnen worden aangebracht. Er is ook aan unit-tests voor regels gedacht. Je formuleert de verwachte resultaten en laat die automatisch door OPA testen.
Een instap in de wereld van Open Policy Agent is vooral de moeite waard als je aan een nieuwe toepassing begint – vooral bij een microservice-architectuur kun je die snel implementeren omdat hij via HTTP werkt. Bij grote bestaande applicaties moet je de gevraagde inspanning niet onderschatten – als de beslissingen diep in de code begraven zijn, zul je niet alleen OPA moeten integreren, maar al die coderegels daar ook uit moeten slopen. Je wordt wel beloond met een flexibel en eenvoudig te onderhouden centraal punt voor alle vormen van besluitvorming.