Kapitel 4. Sicherheit der Workload-Laufzeit

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Hinweis

In diesem Kapitel geht es um Pod-Sicherheitsrichtlinien (PSP), eine Funktion, die in Kubernetes v1.25 (Oktober 2022) veraltet ist und durch Pod-Sicherheitszulassung (PSA) ersetzt wurde. Wir haben die Abschnitte dieses Kapitels, die sich mit PSP befassen, beibehalten, da wir wissen, dass nicht alle Entwickler/innen sofort auf v1.25 umsteigen werden. Weitere Informationen findest du in der aktualisierten Dokumentation von Kubernetes über Pod-Sicherheitsrichtlinien.

Der standardmäßige Pod-Bereitstellungsmechanismus von Kubernetes bietet eine große Angriffsfläche, die von Angreifern genutzt werden kann, um den Cluster auszunutzen oder aus dem Container zu entkommen. In diesem Kapitel erfährst du, wie du Pod-Sicherheitsrichtlinien (PSPs) implementierst, um die Angriffsfläche der Pods zu begrenzen, und wie du Prozesse (z. B. Prozessrechte), Dateizugriff und Laufzeitsicherheit für deine Workloads überwachen kannst. Hier sind ein paar Details zu den Themen, die wir besprechen werden:

  • Wir gehen auf die Implementierungsdetails von PSPs ein, wie z.B. Pod-Sicherheitskontexte, und erläutern auch die Grenzen von PSPs. Beachte, dass PSPs seit Kubernetes v1.21 veraltet sind; wir werden dieses Thema jedoch in diesem Kapitel behandeln, da wir wissen, dass PSPs weit verbreitet sind.

  • Wir werden die Prozessüberwachung besprechen, die sich auf die Notwendigkeit einer Kubernetes-nativen Überwachung konzentriert, um verdächtige Aktivitäten zu erkennen. Wir werden die Laufzeitüberwachung und -durchsetzung mithilfe von Kernel-Sicherheitsfunktionen wie seccomp, SELinux und AppArmor behandeln, um zu verhindern, dass Container auf Host-Ressourcen zugreifen.

  • Wir werden sowohl die Erkennung als auch die Abwehr von Schwachstellen zur Laufzeit, die Isolierung von Arbeitslasten und die Eingrenzung des Aktionsradius behandeln.

Pod Sicherheitsrichtlinien

Kubernetes bietet eine Möglichkeit, deine Pods und Container mit Hilfe von PSPs sicher an Bord zu holen. Dabei handelt es sich um eine Cluster-Ressource, die eine Reihe von Bedingungen prüft, bevor ein Pod zugelassen und für den Betrieb in einem Cluster eingeplant wird. Dies geschieht über einen Kubernetes Admission Controller, der jede Anfrage zur Erstellung eines Pods daraufhin prüft, ob sie mit dem dem Pod zugewiesenen PSP übereinstimmt.

Bitte beachte, dass PSPs mit der Kubernetes Version 1.21 veraltet sind und mit der Version 1.25 abgeschafft werden sollen. Sie sind jedoch in Produktionsclustern weit verbreitet. Daher soll dieser Abschnitt dir helfen zu verstehen, wie sie funktionieren und welche bewährten Methoden es für die Implementierung von PSPs gibt.

Mit PSPs kannst du Regeln durchsetzen, z. B. dass Pods nicht als Root laufen dürfen oder dass Pods nicht das Host-Netzwerk oder den Host-Namensraum nutzen oder als privilegiert laufen dürfen. Die Richtlinien werden zum Zeitpunkt der Pod-Erstellung durchgesetzt. Durch den Einsatz von PSPs kannst du sicherstellen, dass Pods mit den für den Betrieb erforderlichen Mindestprivilegien erstellt werden, was die Angriffsfläche für deine Anwendung verringert. Außerdem hilft dir dieser Mechanismus dabei, verschiedene Standards wie PCI, SOC 2 oder HIPAA einzuhalten, die die Anwendung des Prinzips der geringsten Privilegien vorschreiben. Wie der Name schon sagt, verlangt dieses Prinzip, dass jedem Prozess, Benutzer oder in unserem Fall jedem Workload die geringstmöglichen Rechte eingeräumt werden, damit er funktionieren kann.

Pod-Sicherheitsrichtlinien verwenden

Kubernetes-PSPs werden empfohlen, aber über einen optionalen Admission Controller implementiert. Die Durchsetzung von PSPs kann durch die Aktivierung eines Admission Controllers aktiviert werden. Das bedeutet, dass das Manifest des Kubernetes-API-Servers ein PodSecurityPolicy-Plug-in in seiner --enable-admission-plugins-Liste enthalten sollte. Viele Kubernetes-Distributionen unterstützen PSPs nicht oder deaktivieren sie standardmäßig, daher lohnt es sich, bei der Auswahl der Kubernetes-Distributionen darauf zu achten.

