Jede Anwendung, auf die mehr als ein Nutzer zugreift, muss die entscheidende Frage beantworten: Darf dieser Benutzer die gewünschte Aktion wirklich ausführen? Was ein Anwender in einer Software darf, legt meist ein Rollenmodell fest. Doch insbesondere in der Cloud oder für Infrastructure-as-Code birgt dies zahlreiche Herausforderungen. Mit dem freien Open Policy Agent steuern Admins Nutzerrechte flexibler.
Infrastructure-as-Code (IaC) ist mittlerweile ein Erfolgsrezept für deklarativen, maschinenlesbaren Code und daher liegt es nahe, es auf das Thema Security und insbesondere auf das Verfassen von Policies zu übertragen. Mit diesem Ansatz versuchen Firmen in einer skalierbaren Art und Weise, Regeln innerhalb einer Organisation zu implementieren. Ein Vertreter dieser Gattung, der in jüngster Zeit immer mehr Aufmerksamkeit erhält, ist der Projekt Open Policy Agent (OPA) [1], hinter dem das Startup-Unternehmen Styra steht. OPA ist eine universell einsetzbare Policy-Engine, die eine einheitliche, kontextbewusste Durchsetzung von Richtlinien über den gesamten Stack hinweg ermöglicht.
Open Policy Agent im Überblick
OPA wird von der CNCF (Cloud Native Computing Foundation) gehostet – also von der Organisation, die für Kubernetes verantwortlich zeichnet. Konzipiert ist OPA für cloudnative Umgebungen und kombiniert die relativ einfach zu erlernende und lesbare Policy-Sprache "Rego" mit einem Richtlinienmodel sowie einer API. Dies ermöglicht eine Art universelles Framework, um Regeln auf alle Arten von Stacks anzuwenden. Einer der großen Vorteile von OPA ist somit die Entkopplung von Security-Policies und Code und seiner Anwendung – unabhängig, wie oft sich der Code ändert.
Technisch ist OPA an Input gebunden. Sobald diese Daten vorliegen, entscheidet der OPA-Code darüber, wie mit dem entsprechenden Input umzugehen ist (beispielsweise das Erlauben oder Verbieten mit einer allow/deny-Policy). Ein weiterer Vorteil ist die Tatsache, dass OPA Input und Output sowohl im JSON- als auch im YAML-Format verarbeitet, sodass sich IT-Verantwortliche dabei nicht an eine vordefinierte API halten müssen. Insgesamt gestaltet sich das Verfassen von Regeln so relativ leicht – darüber hinaus unterstützt OPA REPL, also das Shell-basierte Ausführen von Code. Praktisch ist auch, dass Sie nicht alle Policies selbst verfassen müssen, denn im Internet finden sich für viele Anwendungsfälle bereits fertige Policy-Bundles, die ein brauchbares, vordefiniertes Regelwerk beinhalten. Zum Üben existiert sowohl ein frei zugänglicher Playground als auch eine freie Styra Academy.
Infrastructure-as-Code (IaC) ist mittlerweile ein Erfolgsrezept für deklarativen, maschinenlesbaren Code und daher liegt es nahe, es auf das Thema Security und insbesondere auf das Verfassen von Policies zu übertragen. Mit diesem Ansatz versuchen Firmen in einer skalierbaren Art und Weise, Regeln innerhalb einer Organisation zu implementieren. Ein Vertreter dieser Gattung, der in jüngster Zeit immer mehr Aufmerksamkeit erhält, ist der Projekt Open Policy Agent (OPA) [1], hinter dem das Startup-Unternehmen Styra steht. OPA ist eine universell einsetzbare Policy-Engine, die eine einheitliche, kontextbewusste Durchsetzung von Richtlinien über den gesamten Stack hinweg ermöglicht.
Open Policy Agent im Überblick
OPA wird von der CNCF (Cloud Native Computing Foundation) gehostet – also von der Organisation, die für Kubernetes verantwortlich zeichnet. Konzipiert ist OPA für cloudnative Umgebungen und kombiniert die relativ einfach zu erlernende und lesbare Policy-Sprache "Rego" mit einem Richtlinienmodel sowie einer API. Dies ermöglicht eine Art universelles Framework, um Regeln auf alle Arten von Stacks anzuwenden. Einer der großen Vorteile von OPA ist somit die Entkopplung von Security-Policies und Code und seiner Anwendung – unabhängig, wie oft sich der Code ändert.
Technisch ist OPA an Input gebunden. Sobald diese Daten vorliegen, entscheidet der OPA-Code darüber, wie mit dem entsprechenden Input umzugehen ist (beispielsweise das Erlauben oder Verbieten mit einer allow/deny-Policy). Ein weiterer Vorteil ist die Tatsache, dass OPA Input und Output sowohl im JSON- als auch im YAML-Format verarbeitet, sodass sich IT-Verantwortliche dabei nicht an eine vordefinierte API halten müssen. Insgesamt gestaltet sich das Verfassen von Regeln so relativ leicht – darüber hinaus unterstützt OPA REPL, also das Shell-basierte Ausführen von Code. Praktisch ist auch, dass Sie nicht alle Policies selbst verfassen müssen, denn im Internet finden sich für viele Anwendungsfälle bereits fertige Policy-Bundles, die ein brauchbares, vordefiniertes Regelwerk beinhalten. Zum Üben existiert sowohl ein frei zugänglicher Playground als auch eine freie Styra Academy.
Natürlich gibt es auch einige Hürden: So ist Rego unter Umständen komplett neu zu erlenen. Zudem basiert OPA auf der Programmiersprache Go und auch nur für diese existiert zum aktuellen Zeitpunkt ein Client-SDK.
Die OPA-Anwendungsfälle sind erwartungsgemäß alle der Security zugehörig:
- Bei der Benutzerautorisierung gelingt es mit der Ausdruckskraft von OPA und Rego leicht, Regeln zu definieren und diese an einem zentralen Ort vorzuhalten.
- Auch beim Einsatz von API-Gateways wie Kong, Traefik oder Tyk kann OPA in Sachen Autorisierung helfen.
- CI/CD-Inspektion: Dort gibt es verschiedene Einsatzgebiete für OPA wie zum Beispiel die Überprüfung von IaC-Code hinsichtlich eines Regelwerks. Typische Beispiele sind hier das Verhindern von öffentlichen IP-Adressen in virtuellen Maschinen.
- Im Kubernetes-Umfeld spielt OPA von je her eine große Rolle. Dabei können Admission Controller Anfragen an OPA senden, um eine Entscheidung zu erhalten, welche Ressourcen in einem Kubernetes Cluster deployt werden dürfen.
Installation von OPA
OPA selber besteht aus einer einzelnen Binär-Datei und ist somit schnell installiert. Dabei existiert Unterstützung für Linux, macOS und Windows. Unter Linux gelingt die Installation wie folgt:
Nach dem Download müssen Sie als Erstes die Datei-Attribute so ändern, dass das Binary ausgeführt werden kann: chmod 755 opa. Anschließend fügen Sie den Ort der OPA-Binaries der PATH-Variable hinzu:
export PATH=<Verzeichnispfad des OPA-Binary>
Falls Sie mit Visual Studio Code arbeiten, empfiehlt sich zusätzlich noch die Installation eines Plug-ins. Dazu wählen Sie im linken Bereich das Extension-Menü und suchen nach dem "Open Policy Agent Plug-in". Mit einem Klick auf "Install" installieren Sie es anschließend (Bild 1). Sobald Sie die Extension installiert haben, sind Sie in der Lage, eine neue Datei in Rego mit Regeln zu erzeugen. Ein einfaches Beispiel lautet wie folgt:
package hello
default allow_hello = false
default allow_world = false
allow_hello {
"hello" != ""
}
allow_world {
"world" != "world"
}
Um es auszuwerten, führen Sie mithilfe der Command-Palette im Menü "View" den Befehl "OPA Evaluate Package" aus, woraufhin sich ein zweiter Tab öffnet und das Ergebnis anzeigt. Das Ergebnis auf der rechten Seite ist leicht nachvollziehbar: Zuerst werden die Variablen "allow_ hello" und "allow_world" auf "false" gesetzt und anschließend ihre Werte mit Hilfe einer Funktion überprüft.
Bild 1: Für Visual Studio Code gibt es ein OPA-Plug-in, das die Arbeit deutlich erleichtert.
Mit Variablen, Objekten und Funktionen arbeiten
Zum Ausführen von einfachen Ausdrücken können Sie auch vom Terminal aus eine interaktive Shell mit dem Befehl opa run starten und Befehle ausführen. Hier ein Beispiel zum Definieren von Variablen:
greeting := "Hello"
max_height := 42
pi := 3.14159
allowed := true
location := null
Die Werte lassen sich nun wie folgt ausgeben – auf Wunsch in einem Array:
[greeting, max_height, pi, allowed, location]
[
"Hello",
42,
3.14159,
true,
null
]
Auch die Definition von Objekten ist möglich. Im folgenden Beispiel definieren wir ein Objekt, das wiederum aus Unterelementen besteht – darunter ein Array aus Strings zur Definition von IP-Adressen. Der zweite Block zeigt, wie Sie auf Elemente darin zugreifen:
ips_by_port := {
80: ["1.1.1.1", "1.1.1.2"],
443: ["2.2.2.1"],
}
ips_by_port[80]
[
"1.1.1.1",
"1.1.1.2"
]
Ein wichtiges Element von Rego sind die Rules. Im nachfolgenden Beispiel gibt es einen Default-Wert mit "False". Falls ein Input einen Wert "Role" mit "Admin" aufweist, ändert sich der Wert auf "true". Alternativ kann dies auch geschehen, wenn die Rolle "User" lautet und das Feld "has_permissions" auf den Wert "true" gesetzt ist:
default allow = false
allow = true {
input.role == "admin”
}
allow = true {
input.role == "user”
input.has_permission == true
}
Auch Funktionen lassen sich definieren. Folgendes Codefragment zeigt, wie Sie eine Multiply-Funktion implementieren und aufrufen:
package function
multiply(a,b) = m {
m := a*b
}
result1 = r {
r := multiply(3,4)
}
result2 = r {
r := multiply(3,9)
}
Die Rego-Programmiersprache bringt von Haus Funktionen für folgende Bereiche mit:
- Funktionen für Zahlen
- Bit-Manipulation
- Aggregationsfunktionen
- Funktionen für Mengen (Array, Set, Object)
- String-Funktionen
- Reguläre Ausdrücke
- Funktionen zum Arbeiten mit HTTP
- Funktionen zum Arbeiten mit JSON Web Token (JWT)
Listing 1 zeigt exemplarisch einige Funktionsaufrufe.
Listing 1: Beispiele für Funktionsaufrufe
# Hier wird aufgerundet
# 11
round(10.9)
# Ein Array aus Zahlen generieren
# [9, 8, 7, 6, 5, 4, 3, 2, 1]
numbers.range(9, 1)
# Der Typ der Übergabe ist "array"
# "array"
type_name(["apple","banana","pineaplle"])
# Die Eingabe ist ein Set
# true
is_set({1,2,3})
# Zwei Elemente sind im Objekt enthalten
# 2
count({"width":1024, "height":768})
# [1,2,3,4,5,4,5,6]
array.concat([1,2,3,4,5],[4,5,6])
# Extraktion von key3, weil dies nicht im Objekt enthalten ist, wird der Wert "val3" zurückgegeben
obj := {"key1":"val1", "key2":"val2"}
# "val3”
object.get(obj, "key3", "val3")
# An welcher Stelle steht das Wort "world"
# 7
indexof("Hello, world", "world")
# POST-HTTP-Aufruf mit Header und Body
http.send({"url":"http://httpbin.org/post", "method":"post", "timeout":"3s", "headers":{"token":"111"}, "body":{"key": "val"}})
Beispiel für eine Policy
Mit den gezeigten Sprachkonstrukten gelingt es schnell, Policies zu definieren, mit denen Sie Anomalien, Fehlkonfigurationen oder Fälle von "Poor Practice" erkennen. Der Code im Listing 2 zeigt beispielhaft, wie Sie AWS-Nutzer identifizieren, die bei der Anmeldung keine Multifaktor-Authentifizierung (MFA) nutzen.
Diesen Input können Sie aus AWS exportieren und in der Datei "input.json" als Input für die Policy speichern. Sie erkennen so leicht, ob jeder Nutzer ein MFA-Gerät hinterlegt hat:
package match
is_array(input.MFA)
count(input.MFA) > 0
}
match {
not active_with_mfa
}
OPA und Kubernetes
Einer der bekanntesten Use Cases für OPA ist der Einsatz in Kubernetes. Technisch installieren Sie OPA als Opd in den Cluster. Der Admission Controller leitet daraufhin den Request des Benutzers an die im Validating-Mutating-Webhook hinterlegte Adresse weiter. Die App, die auf dieser Addresse antwortet ändert das erhaltene Manifest ab (mutating) oder sendet eine allow-deny Antwort zurück. Beides wird vom Admission Controller erzwungen.
Die Controller sind – kurz gesagt – dafür verantwortlich, eingehende (und authentifizierte) API-Requests zu überprüfen und abhängig vom Ergebnis entweder zu erlauben oder zurückzuweisen. Intern hat der Admission-Control-Prozess zwei Phasen: Eine Mutating-Phase, in der Anfragen geändert werden können, und eine Validation-Phase zur Überprüfung. Eine Ausprägung eines solchen Admission Controllers kann dementsprechend an jeder dieser beiden Stellen wirken.
Ein Beispiel für einen derartigen Controller ist der "LimitRanger", der Pods mit den Default-Request-Ressourcen versieht, gleichzeitig aber verhindert, dass Pods mehr Ressourcen beziehen, als per Limit vorhergesehen. Insgesamt gibt es über 30 solcher Admission Controller, die standardmäßig an Bord sind – zwei sind aber besondere Beachtung wert: "ValidatingAdmissionWebhooks" und "MutatingAdmissionWebhooks". Beide implementieren selbst keine Logik, sondern erlauben die Registrierung von REST-Endpunkten eines Services, der selbst im Cluster läuft.
OPA wird dann auch als ein solcher Admission Controller eingesetzt und kann somit eine Reihe von Sicherheitsmerkmalen im Cluster konfigurieren:
- Definieren, dass alle Container Sidecars haben müssen – beispielsweise, um Logging- oder Auditing-Aufgaben durchzuführen.
- Das Versehen aller Ressourcen mit Annotationen.
- Abändern der Container-Image-Definition, sodass Images nur aus einer definierten Image-Registry geladen werden können.
- Setzen von Node, Pod Affinity und Anti-Affinity-Selektoren auf Deployments.
Die Installation von OPA in Kubernetes gelingt recht einfach und kann auf der Seite [2] nachvollzogen werden.
Das aktuelle Major-Release von Gatekeeper wurde im Jahr 2019 als Kollaboration der Firmen Google, Microsoft, Red Hat und Styra veröffentlicht und ist in Bild 2 visualisiert.
Bild 2: Gatekeeper integriert sich mittels Webhooks in die Kubernetes-Architektur.
Um nun Regeln zu definieren, ist es notwendig, ein Constraint-Template zu konfigurieren. Mithilfe eines solchen definieren Sie einerseits den Rego-Code, der bei der Überprüfung von Ressourcen zum Tragen kommt, anderseits bestimmen Sie, auf welche Elemente das entsprechende Constraint angewendet wird.
Listing 3 zeigt, wie Sie ein entsprechendes Template aufsetzen, wenn Sie erzwingen wollen, dass Namespaces mit Labels versehen werden.Der API-Typ ist dabei "templates.gatekeeper.sh/v1beta1". Das Namensfeld in der Metadata-Sektion zeigt den Namen des Constraints an. Innerhalb des spec-Bereiches definieren Sie eine Custom Resource Defintion (CRD) in Kubernetes mit dem Namen "K8sRequiredLabels" und konfigurieren das Schema über den open-APIV3Schema-Block. Das Objekt hat dabei ein Attribut mit dem Namen "labels" und besteht aus einem Array von Strings.
Listing 3: In Kubernetes Namespaces mit Labels versehen
In der Target-Section legen Sie per Constraint fest, wer für die Evaluierung verantwortlich ist – dies ist der installierte Admission Controller. Im rego-Attribut hinterlegen Sie anschließend noch den Rego-Code, mit dem Sie überprüfen, ob ein Namespace auch die gewünschten Labels mit sich trägt.
Nun müssen Sie das Constraint in den Kubernetes-Cluster hochladen. Dies gelingt mit dem kubectl-Kommando:
Anschließend können Sie das Constraint nutzen, um zu definieren, welche Labels auf den Namespaces vorhanden sein müssen. Dazu erstellen Sie eine YAML-Datei mit dem Inhalt aus Listing 4.
An dieser Stelle sollten Sie die OPA-Regeln noch testen und einen Namespace mit fehlenden Labels anlegen. Folgende Fehlermeldung sollte dabei erscheinen:
kubectl apply -f badns.yaml
Error from server ([denied by ns-must-have-gk] you must provide labels: {"gatekeeper"}): error when creating "badns.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by ns-must-have-gk] you must provide labels: {"gatekeeper"}
IaC und OPA
Aufgrund seiner Flexibilität kann OPA in unterschiedlichsten Security-Anwendungsfällen zum Einsatz kommen. Beliebt ist zum Beispiel die Überprüfung von Terraform-Code. Diese können Sie manuell ausführen oder aber in eine IaC-Pipeline einbauen und somit automatisch beim Deployment ausführen. Damit dies gelingt, müssen Sie den Terraform-Ausführungsplan abspeichern und in das JSON-Format überführen:
terraform init
terraform plan --out tfplan.binary
terraform show -json tfplan.binary > tfplan.json
Nun machen Sie sich an die Implementierung der Policy. Das Beispiel in Listing 5 zeigt, wie Sie sicherstellen, dass alle S3-Buckets in AWS eine Private Access Control List (ACL) haben. Um diesen Test auszuführen, nutzen Sie:
opa eval --fail-defined --format raw --input tfplan.json --data
Styra hat neben der Open-Source-Variante von OPA auch ein kommerzielles Produkt mit dem Namen "Styra Declarative Authorization Service" (Styra DAS) im Angebot. Dabei handelt es sich um eine Control Plane für OPA, mit der Sie zentral alle Policies verwalten. Besonderen Mehrwert bietet das Produkt, da es viele vordefinierte Regelwerke mit sich bringt. Für Admission Controller sind es beispielsweise über 100 Policies, inklusive bekannter Policy-Packs wie PCI, MITRE oder CIS. Darüber hinaus existiert eine Unterstützung für Single Sign-on sowie eine Integration in GIT.
Es existieren zwei DAS-Versionen, wobei "DAS Free" auf 100 Regeln, maximal vier Systeme beziehungsweise zehn Kubernetes-Knoten beschränkt ist. Variante zwei ist "DAS Enterprise", das die freie Version um die oben genannten Features ergänzt. Darüber hinaus bietet Styra DAS noch Unterstützung für Terraform in Form einer Vielzahl an vorgefertigten Policies. Neben den genannten Security-Benchmarks ermöglicht das Produkt ein zentrales Monitoring über die Policies sowie eine Auswertung über deren Einhaltung. Bei Micoservices gibt es zudem die Option, sämtliche Logik für die Autorisierung an einer zentralen Stelle zu verwalten und auf Microservices anzuwenden.
Fazit
OPA hat im Kubernetes-Umfeld längst Einzug an zentraler Stelle gehalten. Aufgrund der Flexibilität seiner Programmiersprache Rego verbreitert sich das Einsatzfeld zunehmend auf das cloudnative Umfeld. Es hat das Zeug, zu einem Standard in Security-Belangen zu werden, auf den auch große Firmen wie Google, Red Hat, Microsoft oder VMware in ihren Produkten setzen.