Tipps und Best Practices für sichere Container-Images
6. Juli 2021
0 Min. LesezeitWenn Sie mit dem Scannen Ihrer Container-Images beginnen, kann es beunruhigend sein, eine große Zahl von Sicherheitslücken zu entdecken. Unten sehen Sie einen Scan, den ich letzte Woche für ein anfälliges Node-Image durchgeführt habe, das ich erstellt hatte. Das ist zwar ein ziemlich extremes Beispiel, aber Sie sehen, dass dieses Image direkt nach der Installation mehr als 800 Sicherheitslücken aufweist.

Angesichts dessen erstarren viele von uns wie das Kaninchen vor der Schlange, wenn sie mit einer langen Liste von CVEs konfrontiert werden – besonders, wenn ihr Schwerpunkt auf der Anwendungsentwicklung und nicht auf der Systemadministration liegt. Was soll ich mit diesen Informationen anfangen, wo soll ich beginnen? Ich wollte doch nur ein Image, in dem meine Node-Anwendung ausgeführt werden kann, und stehe schon vor der riesigen Aufgabe, es abzusichern.
Wichtig ist vor allem, dass das Beheben dieser Probleme in Containern nicht dasselbe ist wie in einem Betriebssystem. Wir werden keine einzelnen Pakete aktualisieren oder das gesamte System verwalten. Bei Containern müssen wir verstehen, wie Sicherheitslücken in unsere Images gelangt sind, bevor wir über geeignete Strategien zu ihrer Behebung nachdenken können – wie etwa in unserer Liste mit 10 Best Practices für Docker-Sicherheit.
Was steckt in meinem Container-Image?
Zunächst sollten Sie in diesem Zusammenhang verstehen, wie die von uns verwendeten Images aufgebaut sein könnten. Sofern wir Images nicht von Grund auf neu erstellen, beginnen wir wahrscheinlich mit einem Basis-Image in unserer Dockerfile. Obwohl wir es als unser Basis-Image bezeichnen, basiert das von uns verwendete Image wahrscheinlich ebenfalls auf einem übergeordneten Image, dem während des Build-Prozesses Software hinzugefügt wurde. Auch das übergeordnete Image wurde auf irgendeine Weise erstellt – vielleicht auf Grundlage eines weiteren übergeordneten Images oder mithilfe eines Tools zum Erstellen von Root-Dateisystemen. Zu verstehen, wie die von uns gescannte Software überhaupt in unsere Images gelangt ist, ist entscheidend für die Wahl unserer Strategie zur Verringerung der Zahl der Sicherheitslücken.
Sehen wir uns als Beispiel das offizielle Nginx-Image auf Docker Hub an. Wenn wir die Dockerfile dieses Images untersuchen, sehen wir, dass es auf dem schlanken Debian-Buster-Image basiert, dem beim Erstellen des Nginx-Images Software und Konfiguration hinzugefügt werden.
Das Debian-Buster-Image wiederum wird mithilfe einer weiteren Dockerfile erstellt, die ein Scratch-Image verwendet und ein Tarball hinzufügt.
Wenn wir untersuchen, wie dieser Tarball erstellt wird, sehen wir, dass er eine Ausgabe des Tools debuerreotype ist. Dabei handelt es sich um eine Reihe von Skripten, die das Debian-Projekt zum Erstellen von Root-Dateisystemen verwendet. So geht Debian vor; bei den übrigen Betriebssystemen, die üblicherweise als Basis-Images eingesetzt werden, gibt es jedoch unterschiedliche Methoden.
All das zeigt: Selbst wenn wir uns nur unsere Basis-Images ansehen, kann der Weg, auf dem Software in sie gelangt, ein langer und möglicherweise komplizierter Prozess sein. Ohne die verschiedenen Vorgehensweisen zu verstehen, lässt er sich nur schwer nachvollziehen.
Scratch
Manche würden nun sagen, Sie sollten einfach Scratch verwenden und Ihre Images selbst von Grund auf neu erstellen – ausgehend von einem leeren Dateisystem. Unter bestimmten Umständen ist das ein sinnvoller Ansatz und eignet sich möglicherweise gut für Binärdateien kompilierter Programmiersprachen ohne Abhängigkeiten, etwa Go oder C. Bei den meisten anderen Anwendungen sind Sie jedoch für die Wartung aller Bestandteile des Images verantwortlich. Das kann auf Dauer einen erheblichen Mehraufwand bedeuten. Wenn wir viele verschiedene Container-Images erstellen, kann dieser Aufwand schnell überwältigend werden – und der Vorteil einer kleineren Angriffsfläche geht möglicherweise durch den Wartungsaufwand verloren.
Vertrauen oder nicht vertrauen?
Bei der Verwaltung von Sicherheitslücken in Images ist das Vertrauen in unser Basis-Image also ein wichtiger Aspekt. Wir müssen stets abwägen, ob wir dem Upstream-Image vertrauen oder den gesamten Build-Prozess unserer Images selbst verantworten und damit auch die Verantwortung für sämtliche darin installierte Software übernehmen.
Wie oben beschrieben, müssen wir auch der gesamten Kette von Build-Prozessen vertrauen, die in das von uns verwendete Image eingeflossen sind. Diese lässt sich möglicherweise nur schwer vollständig nachvollziehen. Viele Images in öffentlichen Registries sind möglicherweise schlecht erstellt oder werden nicht gewartet. Generell bieten öffentliche Registries für die meisten dort gehosteten Images keine Qualitätssicherung. Das unterscheidet sich natürlich nicht von der Art, wie wir den Großteil unserer Open-Source-Software verwenden. Viele der Qualitätsmerkmale, die unsere Auswahl dabei beeinflussen, gelten auch hier: Wird die Software regelmäßig gewartet und aktualisiert? Gibt es eine große Community? Wird sie von kommerziellen Unternehmen unterstützt? Diese Informationen sind online verfügbar. Nehmen Sie sich also Zeit, um zu recherchieren, was Sie tatsächlich verwenden. Snyk Advisor ist ein großartiges Tool für Ihre Recherche.
Wenn wir uns entschieden haben, unserem Upstream-Basis-Image zu vertrauen, sollten wir bei Problemen im Basis-Image upstream nach einer Korrektur suchen, statt am Ende unsere eigene Abspaltung des Basis-Images mit aktualisierten Paketen zu warten. Container sind von Natur aus auf Unveränderlichkeit ausgelegt. Wenn wir in unserem Container-Build-Prozess beginnen, Pakete zu aktualisieren, untergraben wir das Konzept der Verwendung eines Basis-Images. Diese Strategie wird schnell unüberschaubar, weil wir dann faktisch für die Wartung unseres Images verantwortlich sind.
Doch ein Basis-Image auszuwählen, ist nicht immer so einfach, wie es scheint. Das „offizielle“ Python-Basis-Image auf Docker Hub enthält beispielsweise viele Sicherheitslücken und ist sehr groß. Das ist bei offiziellen Runtime-Images recht typisch, da sie von vornherein für jeden Anwendungsfall ausgelegt sein müssen. Wir könnten die schlanke Version in Betracht ziehen, die kleiner ist und weniger Sicherheitslücken aufweist, oder nach einer Alternative suchen. Doch im Repository gibt es sehr viele Tags – wie sollen wir uns entscheiden?
Zunächst einmal ist das generische Tag latest für ein Image eines Sprach-Frameworks wahrscheinlich nicht das Richtige für den produktiven Einsatz. Es ist schwer zu erkennen, welche Framework-Version verwendet wird, und diese könnte sich künftig ändern. Aber slim ist nicht automatisch die beste Wahl: Sie erhalten möglicherweise weniger Sicherheitslücken, müssen dann aber womöglich die Build-Abhängigkeiten selbst verwalten.
Die beste Lösung: Multi-Stage-Builds
Am besten verwenden Sie Multi-Stage-Builds: Dabei nutzen wir das größere, allgemeinere Image für die Software-Builds und kopieren anschließend die Build-Artefakte in unsere schlanke Version für den Produktionseinsatz. So müssen wir unsere Build-Abhängigkeiten nicht verwalten und profitieren trotzdem von der geringeren Größe und der niedrigeren Zahl an Sicherheitslücken der schlanken Version. Außerdem sollten wir uns auf bestimmte Runtime-Versionen festlegen. So wissen wir genau, welche Runtime-Umgebung wir erhalten und dass sie sich nicht unbemerkt ändert.
Best Practices für die Auswahl von Basis-Images
Für die Auswahl unserer Basis-Images gibt es einige allgemeine Empfehlungen.
Vertrauen Sie einem Upstream-Anbieter, der Ihnen die aufwendige Arbeit und das Beheben von Sicherheitslücken abnimmt. Dort arbeiten größere Teams an diesen Aufgaben, sodass Probleme deutlich schneller behoben werden.
Legen Sie für Ihre Apps Versionen der Images fest – mindestens die Hauptversion, besser noch die Nebenversion. So ändern sich die Rahmenbedingungen künftig nicht unbemerkt.
Gewöhnen Sie sich an Multi-Stage-Builds. Damit können Sie schlanke Images für die Bereitstellung nutzen und zugleich beim Build von bewährten Kombinationen profitieren.
Erstellen Sie regelmäßig neue Builds. So erhalten Sie oft bereits im Rahmen des Build-Prozesses Sicherheitskorrekturen.
Erwägen Sie von Zeit zu Zeit ein Upgrade. Neue Versionen enthalten ebenfalls weitere Sicherheitskorrekturen.
Scannen Sie Ihre Images immer auf Sicherheitslücken
Wenn Sie Ihre Images und Dockerfiles mit Snyk scannen, erfahren Sie, welche alternativen Basis-Images Sie verwenden können, um die Gesamtzahl der Sicherheitslücken zu senken. Snyk kann außerdem automatisch PRs in Ihren Dockerfiles erstellen, um das Basis-Image zu ändern. Und das Beste: Sie können das kostenlos nutzen.

Im zweiten Teil dieser Blogreihe sehen wir uns die Software an, die wir selbst zu Basis-Images hinzufügen, und wie wir dort Sicherheitslücken beheben können. Schauen Sie bald wieder vorbei oder folgen Sie @snyksec auf Twitter, um benachrichtigt zu werden, sobald der Beitrag erscheint.
Container-Sicherheit mit Fokus auf Entwickler
Snyk findet und behebt automatisch Schwachstellen in Container-Images und Kubernetes-Workloads.
