Aller au contenu principal

Ressources

DevSecOps en pratique : scanner, signer et promouvoir l'image qu'on déploie vraiment

DevSecOps en pratique : scan bloquant du digest final, SBOM, signature, contrôle d'admission, promotion GitOps, et ce que le CRA change pour un éditeur.

Par , publié le · 22 min de lecture

Base d'expérience : Méthode et documentation officielle (Kubernetes, Sigstore, SLSA, GitHub, Trivy, Kyverno, EUR-Lex) : chaîne de livraison construite une fois, scannée, signée, attestée et admise sur le même digest, sans cas client ni retour d'expérience publié.

Relecture factuelle : Claude Code (revue indépendante déléguée par Corentin Mas, 28/09/2026), le

Une chaîne CI/CD peut afficher tous les contrôles d'un programme DevSecOps, analyse du code et des dépendances, scan d'image, signature, et laisser pourtant partir en production une image que personne n'a analysée. Un scénario suffit : la pull request scanne sa propre image, puis la chaîne post-fusion reconstruit, signe et promeut une autre image, avec un scan seulement informatif. Or les questionnaires des clients régulés, comme le règlement européen sur la cyberrésilience (CRA, Cyber Resilience Act), portent précisément sur l'artefact livré, l'inventaire de ses composants et le traitement de ses vulnérabilités.

Cet article définit ce que DevSecOps change concrètement, décrit la chaîne qui relie un commit à un digest scanné, signé, attesté puis admis dans le cluster, avec un workflow GitHub Actions et une politique Kyverno minimaux, et situe ce que le CRA impose à un éditeur. Les outils sont cités à titre d'exemple, sans recommandation. Cette lecture est établie sur cosign 3.1.3, Kyverno 1.19, Trivy 0.74, SLSA 1.2, les runners GitHub Actions Ubuntu 24.04 et le règlement (UE) 2024/2847 ; les sources citées ont été vérifiées le .

Ce que DevSecOps change concrètement

DevSecOps désigne l'intégration de la sécurité dans la chaîne de développement, de livraison et d'exploitation d'un logiciel : chaque changement traverse des contrôles automatisés (analyse du code, des dépendances et de l'image, vérification des signatures) dont le verdict peut bloquer la mise en production, et dont le résultat reste attaché à l'artefact livré comme preuve. La sécurité devient une propriété de la chaîne de livraison, et non une revue menée une fois le logiciel terminé. Le NIST, dans sa publication SP 800-204C de mars 2022, présente d'ailleurs DevSecOps comme un paradigme qui s'appuie sur les chaînes d'intégration, de livraison et de déploiement continus (CI/CD).

Par rapport à une chaîne qui « a des scanners », trois choses changent.

  1. Le verdict est bloquant et son seuil est écrit. Un contrôle qui ne bloque jamais produit un rapport, pas une décision. Le seuil tient en une phrase vérifiable : par exemple, aucune vulnérabilité CRITICAL ou HIGH corrigeable, sauf exception enregistrée, justifiée et datée.
  2. L'objet du contrôle est l'artefact, identifié par son digest. Une branche, un numéro de build ou un tag ne désignent pas un contenu ; un digest, si. Scan, SBOM, signature, provenance et admission doivent porter sur le même digest.
  3. La preuve est un sous-produit de la chaîne. Rapport de scan, inventaire des composants (SBOM, Software Bill of Materials), signature et provenance sont produits à chaque livraison et conservés avec l'image. Répondre à un questionnaire de sécurité devient une requête, pas une reconstitution.

DevSecOps n'est ni un outil que l'on achète, ni une équipe sécurité qui relit chaque pull request, ni un empilement de scanners dont personne ne lit les alertes.

DevSecOps ou DevOps : la différence tient au droit de veto

DevOps automatise la livraison pour livrer souvent et de façon reproductible. DevSecOps ajoute à cette chaîne des contrôles dotés d'un droit de veto, et les responsabilités qui vont avec : qui fixe les seuils, qui accorde une exception, qui la fait expirer. Le tableau situe les contrôles usuels ; les outils cités sont des exemples, pas une recommandation.