Sobald die PSPs aktiviert sind, kannst du sie in drei Schritten anwenden, wie in Abbildung 4-1 dargestellt. Eine bewährte Methode ist es, PSPs auf Gruppen und nicht auf einzelne Dienstkonten anzuwenden.

Abbildung 4-1. Verfahren zur Anwendung von PSPs

Schritt 1 ist die Erstellung eines PSP. Schritt 2 ist die Erstellung von ClusterRole mit dem Verb use, das die Pod-Deployment-Controller zur Nutzung der Richtlinien berechtigt. Schritt 3 ist die Erstellung von ClusterRoleBindings, mit denen die Richtlinien für die Gruppen (d.h. system:authenticated oder system:unauthenticated) oder Dienstkonten durchgesetzt werden können.

Ein guter Ausgangspunkt ist die PSP-Vorlage aus dem Kubernetes-Projekt:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restricted
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: |
    'docker/default,runtime/default'
    apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
    seccomp.security.alpha.kubernetes.io/defaultProfileName:  'runtime/default'
    apparmor.security.beta.kubernetes.io/defaultProfileName:  'runtime/default'
spec:
  privileged: false
  # Required to prevent escalations to root.
  allowPrivilegeEscalation: false
  # This is redundant with non-root + disallow privilege escalation,
  # but we can provide it for defense in depth.
  requiredDropCapabilities:
    - ALL
  # Allow core volume types.
  volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    # Assume that persistentVolumes set up by the cluster admin are safe to use.
    - 'persistentVolumeClaim'
  hostNetwork: false
  hostIPC: false
  hostPID: false
  runAsUser:
    # Require the container to run without root privileges.
    rule: 'MustRunAsNonRoot'
  seLinux:
    # This policy assumes the nodes are using AppArmor rather than SELinux.
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  fsGroup:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  readOnlyRootFilesystem: false

Im folgenden Beispiel wendest du diese Richtlinie auf alle authentifizierten Benutzer an, die die rollenbasierte Zugriffskontrolle von Kubernetes nutzen:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole  Policy
metadata:
  name: psp-restricted
rules:
- apiGroups:
  - policy
  resourceNames:
  - restricted
  resources:
  - podsecuritypolicies
  verbs:
  - use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: psp-restricted-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: psp-restricted
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind : Group
    name: system:authenticated

Pod-Sicherheitsrichtlinien-Funktionen

Konzentrieren wir uns auf die Fähigkeiten der PSPs, die du je nach Anwendungsfall und internem Bedrohungsmodell unter nutzen kannst. Du kannst die gerade besprochene PSP-Vorlage verwenden, um deine eigenen PSPs zu erstellen. In dieser Vorlage werden die meisten PSP-Funktionen genutzt, um eine restriktive Richtlinie zu formulieren.

Um die Auswirkungen einer Fähigkeit zu erklären, schauen wir uns ein Beispiel an, in dem du die Fähigkeiten siehst, die dem mit privileged:true und mit privileged:false erstellten Pod gewährt werden. Mit dem Linux-Dienstprogramm capsh kannst du die Berechtigungen von root-Benutzern in Containern überprüfen. Wie du in Abbildung 4-2 sehen kannst, verfügt der privilegierte Pod über eine Vielzahl von Fähigkeiten in seinem Linux-Namensraum, was für einen Angreifer eine größere Angriffsfläche bedeutet, um aus deinem Container zu entkommen.

Abbildung 4-2. Pod-Fähigkeiten für Standard- und privilegierte Pods

Tabelle 4-1 fasst die Fähigkeiten von Pods zusammen, wie sie in der Kubernetes PSP-Dokumentation beschrieben sind.

