Skip to main content

In this article

Kommandozeilentools für Container – Snyk mit Buildah, Podman und Skopeo

Artikel von

9. Dezember 2020

0 Min. Lesezeit

Das Container-Ökosystem ist gereift. Eines gibt es dabei mehr als genug: Auswahlmöglichkeiten – sowohl bei der eingesetzten Software als auch bei der Art, wie wir sie miteinander verbinden.

Eine dieser Möglichkeiten ist die Kombination aus Buildah, Podman und Skopeo – drei Open-Source-Kommandozeilentools, die ihren Ursprung im RedHat-Ökosystem haben. Wie der Name schon sagt, bietet Buildah zahlreiche Funktionen zum Erstellen von OCI-konformen Containern und Images. Podman dient der Verwaltung und Ausführung von OCI-Containern, während Skopeo ein Tool für die Arbeit mit Container-Registries ist.

In diesem Blogbeitrag sehen wir uns an, wie offene Standards dafür sorgen, dass Snyks Container-Scanning mit all diesen Technologien funktioniert.

Sehen wir uns zunächst Skopeo an. Mit diesem Kommandozeilentool lassen sich Container-Images zwischen Repositories verschieben und lokal speichern. Skopeo kann Images sowohl als Docker-Archive als auch im OCI-Archivformat ausgeben. Beide Formate unterstützt Snyk beim Scannen des Dateisystems. Laden wir damit ein Image von Quay.io herunter und scannen es lokal aus unserem Dateisystem :

$ skopeo copy docker://quay.io/tutum/hello-world oci-archive:hello-world.tar
$ snyk container test oci-archive:hello-world.tar 

Testing oci-archive:hello-world.tar…

{-----OUTPUT SNIPPED-----}

✗ High severity vulnerability found in openssl
  Description: Out-of-Bounds
  Info: https://snyk.io/vuln/SNYK-UBUNTU1210-OPENSSL-374571
  Introduced through: openssl@1.0.1c-3ubuntu2.6, ca-certificates@20120623, ssl-cert@1.0.32, meta-common-packages@meta
  From: openssl@1.0.1c-3ubuntu2.6
  From: ca-certificates@20120623 > openssl@1.0.1c-3ubuntu2.6
  From: ssl-cert@1.0.32 > openssl@1.0.1c-3ubuntu2.6
  and 1 more...
  Fixed in: 1.0.1c-3ubuntu2.7

Organization:      matt-jarvis-snyk
Package manager:   deb
Project name:      docker-image|hello-world.tar
Docker image:      oci-archive:hello-world.tar
Licenses:          enabled

Tested 237 dependencies for known issues, found 25 issues.

Ubuntu 12.10 is no longer supported by the Ubuntu maintainers. Vulnerability detection may be affected by a lack of security updates.

Wie Sie sehen, verwendet dieses Image eine nicht mehr unterstützte Ubuntu-Version und enthält daher mehrere Sicherheitslücken, die nicht behoben werden. Mit Skopeo können wir also Images aus jeder standardkonformen Container-Registry in einem von Snyk unterstützten Format herunterladen und lokal scannen.

Buildah

Als Nächstes sehen wir uns Buildah an. Buildah ist ein Kommandozeilentool zum Erstellen von Images, nicht zu deren Ausführung. Es ist daher für den Einsatz mit Podman vorgesehen. Außerdem ist es für den Rootless-Betrieb ausgelegt: Mithilfe von User-Namespaces im Linux-Kernel wird eine rootähnliche Shell isoliert, die nur über Berechtigungen auf Benutzerebene verfügt. So können Administratoren Entwicklerinnen und Entwicklern das Erstellen und Verwalten von Container-Images ermöglichen und gleichzeitig im Build-Umfeld das Prinzip der geringsten Berechtigungen einhalten.

Buildah kann zwar ähnlich wie Docker selbst mit Dockerfiles arbeiten, ermöglicht aber auch die Entwicklung von Containern und Images mit anderen Workflows, zum Beispiel mit Bash oder Python. Das bietet mehr Flexibilität und kann das Debugging von Container-Builds vereinfachen. Buildah ermöglicht etwa das Einbinden des Zwischencontainers während des Build-Prozesses. So können Skripte vor dem Fortfahren die korrekte Ausführung überprüfen, Software extern erstellen oder sogar Benutzereingaben entgegennehmen. Damit gibt es eine Alternative zu den mitunter komplexen mehrstufigen Build-Prozessen, die sich bei Docker als Best Practice etabliert haben.

Sehen wir uns an, wie Buildah in der Praxis eingesetzt wird.

Zunächst ist wichtig zu wissen, dass wir unsere Container einfach mit Dockerfiles erstellen können, wenn dieser Workflow für Sie am besten passt. Hier ein Beispiel für ein Dockerfile :

