Ce qui se passe
Mike Cardwell monte un serveur sous Flatcar Container Linux et décide que chaque conteneur qu'il y pose sera durci dès le départ. Premier candidat : PowerDNS Recursor.
Le constat de départ vaut pour la plupart des images officielles. Celle de pdns-recursor est Debian avec un binaire posé dessus : environ 355 Mo et 7 500 fichiers, dont un shell et tout l'outillage système — c'est-à-dire tout ce dont un attaquant a besoin une fois entré.
Il reconstruit donc l'image : compilation statique avec Musl, vérification
PGP de l'archive source contre les clés de publication documentées, puis copie
des exécutables dans une image scratch. Résultat : 36 Mo, deux fichiers.
Le support SNMP et l'option url de zoneToCache sont volontairement exclus,
parce que leurs dépendances traînent un historique de CVE.
Le durcissement à l'exécution
Le point d'entrée n'est pas le démon mais un petit programme C, le launcher, qui applique Landlock avant de passer la main : chemins lisibles réduits à la configuration, chemins inscriptibles réduits au strict nécessaire, et restriction des ports TCP en connexion comme en écoute — les ports UDP aussi si le noyau est assez récent. Un attaquant ne peut alors ni déposer un binaire pour l'exécuter, ni ouvrir une connexion vers un serveur de commande.
Vient ensuite un seccomp.json en liste blanche, généré à la trace avec
strace et validé par des tests de fumée sur plusieurs architectures et noyaux.
Il ferme io_uring, bpf, userfaultfd, keyctl — et surtout memfd_create,
la route sans fichier qu'on emprunte justement quand il n'y a ni shell ni droit
d'exécution. Le fichier ne vit pas dans l'image : il faut le récupérer et le
référencer.
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=8m
security_opt:
- seccomp:/path/to/seccomp.json
- no-new-privileges:true
cap_drop: [ALL]
ipc: none
sysctls:
net.ipv4.ip_unprivileged_port_start: 53Le tmpfs de huit mégaoctets est le seul aveu : rec_control a besoin d'une
socket dans /tmp. Il est monté noexec.
Le retournement : zéro CVE n'est pas une bonne note
C'est la partie la plus instructive. Trivy, Grype et OSV Scanner
inspectent les index de paquets de la distribution, et un SBOM s'il en existe un.
Une image scratch n'a ni l'un ni l'autre : les scanners ne trouvent rien, ce
qui ressemble à un sans-faute et n'est qu'un angle mort.
La parade tient en trois pièces :
- Un SBOM CycloneDX produit à la construction depuis l'éditeur de liens, écrit dans l'image ; converti en SPDX et attaché au registre avec une provenance SLSA (révision Git, Dockerfile, arguments de build).
- Un fichier OpenVEX versionné dans le dépôt, qui déclare les alertes non applicables et pourquoi. Il a servi dès la première analyse : le SBOM a fait remonter des CVE critiques sur Handlebars.js, sans rapport avec l'usage réel.
- Une signature cosign — identité OIDC de courte durée fournie par GitLab, échangée contre un certificat Fulcio, publiée dans Rekor.
Le même défaut vaut à l'envers pour l'image officielle : PowerDNS n'y figure pas dans la liste des paquets Debian, donc une CVE PowerDNS n'y est pas détectée.
Ce que ça change
L'autre différence est la fraîcheur. Une image officielle fige les dépendances du jour de sa publication. Ici, les deux trains stables sont reconstruits chaque jour ; si un OpenSSL corrigé arrive dans Alpine, l'image suivante l'embarque. Un même tas de version peut donc désigner des constructions différentes — d'où les tags à empreinte pour qui veut figer.
Source : grepular.com