Contrôles DevSecOps usuels : moment d'exécution, objet analysé, ce que chaque contrôle bloque et exemples d'outils
ContrôleMomentObjet analyséCe qu'il bloqueExemples d'outils
SAST (analyse statique du code)Pull requestCode modifiéLa fusionCodeQL, Semgrep
SCA (analyse des dépendances)Pull requestManifestes et fichiers de verrouillageLa fusiondependency-review-action, OSV-Scanner
Détection de secretsPull requestDiff et historique GitLa fusionGitleaks
Analyse de l'infrastructure as codePull requestTerraform, charts Helm, manifestesLa fusionTrivy, Checkov
Scan d'imageAprès constructionDigest finalLa promotionTrivy, Grype
SBOMAprès constructionDigest finalRien : c'est une preuveSyft, Trivy, BuildKit
Signature et provenanceAprès le verdict du scanDigest finalL'admission, si elle vérifiecosign, actions/attest
Contrôle d'admissionCréation du podImages du podLe déploiementKyverno, policy-controller

Le piège du digest : on scanne une image, on en déploie une autre

La documentation Kubernetes est explicite : un digest est une empreinte du contenu de l'image et il est immuable, alors qu'un tag peut être déplacé vers une autre image ; si une référence porte un tag et un digest, seul le digest sert au téléchargement. Elle recommande de remplacer <image-name>:<tag> par <image-name>@<digest> pour que le pod exécute toujours le même code. Toute la chaîne décrite ici suppose que chaque contrôle désigne l'image sous cette forme.

Le piège n'a rien d'exotique. La pull request construit son image et la scanne. Après la fusion, une autre chaîne reconstruit l'image, la signe et met à jour le dépôt GitOps. Même code, contenu pas nécessairement identique : une image de base référencée par tag a pu être republiée, une dépendance transitive non verrouillée résolue autrement. Et même à contenu identique, la base de vulnérabilités du scanner a pu évoluer entre les deux passages.

Si ce second scan est informatif, la signature apposée ensuite porte sur un digest qu'aucun contrôle bloquant n'a examiné. La référence de cosign sign demande de signer par digest plutôt que par tag, pour signer ce que l'on croit signer ; mais une signature prouve seulement qu'une identité a signé un digest, pas que son contenu est conforme.

La correction tient en deux décisions. Le scan du digest final devient bloquant pour la promotion : sans verdict favorable sur ce digest, aucune mise à jour GitOps n'est écrite. Les promotions sont sérialisées par environnement, avec un contrôle de fraîcheur avant écriture. Un relevé permet ensuite de vérifier, conteneur par conteneur, que les digests scanné, déclaré et exécuté sont identiques.

Trois digests à réconcilier

Digests construit et scanné, déclaré et exécuté : où les lire, par quelle commande ou quel champ, et ce que chacun établit
DigestOù le lireCommande ou champCe qu'il établit
Construit et scannéSortie digest de la construction, rapport de scantrivy image ... "${IMAGE}@${DIGEST}"Le contenu analysé et son verdict
DéclaréDépôt GitOpsyq '.image.digest' envs/production/values.yamlCe que l'outil GitOps doit déployer
ExécutéStatut des pods.status.containerStatuses[*].imageIDCe que les nœuds ont démarré
kubectl get pods -n mon-app -l app.kubernetes.io/name=mon-app \
  -o jsonpath='{range .items[*]}{range .status.containerStatuses[*]}{.imageID}{"\n"}{end}{end}' \
  | sort | uniq -c

Deux précautions. L'API Kubernetes précise que imageID peut ne pas correspondre à l'image de la spécification du pod, parce que le runtime a pu la résoudre : pour une image multi-architecture, il faut savoir si l'on compare le digest de l'index ou celui du manifeste de plateforme. Et un état « Synced » dans l'outil GitOps dit que le cluster correspond au dépôt : si le dépôt déclare un tag, cet état ne dit rien du contenu exécuté.

La chaîne : du commit au digest scanné, signé et attesté

Le principe : construire une seule fois après la fusion, puis faire porter tous les contrôles et toutes les preuves sur ce digest, jusqu'à l'admission.

La chaîne DevSecOps du commit au pod

Le schéma suit une modification depuis la pull request jusqu'aux pods, en montrant les deux points où la chaîne peut refuser le digest.

Schéma défilable horizontalement ; sa version textuelle complète suit.

Lire le schéma sous forme textuelle
  1. La pull request passe les contrôles d'avant fusion : analyse statique du code, analyse des dépendances, détection de secrets, analyse de l'infrastructure as code.
  2. Après la fusion sur main, l'image est construite une seule fois ; elle est désormais désignée par son digest D.
  3. Le digest D est scanné. Si une vulnérabilité hors exception est trouvée, la promotion est refusée.
  4. Sinon, D est signé, et son SBOM ainsi que sa provenance sont attestés.
  5. La promotion, sérialisée par environnement, vérifie la signature et l'attestation SBOM de D, puis contrôle que le commit promu est toujours le plus récent.
  6. Le dépôt GitOps reçoit la référence image@D.
  7. À la création des pods, le contrôle d'admission vérifie la signature et l'attestation SBOM de D : en cas d'échec le pod est rejeté, sinon les pods exécutent D.
