Kubernetes Policies mit OPA Gatekeeper

20 Februar, 2025

Daniel Bodky
Daniel Bodky
Senior Platform Advocate

Daniel kam nach Abschluss seines Studiums im Oktober 2021 zu NETWAYS und beriet zwei Jahre lang Kunden zu den Themen Icinga2 und Kubernetes, bevor es ihn weiter zu Managed Services zog. Seitdem redet und schreibt er viel über cloud-native Technologien und ihre spannenden Anwendungsfälle und gibt sein Bestes, um Neues und Interessantes rund um Kubernetes zu vermitteln. Nebenher schreibt er in seiner Freizeit kleinere Tools für verschiedenste Einsatzgebiete, nimmt öfters mal ein Buch in die Hand oder widmet sich seinem viel zu großen Berg Lego. In der wärmeren Jahreszeit findet man ihn außerdem oft auf dem Fahrrad oder beim Wandern.

von | Feb. 20, 2025

Hat man mit Kubernetes erst einmal losgelegt und befindet sich im produktiven Betrieb, kommen im Laufe der Zeit häufig neue Anforderungen auf die bestehenden Umgebungen zu. Diese können vielfältig sein: Von konsistenten Labels für ausgerollte Anwendungen bis hin zu sicherheitsrelevanten Einstellungen gibt es Vieles, was sich ohne eine Art von Kubernetes Policies nicht umsetzen lässt.

Durch seinen modularen Ansatz bietet Kubernetes hier nicht viele Möglichkeiten von sich aus – Netzwerkverkehr lässt sich unter Zuhilfename eines CNI mit NetworkPolicies regeln, und der Pod Security Admission Controller bietet verschiedene vordefinierte Profile für Anwendungssicherheit, doch darüber hinaus muss man sich mit externen Anwendungen behelfen.
Zwei häufig genutzte Tools hierfür sind Kyverno und der Open Policy Agent (OPA) mit seiner Kubernetes Implementierung Gatekeeper, um Kubernetes Policies einzuführen – um Gatekeeper wird es im heutigen Tutorial gehen.

Mach‘ direkt mit!
Verfolge dieses Tutorial live und in einer echten Kubernetes Umgebung in unserem kostenlosen NWS Playground.

Open Policy Agent und Gatekeeper erklärt

Der Open Policy Agent (OPA) bietet Policy-basierte Kontrolle für cloud-native Umgebungen – diese beschränken sich nicht nur auf Kubernetes: OPA bietet deklarativekontextabhängige Unterstützung für Policies rund um Kubernetes, Envoy, verschiedene HTTP und GraphQL APIs, Kafka und andere Szenarien.

Ermöglicht wird diese Vielfalt von Anwendungsbereichen durch OPAs Policy Sprache Rego, die sich wiederum stark an Datalog orientiert und von ihr inspiriert wurde. Jede Abfrage in Rego definiert und prüft hierbei gewisse Annahmen zu Daten, die OPA zum Zeitpunkt der Überprüfung zur Verfügung stehen, bspw. der Inhalt von API-Anfragen. Eine einfache Kubernetes Policy könnte in Rego z.B. wie folgt aussehen:

package kubernetes.admission

deny contains msg if {
    input.request.kind.kind == "Pod"
    image := input.request.object.spec.containers[_].image
    not startswith(image, "myregistry.com/")
    msg := sprintf("image '%v' comes from untrusted registry", [image])
}

Diese Policy lehnt Anfragen an die Kubernetes-API zur Erstellung von Pods ab, wenn alle in der Regel definierten Annahmen als true evaluiert wurden. OPA iteriert bei der Evaluierung der Anfrage von sich aus über alle Eingaben und prüft, ob die Annahmen im jeweiligen Fall true oder false sind.
Im obigen Beispiel würde also die Erstellung eines Pods verweigert werden, wenn mindestens ein im Pod definierter Container ein Image aus einer anderen Registry als myregistry.com beziehen möchte.

