Conteneurisation sécurisée et supply chain, de nouvelles approches de construction.
- Laura Bernard
- il y a 3 jours
- 6 min de lecture
La sécurisation de la supply chain est une demande croissante des équipes manipulant des briques logicielles. L'informatique s'est construite sur cet imbriquement de composants mettant en relation la production d'équipes indépendantes. La diversité de ces briques (binaires, scripts, systèmes de fichiers,..) et de leur provenance (répertoires git, OCI, paquets,...) multiplie les dépendances explicites et implicites (bibliothèques compilées, répertoires par défaut). Nous allons nous concentrer sur une brique, la brique OS conteneurisée qui englobe une diversité de sous-briques de support pour fonctionner.

L’utilisation de conteneur vise à fournir une base stable et isolée à un ensemble de services logiciels. Cette base peut être la plus minimale possible, quitte à s’affranchir d’un système d’opération (voir l’initiative distroless de Google). Si cette approche est intéressante pour améliorer la taille de l’image, elle permet aussi de diminuer le nombre total de programmes dans le conteneur.
Chaque programme composant un OS vient ajouter un ensemble de dépendances aussi bien externes (libraries systèmes, bases de données, interpréteurs de langage …) qu'internes (librairies compilées, scripts d’installation, blob constructeur, …). Ces dépendances peuvent elles même être explicites pour un gestionnaire de paquets qui les installera automatiquement ou implicites, le paquet s’attendant à trouver un ensemble cohérent de services et fichiers disponibles dans l’OS pour bien fonctionner.
Chaque programme vient ajouter plusieurs types de risques opérationnels durant l’exploitation d’un conteneur, à savoir :
La traçabilité : qui a produit le binaire? Avec quelles bibliothèques et dépendances en quelle version ? ⇒ Ces informations sont explicitées dans un SBOM (Software Bill Of Materials)
L’intégrité : quelles altérations le programme a-t-il suivies durant les différentes étapes de sa compilation à sa distribution ?
La vulnérabilité : quelles sont les failles de chaque programme? A quels risques opérationnels ces failles nous exposent-t-elles? Existe-t-il des remédiations à ces risques?
Ainsi la maîtrise des programmes embarqués dans une image conteneur vient limiter les facteurs d’exposition au risque. Ces facteurs peuvent être d’autant plus réduits par une diminution du nombre total de programmes pour tenter de refermer cette fenêtre.
Plusieurs acteurs viennent proposer différentes méthodes de build d’images venant répondre à ces problématiques.
Le problème du multistage
La syntaxe docker permet dans un Dockerfile de venir cibler des fichiers/binaires pour les transporter d’un stage de “build” à un stage de “run”. Cette approche permet par exemple au premier stage de contenir toutes les dépendances et librairie de build avant de COPY –from builder-stage /opt/built-binary dans le stage final de production.
Le problème est dans le ciblage comme dit précédemment si l’on veut récupérer et réempaqueter un service distribué par un gestionnaire de paquet (apt, rpm, …) il faut s’assurer de bien copier toutes les dépendances externes et implicites.
La syntaxe docker de COPY étant basée sur un dossier ou fichier précis nous devons partir à la chasse aux dépendances comme:
Les binaires (souvent dans /usr/bin)
Les librairies dont les binaires dépendent (trouvable avec ldd)
Les fichiers tiers de configuration (partiellement trouvable avec dpkg -L)
FROM debian:bookworm-slim AS builder
RUN apt-get update \
&& apt-get install -y --no-install-recommends haproxy
COPY --from=builder /usr/sbin/haproxy /usr/sbin/haproxy
# /etc/haproxy/*.cfg ??
# /etc/default/haproxy ??
# /usr/lib/ ??
# /... ?
Nous allons explorer trois réponses au problème que nous avons exposé:
DHI (Docker Hardened Image) de Docker
Cette nouvelle offre de Docker vient proposer un ensemble d’outils pour travailler en build multistage avec une syntaxe nouvelle en fichier yaml. Cette syntaxe déclarative est très orientée pour une utilisation dans le contexte de la nouvelle proposition de Docker pour des images et chart sécurisés avec un catalogue public comme Bitnami.
La syntaxe utilise implicitement une image minimale et durcie (hardenée) Debian ou Alpine les paquets utilisables venant forcément de ces deux écosystèmes.
Le build se fait forcément en utilisant docker buildx build, podman/buildah ne supportant pas cette syntaxe non standardisée.
Voici un exemple d’utilisation pour une image HAProxy Debian minimale:
# syntax=dhi.io/build:2-debian13
name: HAProxy
image: my-registry/haproxy-dhi-deb
variant: runtime
tags:
- "3.0.11"
- "3.0"
platforms:
- linux/amd64
- linux/arm64
contents:
packages:
- ca-certificates
- libc-bin
- libcrypt1
- libssl3t64
- haproxy
accounts:
run-as: nonroot
users:
- name: nonroot
uid: 65532
gid: 65532
groups:
- name: nonroot
gid: 65532
members:
- nonroot
entrypoint:
- haproxy
cmd:
- -f
- /usr/local/etc/haproxy/haproxy.cfg
ports:
- 8080/tcp
Nous pouvons facilement déclarer nos paquets qui seront automatiquement copiés sur l’image finale. Pour un exemple plus complet on peut observer l’image officielle HAProxy DHI qui déclare plusieurs build intermédiaires avec chacun ses propres dépendances de provenances différentes (apt, git, artefact, …) vérifiées par checksum. Si cette syntaxe de build rend accessible la capacité de maîtriser la construction d’image elle reste assez opaque. En effet l’authentification à docker hub est par défaut activée et une partie de l’infrastructure supportant le build du catalogue est aussi privée.
Enfin DHI avec buildx/scout supporte nativement la génération de SBOM et divers outils et artefacts attestant de la provenance de l’image.
Wolfi/Apko de Chaingard
Chainguard propose aussi et depuis plus longtemps une méthode similaire de build d’image entièrement basée sur Wolfi un fork d’Alpine durcie. La construction d'images se fait à partir d’une syntaxe déclarative yaml dédiée appelée Apko.
# haproxy.apko.yaml
contents:
repositories:
packages:
- alpine-baselayout
- ca-certificates-bundle
- haproxy
accounts:
groups:
- groupname: haproxy
gid: 65532
users:
- username: haproxy
uid: 65532
gid: 65532
run-as: haproxy
paths:
- path: /var/lib/haproxy
type: directory
uid: 65532
gid: 65532
permissions: 0o755
- path: /run
type: directory
uid: 65532
gid: 65532
permissions: 0o755
entrypoint:
command: /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg
archs:
- x86_64
- aarch64
On retrouve ainsi des concepts déclaratifs avec une syntaxe un peu plus allégée. En effet là où DHI vient empaqueter et compiler dans le même fichier, Chainguard renvoie vers son outil de compilation de sources melange permettant de créer les paquets Alpine utilisables ensuite dans Wolfi. Les deux outils supportent aussi nativement la vérification de checksum pour garantir l’intégrité des dépendances. Apko génère aussi par défaut les SBOM et gère les attestations de provenance. La principale différence avec DHI se fait sur un catalogue privé non accessible d’images durcies et charts.
BCI (Base Container Image) de SUSE/OpenSUSE
Basé sur SLES Micro une version durcie minimale de SUSE, BCI est le seul acteur proposant une approche simple par Dockerfile. En effet, nous pouvons tirer parti de la capacité de zypper à se chroot dans le système de fichier de l’image BCI micro.
Cela permet d’avoir un environnement complet de paquetage/build avec une image minimale en sortie.
# syntax=docker/dockerfile:1
FROM registry.suse.com/bci/bci-micro:latest AS micro
FROM registry.suse.com/bci/bci-base:latest AS builder
COPY --from=micro / /chroot/
RUN zypper ar -f http://download.opensuse.org/distribution/leap/15.6/repo/oss/ repo-oss && \
zypper ar -f http://download.opensuse.org/update/leap/15.6/oss/ repo-update
RUN zypper --installroot /chroot -n --gpg-auto-import-keys in --no-recommends \
haproxy && \
zypper --installroot /chroot clean -a && \
rm -rf /chroot/var/log/ && \
chown 1000:1000 /chroot/var/lib/haproxy /chroot/etc/haproxy
FROM micro
WORKDIR /
COPY --from=builder /chroot/ /
EXPOSE 8080
USER 1000:1000
ENTRYPOINT ["/usr/sbin/haproxy", "-f", "/etc/haproxy/haproxy.cfg"]
La génération de SBOM n’est pas intégrée mais peut facilement se réaliser avec le CLI Syft qui est l’outil intégré à buildx pour la génération de SBOM.
Bonus : méthode quick and dirty
En lisant cet article vous auriez pu vous dire : "mais il existe déjà une image HAProxy Debian qui ne fait que 37MB !"
Et vous auriez raison mais celle-ci tombe dans les problématiques que nous avons décrites auparavant.
...
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \
find /usr/local -type f -executable -exec ldd '{}' ';' \
| awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \
| sort -u \
| xargs -r dpkg-query --search \
| cut -d: -f1 \
| sort -u \
| xargs -r apt-mark manual \
; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
apt-get dist-clean; \
...
En regardant le Dockerfile source, on retrouve les appels ici automatisés à ldd et dpkg-query pour tenter de rassembler toutes les dépendances du paquet. On peut tout de même reconnaître comme intéressante l’utilisation de apt-mark sur tous les paquets système sauf ceux nécessaires au binaire qui seront les seuls à rester après le apt-get purge.
Cela ne garantit pas l'exhaustivité des copies, des dépendances secondaires peuvent être oubliées. De plus, l’image source debian:trixie-slim n’est pas spécialement durcie et contient encore des paquets comme apt, ce qui augmente la surface d’exposition que l’on souhaite limiter.
Conclusion et suite ?
Cette première approche des différentes méthodes de construction d’images de conteneur sécurisées nous amène à une réflexion globale sur la façon dont les logiciels sont compilés, distribués, utilisés. Si nous avons exploré des solutions plus ou moins ouvertes, je vous propose de venir continuer cette réflexion dans une partie 2 portant sur l’écosystème général des images et charts sécurisés et des différents acteurs qui viennent combler le vide laissé par Bitnami.
Sources :
Dans le contenu
Remerciements à Vincent, Mikael, Anthony et Quentin pour vos conseils.
Auteur : Théophile Orliac



Commentaires