Modèle explicatif, sans donnée client, d'après les documentations officielles de Kubernetes, Sigstore, GitHub Actions et Kyverno.

Avant la fusion : code, dépendances, secrets

Ces contrôles bloquent la fusion, pas la promotion. Sur GitHub, actions/dependency-review-action fait échouer une pull request qui introduit une vulnérabilité de sévérité au moins égale à fail-on-severity (défaut : low) ; sur un dépôt privé, elle suppose une licence GitHub Advanced Security. Référencer l'image de base par digest et verrouiller les dépendances, transitives comprises, réduit l'écart entre l'image de pull request et celle de la fusion. Le scan de l'image de pull request reste un signal précoce, jamais un verdict de promotion.

Construire une fois, scanner le digest final

Avec Trivy, le scan bloquant tient en une ligne : trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed "${IMAGE}@${DIGEST}". --severity écrit la politique. Selon la documentation, --ignore-unfixed est un raccourci qui ignore les statuts affected, will_not_fix, fix_deferred et end_of_life : le verdict ne porte que sur ce qui est corrigeable ; comme le rapport n'affiche alors que les vulnérabilités corrigeables, un second passage non bloquant sans ce drapeau garde les autres visibles. Pour une image multi-architecture, Trivy charge par défaut la variante linux/amd64 ; les autres plateformes se scannent avec --platform.

Les exceptions font partie de la politique. Trivy accepte un fichier .trivyignore.yaml, encore expérimental, à passer explicitement par --ignorefile .trivyignore.yaml, dont chaque entrée peut porter une date d'expiration (expired_at) et une justification (statement), ainsi que des déclarations VEX (Vulnerability Exploitability Exchange) par --vex. Une exception sans date ni responsable devient une exemption permanente.

SBOM : CycloneDX ou SPDX

Norme de référence et outils de production possibles pour les formats de SBOM CycloneDX et SPDX
CritèreCycloneDXSPDX
NormeECMA-424ISO/IEC 5962:2021 (SPDX 2.2.1)
Production possibleTrivy (--format cyclonedx), SyftBuildKit (format de son attestation SBOM), Trivy, Syft

Le format se choisit selon ce que consomment les destinataires. Le point décisif est ailleurs : le SBOM doit être produit à partir du digest livré et lui être rattaché par une attestation signée. BuildKit attache à l'image un SBOM au format SPDX, mais n'analyse par défaut que l'étape finale du Dockerfile ; dans le workflow ci-dessous, Trivy produit un SBOM CycloneDX que cosign atteste et signe.

Signature sans clé et provenance SLSA

Avec Sigstore, la permission id-token: write permet à cosign d'obtenir le jeton OIDC (OpenID Connect) du workflow ; l'autorité de certification Fulcio délivre un certificat de courte durée lié à cette identité, et la signature est inscrite dans le journal de transparence Rekor. L'identité a la forme https://github.com/<organisation>/<dépôt>/.github/workflows/<fichier>@<référence>, l'émetteur est https://token.actions.githubusercontent.com, et cosign verify exige les deux en mode sans clé, sous forme exacte ou d'expression régulière. Une expression régulière qui accepte tout workflow du dépôt accepte aussi un workflow modifié sur une branche : il faut l'ancrer sur le fichier de livraison et la référence attendue. Sur l'instance publique, le journal Rekor est public : l'identité du workflow, qui contient le nom du dépôt, y devient visible.

La provenance dit comment et où l'artefact a été construit. SLSA (Supply-chain Levels for Software Artifacts) gradue sa piste « Build » : au niveau 1, la provenance existe mais reste facile à contourner ou à falsifier ; au niveau 2, la falsifier exige une attaque explicite ; au niveau 3, une vulnérabilité hors de portée de la plupart des attaquants. Aucun niveau ne dit que le code est sain. Les attestations d'artefact GitHub fournissent par elles-mêmes le niveau 2 de SLSA v1.0, le niveau 3 avec des workflows réutilisables ; pour un dépôt privé ou interne, elles exigent GitHub Enterprise Cloud et passent par l'instance Sigstore privée de GitHub. La provenance de BuildKit suit par défaut le schéma SLSA v0.2 et, en mode=max, expose les valeurs des arguments de construction, qui ne doivent jamais porter de secret. La documentation GitHub résume l'enjeu : générer des attestations n'apporte à lui seul aucun bénéfice de sécurité, il faut les vérifier.

Le scanner fait partie de la chaîne d'approvisionnement