Um Kubernetes Policies für OPA zu testen, bietet sich der offizielle Rego Playground an. In diesem können Rego-Module definiert und mit beliebigen Eingaben getestet werden.

Gatekeeper ist eine Implementierung von OPA speziell für Kubernetes Policies, mit ein paar nützlichen Erweiterungen:

  • eine erweiterbare, parametrisierbare Bibliothek mit Kubernetes Policies
  • Kubernetes CustomResourceDefinitions (CRDs) für den Import und die Nutzung bestehender Policies (Constraints)
  • Kubernetes CRDs für die Definition neuer Policies (ConstraintTemplates)
  • Kubernetes CRDs für die Mutation von Anfragen und Daten
  • Audit-Funktionalität
  • Support für externe Daten und Datenquellen

Installation von OPA Gatekeeper

Möchte man Kubernetes Policies mit dem OPA Gatekeeper umsetzen, installiert man ihn zuerst in seiner Umgebung. Dies kann bspw. mit Hilfe der offiziellen Helmchart geschehen:

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install -n gatekeeper-system gatekeeper gatekeeper/gatekeeper --create-namespace

Nach ein paar Sekunden sind der Gatekeeper Controller und der Audit Helfer im entsprechenden Namespace zu finden:

kubectl get all -n gatekeeper-system
NAME                                                 READY   STATUS    RESTARTS   AGE
pod/gatekeeper-audit-8948486cd-754x6                 1/1     Running   0          118s
pod/gatekeeper-controller-manager-5f9dfb6899-4m92s   1/1     Running   0          118s
pod/gatekeeper-controller-manager-5f9dfb6899-8ns8j   1/1     Running   0          118s
pod/gatekeeper-controller-manager-5f9dfb6899-drmqn   1/1     Running   0          118s

NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/gatekeeper-webhook-service   ClusterIP   10.254.22.244   <none>        443/TCP   118s

NAME                                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/gatekeeper-audit                1/1     1            1           118s
deployment.apps/gatekeeper-controller-manager   3/3     3            3           118s

NAME                                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/gatekeeper-audit-8948486cd                 1         1         1       118s
replicaset.apps/gatekeeper-controller-manager-5f9dfb6899   3         3         3       118s

Auch die zuvor erwähnten CRDs sind bereits installiert und unserem Cluster bekannt:

kubectl get crds | grep gatekeeper
assign.mutations.gatekeeper.sh                       2025-02-19T10:09:19Z
assignimage.mutations.gatekeeper.sh                  2025-02-19T10:09:19Z
assignmetadata.mutations.gatekeeper.sh               2025-02-19T10:09:19Z
configpodstatuses.status.gatekeeper.sh               2025-02-19T10:09:19Z
configs.config.gatekeeper.sh                         2025-02-19T10:09:19Z
constraintpodstatuses.status.gatekeeper.sh           2025-02-19T10:09:19Z
constrainttemplatepodstatuses.status.gatekeeper.sh   2025-02-19T10:09:19Z
constrainttemplates.templates.gatekeeper.sh          2025-02-19T10:09:19Z
expansiontemplate.expansion.gatekeeper.sh            2025-02-19T10:09:19Z
expansiontemplatepodstatuses.status.gatekeeper.sh    2025-02-19T10:09:19Z
modifyset.mutations.gatekeeper.sh                    2025-02-19T10:09:19Z
mutatorpodstatuses.status.gatekeeper.sh              2025-02-19T10:09:19Z
providers.externaldata.gatekeeper.sh                 2025-02-19T10:09:19Z
syncsets.syncset.gatekeeper.sh                       2025-02-19T10:09:19Z

Im nächsten Schritt können nun bestehende Kubernetes Policies aus der OPA Gatekeeper Library importiert oder eigene Policies geschrieben werden.

Nutzung bestehender Kubernetes Policies

Möchte man bestehende Kubernetes Policies aus der OPA Gatekeeper Library einbinden, geschieht das durch ein einfaches kubectl apply. Für diesen Blogposts werden wir uns drei bestehende Policies anschauen:

  • K8sAllowedRepos zur Einschränkung, aus welchen Registries Containerimages bezogen werden dürfen
  • K8sRequiredLabels zur Forcierung zwingend zu setzender Label
  • K8sContainerLimits zur Forcierung zwingend zu setzender ResourceLimits

Installation bestehender ConstraintTemplates

Die entsprechenden ConstraintTemplates können wir mit kubectl direkt aus dem Policy Repository installieren:

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/allowedrepos/template.yaml

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredlabels/template.yaml

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/containerlimits/template.yaml

Die erfolgreiche Installation kann ebenfalls überprüft werden:

kubectl get ConstraintTemplates
NAME                 AGE
k8sallowedrepos      8s
k8scontainerlimits   8s
k8srequiredlabels    8s

Ein ConstraintTemplate definiert grundsätzlich zwei verschiedene Dinge:

  • den Aufbau des auf dem ConstraintTemplate aufbauenden, parametrisierbaren Constraints als OpenAPI Spezifikation
  • die anzuwendende(n) Regel(n)

Nutzung installierter ConstraintTemplates durch Constraints

Sind die ConstraintTemplates erst einmal installiert, können sie durch Constraints in konkrete Kubernetes Policies gegossen werden.

Ein Constraint kann hierbei je nach OpenAPI Spezifikation ganz unterschiedlich aussehen. Die folgenden Beispiele definieren und forcieren aufbauend auf den drei zuvor installierten ConstraintTemplates konkrete Kubernetes Policies für das betroffene Cluster.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: repo-is-quay
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "default"
  parameters:
    repos:
      - "quay.io/"

Dieser Constraint erlaubt im Namespace default ausschließlich Containerimages, die von quay.io bezogen werden.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: all-must-have-team
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    message: "All deployments must have a `team` label that points to your team name"
    labels:
      - key: team
        allowedRegex: "^team-[a-zA-Z]+$"

Dieser Constraint forciert die Existenz eines Labels team für alle Deployments. Der Wert des Labels muss zusätzlich der Form team-xxx entsprechen.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sContainerLimits
metadata:
  name: container-must-have-memory-limits
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "limited-resources"
  parameters:
    cpu: "-1"                                   # do not enforce CPU limits
    memory: "1Gi"

Dieser Constraint forciert im Namespace limited-resources ein zu setzendes Memory Limit von 1Gi (1 Gibibyte).

Die entsprechenden YAML-Blöcke können mittels kubectl apply installiert und im Anschluss auf Funktionalität überprüft werden.

Testen der definierten Kubernetes Policies

Die definierten Policies können durch Erstellung erlaubter oder eben unerlaubter Objekte getestet werden. Für die Kubernetes Policies K8sAllowedRepos erstellen wir zwei Pods im Namespace default:

kubectl run -n default denied-pod --image mysql:latest
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [repo-is-quay] container <denied-pod> has an invalid image repo <mysql:latest>, allowed repos are ["quay.io/"]

kubectl run -n default allowed-pod --image quay.io/fedora/mysql-80
pod/allowed-pod created

Gatekeeper verwehrt die Erstellung des ersten Pods mit einer konkreten Fehlermeldung, was dem Constraint widerspricht.

Für die Policy K8sRequiredLabels erstellen wir die folgenden zwei Deployments:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: denied-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - image: quay.io/fedora/mysql-80
          name: mysql
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: allowed-deployment
  labels:
    team: team-database
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - image: quay.io/fedora/mysql-80
          name: mysql

Auch in diesem Szenario erhalten wir von Gatekeeper eine Fehlermeldung für das nicht entsprechend der Kubernetes Policy gelabelte Deployment:

kubectl apply -f deployments.yaml
deployment.apps/allowed-deployment created
Error from server (Forbidden): error when creating "deployments.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [all-must-have-team] missing required label, requires all of: team
[all-must-have-team] regex mismatch