$ cat Dockerfile 
FROM centos:8
LABEL maintainer Matt Jarvis <matt@mattjarvis.org.uk>

RUN dnf install -y git gcc findutils make help2man texinfo gperf gettext-devel autoconf automake && dnf clean all

RUN git clone https://git.savannah.gnu.org/git/hello.git /opt/hello

WORKDIR /opt/hello

RUN ./bootstrap --skip-po && ./configure && make && make install

RUN ./hello
ENTRYPOINT "/usr/local/bin/hello"

Hier verwenden wir das CentOS-8-Image als Basis, klonen den GNU-Hello-Quellcode, installieren einige Voraussetzungen und erstellen und installieren anschließend die Binärdatei.

Mit Buildah können wir das mithilfe der Option bud ( build-using-dockerfile) automatisch erstellen :

$ buildah bud

Das ist eindeutig keine gute Vorgehensweise: Unser finales Image enthält sowohl den Quellcode als auch die Build-Voraussetzungen und verstößt gegen die meisten Best Practices für die sichere Image-Entwicklung. Mit Docker wäre ein mehrstufiger Build hier die Best Practice. Wir könnten Buildah mit einem mehrstufigen Dockerfile verwenden. Noch interessanter ist jedoch, dass wir auch native Buildah-Befehle und entsprechende Skripte nutzen können. Um genau das nachzubilden, was wir oben mit dem Dockerfile gemacht haben, würden wir folgendes Skript ausführen :

$ cat single-stage.sh 
#!/usr/bin/env bash

set -o errexit

# Create a container
container=$(buildah from centos:8)

# Labels are part of the "buildah config" command
buildah config --label maintainer="Matt Jarvis <matt@mattjarvis.org.uk>" $container

# Install pre-reqs
buildah run $container dnf install -y git gcc findutils make help2man texinfo gperf gettext-devel autoconf automake
buildah run $container dnf clean all

# Clone the git repository
buildah run $container git clone https://git.savannah.gnu.org/git/hello.git /opt/hello

# Workingdir is also a "buildah config" command
buildah config --workingdir /opt/hello $container

buildah run $container ./bootstrap --skip-po
buildah run $container ./configure
buildah run $container make
buildah run $container make install

# Entrypoint is also a “buildah config” command
buildah config --entrypoint /usr/local/bin/hello $container

# Finally save the running container to an image
buildah commit --format docker $container hello:latest

Buildah ermöglicht es uns außerdem, während des Build-Prozesses den Zwischencontainer einzubinden. So können wir den Quellcode auf dem Host erstellen und einfach die resultierende Binärdatei in unseren Container kopieren, bevor wir daraus ein Image erstellen. Auf diese Weise können wir während der Builds die Ressourcen des Hosts nutzen und beispielsweise auch den Paketmanager des Hosts verwenden, um Pakete in unserem Container zu installieren. Dafür benötigen wir keine Root-Berechtigungen: Buildah unterstützt User-Namespaces, mit denen Sie das Einbinden ohne Berechtigungen ermöglichen können. Dazu dient der Befehl unshare :

$ buildah unshare
$ cat host-mount.sh 
#!/usr/bin/env bash

set -o errexit

# Create a container
container=$(buildah from centos:8)
mountpoint=$(buildah mount $container)

buildah config --label maintainer="Matt Jarvis <matt@mattjarvis.org.uk>" $container

# Clone the git repository to the host
git clone https://git.savannah.gnu.org/git/hello.git hello

pushd hello
./bootstrap --skip-po
./configure
make
# Install to the container
make install DESTDIR=${mountpoint}
popd

chroot $mountpoint bash -c "/usr/local/bin/hello"

buildah config --entrypoint "/usr/local/bin/hello" $container
buildah commit --format docker $container hello
buildah unmount $container

Wie Sie im obigen Beispiel sehen, erhalten wir nach Ausführung des unshare-Befehls eine Root-Shell. Diese befindet sich jedoch in einem User-Namespace und verfügt nicht über vollständige Root-Berechtigungen. So können wir das Container-Dateisystem während des Build-Prozesses einbinden. Da es sich um ein Bash-Skript handelt, können wir außerdem die Funktionen einer Programmiersprache nutzen – etwa Bedingungen und Schleifen oder sogar Benutzereingaben während des Prozesses abfragen.

Podman