En mars 2026, l'écosystème Trivy a lui-même été compromis : selon l'avis du projet, un binaire Trivy 0.69.4 malveillant a été diffusé, 76 des 77 tags de version de aquasecurity/trivy-action et les 7 tags existants de aquasecurity/setup-trivy ont été déplacés vers du code malveillant, qui collectait notamment les identifiants présents sur le runner. La documentation GitHub indique qu'épingler une action sur un SHA de commit complet est actuellement le seul moyen de l'utiliser comme version immuable. D'où trois règles : épingler les actions par SHA, épingler la version des outils, séparer les jobs par privilège pour que le scanner ne puisse ni signer ni écrire dans le registre.

Un workflow minimal

Le workflow suivant applique ces règles avec le registre de conteneurs GitHub et un dépôt GitOps séparé. Les noms mon-org, mon-app et gitops sont fictifs ; les SHA correspondent aux versions en commentaire, résolues le .

# .github/workflows/livraison.yml
name: livraison

on:
  push:
    branches: [main]

permissions:
  contents: read

env:
  IMAGE: ghcr.io/mon-org/mon-app # nom en minuscules, sans tag

jobs:
  build:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: write
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
      - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: build
        uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
        with:
          context: .
          push: true
          tags: ${{ env.IMAGE }}:${{ github.sha }}

  scan:
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: read # lecture seule : ni signature ni écriture au registre
    env:
      DIGEST: ${{ needs.build.outputs.digest }}
    steps:
      - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
        with:
          version: v0.74.0
      - name: Scan bloquant du digest final
        run: trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed "${IMAGE}@${DIGEST}"
      - name: SBOM CycloneDX du même digest
        run: trivy image --format cyclonedx --output sbom.cdx.json "${IMAGE}@${DIGEST}"
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: sbom
          path: sbom.cdx.json

  sign:
    needs: [build, scan]
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: write
      id-token: write # jeton OIDC pour la signature sans clé
      attestations: write
      artifact-metadata: write
    env:
      DIGEST: ${{ needs.build.outputs.digest }}
    steps:
      - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
        with:
          cosign-release: v3.1.3
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: sbom
      - name: Signer le digest et attester son SBOM
        run: |
          cosign sign --yes "${IMAGE}@${DIGEST}"
          cosign attest --yes --type cyclonedx --predicate sbom.cdx.json "${IMAGE}@${DIGEST}"
      - name: Provenance SLSA (attestation GitHub)
        uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
        with:
          subject-name: ${{ env.IMAGE }}
          subject-digest: ${{ needs.build.outputs.digest }}
          push-to-registry: true

  promote-production:
    needs: [build, sign]
    runs-on: ubuntu-24.04
    environment: production
    concurrency:
      group: promotion-production
      queue: max
    permissions:
      contents: read
      packages: read
    env:
      DIGEST: ${{ needs.build.outputs.digest }}
      IDENTITE: https://github.com/${{ github.repository }}/.github/workflows/livraison.yml@refs/heads/main
      EMETTEUR: https://token.actions.githubusercontent.com
    steps:
      - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
        with:
          cosign-release: v3.1.3
      - name: Vérifier signature et SBOM du digest avant écriture
        run: |
          cosign verify --certificate-identity "$IDENTITE" \
            --certificate-oidc-issuer "$EMETTEUR" "${IMAGE}@${DIGEST}" > /dev/null
          cosign verify-attestation --type cyclonedx --certificate-identity "$IDENTITE" \
            --certificate-oidc-issuer "$EMETTEUR" "${IMAGE}@${DIGEST}" > /dev/null
      - name: Contrôle de fraîcheur
        id: fraicheur
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          tete=$(gh api "repos/${GITHUB_REPOSITORY}/commits/main" --jq .sha)
          if [ "$tete" = "$GITHUB_SHA" ]; then
            echo "a_jour=true" >> "$GITHUB_OUTPUT"
          else
            echo "::notice::main pointe sur ${tete} : promotion de ${GITHUB_SHA} abandonnée"
          fi
      - if: steps.fraicheur.outputs.a_jour == 'true'
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          repository: mon-org/gitops
          token: ${{ secrets.GITOPS_TOKEN }}
      - name: Écrire le digest dans le dépôt GitOps
        if: steps.fraicheur.outputs.a_jour == 'true'
        run: |
          yq -i ".image.digest = \"${DIGEST}\"" envs/production/values.yaml
          git config user.name "livraison"
          git config user.email "livraison@users.noreply.github.com"
          git diff --quiet || {
            git commit -am "production: ${GITHUB_SHA::12} ${DIGEST}"
            git push
          }