Für das dritte Szenario erstellen wir drei weitere Pods:

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: unaffected-pod
spec:
  containers:
    - image: quay.io/fedora/mysql-80
      name: mysql
---
apiVersion: v1
kind: Pod
metadata:
  namespace: limited-resources
  name: affected-pod-without-limits
spec:
  containers:
    - image: quay.io/fedora/mysql-80
      name: mysql
---
apiVersion: v1
kind: Pod
metadata:
  namespace: limited-resources
  name: affected-pod-with-limits
spec:
  containers:
    - image: quay.io/fedora/mysql-80
      name: mysql
      resources:
        limits:
          memory: 1Gi

Erneut erhalten wir eine Fehlermeldung für den Pod affected-pod-without-limits. Anzumerken ist außerdem, dass der Pod unaffected-pod trotz fehlender Limits erfolgreich erstellt wird – der Constraint greift laut Definition nur im Namespace limited-resources.

kubectl create namespace limited-resources
kubectl apply -f pods.yaml
pod/unaffected-pod created
pod/affected-pod-with-limits created
Error from server (Forbidden): error when creating "pods.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [container-must-have-memory-limits] container <mysql> has no resource limits

Unit-Tests für Kubernetes Policies

Da es in realen Szenarien selten sinnvoll ist, alle Kubernetes Policies im laufenden Betrieb manuell zu testen, ermöglicht OPA Gatekeeper automatisiertes Unit-Testing definierter Policies. Hierfür wird auf die gator CLI des Gatekeeper-Projekts zurückgegriffen.

Tests können zum Einen via gator test angestoßen werden und testen eine beliebige Anzahl an gegebenen Kubernetes Objekten gegen ein Set an ConstraintTemplates und Constraints.
Zum Anderen können mittels gator verify ganze Testsuites bestehend aus mehreren Tests und Testcases überprüft werden. Ein Test besteht hierbei aus einem konkreten ConstraintTemplate und Constraint sowie zu testenden Eingaben in Form von Kubernetes Objekten (als Cases).

Dieses Vorgehen ermöglicht automatisiertes Testen erstellter Kubernetes Policies bspw. in CI Pipelines, ohne manuell in der tatsächlichen Umgebung testen zu müssen. In der gator CLI Dokumentation finden sich weiterführende Erklärungen und Beispiele.

Kubernetes Policies für Jedermann

OPA Gatekeeper mit seiner umfangreichen Bibliothek an vorgefertigten Kubernetes Policies für viele erdenkliche Szenarien bietet einen guten Einstieg, den Alltagsbetrieb auf Kubernetes zu vereinheitlichen und abzusichern.
Sollten noch spezifischere Richtlinien nötig sein, hilft der Rego Playground und die Möglichkeit, erstellte Policies unabhängig von der tatsächlichen Umgebung testen zu können. Auf diese Weise kann ein sicherer Betrieb, auch in Multi-Tenancy-Szenarien oder stark reglementierten Umgebungen gewährleistet werden.

Wichtig ist an dieser Stelle, dass OPA Gatekeeper nicht die einzige Lösung für diese Probleme bietet – Anwendungen wie Kyverno haben ebenfalls viele Anwender, und kombiniert mit Anwendungen zur Laufzeitabsicherung Deiner Kubernetes Umgebung wie Tetragon oder Falco kann ein ganzheitlicher Ansatz zur Sicherung von Kubernetes Umgebungen geschaffen werden. Auf den ein oder anderen Lösungsansatz werden wir sicher in Zukunft auch hier auf unserem Blog eingehen.

Bis dahin wünschen wir dir viel Erfolg beim Umsetzen der für dich nützlichen Kubernetes Policies – solltest du hierbei Hilfe benötigen, steht dir unser MyEngineer® natürlich jederzeit zur Verfügung!

Unser Portfolio

0 Kommentare

Einen Kommentar abschicken

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Wie hat Dir unser Artikel gefallen?