Un pipeline qui construit une image Docker sans la signer ni documenter ses composants ne fournit aucune garantie d’intégrité. Un attaquant qui compromet le registre, le runner ou un include peut remplacer l’artefact silencieusement. Ce guide vous montre comment ajouter trois couches de protection — SBOM, signature, attestation — dans vos pipelines GitLab CI, avec des outils open source que vous pouvez tester immédiatement sur votre instance KVM.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »À la fin de ce guide, vous saurez :
- Générer un SBOM (Software Bill of Materials) au format SPDX ou CycloneDX avec Syft et Trivy
- Signer une image OCI avec cosign (Sigstore) et stocker la signature dans le registre
- Produire une attestation SLSA de provenance rattachée à l’image
- Vérifier la signature et l’attestation dans un pipeline de déploiement
- Exploiter le SBOM pour détecter les vulnérabilités connues
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »La sécurité supply chain CI/CD n’est pas un exercice théorique. Vous en avez besoin quand :
- Vous déployez des images Docker en production et devez prouver leur intégrité à un auditeur ou un client
- Votre organisation doit se conformer à SLSA Level 2+, au SSDF du NIST ou à la directive NIS2
- Vous utilisez des images de base tierces et devez savoir exactement quels composants elles embarquent
- Un incident supply chain (dépendance compromise, registre altéré) vous oblige à tracer ce qui a été déployé
- Vous gérez un registre interne et voulez bloquer les images non signées
Les trois couches de la supply chain CI
Section intitulée « Les trois couches de la supply chain CI »La sécurité supply chain repose sur trois mécanismes complémentaires :
| Couche | Question | Outil | Format |
|---|---|---|---|
| SBOM | Quels composants contient mon image ? | Syft, Trivy | SPDX, CycloneDX |
| Signature | L’image a-t-elle été modifiée ? | cosign (Sigstore) | Signature OCI |
| Attestation | Qui l’a buildée, quand, comment ? | cosign attest | In-toto / SLSA |
Chaque couche répond à une question différente. Un SBOM sans signature ne prouve rien. Une signature sans attestation ne dit pas d’où vient l’artefact. Les trois ensemble forment une chaîne de confiance vérifiable.
Générer un SBOM dans le pipeline
Section intitulée « Générer un SBOM dans le pipeline »Avec Syft
Section intitulée « Avec Syft »Syft (Anchore) analyse une image et produit un SBOM détaillé :
sbom-syft: stage: test image: name: anchore/syft:latest entrypoint: [""] script: - syft $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -o spdx-json=sbom-spdx.json - syft $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -o cyclonedx-json=sbom-cdx.json artifacts: paths: - sbom-spdx.json - sbom-cdx.json expire_in: 90 daysLe SBOM liste chaque paquet, bibliothèque et dépendance avec sa version exacte.
Avec Trivy
Section intitulée « Avec Trivy »Trivy peut produire un SBOM et scanner les vulnérabilités en un seul passage :
sbom-trivy: stage: test image: name: aquasec/trivy:latest entrypoint: [""] script: - trivy image --format spdx-json --output sbom-trivy.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - trivy sbom sbom-trivy.json --severity HIGH,CRITICAL --exit-code 1 artifacts: paths: - sbom-trivy.json expire_in: 90 daysLa deuxième commande utilise le SBOM comme entrée pour détecter les CVE. Si une vulnérabilité HIGH ou CRITICAL est trouvée, le job échoue.
Vérifier le contenu du SBOM
Section intitulée « Vérifier le contenu du SBOM »# Compter les composantsjq '.packages | length' sbom-spdx.json
# Lister les composants avec leur versionjq -r '.packages[] | "\(.name) \(.versionInfo)"' sbom-spdx.json | head -20
# Chercher un paquet spécifiquejq '.packages[] | select(.name | contains("openssl"))' sbom-spdx.jsonRésultat attendu :
142alpine-baselayout 3.4.3-r2alpine-keys 2.4-r1apk-tools 2.14.0-r5busybox 1.36.1-r15...Signer une image avec cosign
Section intitulée « Signer une image avec cosign »Installer cosign
Section intitulée « Installer cosign »cosign fait partie du projet Sigstore. Il signe des artefacts OCI et stocke la signature directement dans le registre, à côté de l’image :
sign-image: stage: deploy image: alpine:3.20 before_script: - apk add --no-cache cosign - echo "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key script: - cosign sign --key /tmp/cosign.key --yes $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA after_script: - rm -f /tmp/cosign.key variables: COSIGN_PASSWORD: $COSIGN_KEY_PASSWORDGénérer la paire de clés
Section intitulée « Générer la paire de clés »Sur votre poste (une seule fois) :
cosign generate-key-pairRésultat :
Enter password for private key:Private key written to cosign.keyPublic key written to cosign.pubStockez cosign.key dans une variable CI protégée et masquée. Versionnez cosign.pub dans le dépôt pour la vérification.
Mode keyless avec Sigstore
Section intitulée « Mode keyless avec Sigstore »Le mode keyless élimine la gestion des clés. cosign obtient un certificat éphémère via Fulcio, authentifié par le token OIDC de GitLab :
sign-keyless: stage: deploy image: alpine:3.20 id_tokens: SIGSTORE_ID_TOKEN: aud: sigstore before_script: - apk add --no-cache cosign script: - cosign sign --yes --fulcio-url=https://fulcio.sigstore.dev --rekor-url=https://rekor.sigstore.dev --oidc-issuer=$CI_SERVER_URL --identity-token=$SIGSTORE_ID_TOKEN $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHAVérifier une signature
Section intitulée « Vérifier une signature »# Avec clécosign verify --key cosign.pub $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
# Keyless — vérifier l'identité du pipelinecosign verify \ --certificate-identity-regexp="https://gitlab.example.com/my-group/my-project//.*" \ --certificate-oidc-issuer=https://gitlab.example.com \ $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHARésultat attendu :
Verification for registry.example.com/my-group/my-project:abc1234 --The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public keySi la vérification échoue, la sortie affiche Error: no matching signatures.
Produire une attestation SLSA
Section intitulée « Produire une attestation SLSA »Une attestation de provenance lie l’artefact à son contexte de build : qui, quand, comment, avec quoi.
Attacher le SBOM comme attestation
Section intitulée « Attacher le SBOM comme attestation »attest-sbom: stage: deploy image: alpine:3.20 needs: - sbom-syft - sign-image before_script: - apk add --no-cache cosign - echo "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key script: - cosign attest --key /tmp/cosign.key --yes --predicate sbom-spdx.json --type spdxjson $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA after_script: - rm -f /tmp/cosign.key variables: COSIGN_PASSWORD: $COSIGN_KEY_PASSWORDAttestation de provenance SLSA
Section intitulée « Attestation de provenance SLSA »Pour une attestation conforme SLSA, utilisez le prédicat slsaprovenance :
attest-provenance: stage: deploy image: alpine:3.20 before_script: - apk add --no-cache cosign jq - echo "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key script: - | cat > provenance.json <<EOF { "buildType": "https://gitlab.com/gitlab-ci", "builder": { "id": "$CI_RUNNER_ID" }, "invocation": { "configSource": { "uri": "$CI_PROJECT_URL", "digest": {"sha1": "$CI_COMMIT_SHA"}, "entryPoint": ".gitlab-ci.yml" } }, "metadata": { "buildStartedOn": "$CI_JOB_STARTED_AT", "completeness": { "parameters": true, "environment": true, "materials": false } }, "materials": [ { "uri": "$CI_REGISTRY_IMAGE", "digest": {"sha256": "$(crane digest $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA | cut -d: -f2)"} } ] } EOF - cosign attest --key /tmp/cosign.key --yes --predicate provenance.json --type slsaprovenance $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA after_script: - rm -f /tmp/cosign.key variables: COSIGN_PASSWORD: $COSIGN_KEY_PASSWORDVérifier une attestation
Section intitulée « Vérifier une attestation »# Vérifier l'attestation SBOMcosign verify-attestation --key cosign.pub \ --type spdxjson \ $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA | jq '.payload' | base64 -d | jq .
# Vérifier l'attestation de provenancecosign verify-attestation --key cosign.pub \ --type slsaprovenance \ $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA | jq '.payload' | base64 -d | jq .Résultat attendu (provenance) :
{ "buildType": "https://gitlab.com/gitlab-ci", "builder": { "id": "42" }, "invocation": { "configSource": { "uri": "https://gitlab.example.com/my-group/my-project", "digest": {"sha1": "a1b2c3d4..."}, "entryPoint": ".gitlab-ci.yml" } }}Pipeline complet : build → SBOM → sign → attest → verify
Section intitulée « Pipeline complet : build → SBOM → sign → attest → verify »Voici un pipeline qui enchaîne toutes les étapes :
stages: - build - test - sign - verify
variables: IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build: stage: build image: name: gcr.io/kaniko-project/executor:v1.23.2-debug entrypoint: [""] script: - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile Dockerfile --destination $IMAGE --digest-file /tmp/digest artifacts: paths: - /tmp/digest
sbom: stage: test image: name: anchore/syft:latest entrypoint: [""] script: - syft $IMAGE -o spdx-json=sbom.json artifacts: paths: - sbom.json
vuln-scan: stage: test image: name: aquasec/trivy:latest entrypoint: [""] script: - trivy sbom sbom.json --severity HIGH,CRITICAL --exit-code 1 needs: - sbom
sign-and-attest: stage: sign image: alpine:3.20 needs: - build - sbom before_script: - apk add --no-cache cosign - echo "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key script: - cosign sign --key /tmp/cosign.key --yes $IMAGE - cosign attest --key /tmp/cosign.key --yes --predicate sbom.json --type spdxjson $IMAGE after_script: - rm -f /tmp/cosign.key variables: COSIGN_PASSWORD: $COSIGN_KEY_PASSWORD
verify-before-deploy: stage: verify image: alpine:3.20 before_script: - apk add --no-cache cosign script: - cosign verify --key cosign.pub $IMAGE - cosign verify-attestation --key cosign.pub --type spdxjson $IMAGE allow_failure: falseBloquer les images non signées
Section intitulée « Bloquer les images non signées »Pour empêcher le déploiement d’images non vérifiées, ajoutez un job de vérification obligatoire dans le pipeline de déploiement :
verify-signature: stage: pre-deploy image: alpine:3.20 before_script: - apk add --no-cache cosign script: - | if ! cosign verify --key cosign.pub $DEPLOY_IMAGE 2>/dev/null; then echo "ERREUR : image non signée ou signature invalide" echo "Image : $DEPLOY_IMAGE" exit 1 fi echo "Signature vérifiée" allow_failure: falsePour Kubernetes, Kyverno et Connaisseur peuvent bloquer les images non signées au niveau du cluster.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Correction |
|---|---|---|
Error: no matching signatures | Image non signée ou mauvaise clé | Vérifier que cosign.pub correspond à la clé utilisée pour signer |
UNAUTHORIZED: authentication required | docker login non fait | Ajouter cosign login au before_script avec CI_REGISTRY_* |
Error: signing [image]: getting remote image | Image introuvable | Vérifier que le tag $CI_COMMIT_SHORT_SHA existe dans le registre |
| SBOM vide (0 packages) | Image scratch ou distroless | Syft ne peut pas scanner un filesystem vide — scanner le Dockerfile ou les sources |
MANIFEST_UNKNOWN sur l’attestation | Le registre ne supporte pas OCI artifacts | Mettre à jour le registre ou utiliser --attachment-tag-prefix |
| cosign keyless échoue | OIDC mal configuré | Vérifier id_tokens dans le .gitlab-ci.yml et /.well-known/openid-configuration |
| Trivy SBOM scan timeout | Image volumineuse | Ajouter --timeout 15m et utiliser le cache Trivy DB |
À retenir
Section intitulée « À retenir »La sécurité supply chain CI repose sur trois couches complémentaires :
- SBOM : documenter ce que contient chaque image. Syft ou Trivy génèrent un inventaire complet au format standard (SPDX, CycloneDX). Sans SBOM, vous ne pouvez pas répondre à la question “est-ce que ce CVE nous concerne ?”
- Signature : prouver l’intégrité. cosign signe l’image et stocke la signature dans le registre. Toute modification invalide la signature. Le mode keyless avec Sigstore élimine la gestion des clés.
- Attestation : prouver la provenance. L’attestation SLSA lie l’artefact au pipeline qui l’a construit. Sans attestation, une signature prouve l’intégrité mais pas l’origine.
- Vérification : le maillon critique. Signer sans vérifier est inutile. Le job
verify-before-deploydoit être obligatoire et bloquant dans le pipeline de déploiement. - Automatisation : chaque étape doit être dans le pipeline, pas dans un script manuel. Ce qui n’est pas automatisé ne sera pas fait.
- SLSA — Supply-chain Levels for Software Artifacts — OpenSSF
- Sigstore — cosign — Sigstore
- Syft — SBOM generation — Anchore
- Trivy — Vulnerability scanner and SBOM — Aqua Security
- GitLab CI — Signing with cosign — GitLab Docs
- SPDX Specification — Linux Foundation
- CycloneDX Specification — OWASP
- NIST SSDF — Secure Software Development Framework — NIST
Testez vos connaissances
Section intitulée « Testez vos connaissances »Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications