Sicherheitslücken In Docker-Images zu identifizieren, ist im Betrieb für viele Projekte zu aufwändig. ClairInABox liefert in dieser Situation einen leichtgewichtigen Einstieg in das Scannen von Containern.
Neue Sicherheitsherausforderungen durch Docker
Mit der Zunahme von zum Teil spektakulären Exploits, die es sogar in die Hauptnachrichten schaffen, steigt das Bewusstsein dafür, dass Security heute im Projekt oft schon von Anfang an mit berücksichtigt werden muss.
Das Mitdenken "von Anfang an" bedeutet, schon in frühen Entwicklungsphasen auf mögliche Schwachstellen zu schauen - hierfür hat sich der Begriff "shift left" etabliert. Für Code gibt es hier schon lange Tools zur statischen Analyse wie SonarQube oder den OWASP Dependency Check, die noch vor dem Deployment Alarm schlagen, wenn sie Fehler und Schwachstellen finden.
Ein wichtiger Aspekt, der leider trotzdem häufig vernachlässigt wird, sind kontinulierliche Security Tests während der Lebensphase eines Systems - auch dann wenn die eigentliche Entwicklung längst abgeschlossen ist. Build-Server und Pipelines erlauben es, die gleichen Scanner regelmäßig in einem Nightly laufen zu lassen und so vor Gefahren zu warnen, die beim Deployment noch nicht bekannt waren.
Mit zunehmender Verlagerung in die Cloud, der Allgegenwart von Microservices und insbesondere im Rahmen von DevOps kommt man heutzutage an einer Technologie kaum vorbei: Docker. Docker-Images werden für das Deployment von Apps verwendet, aber auch als Build-Images zum Bauen, Testen und Deployen von Artefakten. Je nach Image enthält ein Docker-Container dabei potenziell ein vollwertiges Betriebssystem, welches regelmäßig mit Security-Patches versorgt werden will.
Docker-Images sind somit oft integraler Bestandteil einer Anwendung und müssen somit genauso auf Schwachstellen abgeklopft werden wie "normaler" Code. Unser Projekt ClairInABox zielt darauf ab, genau das deutlich einfacher zu machen. ClairInABox ist selbst ein Docker-Image, mit dem eigene Images verhältnismäßig einfach gescannt werden können. Insbesondere wollen wir die schnelle Integration in CI/CD-Tools wie Gitlab CI und Jenkins ermöglichen.
Was ist Clair?
Clair ist Tool zur statischen Analyse von Docker-Containern, entwickelt von CoreOS. Im Original ist es eher für größere Unternehmen konzipiert, die die nötige Infrastruktur aufsetzen und betreiben: Man benötigt einen dedizierten Clair-Server und eine Postgres-Datenbank. Der damit verbundene Betriebsaufwand dürfte für viele Projekte zu groß sein. arminc hat daher bereits praktische Tools bereitgestellt, welche die Verwendung von Clair erleichtern:
- clair-db ist ein Docker-Image für die Postgres-Datenbank, welches regelmäßig neu gebaut und mit aktuellen Informationen zu Schwachstellen befüllt wird
- clair-local-scan ist ein zweites Docker-Image, welches mit einem Aufruf das eigene Image scannt, gegen die Datenbank und eine optionale Whitelist vergleicht und einen entsprechenden Report generiert. Für eine Einbindung in CI/CD muss man damit zuerst das Docker-Image mit der Datenbank hochfahren und anschließend das Scanner-Image ausführen.
Was ist ClairInABox?
Unser Projekt, ClairInABox, zielt darauf ab, aus diesen zwei Schritten einen zu machen. Das von uns bereitgestellte Image verfolgt einen Docker-in-Docker-Ansatz: Es startet intern zuerst das Image clair-db und anschließend ein Clair-Server-Image, welches inhaltlich eng an clair-local-scan angelehnt ist. Dabei wird darauf geachtet, dass die beiden Images in einem gemeinsamen, getrennten Netzwerk kommunizieren. So wird gewährleistet, dass mehrere ClairInABox-Instanzen gleichzeitig auf einem Build-Server (z.B. Jenkins) laufen können. Nach dem Scan werden die Resultate extrahiert, wir konvertieren den JSON-Report des Clair-Servers noch in ein einfaches, gut lesbares HTML.
Minimalbeispiel
Folgendermaßen kann ClairInABox aus der (Linux-)Konsole aufgerufen werden
docker run --rm -d -v /var/run/docker.sock:/var/run/docker.sock
-e PROJECT_NAME=IchScanneEinGanzAltesPython
-e IMAGE_TO_SCAN=python:3.8
iteratec/clairinabox:latest
Beispiel: Gitlab CI
Bei folgendem Beispiel ist zu beachten, dass im Gitlab CI Runner zusätzliche Konfigurationen vorgenommen werden müssen, um Docker in Docker auszuführen. (Siehe hier)
Eine Einbindung von ClairInABox in Gitlab CI sähe beispielsweise so aus
clair-scan:
stage: test
image: docker:stable # 1
script:
- export WHITELIST_CONTENT=`cat whitelist.yaml` # 2
- > # 3
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
-e PROJECT_NAME=<your project>
-e IMAGE_TO_SCAN_REPO_USERNAME=$CIAB_TARGET_REGISTRY_USERNAME
-e IMAGE_TO_SCAN_REPO_PASSWORD=$CIAB_TARGET_REGISTRY_PASSWORD
-e IMAGE_TO_SCAN_REPO_URL=$CIAB_TARGET_REGISTRY_URL
-e WHITELIST="$WHITELIST_CONTENT"
-e IMAGE_TO_SCAN=<your image URi>
iteratec/clairinabox:lates
- Das Clair-Image ist nicht das Basis-Image, wir wollen es mit konkreten Parametern ausführen, die wir z.T. erst aus Konfigurationsdateien auslesen.
- Die Whitelist an akzeptierten Schwachstellen wird zur Zeit als Text übergeben, daher lesen wir den Inhalt der Whitelist-Datei in eine Variable ein.
- Hier wird das Clair-Image im Docker-in-Docker-Modus ausgeführt. In diesem Beispiel sind die Credentials für das Docker-Repository, aus dem das zu scannende Image gezogen wird, als Gitlab-CI-Credentials und damit als Umgebungsvariablen konfiguriert. Das ist natürlich nur dann notwendig, wenn das zu scannende Image nicht öffentlich verfügbar ist.
Scan-Output lesen
In dieser Form wird ClairInABox das Image scannen und fehlschlagen, wenn es eine Schwachstelle findet, die über dem Threshold liegt. Das ist natürlich etwas wenig an Informationen. ClairInABox generiert mehrere Artefakte, die allerdings im Docker-Image liegen:
- /result/clair-report.json: Der von Clair erzeugte Report als JSON
- /result/clair-log.log: Der Logging-Output von Clair. In der Regel nur relevant, wenn der Scan an sich nicht funktioniert.
- /result/clair-report.html: Eine einfache HTML-Version des Reports, die von ClairInABox generiert wird
Da wir ClairInABox nicht als Basis-Image nutzen, ist es leider etwas umständlich, an die Artefakte zu kommen:
variables:
ARTIFACT_PATH: /builds/shared/$CI_PROJECT_PATH # 4
clair-scan:
...
artifacts: # 5
paths:
- clair-report.json
- clair-report.html
- clair-log.log
script:
...
- mkdir -p $ARTIFACT_PATH
- set +e # 6
- > # 7
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
-v $ARTIFACT_PATH:/result
-e PROJECT_NAME=<your project>
-e IMAGE_TO_SCAN_REPO_USERNAME=$CIAB_TARGET_REGISTRY_USERNAME
-e IMAGE_TO_SCAN_REPO_PASSWORD=$CIAB_TARGET_REGISTRY_PASSWORD
-e IMAGE_TO_SCAN_REPO_URL=$CIAB_TARGET_REGISTRY_URL
-e WHITELIST="$WHITELIST_CONTENT"
-e IMAGE_TO_SCAN=<your image URi>
iteratec/clairinabox:latest
- returnCode=$? # 8
- cp $ARTIFACT_PATH/clair-report.json ./clair-report.json
- cp $ARTIFACT_PATH/clair-log.log ./clair-log.log
- cp $ARTIFACT_PATH/clair-report.html ./clair-report.html
- set -e
- echo $returnCode
- $returnCode
- Wir brauchen einen Pfad in unserem Runner, den wir mit dem Result-Verzeichnis im ClairInABox-Image mounten können.
- Hier werden die Artefakte archiviert, die wir extrahieren
- Wir müssen dafür sorgen, dass Gitlab CI die Pipeline nicht abbricht, wenn ClairInABox fehlschlägt
- Beim Aufruf von ClairInABox mounten wir unser extra angelegtes Verzeichnis ins Verzeichnis /result im Image.
- Wir speichern uns das Ergebnis des Aufrufs von ClairInABox, kopieren die Artefakte aus dem gemounteten Verzeichnis in den Runner und aktivieren anschließend wieder das Fehlschlagen bei einem Nicht-0-Return-Code, bevor wir den Return Code ausgeben.
Einmal ist keinmal!
Die Datenbank, die Clair zugrundeliegt, wird regelmäßig geupdatet. ClairInABox zieht sich bei jeder Ausführung automatisch die aktuelle Version. Daher ist es sinnvoll, entsprechende Scans regelmäßig (beispielsweise: Einmal täglich/nächtlich) durchzuführen und mit einem entsprechenden Alerting zu verbinden. So werden neue Schwachstellen zeitnah erkannt und können behoben werden bevor ein Angreifer sie ausnutzen kann.
Links
- Unser Projekt in Github: https://github.com/iteratec/ClairInABox
- Das Scanner-Projekt, auf dem ClairInABox basiert: https://github.com/arminc/clair-scanner
- Das Clair-Projekt selbst: https://github.com/quay/clair
Contributors
Simon Hampe, Michael Lörscher, Paul Schmelzer, Benedikt Zirbes