Points de lecture :

  • scan n'a que la lecture du registre : une vulnérabilité CRITICAL ou HIGH corrigeable fait échouer le job, donc ni signature ni promotion.
  • sign est le seul job doté de id-token: write. cosign-installer 4.1.2 installe par défaut cosign 3.0.6 ; l'exemple impose 3.1.3, publiée en août 2026 avec la correction d'un contournement de vérification (avis GHSA-fx35-mq7g-6g98). Pour un dépôt privé hors GitHub Enterprise Cloud, actions/attest doit céder la place à un autre générateur de provenance.
  • promote-production revérifie signature et SBOM avec l'identité exacte du workflow, contrôle la fraîcheur, puis écrit le digest avec un jeton limité au dépôt GitOps ; le chart doit consommer {{ .Values.image.repository }}@{{ .Values.image.digest }}. yq et gh sont préinstallés sur l'image Ubuntu 24.04 des runners GitHub. Une promotion en recette suit le même modèle, en amont.

Ce workflow est un exemple minimal, relu contre la documentation des actions citées mais non exécuté tel quel.

Promouvoir et admettre le seul digest vérifié

Sérialiser ne suffit pas : le contrôle de fraîcheur

Deux fusions rapprochées lancent deux exécutions de la chaîne, et rien ne garantit qu'elles terminent dans l'ordre des commits. La clé concurrency de GitHub Actions sérialise selon des règles précises : par défaut, un seul job attend dans un groupe et tout nouveau job en attente annule le précédent ; avec queue: max, jusqu'à 100 jobs attendent et sont servis dans l'ordre où chacun a commencé à attendre, pas dans l'ordre de déclenchement des workflows. Si A est fusionné avant B mais que la chaîne de B termine la première, B est promu, puis A arrive dans la file et rétablirait une version plus ancienne. La sérialisation empêche deux écritures simultanées, pas une régression.

Le contrôle de fraîcheur du workflow n'autorise que le commit en tête de main. Si la chaîne de ce commit échoue au scan, rien de plus récent n'est promu avant la fusion suivante, ce qui peut être le comportement voulu. Une variante enregistre le commit en production et n'accepte que ses descendants (git merge-base --is-ancestor "$COMMIT_DEPLOYE" "$GITHUB_SHA"), au prix d'un historique complet. Le contrôle s'exécute après l'éventuelle approbation manuelle de l'environnement ; si le git push échoue parce que le dépôt GitOps a changé, la reprise refait le contrôle au lieu de forcer.

Sur GitLab CI, resource_group garantit qu'un seul job du groupe s'exécute à la fois, et le réglage « Prevent outdated deployment jobs » vise précisément le cas d'un déploiement plus ancien qui écraserait un déploiement plus récent.

Vérifier à l'admission : Kyverno ou policy-controller

La vérification avant promotion protège le chemin prévu ; le contrôle d'admission protège les autres : un kubectl apply manuel, un chart tiers, une image poussée hors chaîne. Kyverno 1.19, publiée en août 2026, déclare dépréciés ClusterPolicy et Policy, donc leurs règles verifyImages, et annonce leur retrait en 1.20. La vérification d'image passe par ImageValidatingPolicy, dans l'API policies.kyverno.io/v1.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
  name: images-signees-par-la-livraison
spec:
  validationActions: [Deny] # commencer en [Audit] sur un cluster existant
  failurePolicy: Fail
  webhookConfiguration:
    timeoutSeconds: 15
  matchConstraints:
    resourceRules:
      - apiGroups: ['']
        apiVersions: ['v1']
        operations: ['CREATE', 'UPDATE']
        resources: ['pods']
  matchImageReferences:
    - glob: 'ghcr.io/mon-org/mon-app*'
  attestors:
    - name: livraison
      cosign:
        keyless:
          identities:
            - subject: 'https://github.com/mon-org/mon-app/.github/workflows/livraison.yml@refs/heads/main'
              issuer: 'https://token.actions.githubusercontent.com'
        ctlog:
          url: 'https://rekor.sigstore.dev'
  attestations:
    - name: sbom
      intoto:
        type: https://cyclonedx.org/bom
  validationConfigurations:
    mutateDigest: true
    verifyDigest: true
    required: true
  validations:
    - expression: >-
        images.containers.map(image, verifyImageSignatures(image, [attestors.livraison])).all(e, e > 0)
      message: image non signée par le workflow de livraison attendu
    - expression: >-
        images.containers.map(image, verifyAttestationSignatures(image, attestations.sbom, [attestors.livraison])).all(e, e > 0)
      message: attestation SBOM CycloneDX absente ou non signée