Tabelle 4-1. Pod-Fähigkeiten
Feld Verwendet
privilegiert Erlaubt Containern, Fähigkeiten zu erlangen, die den Zugriff auf Host-Mounts, das Dateisystem zum Ändern von Einstellungen und vieles mehr umfassen. Du kannst die Fähigkeiten mit dem Befehl capsh --print überprüfen.
hostPID, hostIPC Gib dem Container Zugriff auf die Namensräume des Hosts, in denen die Prozess- und Ethernet-Schnittstellen für ihn sichtbar sind.
hostNetwork, hostPorts Gib der Container-IP Zugriff auf das Hostnetzwerk und die Ports.
Bände Erlaube Datenträgertypen wie configMap, emtyDir oder secret.
allowedHostPaths Erlaube das Whitelisting von Host-Pfaden, die von hostPath-Volumes verwendet werden können (z. B. /tmp).
allowedFlexVolumes Erlaube bestimmte FlexVolume-Treiber (z. B. azure/kv).
fsGroup Lege eine GID oder einen Bereich von GIDs fest, dem die Volumes des Pods gehören.
readOnlyRootFilesystem Setzt das Root-Dateisystem des Containers auf schreibgeschützt.
runAsUser, runAsGroup, supplementalGroups Definiere die Container UID und GID. Hier kannst du Nicht-Root-Benutzer oder -Gruppen angeben.
allowPrivilegeEscalation, defaultAllowPrivilegeEscalation Schränke die Privilegienerweiterung nach Prozess ein.
defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities Füge nach Bedarf Linux-Funktionen hinzu oder lasse sie weg.
SELinux Definiere den SELinux-Kontext des Containers.
allowedProcMountTypes Erlaubte Proc-Mount-Typen nach Container.
forbiddenSysctls,allowedUnsafeSysctls Setzt das vom Container verwendete sysctl-Profil.
Anmerkungen Lege die von Containern verwendeten AppArmor- und Seccomp-Profile fest.

AppArmor- und seccomp-Profile werden mit PSP-Anmerkungen verwendet, wobei du das Standardprofil der Laufzeitumgebung (Docker, CRI) verwenden oder ein benutzerdefiniertes Profil auswählen kannst, das du auf den Host geladen hast. Mehr über diese Verteidigungen erfährst du unter "Prozessüberwachung".

Pod-Sicherheitskontext

Im Gegensatz zu PSPs, die clusterweit definiert werden, kann ein Pod securityContext zur Laufzeit bei der Erstellung eines Deployments oder Pods definiert werden. Hier ist ein einfaches Beispiel für einen Pod securityContext in Aktion, bei dem der Pod mit dem Root-Benutzer (uid=0) erstellt wird und nur vier Fähigkeiten zulässt:

kind: Pod
apiVersion: v1
metadata:
  name: attacker-privileged-test
  namespace: default
  labels:
    app: normal-app
spec:
  containers:
  - name: attacker-container
    image: alpine:latest
    args: ["sleep", "10000"]
    securityContext:
      runAsUser: 0
      capabilities:
        drop:
          - all
        add:
          - SYS_CHROOT
          - NET_BIND_SERVICE
          - SETGID
          - SETUID

Dieses Codeschnipsel zeigt, wie du einen Pod erstellen kannst, der als Root läuft, aber durch die Angabe eines Sicherheitskontextes auf eine Teilmenge von Fähigkeiten beschränkt ist. Abbildung 4-3 zeigt die Befehle, die du ausführen kannst, um zu überprüfen, ob der Pod als Root mit den eingeschränkten Fähigkeiten läuft.

Abbildung 4-3. Vier zulässige Pod-Fähigkeiten

Der Pod securityContext, wie in Abbildung 4-3 dargestellt, kann verwendet werden, ohne dass PSPs clusterweit aktiviert werden. Sobald die PSPs jedoch aktiviert sind, musst du securityContext definieren, um sicherzustellen, dass Pods richtig erstellt werden. Da der securityContext ein PSP-Konstrukt hat, gelten alle Fähigkeiten der PSPs für den securityContext.

Grenzen der PSPs

Einige der Einschränkungen von PSPs sind:

  • PodSecurityPolicySpec hat Verweise auf allowedCapabilities, privileged oder hostNetwork. Diese Erzwingungen können nur auf Linux-basierten Laufzeiten funktionieren.

  • Wenn du einen Pod mit Controllern erstellst (z. B. Replikationscontroller), solltest du prüfen, ob PSPs für die Verwendung durch diese Controller zugelassen sind.

  • Wenn PSPs clusterweit aktiviert sind und ein Pod aufgrund eines falschen PSPs nicht startet, wird es hektisch, das Problem zu beheben. Wenn PSPs in Produktionsclustern clusterweit aktiviert sind, musst du außerdem jede einzelne Komponente in deinem Cluster testen, einschließlich der Abhängigkeiten, wie z. B. mutierende Zulassungssteuerungen und widersprüchliche Urteile.

  • Azure Kubernetes Service (AKS) hat die Unterstützung für PSPs abgeschafft und bevorzugt OPA Gatekeeper für die Richtliniendurchsetzung, um flexiblere Richtlinien mit der OPA Engine zu unterstützen.

  • PSP sind veraltet und werden voraussichtlich mit Kubernetes v1.25 entfernt.

  • Kubernetes kann Kanten haben, an denen PSPs umgangen werden können (z.B. TOB-K8S-038).

