Die Sicherheit muss ein integraler Bestandteil des Lebenszyklus in der Software-Entwicklung sein, der bei jedem Schritt des Prozesses berücksichtigt wird. Dies ist der zweite Teil der Artikelreihe „Absicherung von Kubernetes für native Cloud-Anwendungen“. Puja Abbassi, Developer Advocate & Product Owner bei Giant Swarm, beleuchtet darin im Detail die Sicherheitsaspekte einer Kubernetes-Plattform auf Infrastrukturebene.
Im ersten Artikel der Serie Sicherung von Kubernetes für native Cloud-Anwendungen habe ich erörtert, warum es so schwierig ist, Kubernetes zu sichern. Hinzu kam ein Überblick über die verschiedenen Ebenen, die unsere Aufmerksamkeit erfordern, wenn wir uns mit der Sicherung dieser Plattform befassen. Im heutigen Artikel möchte ich mich mit Sicherheit speziell auf der Infrastrukturebene befassen.
Die allererste Ebene im Stack ist die grundlegende Infrastrukturebene. Wir könnten diese auf viele verschiedene Arten definieren. Für unsere Erörterung ist sie die Summe der Infrastrukturkomponenten, auf denen Kubernetes aufgesetzt wird. Es ist die physische oder virtualisierte Hardware-Ebene für Rechen-, Speicher- und Netzwerkzwecke und die Umgebung, in der sich diese Ressourcen befinden. Dazu gehören auch das Betriebssystem – in den meisten Fällen Linux, jedoch in letzter Zeit auch häufiger Microsoft Windows Server – und eine Container-Laufzeitumgebung wie beispielsweise Docker.
Vieles von dem, was wir besprechen, gilt ebenso gut für Infrastrukturkomponenten, die anderen Systemen als Kubernetes zugrunde liegen. Dabei werden wir aber besonders auf die Faktoren achten, welche die Sicherheit von Kubernetes erhöhen.
Maschinen, Rechenzentren und die Public Cloud
Die Cloud als Instrument für den Workload-Einsatz, egal ob in Form einer öffentlichen, privaten oder hybriden Mischung, wird immer stärker angenommen. Während die Notwendigkeit einer spezialisierten Bare-Metal-Server-Bereitstellung noch nicht ganz entfallen ist, machen virtuelle Maschinen den Großteil der heutigen Infrastruktur aus. Es ist jedoch unerheblich, ob die von uns eingesetzten Computer virtuelle (Cloud-basiert oder anderweitig) oder physische Geräte sind. Die Maschine wird sich in einem Rechenzentrum befinden, das von unserer eigenen Organisation oder einem ausgewählten Drittanbieter wie beispielsweise einem Public-Cloud-Anbieter gehostet wird.
Rechenzentren sind komplex und man muss eine Menge bedenken, wenn es um Sicherheit geht. Es ist eine allgemein nutzbare Ressource für das Hosting der Anforderungen an die Datenverarbeitung für eine ganze Organisation oder sogar für die gemeinsamen Workloads zahlreicher unabhängiger Organisationen aus verschiedenen Branchen und Regionen. Daher sind Sicherheitsmaßnahmen für die vielen verschiedenen Facetten der Infrastruktur auf dieser Ebene meist eine umfassende Unternehmens- oder Anbieterverantwortung. Es wird durch Faktoren wie eine nationale oder internationale Regulierung (HIPAA, DSVGO) oder branchenweite Compliance-Anforderungen (PCI DSS) geregelt und führt häufig zu einer Akkreditierung zertifizierter Standards (ISO 27001, FIPS).
Im Falle einer öffentlichen Cloud-Umgebung kann und wird ein Anbieter die notwendige Einhaltung der regulatorischen und der Compliance-Standards auf Infrastrukturebene sicherstellen. Aber irgendwann kommt es auf den Service-Nutzer an (Sie und mich), auf dieser sicheren Grundlage weiter aufzubauen. Das ist eine gemeinsame Verantwortung. Als Nutzer eines öffentlichen Cloud-Dienstes stellt sich die Frage: „Was soll ich sichern und wie soll ich dabei vorgehen?“ Zu diesem Thema gibt es viele verschiedene Ansichten. Eine glaubwürdige Instanz ist jedoch das CIS („Center for Internet Security“), eine gemeinnützige Organisation, die sich dem Schutz öffentlicher und privater Einrichtungen vor bösartigen Cyberaktivitäten verschrieben hat.
CIS-Benchmarks
Das CIS bietet eine Reihe von Werkzeugen, Verfahren und Informationen zur Bekämpfung der potenziellen Bedrohung der Systeme und Daten, auf die wir uns verlassen. CIS-Benchmarks sind beispielsweise plattformbasierte sicherheitsbezogene Best-Practice-Konfigurationsrichtlinien. Sie werden von Sicherheitsexperten und Fachleuten einvernehmlich erstellt. Angesichts der ständig wachsenden Zahl von Unternehmen, die Initiativen für eine Migration auf öffentliche und/oder hybride Cloud-Infrastrukturen starten, hat es sich die CIS zur Aufgabe gemacht, Benchmarks für die großen Anbieter öffentlicher Clouds bereitzustellen. Ein Beispiel ist der CIS Microsoft Azure Foundations Benchmarks. Ähnliche Benchmarks gibt es auch für die anderen großen Anbieter öffentlicher Clouds.
Diese Benchmarks bieten grundlegende Ratschläge zur Sicherheitskonfiguration. Sie beziehen sich unter anderem auf Identitäts- und Zugriffsmanagement (im Englischen abgekürzt mit IAM, Identity and Access Management), Ein- und Ausstiege sowie Protokollierung und Überwachung von bewährten Verfahren („Best Practices“). Die Umsetzung dieser Benchmark-Empfehlungen ist ein guter Anfang. Sie sollte aber nicht das Ende des Weges sein. Jeder Anbieter einer öffentlichen Cloud hat seinen eigenen Satz detaillierter empfohlener Best Practices (AWS Security Best Practices, Azure Security Best Practices und Muster, Best Practices für Unternehmen (Google Cloud Platform). Außerdem können weitere Expertenmeinungen in diesem Bereich, wie beispielsweise die der Cloud Security Alliance, zurate gezogen werden.
Lassen Sie uns kurz ein typisches Cloud-basiertes Szenario betrachten, das aus Sicherheitsperspektive eine sorgfältige Planung erfordert.
Cloud-Szenario: Private vs. öffentliche Netzwerke
Wie können wir die Sicherheit eines Kubernetes-Clusters gewährleisten, wenn wir einerseits den Zugang beschränken und andererseits den erforderlichen Zugang für externe Kunden über das Internet und auch innerhalb unserer eigenen Organisation erlauben wollen?
- Verwenden Sie ein privates Netzwerk für die Maschinen, die Kubernetes hosten. Stellen Sie sicher, dass die Hosts, welche die Nodes des Clusters repräsentieren, keine öffentlichen IP-Adressen haben. Wenn wir die Möglichkeit einer direkten Verbindung mit einem der Host-Rechner unterbinden, reduziert das die verfügbaren Angriffsmöglichkeiten erheblich. Diese einfache Vorsichtsmaßnahme bietet erhebliche Vorteile und würde die Art von Gefährdungen verhindern, bei denen die Rechenressourcen ausgenutzt werden, z. B. für das Kryptowährungs-Mining.
- Verwenden Sie einen Bastion-Host für den Zugriff auf das private Netzwerk. Der externe Zugriff auf das private Netzwerk des Hosts, mit dem die Verwaltung des Clusters erfolgt, sollte über einen entsprechend konfigurierten Bastion-Host erfolgen. Die Kubernetes-API wird oft ebenso in einem privaten Netzwerk hinter dem Bastion Host bereitgestellt. Sie kann auch öffentlich zugänglich sein, aber es wird empfohlen, den Zugriff zumindest durch Whitelisting der IP-Adressen aus dem internen Netzwerk eines Unternehmens und/oder dessen VPN-Servern einzuschränken.
- Verwenden Sie VNET-Peering mit internen Load Balancern/DNS. Manchmal muss auf Workloads, die in einem Kubernetes-Cluster mit einem privaten Netzwerk ausgeführt werden, von anderen privaten, externen Clients zugegriffen werden. In diesem Fall können die Workloads mit einem Dienst bereitgestellt werden, der einen internen Load Balancer aufruft. Um beispielsweise einen internen Load Balancer in einer Azure-Umgebung zu erstellen, müsste der Dienst folgende Anmerkung enthalten: beta.kubernetes.io/azure-load-balancer-internal: „true“. Wenn sich die Clients in einem anderen VNET befinden, müssen diese VNETs gekoppelt werden.
- Verwenden Sie einen externen Load Balancer mit Ingress. Die Workloads sind oft so konzipiert, dass sie von anonymen, externen Clients aus dem Internet genutzt werden. Wie ist es dann möglich, dass der Datenverkehr die Workloads im Cluster findet, wenn er in einem privaten Netzwerk stattfindet? Je nach Anforderung können wir dies auf verschiedene Weise erreichen. Die erste Möglichkeit wäre, die Workloads über ein Kubernetes-Service-Objekt bereitzustellen. Das würde zur Erstellung eines externen Cloud-Load-Balancer-Dienstes (beispielsweise des Azure Load Balancers) in einem öffentlichen Subnetz führen. Dieser Ansatz kann sehr kostspielig sein, da jeder verfügbar gemachte Dienst einen eigenen Load Balancer aufruft. Er kann aber die bevorzugte Lösung für Nicht-HTTP-Dienste sein. Für HTTP-basierte Dienste wäre es kostengünstiger, einen Ingress Controller für den Cluster bereitzustellen, welcher von einem Kubernetes Service-Objekt unterstützt wird. Dieses wiederum würde den Load Balancer erzeugen. Der an den DNS-Namen des Load Balancers adressierte Datenverkehr wird an den oder die Endpunkt(e) des Ingress Controllers weitergeleitet, welcher/welche die mit den definierten Ingress-Objekten verbundenen Regeln auswertet. Danach wird er entsprechend den Regeln zu den Endpunkten der Dienste weitergeleitet.
Dieses Szenario zeigt, dass die Konfiguration der Infrastruktur sorgfältig geprüft werden muss, um dessen Sicherheit zu gewährleisten. Gleichzeitig müssen die Funktionalitäten bereitgestellt werden, die für die Erbringung der Dienste für die vorgesehene Zielgruppe erforderlich sind. Das ist ein wiederkehrendes Szenario und es wird andere Situationen geben, die ähnlich gemeistert werden müssen.
Abriegeln des Betriebssystems und der Container-Laufzeit
Nehmen wir an, dass wir die notwendige Sicherheitskonfiguration geprüft und angewendet haben, um die Infrastruktur auf Maschinenebene und deren Umgebung sicher zu machen. Nun ist es die nächste Aufgabe, das Host-Betriebssystem (OS) jeder Maschine und die Container-Laufzeit, die für die Lebenszyklus-Verwaltung der Container verantwortlich ist, zu sichern.
Betriebssystem
Während Microsoft Windows Server als Betriebssystem für Kubernetes Worker Nodes ausgeführt werden kann, werden die meisten Worker Nodes eine Variante des Linux-Betriebssystems ausführen. Da die Steuerungsebene zurzeit zwingend auf Linux laufen muss, werden wir uns in diesem Teil auf dieses OS konzentrieren.
Für die Wahl der einzusetzenden Linux-Distribution kann es viele Faktoren geben (kommerzielle Distributionen, interne Fähigkeiten, Reife des Betriebssystems). Verwenden Sie aber möglichst eine minimale Distribution, die nur für den Betrieb von Containern entwickelt wurde. Beispiele sind die CoreOS Container Linux, Ubuntu Core und Atomic Host-Varianten. Diese Betriebssysteme wurden auf das absolute Minimum reduziert, um den skalierten Betrieb von Containern zu erleichtern. Daher haben sie eine deutlich reduzierte Angriffsfläche.
Auch hier hat die CIS eine Reihe unterschiedlicher Benchmarks für verschiedene Linux-Varianten und stellt dabei Best-Practice-Empfehlungen zur Sicherung des Betriebssystems bereit. Diese Benchmarks decken die gängigen Linux-Distributionen wie beispielsweise RHEL, Ubuntu, SLES, Oracle Linux und Debian ab. Sollte Ihre bevorzugte Distribution nicht abgedeckt sein, so gibt es einen distributionsunabhängigen CIS-Benchmark. Zudem finden Sie oft distributionsbezogene Richtlinien, wie beispielsweise den CoreOS Container Linux Hardening Guide. Auch für Microsoft Windows Server gibt es einen entsprechenden CIS Benchmark.
Docker Engine
Die letzte Komponente der Infrastrukturebene ist die Container-Laufzeit. Bei den frühen Kubernetes-Versionen hatte man keine Wahl: Die Container-Laufzeit war notwendigerweise die Docker Engine. Mit Einführung des Kubernetes Container Runtime Interface kann die Abhängigkeit von der Docker-Engine jedoch zugunsten einer Laufzeit wie zum Beispiel CRI-O, containerd oder Frakti gelöst werden (Kleinere, leichtere Container-Laufzeiten wie containerd, die speziell für Bootstrapping-Container entwickelt wurden, sind aufgrund ihres besonderen Zwecks von Natur aus sicherer.). Tatsächlich wird mithilfe einer Alpha-Funktion (Runtime Class) ab Kubernetes-Version 1.12 die Ausführung mehrerer Container-Laufzeiten nebeneinander in einem Cluster ermöglicht. Die Container-Laufzeiten müssen aber gesichert werden, unabhängig davon, für welche Sie sich entschieden haben.
Trotz der vielfältigen Auswahl bleibt die Docker-Engine die Standard-Laufzeit für Kubernetes (obwohl diese in naher Zukunft zu containerd geändert werden könnte). Wir wollen hier dennoch die Auswirkungen auf die Sicherheit der Docker-Engine berücksichtigen. Die Engine verfügt über zahlreiche konfigurierbare Sicherheitseinstellungen, von denen einige standardmäßig aktiviert sind, die aber für einzelne Container umgangen werden können. Ein solches Beispiel ist die Whitelist der Linux-Kernelfunktionen, die bei der Erstellung auf jeden Container angewendet werden. Das trägt dazu bei, dass die Privilegien innerhalb eines laufenden Containers verringert werden.
Hier stellt die CIS ebenso einen Benchmark für die Docker-Plattform bereit: den CIS Docker Benchmark. Dieser enthält Best-Practice-Empfehlungen für die Konfiguration des Docker-Daemons für optimale Sicherheit. Es gibt sogar ein praktisches Open-Source-Tool (Skript) namens Docker Bench for Security. Dieses kann gegen eine Docker-Engine ausgeführt werden, um das System auf Konformität mit dem CIS Docker Benchmark zu bewerten. Das Tool kann regelmäßig ausgeführt werden, um jede Drift von der gewünschten Konfiguration offenzulegen.
Bei Prüfung und Messung der Sicherheitskonfiguration der Docker-Engine, die als Container-Laufzeit für Kubernetes verwendet wird, ist einige Vorsicht geboten. Kubernetes ignoriert viele der verfügbaren Funktionen des Docker-Daemons zugunsten seiner eigenen Sicherheitskontrollen. So ist der Docker-Daemon beispielsweise konfiguriert, eine standardmäßige Whitelist der verfügbaren Aufrufe des Linux-Kernelsystems auf jeden erstellten Container anzuwenden. Dafür wird ein seccomp-Profil verwendet. Sofern nicht anders angegeben, wird Kubernetes Docker anweisen, Pod-Container zu erstellen, die aus einer seccomp-Perspektive „uneingeschränkt“ sind, sodass die Container auf jeden verfügbaren Syscall zugreifen können. Anders ausgedrückt: Was auf der unteren Docker-Ebene konfiguriert werden kann, könnte auf einer höheren Ebene im Plattform-Stack rückgängig gemacht werden. Wir werden in einem späteren Artikel darauf zurückkommen, wie man diese Diskrepanzen in den Sicherheitskontexten eindämmen kann.
Zusammenfassung
Es mag verlockend sein, unsere ganze Aufmerksamkeit auf eine sichere Konfiguration der Kubernetes-Komponenten einer Plattform zu richten. Wie wir in diesem Artikel jedoch gesehen haben, sind die Infrastrukturkomponenten der unteren Schicht ebenso wichtig und können auf unsere Gefahr hin ignoriert werden. Die Bereitstellung einer sicheren Infrastrukturebene kann sogar Probleme eindämmen, die wir auf Cluster-Ebene möglicherweise selbst eingeführt haben. Wenn wir beispielsweise unsere Nodes privat halten, so verhindern wir, dass ein unzureichend gesichertes Kubelet für schädliche Zwecke missbraucht wird. Infrastrukturkomponenten verdienen die gleiche Aufmerksamkeit wie die Kubernetes-Komponenten selbst.
Im nächsten Artikel werden wir die Auswirkungen einer Sicherung der nächsten Ebene des Stacks – der Kubernetes-Cluster-Komponenten – besprechen.