L'attesteur fixe la même identité et le même émetteur que le workflow ; https://cyclonedx.org/bom est le type de prédicat que cosign inscrit pour --type cyclonedx. mutateDigest remplace un tag par le digest, verifyDigest exige une vérification par digest, et failurePolicy: Fail refuse l'admission si la vérification ne peut pas avoir lieu : la documentation de Sigstore demande qu'un système de vérification d'attestations échoue fermé plutôt qu'ouvert. Avant de passer en Deny :

  • pour un registre privé, spec.credentials est le seul moyen de fournir des identifiants à ce type de politique ;
  • l'exemple, calqué sur la documentation, n'évalue que images.containers : la couverture des conteneurs d'initialisation et éphémères est à vérifier dans la documentation de la version installée ;
  • une attestation GitHub issue d'un dépôt privé passe par une autre racine de confiance, à déclarer (trustedRoot) ;
  • un passage en [Audit] révèle les images en service qui échoueraient.

L'alternative du projet Sigstore, policy-controller, repose sur ClusterImagePolicy (policy.sigstore.dev/v1beta1), activée par espace de noms avec le label policy.sigstore.dev/include: "true" ; par défaut, une image qu'aucune politique ne couvre y est refusée, et les politiques sont évaluées sur le digest de l'image.

Pour mettre en place cette chaîne sur une plateforme existante, avec la politique d'admission et la promotion GitOps, voir Mise en production Kubernetes : ouvrir par étapes.

Clients régulés et CRA : ce qui change pour un éditeur

Ce qu'un client final régulé peut exiger

Un client final soumis à des exigences réglementaires strictes peut transmettre à ses éditeurs une liste d'exigences de ce type. La chaîne décrite plus haut en produit les preuves.

Exigences qu'un client final régulé peut transmettre à un éditeur et preuve correspondante produite par la chaîne de livraison
ExigencePreuve produite par la chaîne
SBOM par version livréeSBOM du digest, attesté et signé, stocké à côté de l'image
Image signée et vérifiableSignature cosign du digest, avec l'identité du workflow
Aucune vulnérabilité critique ou haute non traitéeVerdict bloquant et fichier d'exceptions datées, versionné
Contrôleur d'admissionPolitique versionnée dans le dépôt GitOps, rapports en mode audit
Images synchronisées vers le registre du clientMême digest, avec signature et attestations copiées

Signature et attestations étant stockées à côté de l'image, elles doivent être copiées avec elle, faute de quoi la vérification échoue dans le registre du client. Les attentes propres aux éditeurs qui livrent aussi une version installable sont décrites sur la page Éditeurs SaaS : livrer on-premise et vendre via AWS Marketplace.

Le CRA à la date du

Le règlement (UE) 2024/2847 du 23 octobre 2024, publié au Journal officiel le 20 novembre 2024, fixe des exigences de cybersécurité pour les produits comportant des éléments numériques.

Dates d'application du règlement (UE) 2024/2847 et obligations concernées
DateCe qui s'applique
11 juin 2026Notification des organismes d'évaluation de la conformité
11 septembre 2026Article 14 : signalement des vulnérabilités activement exploitées et des incidents graves
11 décembre 2027Application de l'ensemble du règlement

Depuis le 11 septembre 2026, un fabricant notifie les vulnérabilités activement exploitées et les incidents graves ayant un impact sur la sécurité de son produit au centre de réponse aux incidents de sécurité informatique (CSIRT) désigné comme coordinateur et à l'Agence de l'Union européenne pour la cybersécurité (ENISA), par la plateforme unique de signalement prévue à l'article 16 du règlement et gérée par l'ENISA. Les délais de l'article 14 comprennent notamment, pour une vulnérabilité activement exploitée, une alerte précoce dans les 24 heures, une notification dans les 72 heures et un rapport final au plus tard 14 jours après la mise à disposition d'une mesure corrective ou d'atténuation ; pour un incident grave, une alerte précoce dans les 24 heures, une notification dans les 72 heures et un rapport final dans le mois qui suit cette notification.

SaaS pur ou version distribuée : une question de champ