Sobald wir dieses Skript ausgeführt und unser Image erstellt haben, können wir es auch mit Snyk scannen. Hier kommt Podman ins Spiel. Podman ist eine daemonlose Container-Engine für die Entwicklung, Verwaltung und Ausführung von OCI-Containern. Da Buildah und Podman auf offenen Standards basieren, konnte Snyk schon immer Images scannen, die mit Podman erstellt oder heruntergeladen wurden: Dazu speichert Podman das Image auf dem Datenträger, wo Snyk es aus dem Dateisystem scannt. Podman kann Images sowohl als Standard-Docker-Archiv als auch im OCI-Archivformat speichern. Beide Formate unterstützt Snyk.

[root@localhost buildah_test]# podman images
REPOSITORY                         TAG     IMAGE ID      CREATED         SIZE
localhost/hello                    latest  975f60fc1f19  15 minutes ago  223 MB
[root@localhost buildah_test]# podman save 975f60fc1f19 -o hello.tar
[root@localhost buildah_test]# snyk container test docker-archive:hello.tar

Testing docker-archive:hello.tar...

{-----OUTPUT SNIPPED-----}

✗ High severity vulnerability found in librepo
  Description: RHSA-2020:3658
  Info: https://snyk.io/vuln/SNYK-CENTOS8-LIBREPO-610057
  Introduced through: librepo@1.11.0-2.el8
  From: librepo@1.11.0-2.el8
  Fixed in: 0:1.11.0-3.el8_2

Organization:      matt-jarvis-snyk
Package manager:   rpm
Project name:      docker-image|hello.tar
Docker image:      docker-archive:hello.tar
Platform:          linux/amd64
Licenses:          enabled

Tested 172 dependencies for known issues, found 28 issues.

Pro tip: use `--file` option to get base image remediation advice.
Example: $ snyk test --docker docker-archive:hello.tar --file=path/to/Dockerfile

To remove this message in the future, please run `snyk config set disableSuggestions=true`

Da unser Basis-Image ein öffentliches Image auf Dockerhub ist, könnten wir es mit der Snyk CLI auch einfach so scannen :

[root@localhost buildah_test]# snyk container test centos:8

In diesem Fall greift Snyk direkt auf Dockerhub zu und scannt einfach das Upstream-Image.

Wenn das Image jedoch nur lokal in Podman vorhanden ist oder sich in einem privaten Repository auf Dockerhub befindet, für das eine Anmeldung erforderlich ist, funktioniert keine dieser Methoden. Versuchen wir beispielsweise, unser erstelltes Image direkt aus Podman zu scannen:

[matt@localhost buildah_test]# podman images
REPOSITORY                         TAG     IMAGE ID      CREATED         SIZE
localhost/hello                    latest  975f60fc1f19  20 minutes ago  223 MB

[matt@localhost buildah_test]# snyk container test localhost/hello
connect ECONNREFUSED 127.0.0.1:443

Für solche Anwendungsfälle verwendet Snyk den lokalen Docker-Client – entweder über die Registry-API oder über den Docker-Socket. Da sich auf dem System keine Docker-Binärdatei befindet, hat Snyk hier versucht, die Registry-API zu verwenden, konnte aber keine Verbindung herstellen. Standardmäßig weiß Snyk nicht, dass diese Images lokal in Podman vorhanden sind oder wie Podman für den Zugriff auf Upstream verwendet werden kann.

Zum Glück bietet Podman mit dem Paket podman-docker einige einfache Kompatibilitätsfunktionen. Es stellt eine gefälschte Docker-Binärdatei bereit, die im Grunde ein Shell-Wrapper für die Podman-Binärdatei ist, und erstellt einen symbolischen Link zwischen dem Podman-API-Socket und dem Standardspeicherort der Docker-Socket-Datei. Standardmäßig verweist dieser Link auf /var/run/podman/podman.sock, was dem Standard entspricht, wenn die Podman-API als Root ausgeführt wird.

[matt@localhost buildah_test]$ sudo dnf install podman-docker

Now let’s try and scan our local image again :

[matt@localhost buildah_test]$ snyk container test localhost/hello
connect EACCES /var/run/docker.sock

Das funktioniert immer noch nicht, aber diesmal erhalten wir eine andere Fehlermeldung. Snyk hat eine Binärdatei namens docker erkannt und versucht nun, eine Verbindung zum Standardspeicherort des privilegierten Docker-Sockets herzustellen. Da die Podman-API noch nicht ausgeführt wird, kann keine Verbindung hergestellt werden und der Vorgang schlägt erneut fehl.

Wie bereits erwähnt, erstellt das Paket podman-docker einen symbolischen Link von /var/run/podman/podman.sock zu /var/run/docker.sock – unter der Annahme, dass die Podman-API mit Root-Berechtigungen ausgeführt wird.

[matt@localhost buildah_test]$ ls -l /var/run/docker.sock 
lrwxrwxrwx. 1 root root 23 Nov 25 07:51 /var/run/docker.sock -> /run/podman/podman.sock

