10 Kubernetes-SecurityContext-Einstellungen, die Sie kennen sollten
10. März 2021
0 Min. LesezeitWorkloads in Kubernetes sicher auszuführen, kann schwierig sein. Viele verschiedene Einstellungen wirken sich auf die Kubernetes-API-Sicherheit aus und erfordern umfangreiches Wissen, um sie korrekt zu konfigurieren. Eines der leistungsfähigsten Werkzeuge, die Kubernetes dafür bereitstellt, sind die securityContext-Einstellungen, die in jedem Pod- und Container-Manifest genutzt werden können. In diesem Cheatsheet sehen wir uns die verschiedenen securityContext-Einstellungen an, erklären ihre Bedeutung und zeigen, wie Sie sie verwenden.

runAsNonRoot
runAsUser / runAsGroup
seLinuxOptions
seccompProfile
privileged / allowPrivilegeEscalation
capabilities
readonlyRootFilesystem
procMount
fsGroup / fsGroupChangePolicy
sysctls
Pod- und Container-Einstellungen
Kubernetes-securityContext-Einstellungen sind sowohl in den APIs PodSpec als auch ContainerSpec definiert. Der Geltungsbereich ist in diesem Dokument durch die Kennzeichnungen [P] und/oder [C] neben den jeweiligen Einstellungen angegeben. Beachten Sie: Ist eine Einstellung in beiden Geltungsbereichen verfügbar und konfiguriert, hat die Container-Einstellung Vorrang.
Sehen wir uns nun in beliebiger Reihenfolge die securityContext-Einstellungen an:
1. runAsNonRoot [P/C]
Ein Container verwendet zwar Namespaces und cgroups, um seine Prozesse einzuschränken, doch eine einzige Fehlkonfiguration bei der Bereitstellung kann diesen Prozessen Zugriff auf Ressourcen des Hosts gewähren. Läuft der Prozess als Root, hat er auf diese Ressourcen denselben Zugriff wie das Root-Konto des Hosts. Werden außerdem andere Pod- oder Container-Einstellungen verwendet, um Einschränkungen zu lockern (z. B. procMount oder capabilities), erhöht eine Root-UID die Risiken einer Ausnutzung zusätzlich. Sofern Sie keinen sehr guten Grund haben, sollten Sie einen Container niemals als Root ausführen.
Was also tun, wenn Sie ein Image bereitstellen möchten, das als Root ausgeführt wird?
Option 1: Den im Basis-Image angegebenen Benutzer verwenden
Oft enthalten Basis-Images bereits einen angelegten Benutzer, überlassen es jedoch den Entwicklungs- oder Bereitstellungsteams, ihn zu verwenden. So wird das offizielle Node.js-Image beispielsweise mit einem Benutzer namens node und der UID 1000 ausgeliefert. Sie können den Container unter diesem Benutzer ausführen, doch in der Dockerfile wird er nicht ausdrücklich als aktueller Benutzer festgelegt. Sie müssen ihn entweder zur Laufzeit mit der Einstellung runAsUser konfigurieren oder den aktuellen Benutzer im Image mithilfe einer abgeleiteten Dockerfile ändern. Bei Ersterem wird vorausgesetzt, dass UID 1000 die Dateien im Anwendungsverzeichnis lesen kann. Sehen wir uns stattdessen ein Beispiel an, in dem wir mithilfe einer abgeleiteten Dockerfile ein eigenes Image erstellen.
Ohne zu sehr ins Detail des Image-Builds einzusteigen, nehmen wir an, wir haben eine bereits erstellte npm-Anwendung. Hier ist eine minimale Dockerfile, um ein Image auf Basis von [**node:slim**](https://hub.docker.com/_/node) zu erstellen und es unter dem bereitgestellten node-Benutzer auszuführen.
Die entscheidende Zeile beginnt mit USER und legt node als Standardbenutzer in jedem Container fest, der aus diesem Image gestartet wird. Wir verwenden die UID statt des Benutzernamens, weil Kubernetes den Standardbenutzernamen eines Images vor dem Start des Containers nicht der UID zuordnen kann. Bei der Bereitstellung mit der Angabe runAsNotRoot: true wird andernfalls ein Fehler ausgegeben.
Option 2: Das Basis-Image enthält keinen Benutzer
Was also tun, wenn das node-Basis-Image keinen Benutzer enthält, den wir verwenden können? Bei vielen Prozessen legen wir einfach selbst einen in einer abgeleiteten Dockerfile an und verwenden ihn. Erweitern wir das vorherige Beispiel:
Wie Sie sehen, besteht die einzige Ergänzung aus der RUN-Zeile, die einen Benutzer anlegt. Die Syntax kann je nach Distribution des Basis-Images variieren. Anschließend habe ich die Verweise auf Benutzer und Pfad entsprechend angepasst.
HINWEIS: Für node.js und npm funktioniert das problemlos. Bei anderen Tools müssen möglicherweise weitere Teile des Dateisystems anderen Besitzern zugewiesen werden. Sehen Sie in der Dokumentation Ihres Tools nach, falls Probleme auftreten.
2. runAsUser / runAsGroup [P/C]
Container-Images können einen bestimmten Benutzer und/oder eine bestimmte Gruppe festlegen, unter denen der Prozess ausgeführt wird. Mit den Konfigurationseinstellungen runAsUser und runAsGroup lässt sich dies überschreiben. Häufig werden diese Einstellungen zusammen mit Volume-Mounts verwendet, die Dateien mit denselben Eigentümer-IDs enthalten.
Die Verwendung dieser Einstellungen birgt Risiken, da Sie damit Laufzeitentscheidungen für den Container treffen, die möglicherweise nicht mit dem ursprünglichen Image kompatibel sind. So wird das offizielle CI-Server-Image jenkins/jenkins beispielsweise unter dem Benutzer und der Gruppe jenkins:jenkins ausgeführt, und die Anwendungsdateien gehören diesem Benutzer. Wenn wir einen anderen Benutzer konfigurieren, kann der Container nicht starten, weil dieser Benutzer nicht in der Datei /etc/passwd des Images vorhanden ist. Selbst wenn er dort vorhanden wäre, hätte er wahrscheinlich Probleme beim Lesen und Schreiben der Dateien, die jenkins:jenkins gehören. Mit einem einfachen docker run-Befehl können Sie das überprüfen:
Wie bereits erwähnt, sollten Container-Prozesse auf keinen Fall als Benutzer root ausgeführt werden. Verlassen Sie sich dafür jedoch nicht auf die Einstellungen runAsUser oder runAsGroup, denn diese könnten in Zukunft entfernt werden. Legen Sie zusätzlich runAsNonRoot auf true fest.
3. seLinuxOptions [P/C]
SELinux ist ein richtlinienbasiertes System zur Kontrolle des Zugriffs auf Anwendungen, Prozesse und Dateien unter Linux. Es implementiert das Framework Linux Security Modules im Linux-Kernel. SELinux basiert auf dem Konzept von Labels. Diese Labels werden auf alle Elemente des Systems angewendet und fassen sie zu Gruppen zusammen. Sie werden als Security Context bezeichnet – nicht zu verwechseln mit dem Kubernetes-securityContext – und bestehen aus user, role, type sowie einem optionalen Feld level im Format user:role:type:level.
SELinux verwendet anschließend Richtlinien, um festzulegen, auf welche anderen gekennzeichneten Objekte im System Prozesse eines bestimmten Kontexts zugreifen können. SELinux kann strikt durchgesetzt werden; in diesem Fall wird der Zugriff verweigert. Alternativ lässt sich der permissive Modus aktivieren, in dem Zugriffe protokolliert werden. In Containern werden der Container-Prozess und das Container-Image typischerweise so mit SELinux-Labels versehen, dass der Prozess nur auf Dateien innerhalb des Images zugreifen kann.
Die Container-Runtime wendet beim Erstellen eines Containers standardmäßige SELinux-Labels an. Mit der Einstellung seLinuxOptions in securityContext lassen sich benutzerdefinierte SELinux-Labels festlegen. Beachten Sie, dass eine Änderung der SELinux-Kennzeichnung eines Containers dem darin ausgeführten Prozess möglicherweise ermöglichen kann, die Grenzen des Container-Images zu überwinden und auf das Host-Dateisystem zuzugreifen.
Diese Funktion ist nur verfügbar, wenn das Host-Betriebssystem SELinux unterstützt.
4. seccompProfile [P/C]
Seccomp steht für secure computing mode und ist eine Funktion des Linux-Kernels, mit der sich die Systemaufrufe einschränken lassen, die ein bestimmter Prozess aus dem Userspace an den Kernel richten kann. Ein seccomp-Profil ist eine JSON-Definition, die in der Regel aus einer Reihe von Systemaufrufen und der Standardaktion besteht, die beim Auftreten eines dieser Aufrufe ausgeführt wird.
Kubernetes ermöglicht die Verwendung benutzerdefinierter Profile über die Einstellung seccompProfile in securityContext.
Für das Feld type gibt es drei mögliche Werte:
Localhost: Die EinstellunglocalhostProfilegibt einen Pfad innerhalb des Containers zu einem seccomp-Profil an.Unconfined: Es wird kein Profil angewendet.RuntimeDefault: Es wird der Standard der Container-Runtime verwendet. Dies ist die Standardeinstellung, wenn kein Typ angegeben ist.
Sie können diese Einstellungen entweder in einem PodSecurityContext oder in securityContext vornehmen. Sind beide festgelegt, gelten die Einstellungen auf Containerebene in securityContext. Beachten Sie, dass die Konfigurations-API securityContext mit Kubernetes v1.19 eingeführt wurde. Bei Bereitstellungen mit früheren Versionen gilt eine andere Syntax. Weitere Informationen und Beispiele finden Sie in der Kubernetes-Dokumentation.
Wie bei den meisten sicherheitsbezogenen Einstellungen gilt auch hier das Prinzip der geringsten Privilegien. Gewähren Sie Ihrem Container nur die erforderlichen Berechtigungen und keine weiteren. Erstellen Sie zunächst ein Profil, das lediglich die ausgeführten Systemaufrufe protokolliert. Testen Sie anschließend Ihre Anwendung, um die zulässigen Systemaufrufe zu ermitteln. Weitere Informationen zu diesem Verfahren finden Sie in den Kubernetes-Tutorials.
5. Privilegierte Container und Rechteausweitung vermeiden [C]
Einem Container den privilegierten Status zu gewähren, ist gefährlich. Dies wird häufig als einfachere Möglichkeit verwendet, bestimmte Berechtigungen zu erteilen, die sich auch gezielt über den Zugriff auf Capabilities steuern lassen. Die genaue Implementierung des privilegierten Flags hängt von der Container-Runtime ab. Es gewährt dem Container jedoch effektiv alle Berechtigungen und hebt die vom Device-cgroup-Controller durchgesetzten Einschränkungen auf. Außerdem kann es die Konfiguration des Linux Security Module ändern und Prozessen im Container ermöglichen, die Containergrenzen zu überwinden.
Container isolieren Prozesse auf dem Host. Deshalb gewährt die Container-Runtime dem Container selbst dann bestimmte Capabilities nicht, wenn er als Root ausgeführt wird. Ist das privilegierte Flag gesetzt, gewährt die Container-Runtime alle Capabilities von Root auf dem System. Aus Sicherheitssicht ist das äußerst gefährlich, da dadurch uneingeschränkter Zugriff auf das zugrunde liegende Host-System möglich wird.
Verwenden Sie das privilegierte Flag nicht. Benötigt Ihr Container zusätzliche Capabilities, fügen Sie über die Capabilities-Einstellungen nur die tatsächlich erforderlichen hinzu. Das privilegierte Flag ist nur nötig, wenn Ihr Container systemweite Einstellungen im Host-Kernel steuern muss – etwa auf bestimmte Hardware zugreifen oder Netzwerke neu konfigurieren – und Zugriff auf das Host-Dateisystem benötigt.
Weitere Informationen zu privilegierten Containern finden Sie in Matts Blogartikel: Privilegierte Docker-Container – brauchen Sie sie wirklich?
6. Linux-Kernel-Capabilities [C]
Capabilities sind Berechtigungen auf Kernel-Ebene, mit denen sich Kernel-Aufrufberechtigungen granularer steuern lassen, als wenn Sie einfach alles als Root ausführen. Dazu zählen beispielsweise das Ändern von Dateiberechtigungen, die Kontrolle des Netzwerk-Subsystems und systemweite Verwaltungsfunktionen. In securityContext bietet Kubernetes Konfigurationsmöglichkeiten, um Capabilities zu entfernen oder hinzuzufügen. Einzelne Capabilities oder eine durch Kommas getrennte Liste können als String-Array angegeben werden. Alternativ können Sie mit der Kurzform -all alle Capabilities hinzufügen oder entfernen. Diese Konfiguration wird an die Container-Runtime weitergegeben, die damit beim Erstellen des Containers dessen Capability-Satz festlegt. Ist in securityContext kein Abschnitt für Capabilities vorhanden, erhält der Container den von der Container-Runtime bereitgestellten Standardsatz.
Empfohlen wird, zunächst alle Capabilities zu entfernen und anschließend nur die wieder hinzuzufügen, die Ihre Anwendung tatsächlich benötigt. Viele Anwendungen benötigen im Normalbetrieb überhaupt keine Capabilities. Testen Sie dies, indem Sie alle entfernen, und untersuchen Sie Fehler anhand der Audit-Logs, um festzustellen, welche Capabilities blockiert wurden.
Wenn Sie in securityContext Capabilities zum Entfernen oder Hinzufügen angeben, lassen Sie das Präfix CAP_ weg, das der Kernel bei der Benennung von Capabilities verwendet. Zur Fehlersuche zeigt das Tool capsh in lesbarer Form genau an, welche Capabilities in Ihrem Container aktiviert sind. Es ist für die meisten Distributionen verfügbar. Lassen Sie es nicht in Produktionscontainern verfügbar, da Angreifer so leicht herausfinden können, welche Capabilities aktiviert sind! Wenn Sie Bitmaps lieber direkt auslesen, können Sie die aktivierten Capabilities auch in der Datei /proc/1/status überprüfen.
Erfahren Sie, wie Sie die Kubernetes-Sicherheit verbessern, indem Sie Standard-Capabilities für einen Container entfernen.
7. Mit einem schreibgeschützten Dateisystem arbeiten [C]
Wird Ihr Container kompromittiert und verfügt über ein Dateisystem mit Lese- und Schreibzugriff, kann ein Angreifer die Konfiguration ändern, Software installieren und möglicherweise weitere Exploits starten. Ein schreibgeschütztes Dateisystem hilft, solche Eskalationen zu verhindern, indem es die möglichen Aktionen eines Angreifers einschränkt. Im Allgemeinen sollten Container nicht in das Container-Dateisystem schreiben müssen. Wenn Ihre Anwendung zustandsbehaftete Daten enthält, sollten Sie eine externe Persistenzmethode wie eine Datenbank, ein Volume oder einen anderen Dienst verwenden. Achten Sie außerdem darauf, dass alle Protokolle in stdout und/oder an einen Log-Forwarder geschrieben werden, wo sie zentral zusammengeführt werden können.
8. procMount [C]
Standardmäßig blenden Container-Runtimes bestimmte Bereiche des /proc-Dateisystems innerhalb eines Containers aus, um potenzielle Sicherheitsprobleme zu verhindern. In manchen Fällen ist jedoch Zugriff auf diese Bereiche von /proc erforderlich, insbesondere bei verschachtelten Containern, wie sie häufig bei Build-Prozessen innerhalb eines Clusters zum Einsatz kommen. Für diesen Eintrag gibt es nur zwei gültige Optionen: Default, womit das Standardverhalten der Container-Runtime beibehalten wird, oder Unmasked, wodurch die gesamte Maskierung des **/proc**-Dateisystems aufgehoben wird.
Diesen Eintrag sollten Sie selbstverständlich nur verwenden, wenn Sie genau wissen, was Sie tun. Wenn Sie ihn tatsächlich zum Erstellen von Images verwenden, prüfen Sie die neueste Version Ihres Build-Tools, da viele Tools dies nicht mehr benötigen. Führen Sie ein Upgrade durch und kehren Sie zum Standardwert für procMount zurück, den Ihr verwendetes Tool vorgibt.
Falls Sie diesen Eintrag dennoch verwenden müssen, sollten Sie dies nur für einen verschachtelten Container tun. Legen Sie das niemals /proc-Dateisystem Ihres Host-Systems für einen Container offen.
9. fsGroup / fsGroupChangePolicy [P]
Mit der Einstellung fsGroup wird eine Gruppe festgelegt, deren Berechtigungen Kubernetes für alle Dateien in Volumes ändert, wenn diese von einem Pod eingebunden werden. Das Verhalten wird auch durch fsGroupChangePolicy gesteuert. Dafür stehen die Werte onRootMismatch und Always zur Verfügung. Bei onRootMismatch werden die Berechtigungen nur geändert, wenn sie nicht bereits mit den Berechtigungen des Container-Roots übereinstimmen.
Seien Sie beim Einsatz von fsGroup vorsichtig. Das Ändern der Gruppenzugehörigkeit eines gesamten Volumes kann bei langsamen und/oder großen Dateisystemen zu Verzögerungen beim Start von Pods führen. Es kann auch andere Prozesse beeinträchtigen, die dasselbe Volume verwenden, wenn sie keine Zugriffsberechtigungen für die neue GID besitzen. Aus diesem Grund implementieren einige Anbieter gemeinsam genutzter Dateisysteme wie NFS diese Funktion nicht. Diese Einstellungen wirken sich außerdem nicht auf kurzlebige Volumes aus.
10. sysctls [P]
Sysctls sind eine Funktion des Linux-Kernels, mit der Administratoren die Kernel-Konfiguration ändern können. In einem vollständigen Linux-Betriebssystem werden sie über /etc/sysctl.conf definiert und können auch mit dem Dienstprogramm **sysctl** geändert werden.
Mit der Einstellung sysctls in securityContext können bestimmte Sysctls im Container geändert werden. Nur eine kleine Teilmenge der Betriebssystem-Sysctls lässt sich pro Container ändern, da diese im Kernel Namespaces zugeordnet sind. Ein Teil davon gilt als sicher. Eine deutlich größere Gruppe gilt als unsicher, je nachdem, wie stark sie sich auf andere Pods auswirken kann. Unsichere Sysctls sind in Clustern in der Regel deaktiviert und müssen vom Cluster-Administrator ausdrücklich freigegeben werden.
Da Änderungen an sysctls das zugrunde liegende Betriebssystem destabilisieren können, sollten Kernel-Parameter nur bei sehr spezifischen Anforderungen geändert werden. Besprechen Sie solche Änderungen außerdem mit Ihrem Cluster-Betreiber.
Hinweis zu securityContext zur Laufzeit
In vielen Fällen werden die hier beschriebenen Sicherheitseinstellungen mit einer richtlinienbasierten Admission Control kombiniert, um sicherzustellen, dass erforderliche Einstellungen konfiguriert sind, bevor Container im Cluster gestartet werden. Durch die Kombination von securityContext-Einstellungen mit einer PodSecurityPolicy können Sie erzwingen, dass nur Container gestartet werden, die der Richtlinie entsprechen und bestimmte securityContext-Einstellungen erfüllen. securityContext-Einstellungen können beim Start auch über Dynamic Admission Control und den Einsatz mutierender Webhooks an die Container-Konfiguration angefügt werden.
Fazit
Beim Härten Ihrer Anwendungsbereitstellungen mit securityContext-Einstellungen gibt es vieles zu beachten. Richtig eingesetzt sind sie ein äußerst wirksames Mittel. Wir hoffen, dass diese Liste Ihren Teams dabei hilft, die passenden Optionen für Ihre Workloads und Umgebungen auszuwählen. Snyk unterstützt Sie dabei, indem es Ihre Kubernetes-YAML-Dateien auf häufige Fehlkonfigurationen überprüft. Registrieren Sie sich über die Schaltfläche unten kostenlos.
Sichern Sie Ihre Infrastruktur an der Quelle
Snyk automatisiert die IaC-Sicherheit und Compliance in Workflows und erkennt abweichende sowie fehlende Ressourcen.


