Ansible CI Toolkit v1: l'année où j'ai cessé de faire confiance à ma propre image
L’image Ansible CI a dix ans, tourne en prod depuis au moins huit ans et elle est publique depuis 2022 (l’épisode un ); en juillet 2026 elle gagne enfin sa version 1.0.0. Rien ne change dans le concept; tout change dans la chaîne de confiance. Cet épisode raconte la transformation d’une image qui fonctionnait en une image auditable, à travers son CHANGELOG, qui se lit comme un journal de CVE.
Fusionner le schisme
L’erreur de l’ère 2022 que je ne pouvais jamais me pardonner: deux images, ansible et ansible-lint, parce que “ce qui appartient à l’image” n’avait pas de réponse. La v1.0.0 les fusionne en un seul Ansible CI Toolkit: ansible-core 2.21.3, ansible-lint 26.8.0, 17 collections Galaxy pinées (Linux, Windows, VMware, AWS/GCP, Kubernetes, Foreman, OVH), reporting ARA et toute la pile Windows/AD: bibliothèques Kerberos, pywinrm avec auth basic/ntlm/credssp/kerberos, un template générique de /etc/krb5.conf que vous pouvez monter, surcharger avec KRB5_CONFIG ou cuire dans un rebuild. Une image, 302 Mo et le job CI est une ligne image:.
Elle a aussi gagné les gardes-fous runtime: utilisateur non-root ansible (UID 1000) avec un workspace /src possédé et les variables d’env des runners éphémères cuites dedans (HOST_KEY_CHECKING=False, SSH_PIPELINING=True, GATHERING=smart, retry files off).
L’échelle de reproductibilité
L’image de 2022 pinait une couche sur trois. Celle de 2026 grimpe l’échelle entière, dans l’ordre:
- Image de base pinée par digest:
alpine:3.24.1@sha256:28bd5..., pas un tag. Un tag est une promesse; un digest est un fait. - Chaque dépendance Python
==-pinée dansrequirements.txt. constraints-lock.txt: unpip freezecomplet d’un build validé, appliqué viaPIP_CONSTRAINTdans le builder. Maintenant les dépendances transitives se résolvent identiquement à chaque rebuild: le lock est un fait sur un build qui a eu lieu, pas un espoir.- Collections Galaxy transitives pinées (
amazon.aws 11.4.0,vmware.vmware 2.10.0, …), avecANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH=errorau build pour qu’une collection qui dépasse son ansible-core échoue bruyamment au lieu de se dégrader en silence. - Signature cosign
, keyless: le certificat Sigstore est émis sur le token OIDC de la pipeline GitLab. Une signature par digest couvre tous les tags pointant dessus,
:latestinclus et la pipeline se vérifie elle-même en ligne (bloquant).
Les seules exceptions délibérées sont des planchers (msgpack>=1.2.1, setuptools>=78.1.1), là où un fix sécurité doit être garanti dans le venv; le lock les plafonne à la version validée. Des pins partout ailleurs, des planchers là où la sécurité l’exige et la différence est documentée au lieu d’être devinée.
Le journal des CVE
Le CHANGELOG est la vraie démo du produit. Deux entrées à encadrer:
- Le pin qui n’était pas une cage: la montée de la base vers
alpine:3.24.1n’a pas apporté le fix OpenSSL 3.5.8 de CVE-2026-14456 (DoS QUIC, HIGH), parce que le snapshot du digest embarquait 3.5.7-r0. Le remède:apk upgrade --no-cachedans les deux stages. Leçon: une base pinée achète la reproductibilité, pas la sécurité; l’upgrade au build est le pont et le trade-off (un peu de dérive entre builds) est documenté plutôt que caché. - Les findings qui n’étaient pas les miens: Trivy
a signalé msgpack et setuptools en HIGH. Les deux paquets système Alpine sont corrigés depuis la 3.24; les findings venaient des copies vendorées de pip lui-même (
pip/_vendorpins msgpack 1.1.2, setuptools 70.3.0), non atteignables au runtime et aucun fix upstream n’existe puisque pip vendorise les siennes. Ils sont entrés dans.trivyignoreavec une justification écrite par entrée et la justification a été réécrite quand la base a changé. Des findings ignorés avec des raisons; les ignores silencieux sont la façon dont les chaînes d’approvisionnement pourrissent.
Ajoutez le reste de la clôture: hadolint sur chaque pipeline, Trivy HIGH/CRITICAL bloquant et un job de test qui tourne dans l’image construite et vérifie les versions, la liste des collections, les binaires, la config krb5 et chaque import Python dont les collections ont besoin (pywinrm, gssapi, ara, boto3, kubernetes, docker). Si l’image ne passe pas son propre examen, elle ne part pas.
Ne jamais croire sa propre image
- “Ça marche” et “c’est auditable” sont des jalons différents. L’astuce venv a survécu intacte depuis la toute première image; la chaîne de confiance non.
- Un pin par digest est de la reproductibilité, pas de la sécurité. La CVE OpenSSL a prouvé le besoin des deux et du besoin de documenter la dérive qu’on accepte.
- Chaque ignore a besoin d’une raison par écrit. Un
.trivyignoresans justification est une liste de tâches déguisée en politique. - Testez dans l’artefact. Le meilleur job de test pour une image, c’est l’image elle-même, entrypoint surchargé, qui fait son propre inventaire.
L’image: gitlab.com/6admin.io/docker/alpine/ansible
, registry.gitlab.com/6admin.io/docker/alpine/ansible/ansible, tags SemVer, digests signés cosign. Et son inventaire se lit depuis le DNS: l’article dns-inventory
est l’autre moitié de la pipeline.