Selon ses considérants, le CRA ne régit pas les services, comme le logiciel en tant que service (SaaS), sauf les solutions de traitement de données à distance liées à un produit : un traitement à distance dont le logiciel est conçu et développé par le fabricant ou sous sa responsabilité, et sans lequel le produit ne pourrait pas remplir l'une de ses fonctions. Les mêmes considérants rappellent que la directive (UE) 2022/2555, dite NIS2, s'applique aux services d'informatique en nuage et aux modèles de service en nuage comme le SaaS. La lecture technique qui en découle reste à faire valider par un conseil juridique :

  • Un SaaS pur, exploité par l'éditeur et consommé à distance sans produit distribué auquel il se rattacherait, n'est en principe pas visé par le CRA ; NIS2 peut s'appliquer à l'éditeur selon sa taille et son activité. Ses clients soumis à NIS2 peuvent de toute façon lui répercuter leurs mesures, parmi lesquelles l'article 21 de NIS2 cite la sécurité de la chaîne d'approvisionnement et la sécurité de l'acquisition, du développement et de la maintenance des systèmes, y compris le traitement et la divulgation des vulnérabilités.
  • Une version distribuée (images et charts pour un déploiement on-premise ou en air gap, agent, client lourd, application mobile, interface en ligne de commande) peut constituer un produit comportant des éléments numériques, et le service en nuage qui lui est nécessaire une solution de traitement de données à distance. Si c'est le cas, les obligations du fabricant s'appliquent : le signalement depuis le 11 septembre 2026, les exigences essentielles, dont la gestion des vulnérabilités, à partir du 11 décembre 2027.

Le règlement fait du SBOM une pièce de la gestion des vulnérabilités (annexe I, partie II, point 1) : les autorités de surveillance du marché peuvent en demander communication au fabricant, et la Commission peut en préciser le format et les éléments par actes d'exécution.

Périmètre du droit

CTN Solutions formule des avis techniques et organisationnels, jamais un avis juridique ni un audit légal. Le conseil juridique relève d'un avocat (loi n° 71-1130) ; la certification des comptes d'un commissaire aux comptes.

Ce que la chaîne permet de tenir, et ce qu'elle ne remplace pas

Un délai de 24 heures suppose de répondre vite à une question précise : quelles versions livrées contiennent le composant touché, et où sont-elles déployées ? Un SBOM rattaché à chaque digest, plus un registre des digests livrés par client et par environnement, en font une requête. Sans eux, la réponse passe par la reconstruction d'anciennes versions, qui ne redonne pas nécessairement les mêmes images. La chaîne ne décide ni de ce qui doit être signalé, ni de la qualification d'un produit : elle fournit les faits sur lesquels ces décisions s'appuient.

Sources officielles et limites de lecture

Faits datés

Les dates, délais et versions cités ont été vérifiés le sur EUR-Lex et les dépôts ou documentations officiels des outils ; ils sont à revérifier avant toute décision. Cette lecture ne qualifie aucun produit au titre du CRA ni aucune entité au titre de NIS2 : le texte du règlement publié au Journal officiel et le conseil juridique de l'éditeur priment.

Vérifié le

Les comportements d'outillage valent pour cosign 3.1.3, Kyverno 1.19, Trivy 0.74, SLSA 1.2 et les runners GitHub Ubuntu 24.04 ; la documentation de la version installée prime. Le workflow et la politique sont des exemples minimaux, non exécutés tels quels, qui supposent le registre GitHub, un dépôt GitOps séparé et une image mono-architecture. Aucune chaîne DevSecOps ne garantit l'absence de vulnérabilité ni la conformité d'un produit : elle rend vérifiable ce qui a été contrôlé, sur quel artefact et selon quel seuil. Pour appliquer cette grille à une plateforme Kubernetes en production, voir Mise en production Kubernetes : ouvrir par étapes.