Jetzt, wo du die PSPs, die bewährten Methoden zu ihrer Umsetzung und die Grenzen der PSPs kennst, wollen wir uns der Prozessüberwachung zuwenden.

Prozessüberwachung

Wenn du einen Workload containerisierst und ihn auf einem Host mit einem Orchestrator wie Kubernetes ausführst, gibt es eine Reihe von Ebenen, die du für die Überwachung eines Prozesses innerhalb eines Containers berücksichtigen musst. Diese beginnen mit den Prozessprotokollen und Artefakten des Containers, dem Zugriff auf das Dateisystem, den Netzwerkverbindungen, den erforderlichen Systemaufrufen, den Kernel-Berechtigungen (spezielle Workloads), den Kubernetes-Artefakten und den Artefakten der Cloud-Infrastruktur. Normalerweise hängt die Sicherheitslage deines Unternehmens davon ab, wie gut deine Lösungen diese verschiedenen Log-Kontexte zusammenfügen können. An dieser Stelle schlägt das herkömmliche Überwachungssystem messbar fehl und es entsteht ein Bedarf an Kubernetes' nativer Überwachung und Beobachtbarkeit. Herkömmliche Lösungen wie Endpoint Detection and Response (EDR) und Endpoint Protection-Systeme haben beim Einsatz in Kubernetes-Clustern die folgenden Einschränkungen:

  • Sie sind sich der Container nicht bewusst.

  • Sie kennen sich nicht mit Containernetzwerken aus und sehen die Aktivitäten in der Regel aus der Perspektive des Hosts, was zu falschen Negativmeldungen über seitliche Bewegungen der Angreifer führen kann .

  • Sie sind blind für den Verkehr zwischen Containern und haben keinen Blick für Underlays wie IPIP oder VXLAN.

  • Sie kennen die Prozess- und Dateiberechtigungen von Containern, die auf den zugrunde liegenden Host zugreifen, nicht.

  • Sie kennen weder die Kubernetes-Container-Laufzeitschnittstelle (CRI) noch ihre Feinheiten und Sicherheitsprobleme, die dazu führen können, dass Container auf Ressourcen des Hosts zugreifen können. Dies wird auch als Privilegienerweiterung bezeichnet.

In den folgenden Abschnitten gehen wir auf verschiedene Techniken ein, die du zur Prozessüberwachung nutzen kannst. Zuerst sehen wir uns die Überwachung mithilfe verschiedener in Kubernetes verfügbarer Protokolle an; dann untersuchen wir die Funktionen von seccomp, SELinux und AppArmor, mit denen du kontrollieren kannst, worauf ein Prozess zugreifen kann (z. B. Systemaufrufe, Dateisystem usw.)

Kubernetes Native Überwachung

Wie in Abbildung 4-4 dargestellt, führt jede Schicht, die zu deinem containerisierten Anwendungsprozess führt, zu Überwachungs- und Protokollierungsanforderungen und einer neuen Angriffsfläche, die sich von dem unterscheidet, was traditionelle IT-Sicherheitsexperten bei der Überwachung von Netzwerken und Anwendungen gewohnt sind. Die Herausforderung besteht darin, diesen Überwachungsaufwand zu reduzieren, da er für die Speicher- und Rechenressourcen sehr teuer werden kann. Das Thema Metriksammlung und wie man sie effizient durchführt, wird in Kapitel 5 ausführlich behandelt.

Abbildung 4-4. Native Überwachung von Kubernetes

Um auf jeder Ebene Schutzmaßnahmen zu ergreifen, solltest du bei der Auswahl der Lösungen die folgenden Optionen in Betracht ziehen:

  • Die Fähigkeit, Prozesse zu blockieren, die von jedem Container oder der Kubernetes-Orchestrierung, die Container erzeugt, gestartet werden.

  • Überwachung der Kernel-Systemaufrufe, die von jedem Containerprozess verwendet werden, und die Möglichkeit, verdächtige Aufrufe zu filtern, zu blockieren und zu melden, um zu verhindern, dass Container auf Host-Ressourcen zugreifen.

  • Überwache jede Netzwerkverbindung (Socket), die von einem Containerprozess ausgeht, und ermögliche die Durchsetzung von Netzwerkrichtlinien.

  • Möglichkeit, einen Container mithilfe von Netzwerkrichtlinien zu isolieren (oder einen Knoten, auf dem dieser Container läuft) und ihn anzuhalten, um verdächtige Aktivitäten zu untersuchen und forensische Daten in Kubernetes zu sammeln. Der Befehlpause für Docker-basierte Container hält die Prozesse in einem Container an, um eine detaillierte Analyse zu ermöglichen. Beachte, dass das Anhalten eines Containers dazu führt, dass der Container den normalen Betrieb aussetzt und nur als Reaktion auf ein Ereignis (z. B. einen Sicherheitsvorfall) verwendet werden sollte.

  • Überwache die Lese- und Schreibvorgänge im Dateisystem, um Änderungen im Dateisystem (Binärdateien, Pakete) zu erkennen und zusätzliche Isolierung durch obligatorische Zugriffskontrolle (MAC), um Privilegieneskalationen zu verhindern.

  • Überwache das Kubernetes-Audit-Log, um zu erfahren, welche Kubernetes-API-Anfragen die Clients stellen, und um verdächtige Aktivitäten zu erkennen.

  • Aktiviere das Logging eines Cloud-Providers für deine Infrastruktur und die Fähigkeit, verdächtige Aktivitäten in der Infrastruktur des Cloud-Providers zu erkennen.

