Kubernetes least privilege Umsetzung am Beispiel der Google Cloud
Jeder kennt es: die Vergabe von Rechten ist immer eine Balance zwischen Sicherheit, Nutzbarkeit und Pflegeaufwand. Werden Berechtigungen sehr großzügig vergeben ist der Aufwand sehr gering und es gibt nur selten Hürden bei der Nutzung; die Sicherheit ist allerdings gefährdet. Bei einer sparsamen Vergabe von Rechten ist die Sicherheit höher, es gibt jedoch aufwändige Prozesse und viel Verwaltungsaufwand.
Kubernetes bietet mit seinem Role-based access control (RBAC) viele Möglichkeiten, die auch ausführlich dokumentiert sind (https://kubernetes.io/docs/reference/access-authn-authz/rbac/). Leider gibt es jedoch nicht viele Praxistipps für die tatsächliche Umsetzung. Um aus dieser Zwickmühle auszubrechen, haben wir Plugins geschrieben, die es Ihnen ermöglichen mit dem Kubernetes-sudo-Context einen einfachen, aber effektiven Einstiegspunkt zum Verwalten von Rechten zu bekommen. Dabei veranschaulicht dieser Blogartikel die Installation der Plugins und die Konfiguration des Clusters am Beispiel eines Managed Kubernetes der Google Cloud Plattform.
Rechte von Entwickelnden im Cluster
Die hier vorgestellte Lösung bezieht sich auf Berechtigungen von Menschen, da Applikationen grundsätzlich nur read-only Zugriff auf ihre eigenen Secrets und die Configmap im Cluster haben sollten. Das ist von der Vergabe von Berechtigungen also übersichtlich. Bei Berechtigungen für Entwickelnde wird es aber schon deutlich komplexer, da sich Aufgaben und Rollen von Menschen über die Zeit ändern können und weil Menschen durch Berechtigungen auch davor geschützt werden können Flüchtigkeitsfehler zu machen. Dadurch entsteht der eingangs erwähnte hohe Pflegeaufwand.
Eine Lösung mit minimalem Pflegeaufwand ist es, allen Entwickelnden die gleichen, umfangreichen Berechtigungen zu geben. Dadurch entsteht jedoch die Gefahr, dass zu jedem Zeitpunkt versehentlich schädliche Änderungen vorgenommen werden können. Vor allem in produktiven Umgebungen kann dies zu kritischen Downtimes und sogar Datenverlust führen.
Deswegen haben wir uns für einen Ansatz entschieden, bei dem für die Ausführung von Befehlen kurzzeitig zusätzliche Berechtigungen genutzt werden, ähnlich dem sudo-Befehl unter Linux.
Umsetzung des least-privilege-Ansatzes mittels sudo-context
Für die Umsetzung der Berechtigungen nach sudo-Art müssen diese Dinge umgesetzt werden:
- Nutzung des Impersonate-Funktion von Kubernetes
- Einrichtung des sudo-Context
- Vergabe von Berechtigungen im Cluster
Diese Schritte werden wir jetzt im Detail beschreiben.
Einrichtung der Impersonate-Funktion
Die sudo-Funktion ist Kubernetes-intern das “impersonate” Feature der Kubernetes API. Es ermöglicht es Befehle als anderer User, Gruppe oder Service Account auszuführen. Der erste Schritt ist es die sudo-Funktion im Cluster zu ermöglichen. Dafür gibt es ja nach Anwendungsfall unterschiedliche Möglichkeiten. Wie diese letztendlich auf Client- und Cluster Seite installiert und konfiguriert werden, folgt im Anschluss.
kubectl-sudo-Plugin
Mit dem kubectl-sudo Plugin können kubectl-Befehle, welche weiterreichende Rechte benötigen, explizit als Mitglied der Admin Gruppe ausgeführt werden. Dadurch verringert sich die Wahrscheinlichkeit, dass versehentlich Ressourcen am Cluster verändert oder gelöscht werden, wenn zum Beispiel Skripte ausgeführt werden oder man sich im falschen Namespace befindet.
Das Plugin funktioniert nur für kubectl. Weitere Tools, welche die kubeconfig nutzen (Helm, fluxctl, k9s, etc.) können es jedoch nicht verwenden. Hier ist ein einfaches Beispiel für die Nutzung des Plugins.
kubectl sudo get pod
Helm-sudo-Plugin
Im Kubernetes-Umfeld sind Helm-Charts sehr wichtig. Um die Funktionalität also auch für Helm nutzen zu können, haben wir ein entsprechendes Plugin entwickelt, das analog zu kubectl-sudo verwendet werden kann. Analog zur Nutzung des kubectl-sudo Plugins ist hier ein Beispiel für das Helm Plugin.
helm sudo list
sudo-Context für weitere Tools
Alternativ und für alle anderen Tools wie fluxctl oder k9s besteht die Möglichkeit einen sudo-Context in der kubeconfig anzulegen. Dieser kann dann folgendermaßen genutzt werden:
kubectl --context SUDO-mycontext # Alternative zu kubectl-sudo
kgpo --context SUDO-mycontext # funktioniert auch mit aliases!
helm --kube-context SUDO-mycontext
fluxctl --context SUDO-mycontext
k9s --context SUDO-mycontext # Änderung auch in k9s möglich ":ctx"
Wenn für bestimmte Tools Auto-Completion Features installiert wurden, erkennen diese die verfügbaren Contexts automatisch und lassen sich mit Tab
auswählen.
Achtung: Beim Verwenden des sudo-Context muss darauf geachtet werden immer den Namespace, in dem ein Command ausgeführt werden soll, mitzugeben. Standardmäßig wird bei der Einrichtung des Contexts nämlich nur der aktuelle Namespace in der kubeconfig gespeichert. Soll nun ein Befehl in einem anderen Namespace ausgeführt werden, muss dies explizit durch einen Parameter mit angegeben werden:
kubectl --context SUDO-mycontext --namespace mynamespace get secret
Der sudo-Context sollte dabei immer nur als Parameter übergeben werden. Er sollte niemals als aktiver Context gesetzt werden, da dadurch dauerhafte Admin-Rechte vergeben würden und somit der Schutz vor versehentlichen Änderungen ausgehebelt wird. Dies ist analog zum sudo su
Befehl unter Linux, mit dem ein User alle Berechtigungen hat und bei riskanten Aktionen nicht aufgehalten wird.
Sowohl bei kubectl sudo
als auch helm sudo
muss jedoch nicht jedes mal der Namespace mit angegeben werden. Hier werden die Befehle immer im aktuellen Namespace des aktuellen Contexts ausgeführt. Deswegen ist für Helm und kubectl die Nutzung der sudo Plugins vorzuziehen.
Einrichtung der lokalen Tools
Zum Erzeugen eines sudo-Context steht dieses Skript zur Verfügung: wget -P /tmp/ "https://raw.githubusercontent.com/cloudogu/sudo-kubeconfig/0.1.0/create-sudo-kubeconfig.sh"
Mit ihm sind lediglich diese Schritte notwendig um eine kubeconfig für den aktuell ausgewählten Kontext interaktiv anzulegen:
chmod +x /tmp/create-sudo-kubeconfig.sh
/tmp/create-sudo-kubeconfig.sh
kubectl --context SUDO-mycontext get pod
Bei Bedarf können die beiden bereits erwähnten Plugins kubectl-sudo und helm-sudo über die Bash installiert werden:
Optional: kubectl-sudo installieren
sudo bash -c 'curl -fSL https://raw.githubusercontent.com/postfinance/kubectl-sudo/master/bash/kubectl-sudo -o /usr/bin/kubectl-sudo && chmod a+x /usr/bin/kubectl-sudo'
kubectl sudo get pod
Optional: helm-sudo installieren
helm plugin install https://github.com/cloudogu/helm-sudo --version=0.0.2
helm sudo list
Technische Realisierung der Autorisierung
Nachdem jetzt auf dem lokalen Rechner die Voraussetzungen für die Benutzung der sudo-Funktion geschaffen sind, müssen die Berechtigungen eingerichtet werden. Die Schritte zur technischen Realisierung zeigen wir beispielhaft anhand eines Managed Kubernetes der Google Cloud Platform.
RBAC
Mit der sudo-Funktion können wir jetzt in die Rolle von Usern, Gruppen und Service Accounts schlüpfen um Befehle auszuführen (impersonate). Damit wir durch das “impersonate” weiterreichende Rechte bekommen, müssen diese mittels des Role-based access control (RBAC) von Kubernetes berechtigt sein. Das “impersonate” wird realisiert von:
- kubectl-sudo:
kubectl --as=$USER --as-group=system:masters "$@"
- helm-sudo:
helm --kube-as-user ${USER} --kube-as-group system:masters "$@"
- und create-sudo-kubeconfig.sh:
as-groups: [ system:masters ]
In einem existierenden k8s-Cluster müssen zwei Ressourcen angelegt werden, um Usern den Zugriff auf das impersonate Feature zu geben:
- Eine ClusterRole, welche es erlaubt das impersonate Feature zu nutzen.
- Ein ClusterRoleBinding, um einzelne User oder Gruppen dazu zu berechtigen die zuvor erstellte ClusterRole zu nutzen.
# sudoer.yaml
# Creates a ClusterRole which allows to impersonate users,
# groups and serviceaccounts
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sudoer
rules:
- apiGroups: [""]
verbs: ["impersonate"]
resources: ["users", "groups", "serviceaccounts"]
# cluster-sudoers.yaml
# Allows users to use kubectl sudo on all resources in the cluster
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-sudoers
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sudoer
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: admins@email.com
- apiGroup: rbac.authorization.k8s.io
kind: User
name: user1@email.com
In der aktuellen ClusterRoleBinding haben alle dort aufgeführten sudo Rechte in allen Namespaces. Es ist ebenfalls möglich mehrere ClusterRoleBindings zu nutzen und somit nur für bestimmte Namespaces zu berechtigen. Dies ist ein guter Ansatz, wenn zum Beispiel verschiedene Teams getrennte Namespaces haben. Dazu muss in der ClusterRoleBinding unter metadata
ein weiteres Attribut namespace: namespace-name
aufgeführt werden.
Auf den ersten Blick sieht es vielleicht so aus, als ob jetzt anonym Änderungen am Cluster möglich wären, da eine andere Rolle angenommen wird. In den Audit Logs in der Google Cloud Platform lässt sich jedoch das eigentliche User Principal, welches eine Änderung vorgenommen hat, einsehen. Dadurch ist also nachvollziehbar welcher User eine Änderung gemacht hat. In Managed Cluster anderer Cloud-Provider funktioniert dies ähnlich.
Google Cloud Platform (GCP)
Damit RBAC und die gerade beschriebenen Anpassungen überhaupt in der GCP wirksam sind, muss zunächst die ursprüngliche Autorisierung per Google Cloud umgangen werden.
Personen, welche die Rolle Owner
, Editor
oder Kubernetes Engine Admin
in der GCP haben, dürfen nämlich normalerweise alles im Cluster ausführen, auch wenn es ihnen durch RBAC nicht explizit erlaubt ist.
Unter IAM muss in der GCP deshalb einmalig eine benutzerdefinierte Rolle angelegt werden, welche nur die Authentifizierung am Kubernetes Cluster erlaubt. Diese Rolle, welche wir “Kubernetes Engine Authentication” nennen, bekommt folgende Berechtigungen zugewiesen:
- container.apiServices.get
- container.apiServices.list
- container.clusters.get
- container.clusters.getCredentials
Diese Rolle bekommt jetzt alle User zugewiesen, die Zugriff auf den Cluster benötigen. Die weiteren Rechte werden dann durch RBAC im Cluster vergeben. Die Rolle kann auch an ganzen Gruppen vergeben werden. Die Gruppen werden wiederum über die GSuite Gruppen verwaltet. Dazu muss allerdings bei der Erstellung des Clusters die Propagierung von Gruppen aktiviert werden (Google Groups for RBAC). Bei einem bereits existierenden Cluster kann diese Einstellung im Nachhinein leider nicht aktiviert werden. Dazu muss er neu aufgebaut werden.
Fazit
Durch das Benutzen von RBAC und sudo-Context sind der Aufwand für die Pflege von Berechtigungen und Sicherheit in einer guten Balance. Zum einen müssen nicht aufwändig für jede Person Berechtigungen gepflegt werden und zum anderen wird die Gefahr von ungewollten Änderungen, z.B. weil man sich im falschen Namespace befindet deutlich reduziert.
Nehmen wir dieses Szenario: Ein Entwickler verprobt in seinem lokalen Dev-Cluster Änderungen eines Deployments. Im Laufe des Arbeitstages fallen kleinere Arbeiten am produktiven Cluster in der GCP an. Nun vergisst der Entwickler in seinen lokalen Context zurückzuwechseln und möchte das Test-Deployment am Ende des Tages löschen. Sowas in diese Richtung ist bestimmt einigen schon einmal passiert. Dies kann zu Downtimes oder im schlimmsten Fall zu Datenverlust führen.
Wenn nun aber am produktiven Cluster nur mit dem sudo-Context oder SUDO-Plugin Änderungen angewendet werden können, schlägt das versehentliche Löschen fehl und dem Entwickler fällt sein Fehler auf. Die Anfälligkeit für versehentliche Fehler wird also geringer bei gleichzeitig einfacher Nutzbarkeit, einfacher Implementierung und hoher Sicherheit. Seitdem wir bei Cloudogu selber RBAC und den damit verbundenen sudo-Context nutzen, arbeiten wir deutlich sicherer an unseren Kubernetes Clustern.