Wolfi OS Une distribution pour les conteneurs
Les concepteurs de Wolfi OS, porté par la société Chainguard ↗ se sont fixé comme objectif de produire des images de conteneurs qui répondent aux exigences de sécurité. Par exemple les images officielles ne doivent contenir aucune vulnérabilité critique connue.
Les spécifications de Wolfi OS
Parmi les spécifications, on retrouve que les outils de construction d’image doivent fournir une SBOM de qualité. Cette distribution fourni le support des librairies Glibc et de Musl. Le support de Musl se fait en s’appuyant sur la distribution Alpine. Du coup, le format des packages retenu est de celui de cette distribution : l’APK. Attention le gestionnaire de package n’est pas installé par défaut pour limiter la taille des images.
Il existe plusieurs façons d’installer des packages supplémentaires dans les images :
- via un Dockerfile.
- via des outils maison
melange
etapko
.
Construction des images avec un Dockerfile
Je vais utiliser un simple api flask et construire l’image comme je le ferai avec donc une alpine. base d’une alpine.
Le code contenant dans le fichier main.py
from flask import Flask
app = Flask(__name__)
@app.route("/")def hello_world(): return "Hello World!"
if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)
Le Dockerfile avec une image alpine :
FROM alpine:3.18 as builderENV PYROOT=/app/venvENV PYTHONUSERBASE=$PYROOT
WORKDIR /app
COPY requirements.txt .RUN apk add python3 py3-pip python3-dev bash && pip3 install --no-cache-dir --no-compile pipenv && pipenv install -r requirements.txt && PIP_USER=1 pipenv sync --system
FROM alpine:3.18 as productionARG version=3.11ENV PYTHONPATH="/app/venv/lib/python3.11/site-packages/"
COPY --from=builder /app /appCOPY main.py /app
RUN adduser -D nonroot && apk add python3 bash --no-cache # && chown -R nonroot.nonroot /appUSER nonroot
WORKDIR /app
ENTRYPOINT [ "python3", "main.py" ]
On build et on vérifie la taille de l’image :
❯ docker build . -t test:0.1 -f Dockerfile[+] Building 23.8s (13/13)...
❯ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEtest 0.1 567ee7108d82 4 seconds ago 54.9MB
Je teste et cela fonctionne !
On va construire une image à base de Wolfi et vérifier la taille obtenue. J’ai dû adapter le Dockerfile car certains packages ne sont pas disponibles :
FROM cgr.dev/chainguard/wolfi-base as builderENV PYROOT=/app/venvENV PYTHONUSERBASE=$PYROOT
WORKDIR /app
COPY requirements.txt .RUN apk add python3 py3-pip python3-dev bash && pip3 install --no-cache-dir --no-compile pipenv && pipenv install -r requirements.txt && PIP_USER=1 pipenv sync --system
FROM cgr.dev/chainguard/wolfi-base as productionARG version=3.11ENV PYTHONPATH="/app/venv/lib/python3.11/site-packages/"
COPY --from=builder /app /appCOPY main.py /app
RUN adduser -D nonroot && apk add python3 bash --no-cache # && chown -R nonroot.nonroot /appUSER nonroot
WORKDIR /app
ENTRYPOINT [ "python3", "main.py" ]
On lance le build, on teste et on vérifie la taille :
docker build . -t test:0.2 -f Dockerfile[+] Building 34.8s (13/13) FINISHED ...
REPOSITORY TAG IMAGE ID CREATED SIZEtest 0.2 4b9f14548425 14 seconds ago 61.5MBtest 0.1 567ee7108d82 3 minutes ago 54.9MB
La taille de l’image est un peu plus importante. La raison est assez simple
wolfi
utilise glibc
contrairement à Alpine les librairies sont un peu plus
grosses.
On utilise apko et melange
La première chose est de construire le package, car on ne peut utiliser de commande dans le fichier apko.yml
package: name: hello-server version: 0.1.0 description: friendly little webserver target-architecture: - all copyright: - license: Apache-2.0 paths: - "*" dependencies: runtime: - python3
environment: contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub repositories: - https://packages.wolfi.dev/os packages: - python3 - py3-pip - bubblewrap - wolfi-base - busybox - ca-certificates-bundle
pipeline: - name: Build Python application runs: | EXECDIR="${{targets.destdir}}/usr/bin" WEBAPPDIR="${{targets.destdir}}/usr/share/webapps/hello-server" mkdir -p "${EXECDIR}" "${WEBAPPDIR}" echo "#!/usr/share/webapps/hello-server/venv/bin/python3" > "${EXECDIR}/hello-server" cat main.py >> "${EXECDIR}/hello-server" chmod +x "${EXECDIR}/hello-server" python3 -m venv "${WEBAPPDIR}/venv" sh -c "source '${WEBAPPDIR}/venv/bin/activate' && pip install -r requirements.txt"
Avant de lancer le build du package, il faut générer une clé pour signer le package :
melange keygen
On se retrouve avec deux fichiers. On peut lancer le build :
melange build melange.yaml --arch amd64 --signing-key melange.rsa
On voit apparaître un dossier packages dans lequel on retrouve notre package portant le nom défini dans le yaml de melange accompagné de l’index du dépot :
-rw-rw-r-- 1 vagrant vagrant 9384665 août 8 21:22 hello-server-0.1.0-r0.apk-rw-rw-r-- 1 vagrant vagrant 931 août 8 21:22 APKINDEX.tar.gz-rw-rw-r-- 1 vagrant vagrant 732 août 8 21:22 APKINDEX.jsondrwxr-xr-x 2 vagrant vagrant 4096 août 8 21:22 .
Maintenant passons à la construction de l’image avec apko. Le fichier de définition :
contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub repositories: - https://packages.wolfi.dev/os - ./packages packages: - hello-serveraccounts: groups: - groupname: nonroot gid: 65532 users: - username: nonroot uid: 65532 run-as: 65532entrypoint: command: /usr/bin/hello-serverarchs: - amd64
On lance le build :
apko build apko.yaml test:0.3 output.tar -k melange.rsa.pub --arch amd64
On obtient un fichier .tar
et de fichiers SBOM. Chargeons les images :
docker load < output.tar
Vérifions la taille de l’image :
❯ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEtest 0.2 4b9f14548425 37 minutes ago 61.5MBtest 0.1 567ee7108d82 41 minutes ago 54.9MBtest 0.3-amd64 6fd4ca756c83 5 days ago 81.2MB
Mince la taille est plus importante que les deux autres ?
Changeons la source en prenant une alpine :
contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub repositories: - https://dl-cdn.alpinelinux.org/alpine/edge/main - ./packages packages: - alpine-base - hello-serveraccounts: groups: - groupname: nonroot gid: 65532 users: - username: nonroot uid: 65532 run-as: 65532entrypoint: command: /usr/bin/hello-serverarchs: - amd64
Si je lance le build sans modifier le package en le passant aussi à Alpine, j’obtiens cette erreur :
Error: failed to load image configuration: unable to fetch remote include from git: failed to parse git reference apko.yaml: not enough path data available2023/08/08 21:39:20 error during command execution: failed to load image configuration: unable to fetch remote include from git: failed to parse git reference apko.yaml: not enough path data available
Il faut donc générer un package basé sur Apline, modifions en conséquence le fichier yaml :
package: name: hello-server version: 0.1.0 description: friendly little webserver target-architecture: - all copyright: - license: Apache-2.0 paths: - "*" dependencies: runtime: - python3
environment: contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub repositories: - https://dl-cdn.alpinelinux.org/alpine/edge/main - https://dl-cdn.alpinelinux.org/alpine/edge/community
packages: - python3 - py3-pip - bubblewrap - alpine-base - busybox - ca-certificates-bundle
pipeline: - name: Build Python application runs: | EXECDIR="${{targets.destdir}}/usr/bin" WEBAPPDIR="${{targets.destdir}}/usr/share/webapps/hello-server" mkdir -p "${EXECDIR}" "${WEBAPPDIR}" echo "#!/usr/share/webapps/hello-server/venv/bin/python3" > "${EXECDIR}/hello-server" cat main.py >> "${EXECDIR}/hello-server" chmod +x "${EXECDIR}/hello-server" python3 -m venv "${WEBAPPDIR}/venv" sh -c "source '${WEBAPPDIR}/venv/bin/activate' && pip install -r requirements.txt"
On relance le build du package, puis de l’image :
melange build melange.yaml --arch amd64 --signing-key melange.rsa...apko build apko.yaml test:0.4 output.tar -k melange.rsa.pub --arch amd64
docker load < output.tardocker images
REPOSITORY TAG IMAGE ID CREATED SIZEtest 0.2 4b9f14548425 About an hour ago 61.5MBtest 0.1 567ee7108d82 About an hour ago 54.9MBalpine 3.18 7e01a0d0a1dc 25 hours ago 7.34MBtest 0.4-amd64 c83ab923d745 4 days ago 65MBtest 0.3-amd64 6fd4ca756c83 5 days ago 81.2MB
La taille est redevenu plus petite mais pas au point de faire la meme taille que celle produite par buildkit.
Par contre avec docker image inspect
on voit que l’image produite avec apko ne
contient qu’un seul layer.
"RootFS": { "Type": "layers", "Layers": [ "sha256:2db1f875cf628b93abf8c0f669820da1ab3e78e0f5deb714d98255635bdcef3c" ] },
Alors que celle produite avec Docker en contient cinq :
"RootFS": { "Type": "layers", "Layers": [ "sha256:399ddd85d8cfda6e6cf657dcd4d29951050a16e3bcebc9408ec10a3828257da7", "sha256:a79c6d92fff863bcb0ea26079582276ed527ef9bc2e61accdfc9839dbab5312d", "sha256:27b0f7412e17ff20fbce4ac5a6a7efc1f160c348b786264c73e1a9615f2bbe77", "sha256:47ab6f534a71889684d37f9e9a3128f51552cb6aebdd12e965cd496e8701c701", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] },
Et les vulnérabilités ?
Je vais utiliser trivy pour scanner les images :
❯ trivy image test:0.12023-08-08T22:05:16.679+0200 INFO Need to update DB2023-08-08T22:05:16.679+0200 INFO DB Repository: ghcr.io/aquasecurity/trivy-db2023-08-08T22:05:16.679+0200 INFO Downloading DB...38.80 MiB / 38.80 MiB [----------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 11.88 MiB p/s 3.5s2023-08-08T22:05:21.031+0200 INFO Vulnerability scanning is enabled2023-08-08T22:05:21.031+0200 INFO Secret scanning is enabled2023-08-08T22:05:21.031+0200 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning2023-08-08T22:05:21.031+0200 INFO Please see also https://aquasecurity.github.io/trivy/v0.43/docs/scanner/secret/#recommendation for faster secret detection2023-08-08T22:05:22.216+0200 INFO Detected OS: alpine2023-08-08T22:05:22.216+0200 INFO Detecting Alpine vulnerabilities...2023-08-08T22:05:22.219+0200 INFO Number of language-specific files: 12023-08-08T22:05:22.219+0200 INFO Detecting python-pkg vulnerabilities...
test:0.1 (alpine 3.18.3)
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
2023-08-08T22:05:22.231+0200 INFO Table result includes only package filenames. Use '--format json' option to get the full path to the package file.
Python (python-pkg)
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0)
┌──────────────────┬────────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────┐│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │├──────────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────┤│ Flask (METADATA) │ CVE-2023-30861 │ HIGH │ 2.1.2 │ 2.2.5, 2.3.2 │ Cookie header ││ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-30861 │└──────────────────┴────────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────┘
Sur celle construite avec Wolfi :
❯ trivy image test:0.3-amd642023-08-08T22:06:30.871+0200 INFO Vulnerability scanning is enabled2023-08-08T22:06:30.872+0200 INFO Secret scanning is enabled2023-08-08T22:06:30.872+0200 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning2023-08-08T22:06:30.872+0200 INFO Please see also https://aquasecurity.github.io/trivy/v0.43/docs/scanner/secret/#recommendation for faster secret detection2023-08-08T22:06:33.891+0200 INFO Detected OS: wolfi2023-08-08T22:06:33.891+0200 INFO Detecting Wolfi vulnerabilities...2023-08-08T22:06:33.892+0200 INFO Number of language-specific files: 0
test:0.3-amd64 (wolfi 20230201)
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
Conclusion
Pour ce qui est de l’objectif d’obtenir une image de petite taille, je n’y arrive pas vraiment. Je manque certainement de connaissances pour ce qu’il faille mettre comme paramètre dans chacun des fichiers de configuration.
Pour ce qui est des vulnérabilités mon test n’est pas suffisant pour tirer des conclusions. Je vais tester d’autres images fourni par Chainguard. Vous les trouverez ici ↗.
Après en avoir testé quelques-unes aucune vulnérabilité remontée par trivy.
Par contre, je trouve la documentation pas assez claire. Heureusement, il suffit de se promener dans les repos pour trouver des exemples.
Je vais continuer de tester apko et melange pour construire des images. Je vais aussi regarder la partie signature des images et comment intégré la SBOM dans ma chaine de compliance.