Es gibt viele Unternehmens- und Open-Source-Lösungen (z. B. Falco), die mit verschiedenen Tools und Mechanismen (wie ebpf, kprobes, ptrace, tracepoints usw.) auf Gruppen von Schichten abzielen, um die Verteidigung auf verschiedenen Ebenen aufzubauen. Du solltest dir ihr Bedrohungsmodell ansehen und Lösungen auswählen, die ihre Anforderungen erfüllen.

Im nächsten Abschnitt lernst du einige der Mechanismen kennen, die Kubernetes bietet, indem es die Linux-Verteidigung näher an den Container heranbringt, was dir bei der Überwachung und Reduzierung der Angriffsfläche auf verschiedenen Ebenen helfen wird. Im vorherigen Abschnitt ging es um die Überwachung, mit der du unbeabsichtigtes (bösartiges) Verhalten erkennen kannst. Mit den folgenden Mechanismen kannst du Kontrollen einrichten, um unbeabsichtigtes (bösartiges) Verhalten zu verhindern.

Kernel-Sicherheitsfunktionen wie seccomp, AppArmor und SELinux können kontrollieren, welche Systemaufrufe für deine containerisierte Anwendung erforderlich sind, jeden Container virtuell isolieren und an die Arbeitslast anpassen, die er ausführt, und MAC verwenden, um den Zugriff auf Ressourcen wie Volumes oder Dateisysteme zu ermöglichen, die Containerausbrüche effizient verhindern. Allein die Nutzung der Funktion mit den Standardeinstellungen kann die Angriffsfläche in deinem Cluster enorm reduzieren. In den folgenden Abschnitten wirst du dir jede Verteidigung genau ansehen und erfahren, wie sie im Kubernetes-Cluster funktioniert, damit du die beste Option für dein Bedrohungsmodell auswählen kannst.

Seccomp

Seccomp ist eine Linux-Kernel-Funktion, mit der die vom Container ausgeführten Systemaufrufe gefiltert werden können. Mit Kubernetes kannst du automatisch Seccomp-Profile anwenden, die von Kubernetes-Laufzeiten wie Docker, podman oder CRI-O auf einen Knoten geladen werden. Ein einfaches seccomp-Profil besteht aus einer Liste von Syscalls und der entsprechenden Aktion, die beim Aufruf eines Syscalls ausgeführt werden soll. Diese Aktion reduziert die Angriffsfläche auf die erlaubten Syscalls und verringert so das Risiko einer Privilegienerweiterung und einer Containerflucht.

Im folgenden seccomp-Profil ist eine Standardaktion SCMP_ACT_ERRNO, die einen Systemaufruf verweigert. Aber defaultAction für syscall chmod wird mit SCMP_ACT_ALLOW überschrieben. Normalerweise werden seccomp-Profile auf allen Knoten von deinen Laufzeiten in das Verzeichnis /var/lib/kubelet/seccomp geladen. Du kannst dein eigenes Profil an der gleichen Stelle hinzufügen:

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "chmod",
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

Um die von deiner Anwendung verwendeten Systemaufrufe zu finden, kannst du strace verwenden, wie im nächsten Beispiel gezeigt. In diesem Beispiel kannst du die vom Dienstprogramm curl verwendeten Systemaufrufe wie folgt auflisten:

$ strace -c -S name curl -sS google.com

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  4.56    0.000242           6        43        43 access
  0.06    0.000003           3         1           arch_prctl
  1.28    0.000068          10         7           brk
  0.28    0.000015          15         1           clone
  4.62    0.000245           5        48           close
  1.38    0.000073          73         1         1 connect
  0.00    0.000000           0         1           execve
  0.36    0.000019          10         2           fcntl
  4.20    0.000223           5        48           fstat
  0.66    0.000035           3        11           futex
  0.23    0.000012          12         1           getpeername
  0.13    0.000007           7         1           getrandom
  0.19    0.000010          10         1           getsockname
  0.24    0.000013          13         1           getsockopt
  0.15    0.000008           4         2           ioctl
 13.96    0.000741           7       108           mmap
 11.94    0.000634           7        85           mprotect
  0.32    0.000017          17         1           munmap
 11.02    0.000585          13        45         1 openat
  0.11    0.000006           6         1           pipe
 19.50    0.001035         115         9           poll
  0.08    0.000004           4         1           prlimit64
  5.43    0.000288           6        45           read
  0.41    0.000022          22         1           recvfrom
 11.47    0.000609          17        36           rt_sigaction
  0.08    0.000004           4         1           rt_sigprocmask
  1.00    0.000053          53         1           sendto
  0.06    0.000003           3         1           set_robust_list
  0.04    0.000002           2         1           set_tid_address
  2.22    0.000118          30         4           setsockopt
  1.60    0.000085          43         2           socket
  0.08    0.000004           4         1         1 stat
  2.35    0.000125          21         6           write
------ ----------- ----------- --------- --------- ----------------
100.00    0.005308                   518        46 total

Die von der Kubernetes-Laufzeit bereitgestellten Standard-Seccomp-Profile enthalten eine Liste gängiger Systemaufrufe, die von den meisten Anwendungen verwendet werden. Wenn du diese Funktion aktivierst, wird die Verwendung gefährlicher Systemaufrufe verhindert, die zu einem Kernel-Exploit und einem Container-Ausbruch führen können. Das Standardprofil für die Docker-Laufzeit seccomp ist als Referenz verfügbar .

Hinweis

Zum Zeitpunkt der Erstellung dieses Artikels war das Profil Docker/default veraltet, daher empfehlen wir dir, stattdessen runtime/default als Seccomp-Profil zu verwenden.

Tabelle 4-2 zeigt die Optionen für den Einsatz des seccomp-Profils in Kubernetes über PSP-Annotationen.

Tabelle 4-2. Seccomp-Optionen
Wert Beschreibung
runtime/default Standard-Container-Laufzeitprofil
Unbegrenzt Kein Seccomp-Profil - diese Option ist in Kubernetes voreingestellt
localhost/<Pfad> Dein eigenes Profil auf dem Knoten, normalerweise im Verzeichnis /var/lib/kubelet/seccomp

SELinux

In der jüngsten Vergangenheit war jeder Ausbruch aus der Container-Laufzeit (Container-Escape oder Privilegieneskalation) eine Art Ausbruch aus dem Dateisystem (z. B. CVE-2019-5736, CVE-2016-9962, CVE-2015-3627 und mehr). SELinux entschärft diese Probleme, indem es kontrolliert, wer auf das Dateisystem und die Interaktion zwischen den Ressourcen (d. h. Benutzer, Dateien, Verzeichnisse, Speicher, Sockets usw.) zugreifen darf. Im Cloud Computing-Kontext ist es sinnvoll, SELinux-Profile auf Arbeitslasten anzuwenden, um eine bessere Isolierung zu erreichen und die Angriffsfläche zu verringern, indem der Zugriff auf das Dateisystem durch den Host-Kernel eingeschränkt wird.

SELinux wurde ursprünglich von der National Security Agency in den frühen 2000er Jahren entwickelt und wird vor allem in Red Hat- und centOS-basierten Distributionen eingesetzt. SELinux ist deshalb so effektiv, weil es einen MAC bereitstellt, der das herkömmliche System der diskretionären Zugriffskontrolle (DAC) von Linux erheblich erweitert.

Beim Linux DAC haben die Benutzer/innen die Möglichkeit, die Berechtigungen für Dateien, Verzeichnisse und den Prozess, der ihnen gehört, zu ändern. Und ein Root-Benutzer hat Zugriff auf alles. Mit SELinux (MAC) hingegen wird jeder Betriebssystemressource vom Kernel ein Label zugewiesen, das als erweiterte Dateiattribute gespeichert wird. Diese Labels werden verwendet, um SELinux-Richtlinien innerhalb des Kernels auszuwerten und jede Interaktion zu erlauben. Wenn SELinux aktiviert ist, kann selbst ein Root-Benutzer in einem Container nicht auf die Dateien eines Hosts in einem gemounteten Volume zugreifen, wenn die Labels nicht korrekt sind.

SELinux arbeitet in drei Modi: Erzwingen, Zulassen und Deaktivieren. Mit Enforcing wird die Durchsetzung der SELinux-Richtlinien aktiviert, mit Permissive werden Warnungen ausgegeben, und mit Disabled werden die SELinux-Richtlinien nicht mehr verwendet. Die SELinux-Richtlinien selbst können weiter in Targeted und Strict unterteilt werden, wobei Targeted-Richtlinien für bestimmte Prozesse und Strict-Richtlinien für alle Prozesse gelten.