Sources

  1. Règlement (UE) 2024/2847 du Parlement européen et du Conseil du 23 octobre 2024 (Cyber Resilience Act), EUR-Lex, https://eur-lex.europa.eu/eli/reg/2024/2847/oj, consulté le 29/09/2026.
  2. Directive (UE) 2022/2555 du Parlement européen et du Conseil (NIS2), article 21, EUR-Lex, https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX%3A32022L2555, consulté le 29/09/2026.
  3. SP 800-204C, Implementation of DevSecOps for a Microservices-based Application with Service Mesh, NIST, https://csrc.nist.gov/pubs/sp/800/204/c/final, consulté le 29/09/2026.
  4. Images, documentation Kubernetes, https://kubernetes.io/docs/concepts/containers/images/, consulté le 29/09/2026.
  5. ContainerStatus, champ imageID, API Kubernetes core/v1, https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api/core/v1/types.go, consulté le 29/09/2026.
  6. Références CLI cosign sign, cosign attest, cosign verify et cosign verify-attestation, cosign v3.1.3, https://github.com/sigstore/cosign/tree/v3.1.3/doc, consulté le 29/09/2026.
  7. Release v3.1.3 de cosign, Sigstore, https://github.com/sigstore/cosign/releases/tag/v3.1.3, consulté le 29/09/2026.
  8. Sigstore overview, documentation Sigstore, https://docs.sigstore.dev/about/overview/, consulté le 29/09/2026.
  9. OIDC usage in Fulcio, documentation Sigstore, https://docs.sigstore.dev/certificate_authority/oidc-in-fulcio/, consulté le 29/09/2026.
  10. In-toto attestation verification, documentation Sigstore, https://docs.sigstore.dev/cosign/verifying/attestation/, consulté le 29/09/2026.
  11. Build: Track Basics, SLSA v1.2, https://slsa.dev/spec/v1.2/build-track-basics, consulté le 29/09/2026.
  12. Artifact attestations, GitHub Docs, https://docs.github.com/en/actions/concepts/security/artifact-attestations, consulté le 29/09/2026.
  13. actions/attest, README v4.2.2, GitHub, https://github.com/actions/attest/tree/v4.2.2, consulté le 29/09/2026.
  14. SBOM attestations, Docker Docs, https://docs.docker.com/build/metadata/attestations/sbom/, consulté le 29/09/2026.
  15. Provenance attestations, Docker Docs, https://docs.docker.com/build/metadata/attestations/slsa-provenance/, consulté le 29/09/2026.
  16. ECMA-424, CycloneDX Bill of Materials Specification, Ecma International, https://ecma-international.org/wp-content/uploads/ECMA-424_2nd_edition_december_2025.pdf, consulté le 29/09/2026.
  17. ISO/IEC 5962:2021, SPDX Specification V2.2.1, ISO, https://www.iso.org/standard/81870.html, consulté le 29/09/2026.
  18. Filtering, documentation Trivy v0.74.0, https://github.com/aquasecurity/trivy/blob/v0.74.0/docs/guide/configuration/filtering.md, consulté le 29/09/2026.
  19. Container Image, documentation Trivy v0.74.0, https://github.com/aquasecurity/trivy/blob/v0.74.0/docs/guide/target/container_image.md, consulté le 29/09/2026.
  20. SBOM, documentation Trivy v0.74.0, https://github.com/aquasecurity/trivy/blob/v0.74.0/docs/guide/supply-chain/sbom.md, consulté le 29/09/2026.
  21. Trivy ecosystem supply chain temporarily compromised, avis GHSA-69fq-xp46-6x23, Aqua Security, https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23, consulté le 29/09/2026.
  22. Secure use reference, GitHub Docs, https://docs.github.com/en/actions/reference/security/secure-use, consulté le 29/09/2026.
  23. Control the concurrency of workflows and jobs, GitHub Docs, https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency, consulté le 29/09/2026.
  24. Workflow syntax for GitHub Actions, GitHub Docs, https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax, consulté le 29/09/2026.
  25. actions/dependency-review-action, README, GitHub, https://github.com/actions/dependency-review-action, consulté le 29/09/2026.
  26. Ubuntu 24.04 runner image, logiciels installés, actions/runner-images, https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md, consulté le 29/09/2026.
  27. Tags des actions citées dans le workflow (SHA de commit), dépôts GitHub publics correspondants, résolus le 29/09/2026 ; action.yml de sigstore/cosign-installer v4.1.2, https://github.com/sigstore/cosign-installer/blob/v4.1.2/action.yml, consulté le 29/09/2026.
  28. ImageValidatingPolicy, documentation Kyverno, https://kyverno.io/docs/policy-types/image-validating-policy/, consulté le 29/09/2026.
  29. Announcing Kyverno Release 1.19, Kyverno, https://kyverno.io/blog/2026/08/20/announcing-kyverno-release-1.19/, consulté le 29/09/2026.
  30. Policy Controller overview, documentation Sigstore, https://docs.sigstore.dev/policy-controller/overview/, consulté le 29/09/2026.
  31. Resource group, documentation GitLab, https://docs.gitlab.com/ci/resource_groups/, consulté le 29/09/2026.
  32. Deployment safety, documentation GitLab, https://docs.gitlab.com/ci/environments/deployment_safety/, consulté le 29/09/2026.
  33. Dépôts officiels des outils cités dans le tableau des contrôles (github/codeql, semgrep/semgrep, google/osv-scanner, gitleaks/gitleaks, bridgecrewio/checkov, anchore/grype, anchore/syft), README à la dernière version publiée, https://github.com/anchore/grype, consulté le 29/09/2026.

Parlons de votre contexte.

Cet article expose une pratique générale ; votre situation a ses propres contraintes. Décrivez-la, nous répondons avec un périmètre.

Ouvrir le formulaire