Ein wesentliches Problem mit dem Docker-Socket besteht darin, dass er standardmäßig mit Root-Berechtigungen läuft. Das gilt in bestimmten Umgebungen als Sicherheitsrisiko. In den meisten lokalen Entwicklungsumgebungen müssen wir die Podman-API nicht auf diese Weise ausführen und können stattdessen an anderer Stelle einen Socket ohne erhöhte Berechtigungen verwenden. Den Speicherort können wir Snyk über die Umgebungsvariable DOCKER_HOST mitteilen. Sie unterstützt die URL-Formate tcp:// und unix://.

Führen wir die Podman-API in einer anderen Shell mit einem Socket ohne erhöhte Berechtigungen aus:

[matt@localhost ~]$ podman system service --time=0 unix://home/matt/podman.sock

Der Prozess läuft im Vordergrund. Sie müssen ihn daher nach dem Test mit Strg+C beenden. Nun müssen wir die Umgebungsvariable DOCKER_HOST festlegen:

[matt@localhost buildah_test]$ export DOCKER_HOST=unix:///home/matt/podman.sock

Versuchen wir abschließend, unser lokales Image erneut mit Snyk zu scannen :

[matt@localhost buildah_test]$ snyk container test localhost/hello

Testing localhost/hello...

{ ----SNIPPED OUTPUT----}

✗ High severity vulnerability found in librepo
  Description: RHSA-2020:3658
  Info: https://snyk.io/vuln/SNYK-CENTOS8-LIBREPO-610057
  Introduced through: librepo@1.11.0-2.el8
  From: librepo@1.11.0-2.el8
  Fixed in: 0:1.11.0-3.el8_2

Organization:      matt-jarvis-snyk
Package manager:   rpm
Project name:      docker-image|localhost/hello
Docker image:      localhost/hello
Platform:          linux/amd64
Licenses:          enabled

Tested 172 dependencies for known issues, found 28 issues.

Pro tip: use `--file` option to get base image remediation advice.
Example: $ snyk test --docker localhost/hello --file=path/to/Dockerfile

To remove this message in the future, please run `snyk config set disableSuggestions=true`

Geschafft! Snyk funktioniert nun korrekt und kommuniziert über den Socket ohne erhöhte Berechtigungen mit Podman.

Wenn Sie DOCKER_HOST dauerhaft in Ihrer Shell konfigurieren möchten, können Sie den Export-Befehl in Ihre Datei .bashrc aufnehmen. So bleibt die Einstellung erhalten.

Abschließend möchten wir diese Konfiguration bei jeder Anmeldung automatisch verfügbar haben. Dazu können wir die Socket-Aktivierungsfunktionen von systemd nutzen, um unseren Socket bei der Anmeldung automatisch zu starten, sobald eine Verbindung hergestellt werden soll.

Zunächst müssen wir den Socket konfigurieren :

[matt@localhost ~]$ cat .config/systemd/user/podman.socket
[Unit]
Description=Podman API Socket
Documentation=man:podman-api(1)

[Socket]
ListenStream=/home/matt/podman.sock
SocketMode=0660

[Install]
WantedBy=sockets.target

Anschließend verknüpfen wir den Socket mit einem Dienst :

[matt@localhost ~]$ cat .config/systemd/user/podman.service
[Unit]
Description=Podman API Service
Requires=podman.socket
After=podman.socket
Documentation=man:podman-api(1)
StartLimitIntervalSec=0

[Service]
Type=oneshot
Environment=REGISTRIES_CONFIG_PATH=/etc/containers/registries.conf
ExecStart=/usr/bin/podman system service unix:///home/matt/podman.sock
TimeoutStopSec=30
KillMode=process

[Install]
WantedBy=multi-user.target
Also=podman.socket

Zum Schluss laden wir die systemd-Konfiguration neu und aktivieren den Dienst!

[matt@localhost ~]$ systemctl --user daemon-reload
[matt@localhost ~]$ systemctl --user enable --now podman.socket

Fazit

Damit funktioniert das Image-Scanning mit der Snyk CLI und Podman genauso wie mit Docker. Entwicklerinnen und Entwickler können so im Rahmen ihres Workflows unkompliziert umfassende Sicherheitsscans lokaler Docker- oder OCI-Images durchführen – ohne erhöhte Berechtigungen. Außerdem haben wir gesehen, wie sich Skopeo und Buildah dank offener Standards mit Snyk einsetzen lassen!

Sie haben noch kein Snyk-Konto? Registrieren Sie sich kostenlos und scannen Sie mit Snyk sowohl Container-Images als auch Open-Source-Abhängigkeiten.

Starten Sie mit Capture the Flag

Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Herausforderungen lösen.