Das folgende ist das SELinux-Label für Docker-Binärdateien auf einem Host, der aus <user:role:type:level> besteht. Hier siehst du den Typ, der lautet container_runtime_exec_t:

$ ls -Z /usr/bin/docker*
-rwxr-xr-x. root root system_u:object_r:container_runtime_exec_t:s0
/usr/bin/docker
-rwxr-xr-x. root root system_u:object_r:container_runtime_exec_t:s0
/usr/bin/docker-current
-rwxr-xr-x. root root system_u:object_r:container_runtime_exec_t:s0
/usr/bin/docker-storage-setup

Um SELinux weiter zu verbessern, wird die Multikategoriesicherheit (MCS) verwendet, die es Benutzern ermöglicht, Ressourcen mit einer Kategorie zu kennzeichnen. Auf eine Datei, die mit einer Kategorie gekennzeichnet ist, können also nur Benutzer oder Prozesse dieser Kategorie zugreifen.

Sobald SELinux aktiviert ist, wählt eine Container-Laufzeitumgebung wie Docker, podman oder CRI-O ein zufälliges MCS-Label aus, um den Container zu starten. Diese MCS-Labels bestehen aus zwei zufälligen Zahlen zwischen 1 und 1023, denen das Zeichen "c" (Kategorie) und eine Empfindlichkeitsstufe (z. B. s0) vorangestellt wird. Ein vollständiges MCS-Label sieht also aus wie "s0:c1,c2". Wie in Abbildung 4-5 zu sehen ist, kann ein Container nur dann auf eine Datei auf einem Host oder einem Kubernetes-Volume zugreifen, wenn sie korrekt als benötigt gekennzeichnet ist. Dies stellt eine wichtige Isolierung zwischen den Ressourceninteraktionen dar, die viele Sicherheitsschwachstellen verhindert, die auf entweichende Container abzielen.

Abbildung 4-5. SELinux erzwingt den Zugriff auf das Dateisystem

Hier ein Beispiel für einen Pod, der mit einem SELinux-Profil ausgestattet ist. Dieser Pod kann nur dann auf die eingehängten Dateien des Host-Volumes zugreifen, wenn sie auf dem Host mit so:c123,c456 gekennzeichnet sind. Auch wenn du den gesamten Host siehst, ist das Dateisystem auf dem Pod gemountet:

apiVersion: v1
metadata:
  name: pod-se-linux-label
  namespace: default
  labels:
    app: normal-app
spec:
  containers:
  - name: app-container
    image: alpine:latest
    args: ["sleep", "10000"]
    securityContext:
      seLinuxOptions:
        level: "s0:c123,c456"
  volumes:
    - name: rootfs
      hostPath:
        path: /

Tabelle 4-3 listet die CVEs auf, die sich auf Container-Escape beziehen und die allein durch die Aktivierung von SELinux auf Hosts verhindert werden. Obwohl SELinux-Richtlinien schwierig zu verwalten sind, sind sie für eine Defense-in-Depth-Strategie entscheidend. Bei Openshift, einer Kubernetes-Distribution, ist SELinux in der Standardkonfiguration mit gezielten Richtlinien aktiviert; bei anderen Distributionen lohnt es sich, den Status zu überprüfen.

Tabelle 4-3. CVEs im Zusammenhang mit Container-Escape
CVE Beschreibung Von SELinux blockiert
CVE-2019-5736 Ermöglicht es Angreifern, die runc-Binärdatei des Hosts zu überschreiben und so Root-Zugriff auf den Host zu erhalten Ja
CVE-2016-9962 RunC exec-Schwachstelle Ja
CVE-2015-3627 Unsichere Ausnutzung von Dateideskriptoren Ja

Kubernetes bietet die folgenden Optionen, um SELinux in PSPs durchzusetzen:

Wert Beschreibung
MustRunAs Du musst seLinuxOptions wie in Abbildung 4-5 gezeigt konfiguriert haben.
RunAsAny Im PSP gibt es keine Standardeinstellungen (kann optional auf Pods und Deployments konfiguriert werden)

AppArmor

Wie SELinux wurde auch AppArmor für die Betriebssysteme Debian und Ubuntu entwickelt. AppArmor funktioniert ähnlich wie SELinux, wobei ein AppArmor-Profil definiert, worauf ein Prozess Zugriff hat. Schauen wir uns ein Beispiel für ein AppArmor-Profil an:

