Wir haben SAST, Secret Detection und Software Composition Analysis bereits in die GitLab-Pipeline integriert. Diese Checks decken Quellcode und Dependencies ab — aber das Artefakt, das wir tatsächlich ausliefern, ist ein Container-Image. Betriebssystem-Pakete, das Base-Image und alles, was im Lauf des Builds hineinkopiert wird, können eigene Schwachstellen mitbringen. In Teil 6 unserer Serie ergänzen Patrick Steger und ich die Pipeline um Container Scanning, bauen ein Docker-Image aus dem zuvor kompilierten Jar und schicken es durch Trivy und Grype.
Was Container Scanning macht#
Container Scanning sucht nach bekannten Schwachstellen innerhalb deines Container-Images. Es scannt jede Datei im Image — OS-Pakete, Libraries, die Application-Binaries — gegen Vulnerability-Datenbanken. GitLab nutzt unter der Haube zwei Open-Source-Tools: Trivy und Grype. Das Feature ist Teil von GitLab Ultimate und funktioniert nur für Linux-Images; Windows Container werden bisher nicht unterstützt.
Ein Hinweis, den Patrick früh anbringt: Container Scanning überschneidet sich mit den SCA-Ergebnissen, die wir bereits erzeugt haben. Mit Ultimate kann GitLab diese Duplikate für dich ausfiltern. Ohne Ultimate wirst du dieselbe Library in beiden Reports auftauchen sehen.
Den Job aktivieren#
Wie in den vorherigen Stages ist der Scanning-Job selbst eine einzige Include-Zeile. Wir holen Container-Scanning.gitlab-ci.yml aus den GitLab-Templates und ergänzen es in der bestehenden Liste der Imports. Der Haken diesmal: Der Job braucht ein Image, das er scannen kann — und das haben wir noch nicht.
Erst das Image bauen#
Bevor Container Scanning laufen kann, müssen wir einen Container produzieren. Wir starten mit dem Dockerfile aus dem Projekt-Template, ändern es aber. Die Originalversion führte mvn package innerhalb des Images aus und baute die ganze Anwendung von Grund auf neu. Das ist Verschwendung — wir haben das Jar bereits im build-Job der Pipeline kompiliert. Also entfernen wir den Maven-Schritt und verwenden COPY target, um das vorgebaute Jar ins Image zu ziehen. Der letzte Eintrag im Dockerfile startet dieses Jar. Als Base-Image dient JDK 13.
Dann erweitern wir die Pipeline um einen build_image-Job. Wir definieren eine Variable CONTAINER_TEST_IMAGE für den Image-Namen in der GitLab-Registry, loggen uns bei Docker ein, bauen das Image und pushen es. Weil wir das kompilierte Jar brauchen, ergänzen wir am Ende ein needs:-Statement, das build_image vom vorherigen build-Job abhängig macht. Ohne diese Abhängigkeit hätte die COPY target-Zeile nichts zum Kopieren.
Um Docker-Befehle innerhalb eines GitLab-Jobs auszuführen, brauchen wir Docker selbst. Wir hängen docker:dind (Docker-in-Docker) als Service in den services:-Block des build_image-Jobs. Damit steht das Docker-Tooling im Runner zur Verfügung.
Schliesslich richten wir den Container-Scanning-Job auf das gerade gebaute Image. Das Template stellt eine vordefinierte Variable namens DOCKER_IMAGE bereit — wir setzen sie im globalen Variables-Block auf CONTAINER_TEST_IMAGE. Damit weiss der Scanner genau, welches Image er aus der Registry ziehen und analysieren soll.
Ganz schön viele Images#
An dieser Stelle lohnt es sich, kurz die Docker-Images zu zählen. Jeder Build-Job läuft in seinem eigenen Image. Jetzt haben wir zusätzlich ein neues Image gebaut, das unsere Anwendung enthält. Und genau dieses neue Application-Image ist es, das Container Scanning analysiert. Drei verschiedene Rollen für “Docker-Image” — und die Pipeline hat sie alle.
Findings — und eine grüne Pipeline#
Die Pipeline läuft. Der Container-Scanning-Job wird grün. Wir öffnen ihn und finden Schwachstellen. Warum ist die Pipeline nicht rot?
GitLab betrachtet einen Job als erfolgreich, wenn er seine Aufgabe abgeschlossen hat — in diesem Fall: “Ich habe das Image gescannt und einen Report erzeugt.” Findings allein lassen die Pipeline nicht scheitern. Aus reiner Security-Sicht würde Patrick einen harten Fail bei jedem Finding bevorzugen, in der Praxis hast du aber fast immer mindestens eine CVE irgendwo, und eine permanent rote Pipeline ist eine Pipeline, der niemand mehr traut. Ein vernünftiger Mittelweg wäre, nur bei High oder Critical zu failen, aber GitLab bietet diesen Schalter aktuell nicht out of the box. Die Remediation, auf die Patrick verweist, sind Merge-Request-Approval-Regeln — wir können verlangen, dass bestimmte Reviewer freigeben, wenn eine Änderung neue Schwachstellen einführt. Das schauen wir uns in einer späteren Session genauer an.
Die Findings selbst kannst du an zwei Orten ansehen. Erstens im Security Tab des Pipeline-Runs, gefiltert nach Tool — wähle “Container Scanning”. Zweitens unter Security & Compliance → Vulnerability Report im Governance-Bereich des Projekts, ebenfalls filterbar nach Tool.
Key Takeaways#
Container Scanning prüft das Artefakt, das du wirklich ausspielst. SAST und SCA decken Source und Dependencies ab. Container Scanning deckt das Image ab: OS-Pakete, Base-Image, kopierte Binaries. Wer das weglässt, liefert blind aus.
Den Build wiederverwenden, nicht im Dockerfile neu bauen.
mvn packageaus dem Dockerfile entfernen und das von der Pipeline bereits erzeugte Jar mitCOPY targetreinziehen. Alles andere dupliziert Arbeit und zerfasert das Artefakt, das du eigentlich getestet hast.Abhängigkeiten explizit über
needs:verdrahten. Container Scanning braucht ein Image. Der Image-Build-Job braucht das kompilierte Jar. Schreib diese Abhängigkeiten ins YAML, sonst überrascht dich die Reihenfolge.Docker-in-Docker ist der Preis fürs Image-Bauen in CI.
docker:dindals Service ambuild_image-Job ergänzen. Ohne diesen Service hat die Docker-CLI im Runner nichts, mit dem sie reden kann.Eine grüne Pipeline mit Vulnerabilities ist Absicht. GitLab markiert den Job als erfolgreich, wenn der Scan durchgelaufen ist. Severity-basierter Pipeline-Fail ist nicht eingebaut. Nutze Merge-Request-Approval-Regeln, damit keine neuen Findings unbemerkt durchrutschen.
Mit Überschneidungen zur SCA rechnen — und einen Plan dafür haben. Container Scanning entdeckt Application-Library-CVEs erneut, die SCA bereits gemeldet hat. Ultimate filtert die Duplikate; ohne Ultimate vorab klären, welcher Report welches Finding besitzt — sonst diskutiert das Team an zwei Stellen gleichzeitig.
