Bereitstellung der Infrastruktur (*8)
- Internet Gateways
- Private Networks
- Firewalls
- Secrets
- etc.
Host Betriebssystem
- Betriebssystem / Linux Kernel (*1)
- Container Runtime (ist typischerweise Teil des Betriebssystems, *2)
Kubernetes
- Kubernetes Prozesse (*7)
Container und Applikation
- Container Base Image (*9)
- Application Frameworks (*3)
- Application-code und Konfiguration (*4)
- Inter Service Communication (*5)
- Authentifizierung und Autorisierung
Kubernetes API (*6)
- Role Based Access Control (RBAC)
- Network Policies
- Container Security Policies (OPA)
Die Sicherheit zu gewährleisten ist auch keine statische Sache, die einmal gemacht wird und dann nie wieder. Frameworks ändern sich, Base-Images müssen neu erstellt werden oder die Firewall-Regeln der Infrastruktur müssen neuen Gegebenheiten, Beispielsweise der Applikationen, angepasst werden. Das alles muss dynamisch und vor allem automatisiert erfolgen.
Vielfach hat man mit einer gewissen Komplexität des Systems zu kämpfen. Dies macht es eben auch wahrscheinlich, irgendwann den Überblick zu verlieren. Je komplexer ein System ist, desto höher ist immer auch die Fehlerwahrscheinlichkeit. Man sollte also stets darauf achten, möglichst einfache und homogene Strukturen zu schaffen. Denn Einfachheit ist Sicherheit.
Bereitstellung der Infrastruktur
Wie Infrastrukturen provisioniert werden, hängt von dem verwendeten Cloudanbieter ab. Je nach Cloudanbieter gibt es gewisse Best Practices, die man anwenden sollte. Wichtig ist in jeden Fall, die Infrastruktur so zu konfigurieren, dass sie so sicher wie möglich ist. Das ohne die eigentliche Entwicklung der Applikation zu stark zu beeinträchtigen oder inflexibel zu machen. Pragmatismus ist hier gefragt. Diese Best Practices heißen beispielsweise, was User und Rechte bezogen auf die Cloudanbieter API angeht: Least Privilege, Separation of Duties, Named Accounts, Audit- und Event-Logging. Aber natürlich müssen die verwendeten Infrastrukturkomponenten genauso sicher sein. So wird man sich genau überlegen, welche Netzwerke privat sein müssen und welche nicht? Welche Netzwerksegmente gibt es und wie dürfen sie miteinander Kommunizieren? Einige wichtige Fragen sind ebenso Datenverschlüsselung, Netzwerkverschlüsselung und Service-Identitäten.
Viele Firmen verwenden Infrastructure as Code Werkzeuge wie zum Beispiel Terraform. Dies ermöglicht unter anderem Infrastrukturkomponenten reproduzierbar zu erstellen. Auch lassen sich solche Werkzeuge relativ einfach automatisieren und mit dem üblichen Git verwalten. Allerdings lebt ein solches Terraform Script nur während man es ausführt. Es ist keine Komponente, die ununterbrochen gestartet ist und Systemzustände überwachen kann. Um dem Rechnung zu tragen entstehen neue Konzepte wie z.b. Crossplane oder Operatoren, die externe Infrastruktur durch Komponenten z.B. in einem sogenannten Management Cluster ständig überwachen können. Dies hat ebenso den Vorteil, dass man sich nicht mit zwei semantisch völlig unterschiedlichen und komplexen APIs herumschlagen muss. Nämlich die von Kubernetes und die eines Cloudanbieters. Ein einheitlicher und automatisierbarer Zugang (über eine API) zu einem System verringert die Fehlerwahrscheinlichkeit und erhöht die Sicherheit.
Host Betriebssystem
Für das Host-Betriebssystemen werden wir üblicherweise eine Linux-Distribution verwenden. Teil dieser Linux-Distributionen ist normalerweise auch die verwendete Container Runtime wie z.b. containerd, CRI-O, Kata oder ähnliches. Für jede Distributionen gibt es in relativ kurzen Abständen neue Versionen, da kritische Vulnerabilities aufgetaucht sind. Für uns bedeutet das, dass wir stetig prüfen müssen, ob es solche Aktualisierungen gibt. Je nach Kritikalität des Problems, müssen wir unser System auch so schnell es geht aktualisieren. Denn wenn wir auf ein Wartungsfenster warten, bedeutet das nichts anderes, als dass des Risiko eines Einbruchs länger besteht als notwendig.
Typischerweise werden solche Aktualisierungen über Neustart des Host Systems gemacht. Daraus ergibt sich allerdings auch, dass die Gesamtapplikation mit einer kurzfristigen Nichtverfügbarkeit von Upstream-Services jederzeit klarkommen muss. Resilience ist also eine wichtige Komponente des Systemdesigns. Je widerstandsfähiger eine Applikation ist, desto simpler ist eine Aktualisierung. Desto einfacher ist es auch, mehr Sicherheit durch schnellere Aktualisierungen zu garantieren.
Kubernetes
Kubernetes besteht, je nach Betrachtungsweise, aus fünf bis sechs unterschiedlichen Prozessen. Wie auch für das Host-Betriebssystemen, gibt es dort ebenfalls Aktualisierungen, die angewendet werden müssen. Probleme bei Kubernetes betreffen oft “nur” die Kubernetes API, die aber normalerweise nicht extern zugänglich ist, und nicht die Applikationswelt. Auch ist typischerweise die Aktualisierung von Kubernetes ohne ein Neustart der Applikations-Container möglich. Dennoch könnten Änderungen an der API durch neuere Kubernetes Versionen kleinere Umbauten erfordern.
Container und Applikation
Container Images sind die Applikationen selbst, plus ihre Ablaufumgebung. Insofern befinden sich in einem Container Image sowohl eine bestimmte Distribution, unterschiedliche Frameworks, als auch der Applikations-Code selbst und eventuell auch seine Konfiguration. Dabei sind Base Images und verwendete Frameworks typischerweise externe Komponenten, deren Notwendigkeit zur Aktualisierung, am Besten automatisiert, überwacht werden muss. Bei der letzten Log4j vulnerability (Log4Shell) hat man allerdings auch gesehen, dass solche Maßnahmen alleine nicht immer ausreichend sind. Insofern ist auch an anderer Stelle der Schutz der Applikationen, beispielsweise eine Kontrolle des IP-Traffics durch Firewalls oder Netzwerkzonen, notwendig.
Aber auch der eigene Applikations-Code kann Angriffsvektoren eröffnen. Deswegen ist es notwendig z.B. als Teil einer CI-Pipeline Angriffsszenarien durchzuführen und automatisiert auszuwerten. Bei besonders kritischen Anwendungen wird es auch sinnvoll sein, von Zeit zu Zeit explizite Penetration-Tests durchzuführen.
Da sich die Applikationen typischerweise bei einem öffentlichen Cloud-Anbieter befinden müssen wir auch eine klare Meinung dazu entwickeln, ob wir den Netzwerken des Anbieters trauen können/wollen oder nicht. Tun wir das nicht, muss die Interservice-Kommunikation verschlüsselt erfolgen. Konsequenterweise genügt aber auch das nicht, und wir müssen nicht nur Serverzertifikate, sondern auch Client-Zertifikate verwenden. Also beispielsweise durch mTLS. Da solche Zertifikate sinnvollerweise recht kurzlebig sind (24h), ist das stetige erzeugen und ausrollen neuer Zertifikate eine gewisse technische Herausforderung und muss automatisiert erfolgen.
Kubernetes API
Auch die Kubernetes API bietet eine ganze Menge Möglichkeiten, sowohl den Zugriff auf die API, als auch die Applikation selbst zu schützen. Role Based Access Control (RBAC) ermöglicht es, die Zugriffsrechte von Operatoren und Anwendern sehr feingranular zu definieren. Auf Seiten des Netzwerks erlauben es die Network Policies kritische Komponenten, Projekte, Teams, Umgebungen, voneinander zu isolieren. Dies kann, je nach verwendeter Netzwerkkomponente, auf dem OSI Layer 4 oder aber auch auf dem OSI Layer 7 erfolgen. So ist es beispielsweise durchaus üblich bei Kubernetes Namespaces allen ausgehenden und eingehenden Traffic zu verhindern. Für spezifische Komponenten, die einen solchen Zugriff benötigen, wie zum Beispiel den Ingress-Controller, wird er wieder erlaubt. Ein solches explizites Whitelisting von Zugriffen hätte auch die Log4Shell vulnerability, schon auf Kubernetes Ebene, weniger problematisch gemacht.
Ein gern vergessener Faktor ist ebenfalls die Frage was Container auf dem entsprechenden Host-System dürfen und was nicht. Die Prozesse eines Containers sind ja, je nach verwendeter Container Runtime, ganz normale Linux Prozesse. Es stellt sich also immer die Frage: Welche Capabilities dürfen sie annehmen? Dürfen sie das Filesystem des Hosts mounten oder nicht? Dürfen sie als privilegierter Container laufen? Auch hier gilt nämlich das Least Privilege Prinzip. Je weniger Rechte ein solcher Container Prozess hat, desto geringer sind die Angriffsmöglichkeiten, desto sicherer ist im Allgemeinen das Gesamtsystem.
Das Sicherstellen solcher Pod oder Container Security Standards erfolgte in der Vergangenheit über die sogenannten Pod Security Policies. Diese Policies müssen allerdings in Zukunft durch einen Pod Security Admission Controller oder ähnlichem (OPA) ersetzt werden. Was auch immer man in Zukunft verwendet, sowohl das Definieren von Container Sicherheitsstandard als auch die Garantie auf deren Einhaltung, ist ein notwendiges Feature einer Container-Plattform.
Zusammenfassung
Die Betrachtung der Sicherheit einer Container Plattform ist nicht nur ein untergeordnetes Übel, sie ist eine absolute Voraussetzung für den Betrieb. Ihre Vielschichtigkeit und Komplexität lässt einen allerdings manchmal verzweifeln. Insbesondere, wenn zusätzlich branchenspezifische Regulatorik beachtet werden muss. Dennoch gibt es für alle Ebenen sehr hilfreiche Werkzeuge, die ein bei der Automatisierung der Aufgabe helfen. Denn nur Automatisierung macht es möglich, die Komplexität unter Kontrolle zu behalten.
Aber auch das Systemdesign kann etwas dazu beitragen die Sicherheit zu erhöhen. Denn je weniger APIs, je homogener die Infrastruktur, je weniger Frameworks wir verwenden und je einfacher unsere Applikationen sind, desto mehr Sicherheit kann schlussendlich gewährleistet werden.