#include <tunables/global>
/{usr/,}bin/ping flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/consoles>
  #include <abstractions/nameservice>

  capability net_raw,
  capability setuid,
  network inet raw,

  /bin/ping mixr,
  /etc/modules.conf r,

  # Site-specific additions and overrides. See local/README for details.
  #include <local/bin.ping>
}

Hier hat ein Ping-Dienstprogramm nur drei Berechtigungen (net_raw, setuid und inet raw sowie Lesezugriff auf /etc/modules.conf). Mit diesen Rechten kann ein Ping-Dienstprogramm das Dateisystem (Schlüssel, Binärdateien, Einstellungen, Persistenz) nicht verändern oder beschreiben und auch keine Module laden, was die Angriffsfläche für böswillige Aktivitäten im Falle einer Kompromittierung reduziert.

Standardmäßig bietet deine Kubernetes-Laufzeitumgebung wie Docker, Podman oder CRI-O ein AppArmor-Profil. Das Laufzeitprofil von Docker wird zu deiner Information bereitgestellt.

Da AppArmor viel flexibler und einfacher zu handhaben ist, empfehlen wir, eine Richtlinie pro Microservice zu erstellen. Kubernetes bietet die folgenden Optionen, um diese Richtlinien über PSP-Annotationen durchzusetzen:

Wert Beschreibung
runtime/default Standardrichtlinie der Runtime
localhost/<profil_name> Auf dem Host geladenes Profil anwenden, normalerweise im Verzeichnis /sys/kernel/security/apparmor/profiles
Unbegrenzt Es wird kein Profil geladen

Sysctl

Kubernetes sysctl ermöglicht es dir, die sysctl-Schnittstelle zu nutzen, um Kernel-Parameter in deinem Cluster zu verwenden und zu konfigurieren. Ein Beispiel für die Verwendung von sysctls ist die Verwaltung von Containern mit ressourcenintensiven Workloads, die eine große Anzahl gleichzeitiger Verbindungen verarbeiten müssen oder einen speziellen Parametersatz (z. B. IPv6-Weiterleitung) benötigen, um effizient zu laufen. In solchen Fällen bietet sysctl eine Möglichkeit, das Kernelverhalten nur für diese Arbeitslasten zu ändern, ohne den Rest des Clusters zu beeinträchtigen.

Die Sysctls werden in zwei Kategorien eingeteilt: sichere und unsichere. Die sichere sysctl wirkt sich nur auf die Container aus, die unsichere sysctl hingegen auf den Container und den Knoten, auf dem sie läuft. Mit Sysctl können Administratoren beide Sysctl-Buckets nach eigenem Ermessen einstellen.

Nehmen wir ein Beispiel, bei dem ein containerisierter Webserver eine hohe Anzahl gleichzeitiger Verbindungen bewältigen muss und den Wert net.core.somaxconn auf einen höheren Wert als den Standardwert des Kernels setzen muss. In diesem Fall kann der Wert wie folgt gesetzt werden:

apiVersion: v1
kind: Pod
metadata:
  name: sysctl-example
spec:
  securityContext:
    sysctls:
    - name: net.core.somaxconn
      value: "1024"

Bitte beachte, dass wir empfehlen, die Knotenaffinität zu verwenden, um Arbeitslasten auf Knoten zu planen, auf denen die Sysctl angewendet wird, für den Fall, dass du eine Sysctl verwenden musst, die für den Knoten gilt. Das folgende Beispiel zeigt, wie PSPs es erlauben, Sysctls zu verbieten oder zu erlauben:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: sysctl-psp
spec:
  allowedUnsafeSysctls:
  - kernel.msg*
  forbiddenSysctls:
  - kernel.shm_rmid_forced

Fazit

In diesem Kapitel haben wir Tools und bewährte Methoden für die Definition und Umsetzung der Sicherheit deiner Workload-Laufzeit behandelt. Die wichtigsten Erkenntnisse sind:

  • Pod-Sicherheitsrichtlinien sind eine hervorragende Möglichkeit, Workload-Kontrollen zum Zeitpunkt der Erstellung des Workloads zu ermöglichen. Sie haben ihre Grenzen, können aber effektiv eingesetzt werden.

  • Du musst eine Lösung wählen, die nativ für Kubernetes ist, um Prozesse zu überwachen und Kontrollen auf der Grundlage deines Bedrohungsmodells für deine Workloads zu implementieren.

  • Wir empfehlen dir, die verschiedenen Sicherheitsoptionen, die im Linux-Kernel verfügbar sind, zu prüfen und die richtigen Funktionen für deinen Anwendungsfall zu nutzen.

Get Kubernetes Sicherheit und Beobachtbarkeit now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.