Aller au contenu principal

Wolfi OS Une distribution pour les conteneurs

· 9 minutes de lecture
Stéphane ROBERT

logo wolfi

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 et apko.

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 builder
ENV PYROOT=/app/venv
ENV 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 production
ARG version=3.11
ENV PYTHONPATH="/app/venv/lib/python3.11/site-packages/"

COPY --from=builder /app /app
COPY main.py /app

RUN adduser -D nonroot && apk add python3 bash --no-cache # && chown -R nonroot.nonroot /app
USER 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 images
REPOSITORY TAG IMAGE ID CREATED SIZE
test 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 builder
ENV PYROOT=/app/venv
ENV 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 production
ARG version=3.11
ENV PYTHONPATH="/app/venv/lib/python3.11/site-packages/"

COPY --from=builder /app /app
COPY main.py /app

RUN adduser -D nonroot && apk add python3 bash --no-cache # && chown -R nonroot.nonroot /app
USER 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 SIZE
test 0.2 4b9f14548425 14 seconds ago 61.5MB
test 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.json
drwxr-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-server
accounts:
groups:
- groupname: nonroot
gid: 65532
users:
- username: nonroot
uid: 65532
run-as: 65532
entrypoint:
command: /usr/bin/hello-server
archs:
- 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 images
REPOSITORY TAG IMAGE ID CREATED SIZE
test 0.2 4b9f14548425 37 minutes ago 61.5MB
test 0.1 567ee7108d82 41 minutes ago 54.9MB
test 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-server
accounts:
groups:
- groupname: nonroot
gid: 65532
users:
- username: nonroot
uid: 65532
run-as: 65532
entrypoint:
command: /usr/bin/hello-server
archs:
- 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 available
2023/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.tar
docker images

REPOSITORY TAG IMAGE ID CREATED SIZE
test 0.2 4b9f14548425 About an hour ago 61.5MB
test 0.1 567ee7108d82 About an hour ago 54.9MB
alpine 3.18 7e01a0d0a1dc 25 hours ago 7.34MB
test 0.4-amd64 c83ab923d745 4 days ago 65MB
test 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.1
2023-08-08T22:05:16.679+0200 INFO Need to update DB
2023-08-08T22:05:16.679+0200 INFO DB Repository: ghcr.io/aquasecurity/trivy-db
2023-08-08T22:05:16.679+0200 INFO Downloading DB...
38.80 MiB / 38.80 MiB [----------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 11.88 MiB p/s 3.5s
2023-08-08T22:05:21.031+0200 INFO Vulnerability scanning is enabled
2023-08-08T22:05:21.031+0200 INFO Secret scanning is enabled
2023-08-08T22:05:21.031+0200 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning
2023-08-08T22:05:21.031+0200 INFO Please see also https://aquasecurity.github.io/trivy/v0.43/docs/scanner/secret/#recommendation for faster secret detection
2023-08-08T22:05:22.216+0200 INFO Detected OS: alpine
2023-08-08T22:05:22.216+0200 INFO Detecting Alpine vulnerabilities...
2023-08-08T22:05:22.219+0200 INFO Number of language-specific files: 1
2023-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-amd64
2023-08-08T22:06:30.871+0200 INFO Vulnerability scanning is enabled
2023-08-08T22:06:30.872+0200 INFO Secret scanning is enabled
2023-08-08T22:06:30.872+0200 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning
2023-08-08T22:06:30.872+0200 INFO Please see also https://aquasecurity.github.io/trivy/v0.43/docs/scanner/secret/#recommendation for faster secret detection
2023-08-08T22:06:33.891+0200 INFO Detected OS: wolfi
2023-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.