{"id":531,"date":"2026-02-21T16:47:55","date_gmt":"2026-02-21T15:47:55","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=531"},"modified":"2026-03-24T12:58:04","modified_gmt":"2026-03-24T11:58:04","slug":"lab-sbom-pipeline-generate-attest-verify-syft-cosign","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-sbom-pipeline-generate-attest-verify-syft-cosign\/","title":{"rendered":"Lab : Construction d&rsquo;un Pipeline SBOM \u2014 G\u00e9n\u00e9rer, Attester et V\u00e9rifier avec Syft et Cosign"},"content":{"rendered":"<h2>Pr\u00e9sentation<\/h2>\n<p>Les Software Bills of Materials (SBOMs) deviennent rapidement un composant obligatoire de la transparence de la cha\u00eene d&rsquo;approvisionnement logicielle. Les d\u00e9crets gouvernementaux, les cadres r\u00e9glementaires comme le NIST SSDF et les normes industrielles exigent d\u00e9sormais des organisations qu&rsquo;elles produisent, distribuent et v\u00e9rifient des SBOMs pour chaque version logicielle. Un SBOM liste chaque composant, biblioth\u00e8que et d\u00e9pendance \u00e0 l&rsquo;int\u00e9rieur de votre logiciel \u2014 permettant aux consommateurs d&rsquo;\u00e9valuer les risques, de suivre les vuln\u00e9rabilit\u00e9s et de v\u00e9rifier la provenance.<\/p>\n<p>Dans ce lab pratique, vous allez construire un <strong>pipeline SBOM complet<\/strong> de z\u00e9ro. \u00c0 la fin, vous serez capable de :<\/p>\n<ul>\n<li>G\u00e9n\u00e9rer des SBOMs aux formats SPDX et CycloneDX avec <strong>Syft<\/strong><\/li>\n<li>Scanner les SBOMs pour d\u00e9tecter les vuln\u00e9rabilit\u00e9s connues avec <strong>Grype<\/strong><\/li>\n<li>Attacher des SBOMs comme attestations sign\u00e9es aux images de conteneurs avec <strong>Cosign<\/strong><\/li>\n<li>Automatiser l&rsquo;ensemble du flux de travail dans <strong>GitHub Actions<\/strong> et <strong>GitLab CI<\/strong><\/li>\n<li>Appliquer les exigences d&rsquo;attestation SBOM au d\u00e9ploiement avec <strong>Kyverno<\/strong><\/li>\n<li>Comparer les SBOMs entre les versions pour d\u00e9tecter les changements de d\u00e9pendances<\/li>\n<\/ul>\n<p>Ce lab refl\u00e8te les flux de travail de production r\u00e9els utilis\u00e9s par les \u00e9quipes adoptant SLSA, in-toto et la s\u00e9curit\u00e9 de la cha\u00eene d&rsquo;approvisionnement bas\u00e9e sur Sigstore.<\/p>\n<h2>Pr\u00e9requis<\/h2>\n<p>Avant de commencer, assurez-vous que les outils suivants sont install\u00e9s et configur\u00e9s :<\/p>\n<table>\n<thead>\n<tr>\n<th>Outil<\/th>\n<th>Fonction<\/th>\n<th>Installation<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Syft<\/strong><\/td>\n<td>G\u00e9n\u00e9ration de SBOM<\/td>\n<td><code>curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/syft\/main\/install.sh | sh -s -- -b \/usr\/local\/bin<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>Grype<\/strong><\/td>\n<td>Analyse de vuln\u00e9rabilit\u00e9s<\/td>\n<td><code>curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/grype\/main\/install.sh | sh -s -- -b \/usr\/local\/bin<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>Cosign<\/strong><\/td>\n<td>Signature et attestation<\/td>\n<td><code>go install github.com\/sigstore\/cosign\/v2\/cmd\/cosign@latest<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>Docker<\/strong><\/td>\n<td>Construction de conteneurs<\/td>\n<td><a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noopener\">docs.docker.com<\/a><\/td>\n<\/tr>\n<tr>\n<td><strong>GitHub CLI (gh)<\/strong><\/td>\n<td>Acc\u00e8s au d\u00e9p\u00f4t et \u00e0 GHCR<\/td>\n<td><code>brew install gh<\/code> ou <a href=\"https:\/\/cli.github.com\/\" target=\"_blank\" rel=\"noopener\">cli.github.com<\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Vous avez \u00e9galement besoin d&rsquo;un <strong>compte GitHub<\/strong> avec acc\u00e8s au GitHub Container Registry (GHCR), et d&rsquo;une application de base avec un <code>Dockerfile<\/code>.<\/p>\n<h2>Configuration de l&rsquo;environnement<\/h2>\n<p>Nous allons cr\u00e9er une application Node.js minimale, la conteneuriser et la pousser vers GHCR. Cette image devient la cible de tous les exercices SBOM suivants.<\/p>\n<h3>\u00c9tape 1 : Cr\u00e9er le d\u00e9p\u00f4t de test<\/h3>\n<pre><code class=\"language-bash\">mkdir sbom-pipeline-lab &amp;&amp; cd sbom-pipeline-lab\ngit init\n<\/code><\/pre>\n<h3>\u00c9tape 2 : Cr\u00e9er une application Node.js simple<\/h3>\n<pre><code class=\"language-bash\">cat &gt; package.json &lt;&lt;'EOF'\n{\n  \"name\": \"sbom-lab-app\",\n  \"version\": \"1.0.0\",\n  \"description\": \"SBOM pipeline lab application\",\n  \"main\": \"server.js\",\n  \"dependencies\": {\n    \"express\": \"^4.18.2\",\n    \"lodash\": \"^4.17.21\",\n    \"axios\": \"^1.6.0\"\n  }\n}\nEOF\n<\/code><\/pre>\n<pre><code class=\"language-bash\">cat &gt; server.js &lt;&lt;'EOF'\nconst express = require('express');\nconst app = express();\n\napp.get('\/', (req, res) =&gt; {\n  res.json({ status: 'ok', message: 'SBOM Pipeline Lab' });\n});\n\napp.listen(3000, () =&gt; console.log('Listening on port 3000'));\nEOF\n<\/code><\/pre>\n<h3>\u00c9tape 3 : Cr\u00e9er le Dockerfile<\/h3>\n<pre><code class=\"language-dockerfile\">FROM node:20-alpine\nWORKDIR \/app\nCOPY package*.json .\/\nRUN npm install --production\nCOPY . .\nEXPOSE 3000\nCMD [\"node\", \"server.js\"]\n<\/code><\/pre>\n<h3>\u00c9tape 4 : Construire et pousser l&rsquo;image de conteneur<\/h3>\n<pre><code class=\"language-bash\"># S'authentifier aupr\u00e8s de GHCR\necho $GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin\n\n# Construire l'image\ndocker build -t ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0 .\n\n# Pousser vers GHCR\ndocker push ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0\n<\/code><\/pre>\n<p>Remplacez <code>YOUR_GITHUB_USERNAME<\/code> par votre nom d&rsquo;utilisateur GitHub r\u00e9el tout au long de ce lab. Une fois le push termin\u00e9, notez le digest complet de l&rsquo;image \u2014 vous en aurez besoin pour les op\u00e9rations de signature.<\/p>\n<pre><code class=\"language-bash\"># Capturer le digest de l'image\nexport IMAGE=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0)\necho $IMAGE\n# Sortie : ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app@sha256:abc123...\n<\/code><\/pre>\n<h2>Exercice 1 : G\u00e9n\u00e9rer un SBOM avec Syft<\/h2>\n<p>Syft est un outil open-source d&rsquo;Anchore qui g\u00e9n\u00e8re des SBOMs en analysant les images de conteneurs, les syst\u00e8mes de fichiers et les archives. Il prend en charge plusieurs formats de sortie, notamment SPDX et CycloneDX \u2014 les deux standards SBOM dominants.<\/p>\n<h3>G\u00e9n\u00e9rer un SBOM SPDX<\/h3>\n<pre><code class=\"language-bash\">syft $IMAGE -o spdx-json=sbom.spdx.json\n<\/code><\/pre>\n<p>Inspecter la sortie :<\/p>\n<pre><code class=\"language-bash\">cat sbom.spdx.json | jq '.packages | length'\n# Sortie : 287 (le nombre varie selon le contenu de l'image)\n\n# Afficher les paquets d\u00e9tect\u00e9s\ncat sbom.spdx.json | jq '.packages[] | {name: .name, version: .versionInfo, license: .licenseDeclared}' | head -40\n<\/code><\/pre>\n<h3>G\u00e9n\u00e9rer un SBOM CycloneDX<\/h3>\n<pre><code class=\"language-bash\">syft $IMAGE -o cyclonedx-json=sbom.cyclonedx.json\n<\/code><\/pre>\n<p>Inspecter la sortie CycloneDX :<\/p>\n<pre><code class=\"language-bash\">cat sbom.cyclonedx.json | jq '.components | length'\n\n# Afficher les composants avec les licences\ncat sbom.cyclonedx.json | jq '.components[] | {name: .name, version: .version, type: .type}' | head -40\n<\/code><\/pre>\n<h3>SPDX vs. CycloneDX : Diff\u00e9rences cl\u00e9s<\/h3>\n<table>\n<thead>\n<tr>\n<th>Caract\u00e9ristique<\/th>\n<th>SPDX<\/th>\n<th>CycloneDX<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Origine<\/strong><\/td>\n<td>Linux Foundation<\/td>\n<td>OWASP<\/td>\n<\/tr>\n<tr>\n<td><strong>Focus principal<\/strong><\/td>\n<td>Conformit\u00e9 des licences et PI<\/td>\n<td>S\u00e9curit\u00e9 et analyse des risques<\/td>\n<\/tr>\n<tr>\n<td><strong>Norme ISO<\/strong><\/td>\n<td>ISO\/IEC 5962:2021<\/td>\n<td>ECMA-424<\/td>\n<\/tr>\n<tr>\n<td><strong>Donn\u00e9es de vuln\u00e9rabilit\u00e9<\/strong><\/td>\n<td>Support natif limit\u00e9<\/td>\n<td>Champ <code>vulnerabilities<\/code> de premier ordre<\/td>\n<\/tr>\n<tr>\n<td><strong>Formats<\/strong><\/td>\n<td>JSON, RDF, XML, YAML, tag-value<\/td>\n<td>JSON, XML, Protobuf<\/td>\n<\/tr>\n<tr>\n<td><strong>Id\u00e9al pour<\/strong><\/td>\n<td>Conformit\u00e9 r\u00e9glementaire, audits de licences<\/td>\n<td>DevSecOps, suivi des vuln\u00e9rabilit\u00e9s<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Pour ce lab, nous utilisons principalement SPDX JSON car c&rsquo;est le format requis par de nombreuses normes d&rsquo;approvisionnement gouvernementales et il s&rsquo;associe bien avec les attestations Cosign. Cependant, les deux formats sont pris en charge par Grype et Cosign.<\/p>\n<h3>G\u00e9n\u00e9rer un r\u00e9sum\u00e9 lisible<\/h3>\n<pre><code class=\"language-bash\">syft $IMAGE -o syft-table\n<\/code><\/pre>\n<p>Cela affiche un tableau de tous les paquets avec le nom, la version et le type \u2014 utile pour une revue rapide pendant le d\u00e9veloppement.<\/p>\n<h2>Exercice 2 : Scanner le SBOM pour les vuln\u00e9rabilit\u00e9s avec Grype<\/h2>\n<p>Grype est le scanner de vuln\u00e9rabilit\u00e9s d&rsquo;Anchore. Il peut scanner les images de conteneurs directement, mais il peut \u00e9galement scanner un fichier SBOM \u2014 ce qui est nettement plus rapide car il saute l&rsquo;\u00e9tape d&rsquo;analyse de l&rsquo;image.<\/p>\n<h3>Scanner le SBOM<\/h3>\n<pre><code class=\"language-bash\">grype sbom:.\/sbom.spdx.json\n<\/code><\/pre>\n<p>Exemple de sortie :<\/p>\n<pre><code class=\"language-text\">NAME          INSTALLED  FIXED-IN   TYPE  VULNERABILITY   SEVERITY\nlodash        4.17.21               npm   CVE-2025-XXXXX  Medium\nnode          20.10.0    20.11.1    apk   CVE-2024-22019  High\nlibcrypto3    3.1.4-r1   3.1.4-r3   apk   CVE-2024-0727   Medium\n<\/code><\/pre>\n<h3>Filtrer par s\u00e9v\u00e9rit\u00e9<\/h3>\n<pre><code class=\"language-bash\"># Afficher uniquement les vuln\u00e9rabilit\u00e9s Critiques et Hautes\ngrype sbom:.\/sbom.spdx.json --fail-on critical\n\n# Sortie des r\u00e9sultats en JSON pour traitement CI\ngrype sbom:.\/sbom.spdx.json -o json &gt; vulnerabilities.json\n\n# Compter les vuln\u00e9rabilit\u00e9s par s\u00e9v\u00e9rit\u00e9\ncat vulnerabilities.json | jq '[.matches[].vulnerability.severity] | group_by(.) | map({severity: .[0], count: length})'\n<\/code><\/pre>\n<h3>Scan d&rsquo;image vs. scan de SBOM<\/h3>\n<p>Il existe une distinction importante entre le scan direct de l&rsquo;image et le scan du SBOM :<\/p>\n<pre><code class=\"language-bash\"># Scan direct de l'image \u2014 Grype t\u00e9l\u00e9charge et analyse les couches de l'image\ngrype $IMAGE\n\n# Scan du SBOM \u2014 Grype lit la liste de paquets pr\u00e9-g\u00e9n\u00e9r\u00e9e\ngrype sbom:.\/sbom.spdx.json\n<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th>Approche<\/th>\n<th>Vitesse<\/th>\n<th>Pr\u00e9cision<\/th>\n<th>Cas d&rsquo;utilisation<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Scan direct de l&rsquo;image<\/td>\n<td>Plus lent (t\u00e9l\u00e9charge les couches)<\/td>\n<td>D\u00e9couvre tous les paquets<\/td>\n<td>Premi\u00e8re analyse, d\u00e9veloppement local<\/td>\n<\/tr>\n<tr>\n<td>Scan du SBOM<\/td>\n<td>Rapide (lit le fichier JSON)<\/td>\n<td>Limit\u00e9 au contenu du SBOM<\/td>\n<td>Pipelines CI, scans r\u00e9p\u00e9t\u00e9s, audit<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Le scan du SBOM est aussi complet que le SBOM lui-m\u00eame. Si Syft a manqu\u00e9 un paquet (par exemple, un binaire compil\u00e9 statiquement), Grype ne trouvera pas de vuln\u00e9rabilit\u00e9s pour celui-ci. Pour une couverture maximale, g\u00e9n\u00e9rez le SBOM avec le flag <code>--catalogers all<\/code> de Syft.<\/p>\n<h2>Exercice 3 : Attacher le SBOM comme attestation Cosign<\/h2>\n<p>Une attestation est une d\u00e9claration sign\u00e9e concernant un artefact logiciel. En attachant le SBOM comme attestation, vous liez cryptographiquement le SBOM au digest sp\u00e9cifique de l&rsquo;image \u2014 n&rsquo;importe qui peut v\u00e9rifier que le SBOM a \u00e9t\u00e9 produit pour cette image exacte et qu&rsquo;il n&rsquo;a pas \u00e9t\u00e9 falsifi\u00e9.<\/p>\n<h3>Attacher l&rsquo;attestation SBOM<\/h3>\n<p>En utilisant le flux keyless (Sigstore Fulcio) de Cosign :<\/p>\n<pre><code class=\"language-bash\">cosign attest --predicate sbom.spdx.json \\\n  --type spdxjson \\\n  --yes \\\n  $IMAGE\n<\/code><\/pre>\n<p>Cette commande :<\/p>\n<ol>\n<li>Ouvre un navigateur pour l&rsquo;authentification OIDC (signature keyless via Fulcio)<\/li>\n<li>Cr\u00e9e une attestation in-toto avec votre SBOM comme pr\u00e9dicat<\/li>\n<li>Signe l&rsquo;attestation et l&rsquo;enregistre dans le journal de transparence Rekor<\/li>\n<li>Pousse l&rsquo;attestation vers le registre aux c\u00f4t\u00e9s de l&rsquo;image<\/li>\n<\/ol>\n<h3>V\u00e9rifier l&rsquo;attestation<\/h3>\n<pre><code class=\"language-bash\">cosign verify-attestation \\\n  --type spdxjson \\\n  --certificate-identity=YOUR_EMAIL@example.com \\\n  --certificate-oidc-issuer=https:\/\/accounts.google.com \\\n  $IMAGE | jq '.payload | @base64d | fromjson | .predicate'\n<\/code><\/pre>\n<p>Remplacez l&rsquo;identit\u00e9 du certificat et l&rsquo;\u00e9metteur OIDC par les valeurs correspondant \u00e0 votre identit\u00e9 Sigstore. Dans les environnements CI (GitHub Actions), ceux-ci seraient :<\/p>\n<pre><code class=\"language-bash\">--certificate-identity=https:\/\/github.com\/YOUR_ORG\/YOUR_REPO\/.github\/workflows\/build.yml@refs\/heads\/main\n--certificate-oidc-issuer=https:\/\/token.actions.githubusercontent.com\n<\/code><\/pre>\n<h3>Inspecter l&rsquo;attestation dans le registre<\/h3>\n<pre><code class=\"language-bash\"># Lister les r\u00e9f\u00e9rents (registres OCI 1.1)\ncosign tree $IMAGE\n<\/code><\/pre>\n<p>Vous devriez voir l&rsquo;attestation list\u00e9e comme r\u00e9f\u00e9rent attach\u00e9 au manifeste de l&rsquo;image. L&rsquo;attestation est stock\u00e9e comme artefact OCI dans le m\u00eame d\u00e9p\u00f4t, tagu\u00e9 avec une convention <code>sha256-&lt;digest&gt;.att<\/code>.<\/p>\n<h2>Exercice 4 : Construire le pipeline complet dans GitHub Actions<\/h2>\n<p>Nous automatisons maintenant tout dans un seul flux de travail CI\/CD. Ce pipeline construit l&rsquo;image, g\u00e9n\u00e8re le SBOM, scanne les vuln\u00e9rabilit\u00e9s et attache le SBOM comme attestation sign\u00e9e.<\/p>\n<p>Cr\u00e9ez <code>.github\/workflows\/sbom-pipeline.yml<\/code> :<\/p>\n<pre><code class=\"language-yaml\">name: SBOM Pipeline\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\npermissions:\n  contents: read\n  packages: write\n  id-token: write  # Required for Cosign keyless signing\n  attestations: write\n\njobs:\n  build-and-attest:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v4\n\n      - name: Set up Docker Buildx\n        uses: docker\/setup-buildx-action@v3\n\n      - name: Log in to GHCR\n        uses: docker\/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build and push image\n        id: build\n        uses: docker\/build-push-action@v5\n        with:\n          context: .\n          push: true\n          tags: ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}:${{ github.sha }}\n\n      - name: Get image digest\n        id: digest\n        run: |\n          echo \"image=${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}\" &gt;&gt; $GITHUB_OUTPUT\n\n      - name: Install Syft\n        uses: anchore\/sbom-action\/download-syft@v0\n\n      - name: Generate SPDX SBOM\n        run: |\n          syft ${{ steps.digest.outputs.image }} -o spdx-json=sbom.spdx.json\n          echo \"### SBOM Summary\" &gt;&gt; $GITHUB_STEP_SUMMARY\n          echo \"Package count: $(cat sbom.spdx.json | jq '.packages | length')\" &gt;&gt; $GITHUB_STEP_SUMMARY\n\n      - name: Install Grype\n        uses: anchore\/scan-action\/download-grype@v4\n\n      - name: Scan SBOM for vulnerabilities\n        run: |\n          grype sbom:.\/sbom.spdx.json -o json &gt; vulnerabilities.json\n          grype sbom:.\/sbom.spdx.json -o table &gt;&gt; $GITHUB_STEP_SUMMARY\n\n          # Fail if critical vulnerabilities are found\n          CRITICAL_COUNT=$(cat vulnerabilities.json | jq '[.matches[] | select(.vulnerability.severity==\"Critical\")] | length')\n          echo \"Critical vulnerabilities: $CRITICAL_COUNT\"\n          if [ \"$CRITICAL_COUNT\" -gt 0 ]; then\n            echo \"::error::Found $CRITICAL_COUNT critical vulnerabilities. Failing pipeline.\"\n            exit 1\n          fi\n\n      - name: Install Cosign\n        uses: sigstore\/cosign-installer@v3\n\n      - name: Attest SBOM\n        run: |\n          cosign attest --predicate sbom.spdx.json \\\n            --type spdxjson \\\n            --yes \\\n            ${{ steps.digest.outputs.image }}\n\n      - name: Verify attestation\n        run: |\n          cosign verify-attestation \\\n            --type spdxjson \\\n            --certificate-identity-regexp=\"https:\/\/github.com\/${{ github.repository }}\/\" \\\n            --certificate-oidc-issuer=https:\/\/token.actions.githubusercontent.com \\\n            ${{ steps.digest.outputs.image }}\n\n      - name: Upload SBOM as artifact\n        uses: actions\/upload-artifact@v4\n        with:\n          name: sbom-spdx\n          path: sbom.spdx.json\n          retention-days: 90\n\n      - name: Upload vulnerability report\n        uses: actions\/upload-artifact@v4\n        if: always()\n        with:\n          name: vulnerability-report\n          path: vulnerabilities.json\n          retention-days: 90\n<\/code><\/pre>\n<p>Ce flux de travail utilise la <strong>signature keyless<\/strong> via l&rsquo;identit\u00e9 OIDC de GitHub Actions. Aucune cl\u00e9 priv\u00e9e n&rsquo;est stock\u00e9e dans les secrets \u2014 Cosign obtient un certificat \u00e9ph\u00e9m\u00e8re aupr\u00e8s de l&rsquo;autorit\u00e9 de certification Fulcio de Sigstore, et l&rsquo;\u00e9v\u00e9nement de signature est enregistr\u00e9 dans le journal de transparence Rekor.<\/p>\n<p>Le pipeline <strong>\u00e9choue si des vuln\u00e9rabilit\u00e9s critiques<\/strong> sont d\u00e9tect\u00e9es. Ajustez le seuil en modifiant le filtre de s\u00e9v\u00e9rit\u00e9 dans l&rsquo;\u00e9tape de scan Grype.<\/p>\n<h2>Exercice 5 : Construire le pipeline dans GitLab CI<\/h2>\n<p>Le pipeline \u00e9quivalent dans GitLab CI utilise les m\u00eames outils mais s&rsquo;adapte au mod\u00e8le bas\u00e9 sur les \u00e9tapes de GitLab et \u00e0 son registre de conteneurs int\u00e9gr\u00e9.<\/p>\n<p>Cr\u00e9ez <code>.gitlab-ci.yml<\/code> :<\/p>\n<pre><code class=\"language-yaml\">stages:\n  - build\n  - sbom\n  - scan\n  - attest\n\nvariables:\n  IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n  IMAGE_DIGEST: \"\"\n\nbuild:\n  stage: build\n  image: docker:24\n  services:\n    - docker:24-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"\/certs\"\n  script:\n    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY\n    - docker build -t $IMAGE .\n    - docker push $IMAGE\n    - |\n      DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE)\n      echo \"IMAGE_DIGEST=$DIGEST\" &gt;&gt; build.env\n  artifacts:\n    reports:\n      dotenv: build.env\n\ngenerate-sbom:\n  stage: sbom\n  image: alpine:3.19\n  needs: [build]\n  before_script:\n    - apk add --no-cache curl jq\n    - curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/syft\/main\/install.sh | sh -s -- -b \/usr\/local\/bin\n  script:\n    - syft $IMAGE_DIGEST -o spdx-json=sbom.spdx.json\n    - echo \"Package count:\" $(cat sbom.spdx.json | jq '.packages | length')\n  artifacts:\n    paths:\n      - sbom.spdx.json\n    expire_in: 90 days\n\nscan-vulnerabilities:\n  stage: scan\n  image: alpine:3.19\n  needs: [generate-sbom]\n  before_script:\n    - apk add --no-cache curl jq\n    - curl -sSfL https:\/\/raw.githubusercontent.com\/anchore\/grype\/main\/install.sh | sh -s -- -b \/usr\/local\/bin\n  script:\n    - grype sbom:.\/sbom.spdx.json -o json &gt; vulnerabilities.json\n    - grype sbom:.\/sbom.spdx.json -o table\n    - |\n      CRITICAL_COUNT=$(cat vulnerabilities.json | jq '[.matches[] | select(.vulnerability.severity==\"Critical\")] | length')\n      echo \"Critical vulnerabilities found: $CRITICAL_COUNT\"\n      if [ \"$CRITICAL_COUNT\" -gt 0 ]; then\n        echo \"ERROR: Critical vulnerabilities detected. Failing pipeline.\"\n        exit 1\n      fi\n  artifacts:\n    paths:\n      - vulnerabilities.json\n    expire_in: 90 days\n    when: always\n\nattest-sbom:\n  stage: attest\n  image: alpine:3.19\n  needs: [build, generate-sbom, scan-vulnerabilities]\n  id_tokens:\n    SIGSTORE_ID_TOKEN:\n      aud: sigstore\n  before_script:\n    - apk add --no-cache curl\n    - curl -sSfL https:\/\/github.com\/sigstore\/cosign\/releases\/latest\/download\/cosign-linux-amd64 -o \/usr\/local\/bin\/cosign\n    - chmod +x \/usr\/local\/bin\/cosign\n  script:\n    - cosign attest --predicate sbom.spdx.json\n        --type spdxjson\n        --yes\n        $IMAGE_DIGEST\n    - cosign verify-attestation\n        --type spdxjson\n        --certificate-identity-regexp=\"https:\/\/gitlab.com\/$CI_PROJECT_PATH\/\/\"\n        --certificate-oidc-issuer=https:\/\/gitlab.com\n        $IMAGE_DIGEST\n<\/code><\/pre>\n<p>Diff\u00e9rences cl\u00e9s par rapport \u00e0 la version GitHub Actions :<\/p>\n<ul>\n<li>GitLab utilise des <strong>stages<\/strong> au lieu d&rsquo;un seul job avec des \u00e9tapes<\/li>\n<li>Le digest de l&rsquo;image est transmis entre les stages via des artefacts <code>dotenv<\/code><\/li>\n<li>Le jeton OIDC de GitLab est expos\u00e9 via <code>id_tokens<\/code> et la variable <code>SIGSTORE_ID_TOKEN<\/code><\/li>\n<li>L&rsquo;\u00e9metteur OIDC pour la v\u00e9rification est <code>https:\/\/gitlab.com<\/code><\/li>\n<\/ul>\n<h2>Exercice 6 : V\u00e9rifier le SBOM au d\u00e9ploiement<\/h2>\n<p>G\u00e9n\u00e9rer et attester des SBOMs n&rsquo;est que la moiti\u00e9 du travail. Le v\u00e9ritable avantage en termes de s\u00e9curit\u00e9 vient de <strong>l&rsquo;application de la v\u00e9rification au moment du d\u00e9ploiement<\/strong> \u2014 en rejetant toute image d\u00e9pourvue d&rsquo;attestation SBOM valide.<\/p>\n<h3>Option A : V\u00e9rifier dans un script de d\u00e9ploiement<\/h3>\n<pre><code class=\"language-bash\">#!\/bin\/bash\n# deploy.sh \u2014 V\u00e9rifier l'attestation SBOM avant le d\u00e9ploiement\nset -euo pipefail\n\nIMAGE=\"$1\"\n\necho \"V\u00e9rification de l'attestation SBOM pour : $IMAGE\"\n\ncosign verify-attestation \\\n  --type spdxjson \\\n  --certificate-identity-regexp=\"https:\/\/github.com\/YOUR_ORG\/YOUR_REPO\/\" \\\n  --certificate-oidc-issuer=https:\/\/token.actions.githubusercontent.com \\\n  \"$IMAGE\" &gt; \/dev\/null 2&gt;&amp;1\n\nif [ $? -eq 0 ]; then\n  echo \"Attestation SBOM v\u00e9rifi\u00e9e avec succ\u00e8s.\"\n  kubectl set image deployment\/myapp myapp=\"$IMAGE\"\nelse\n  echo \"ERREUR : La v\u00e9rification de l'attestation SBOM a \u00e9chou\u00e9. D\u00e9ploiement bloqu\u00e9.\"\n  exit 1\nfi\n<\/code><\/pre>\n<h3>Option B : Appliquer avec Kyverno dans Kubernetes<\/h3>\n<p>Kyverno est un moteur de politiques natif Kubernetes qui peut v\u00e9rifier les signatures et attestations d&rsquo;images au moment de l&rsquo;admission. La politique suivante <strong>rejette tout pod<\/strong> dont l&rsquo;image ne dispose pas d&rsquo;une attestation SBOM valide.<\/p>\n<pre><code class=\"language-yaml\">apiVersion: kyverno.io\/v1\nkind: ClusterPolicy\nmetadata:\n  name: require-sbom-attestation\nspec:\n  validationFailureAction: Enforce\n  webhookTimeoutSeconds: 30\n  rules:\n    - name: check-sbom-attestation\n      match:\n        any:\n          - resources:\n              kinds:\n                - Pod\n      verifyImages:\n        - imageReferences:\n            - \"ghcr.io\/YOUR_ORG\/*\"\n          attestations:\n            - type: spdxjson\n              attestors:\n                - entries:\n                    - keyless:\n                        subject: \"https:\/\/github.com\/YOUR_ORG\/YOUR_REPO\/.github\/workflows\/sbom-pipeline.yml@refs\/heads\/main\"\n                        issuer: \"https:\/\/token.actions.githubusercontent.com\"\n                        rekor:\n                          url: https:\/\/rekor.sigstore.dev\n              conditions:\n                - all:\n                    - key: \"{{ len(spdxVersion) }}\"\n                      operator: GreaterThan\n                      value: \"0\"\n<\/code><\/pre>\n<p>Appliquer la politique :<\/p>\n<pre><code class=\"language-bash\">kubectl apply -f require-sbom-attestation.yaml\n<\/code><\/pre>\n<h3>Test d&rsquo;admission : image avec attestation<\/h3>\n<pre><code class=\"language-bash\"># Ceci devrait \u00eatre ADMIS\nkubectl run test-admitted \\\n  --image=ghcr.io\/YOUR_ORG\/sbom-lab-app@sha256:abc123... \\\n  --restart=Never\n\n# Sortie attendue :\n# pod\/test-admitted created\n<\/code><\/pre>\n<h3>Test d&rsquo;admission : image sans attestation<\/h3>\n<pre><code class=\"language-bash\"># Ceci devrait \u00eatre REJET\u00c9\nkubectl run test-rejected \\\n  --image=ghcr.io\/YOUR_ORG\/sbom-lab-app:unattested \\\n  --restart=Never\n\n# Sortie attendue :\n# Error from server: admission webhook \"mutate.kyverno.svc-fail\" denied the request:\n# resource Pod\/default\/test-rejected was blocked due to the following policies:\n# require-sbom-attestation:\n#   check-sbom-attestation: 'image verification failed for ghcr.io\/YOUR_ORG\/sbom-lab-app:unattested:\n#     attestation spdxjson not found'\n<\/code><\/pre>\n<p>C&rsquo;est la boucle d&rsquo;application qui rend les SBOMs op\u00e9rationnellement significatifs \u2014 sans v\u00e9rification au d\u00e9ploiement, la g\u00e9n\u00e9ration de SBOM n&rsquo;est que de la documentation.<\/p>\n<h2>Exercice 7 : Comparaison de SBOM entre versions<\/h2>\n<p>Lorsque vous publiez une nouvelle version, vous devez comprendre ce qui a chang\u00e9 dans votre arbre de d\u00e9pendances. La comparaison des SBOMs entre versions r\u00e9v\u00e8le les paquets ajout\u00e9s, supprim\u00e9s et mis \u00e0 jour \u2014 essentiel pour \u00e9valuer les nouveaux risques.<\/p>\n<h3>\u00c9tape 1 : G\u00e9n\u00e9rer les SBOMs pour deux versions<\/h3>\n<pre><code class=\"language-bash\"># G\u00e9n\u00e9rer le SBOM pour v1.0.0\nsyft ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.0.0 -o spdx-json=sbom-v1.spdx.json\n\n# G\u00e9n\u00e9rer le SBOM pour v1.1.0 (apr\u00e8s mise \u00e0 jour des d\u00e9pendances)\nsyft ghcr.io\/YOUR_GITHUB_USERNAME\/sbom-lab-app:v1.1.0 -o spdx-json=sbom-v2.spdx.json\n<\/code><\/pre>\n<h3>\u00c9tape 2 : Extraire les listes de paquets<\/h3>\n<pre><code class=\"language-bash\"># Extraire les listes de paquets tri\u00e9es\ncat sbom-v1.spdx.json | jq -r '.packages[] | \"\\(.name)@\\(.versionInfo)\"' | sort &gt; packages-v1.txt\ncat sbom-v2.spdx.json | jq -r '.packages[] | \"\\(.name)@\\(.versionInfo)\"' | sort &gt; packages-v2.txt\n<\/code><\/pre>\n<h3>\u00c9tape 3 : Comparer les listes de paquets<\/h3>\n<pre><code class=\"language-bash\">diff --unified packages-v1.txt packages-v2.txt\n<\/code><\/pre>\n<p>Exemple de sortie :<\/p>\n<pre><code class=\"language-diff\">--- packages-v1.txt\n+++ packages-v2.txt\n@@ -12,7 +12,8 @@\n  express@4.18.2\n  lodash@4.17.21\n- axios@1.6.0\n+ axios@1.7.2\n+ helmet@7.1.0\n  mime-types@2.1.35\n<\/code><\/pre>\n<h3>\u00c9tape 4 : Identifier les vuln\u00e9rabilit\u00e9s nouvellement introduites<\/h3>\n<pre><code class=\"language-bash\"># Scanner uniquement les paquets nouveaux\/modifi\u00e9s\ngrype sbom:.\/sbom-v2.spdx.json -o json &gt; vulns-v2.json\ngrype sbom:.\/sbom-v1.spdx.json -o json &gt; vulns-v1.json\n\n# Comparer le nombre de vuln\u00e9rabilit\u00e9s\necho \"Vuln\u00e9rabilit\u00e9s v1.0.0 : $(cat vulns-v1.json | jq '.matches | length')\"\necho \"Vuln\u00e9rabilit\u00e9s v1.1.0 : $(cat vulns-v2.json | jq '.matches | length')\"\n\n# Trouver les NOUVELLES vuln\u00e9rabilit\u00e9s dans v1.1.0\ncomm -13 \\\n  &lt;(cat vulns-v1.json | jq -r '.matches[].vulnerability.id' | sort -u) \\\n  &lt;(cat vulns-v2.json | jq -r '.matches[].vulnerability.id' | sort -u)\n<\/code><\/pre>\n<p>Ce processus de comparaison peut \u00eatre automatis\u00e9 dans la CI pour commenter les pull requests avec les changements de d\u00e9pendances, offrant aux r\u00e9viseurs une visibilit\u00e9 sur l&rsquo;impact de la cha\u00eene d&rsquo;approvisionnement avant la fusion.<\/p>\n<h2>Nettoyage<\/h2>\n<p>Supprimez les ressources cr\u00e9\u00e9es pendant ce lab :<\/p>\n<pre><code class=\"language-bash\"># Supprimer les fichiers locaux\nrm -f sbom.spdx.json sbom.cyclonedx.json vulnerabilities.json\nrm -f sbom-v1.spdx.json sbom-v2.spdx.json packages-v1.txt packages-v2.txt\nrm -f vulns-v1.json vulns-v2.json\n\n# Supprimer l'image de test de GHCR (n\u00e9cessite gh CLI)\ngh api -X DELETE \/user\/packages\/container\/sbom-lab-app\/versions\/PACKAGE_VERSION_ID\n\n# Supprimer la politique Kyverno\nkubectl delete clusterpolicy require-sbom-attestation\n\n# Supprimer les pods de test\nkubectl delete pod test-admitted test-rejected --ignore-not-found\n\n# Supprimer le d\u00e9p\u00f4t local\ncd .. &amp;&amp; rm -rf sbom-pipeline-lab\n<\/code><\/pre>\n<h2>Points cl\u00e9s \u00e0 retenir<\/h2>\n<ul>\n<li><strong>Les SBOMs sont un pilier de la s\u00e9curit\u00e9 de la cha\u00eene d&rsquo;approvisionnement<\/strong> \u2014 ils fournissent l&rsquo;inventaire qui permet l&rsquo;analyse des vuln\u00e9rabilit\u00e9s, la conformit\u00e9 des licences et la v\u00e9rification de la provenance.<\/li>\n<li><strong>Syft g\u00e9n\u00e8re des SBOMs dans tous les formats majeurs<\/strong> \u2014 SPDX pour la conformit\u00e9 r\u00e9glementaire, CycloneDX pour les flux de travail de s\u00e9curit\u00e9, tous deux pris en charge dans l&rsquo;\u00e9cosyst\u00e8me.<\/li>\n<li><strong>Scanner le SBOM est plus rapide que scanner l&rsquo;image<\/strong> \u2014 utilisez Grype avec le pr\u00e9fixe <code>sbom:<\/code> dans les pipelines CI pour des boucles de r\u00e9troaction rapides sans t\u00e9l\u00e9charger les couches de l&rsquo;image.<\/li>\n<li><strong>Les attestations Cosign lient cryptographiquement les SBOMs aux images<\/strong> \u2014 les attestations sign\u00e9es prouvent qu&rsquo;un SBOM sp\u00e9cifique a \u00e9t\u00e9 produit pour un digest d&rsquo;image sp\u00e9cifique, stock\u00e9 dans le registre aux c\u00f4t\u00e9s de l&rsquo;image.<\/li>\n<li><strong>La v\u00e9rification au d\u00e9ploiement rend les SBOMs ex\u00e9cutoires<\/strong> \u2014 sans contr\u00f4le d&rsquo;admission (Kyverno, OPA Gatekeeper ou scripts de d\u00e9ploiement), les SBOMs restent de la documentation sans force contraignante.<\/li>\n<li><strong>Les comparaisons de SBOM r\u00e9v\u00e8lent les changements de la cha\u00eene d&rsquo;approvisionnement entre versions<\/strong> \u2014 l&rsquo;automatisation de la comparaison des d\u00e9pendances dans la CI donne aux \u00e9quipes une visibilit\u00e9 sur les nouveaux risques avant que le code n&rsquo;atteigne la production.<\/li>\n<\/ul>\n<h2>Prochaines \u00e9tapes<\/h2>\n<p>Continuez \u00e0 construire votre pratique de s\u00e9curit\u00e9 de la cha\u00eene d&rsquo;approvisionnement avec ces guides connexes :<\/p>\n<ul>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/artifact-provenance-attestations-slsa-in-toto-2\/\">Provenance des artefacts et attestations<\/a> \u2014 D\u00e9couvrez comment les cadres SLSA et in-toto \u00e9tendent les attestations au-del\u00e0 des SBOMs pour couvrir la provenance de construction, l&rsquo;int\u00e9grit\u00e9 des sources et les politiques de d\u00e9ploiement.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/signing-verifying-container-images-sigstore-cosign\/\">Signature et v\u00e9rification des images de conteneurs avec Sigstore et Cosign<\/a> \u2014 Approfondissement de la signature keyless, des certificats Fulcio, des journaux de transparence Rekor et des politiques de v\u00e9rification de signature d&rsquo;images.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Pr\u00e9sentation Les Software Bills of Materials (SBOMs) deviennent rapidement un composant obligatoire de la transparence de la cha\u00eene d&rsquo;approvisionnement logicielle. Les d\u00e9crets gouvernementaux, les cadres r\u00e9glementaires comme le NIST SSDF et les normes industrielles exigent d\u00e9sormais des organisations qu&rsquo;elles produisent, distribuent et v\u00e9rifient des SBOMs pour chaque version logicielle. Un SBOM liste chaque composant, biblioth\u00e8que &#8230; <a title=\"Lab : Construction d&rsquo;un Pipeline SBOM \u2014 G\u00e9n\u00e9rer, Attester et V\u00e9rifier avec Syft et Cosign\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-sbom-pipeline-generate-attest-verify-syft-cosign\/\" aria-label=\"En savoir plus sur Lab : Construction d&rsquo;un Pipeline SBOM \u2014 G\u00e9n\u00e9rer, Attester et V\u00e9rifier avec Syft et Cosign\">Lire la suite<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,50],"tags":[],"post_folder":[],"class_list":["post-531","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-software-supply-chain"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/531","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/comments?post=531"}],"version-history":[{"count":2,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/531\/revisions"}],"predecessor-version":[{"id":580,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/531\/revisions\/580"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/media?parent=531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/categories?post=531"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/tags?post=531"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/post_folder?post=531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}