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 Corentin Mas, 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.
- 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.
- 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.
- 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ôle | Moment | Objet analysé | Ce qu'il bloque | Exemples d'outils |
|---|---|---|---|---|
| SAST (analyse statique du code) | Pull request | Code modifié | La fusion | CodeQL, Semgrep |
| SCA (analyse des dépendances) | Pull request | Manifestes et fichiers de verrouillage | La fusion | dependency-review-action, OSV-Scanner |
| Détection de secrets | Pull request | Diff et historique Git | La fusion | Gitleaks |
| Analyse de l'infrastructure as code | Pull request | Terraform, charts Helm, manifestes | La fusion | Trivy, Checkov |
| Scan d'image | Après construction | Digest final | La promotion | Trivy, Grype |
| SBOM | Après construction | Digest final | Rien : c'est une preuve | Syft, Trivy, BuildKit |
| Signature et provenance | Après le verdict du scan | Digest final | L'admission, si elle vérifie | cosign, actions/attest |
| Contrôle d'admission | Création du pod | Images du pod | Le déploiement | Kyverno, 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
| Digest | Où le lire | Commande ou champ | Ce qu'il établit |
|---|---|---|---|
| Construit et scanné | Sortie digest de la construction, rapport de scan | trivy image ... "${IMAGE}@${DIGEST}" | Le contenu analysé et son verdict |
| Déclaré | Dépôt GitOps | yq '.image.digest' envs/production/values.yaml | Ce que l'outil GitOps doit déployer |
| Exécuté | Statut des pods | .status.containerStatuses[*].imageID | Ce 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 -cDeux 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
- 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.
- Après la fusion sur main, l'image est construite une seule fois ; elle est désormais désignée par son digest D.
- Le digest D est scanné. Si une vulnérabilité hors exception est trouvée, la promotion est refusée.
- Sinon, D est signé, et son SBOM ainsi que sa provenance sont attestés.
- 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.
- Le dépôt GitOps reçoit la référence image@D.
- À 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.
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
| Critère | CycloneDX | SPDX |
|---|---|---|
| Norme | ECMA-424 | ISO/IEC 5962:2021 (SPDX 2.2.1) |
| Production possible | Trivy (--format cyclonedx), Syft | BuildKit (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/attestdoit 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 }}.yqetghsont 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éeL'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.credentialsest 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.
| Exigence | Preuve produite par la chaîne |
|---|---|
| SBOM par version livrée | SBOM du digest, attesté et signé, stocké à côté de l'image |
| Image signée et vérifiable | Signature cosign du digest, avec l'identité du workflow |
| Aucune vulnérabilité critique ou haute non traitée | Verdict bloquant et fichier d'exceptions datées, versionné |
| Contrôleur d'admission | Politique versionnée dans le dépôt GitOps, rapports en mode audit |
| Images synchronisées vers le registre du client | Mê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.
| Date | Ce qui s'applique |
|---|---|
| 11 juin 2026 | Notification des organismes d'évaluation de la conformité |
| 11 septembre 2026 | Article 14 : signalement des vulnérabilités activement exploitées et des incidents graves |
| 11 décembre 2027 | Application 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
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
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
- 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.
- 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.
- 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.
- Images, documentation Kubernetes, https://kubernetes.io/docs/concepts/containers/images/, consulté le 29/09/2026.
- 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.
- 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.
- Release v3.1.3 de cosign, Sigstore, https://github.com/sigstore/cosign/releases/tag/v3.1.3, consulté le 29/09/2026.
- Sigstore overview, documentation Sigstore, https://docs.sigstore.dev/about/overview/, consulté le 29/09/2026.
- OIDC usage in Fulcio, documentation Sigstore, https://docs.sigstore.dev/certificate_authority/oidc-in-fulcio/, consulté le 29/09/2026.
- In-toto attestation verification, documentation Sigstore, https://docs.sigstore.dev/cosign/verifying/attestation/, consulté le 29/09/2026.
- Build: Track Basics, SLSA v1.2, https://slsa.dev/spec/v1.2/build-track-basics, consulté le 29/09/2026.
- Artifact attestations, GitHub Docs, https://docs.github.com/en/actions/concepts/security/artifact-attestations, consulté le 29/09/2026.
- actions/attest, README v4.2.2, GitHub, https://github.com/actions/attest/tree/v4.2.2, consulté le 29/09/2026.
- SBOM attestations, Docker Docs, https://docs.docker.com/build/metadata/attestations/sbom/, consulté le 29/09/2026.
- Provenance attestations, Docker Docs, https://docs.docker.com/build/metadata/attestations/slsa-provenance/, consulté le 29/09/2026.
- 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.
- ISO/IEC 5962:2021, SPDX Specification V2.2.1, ISO, https://www.iso.org/standard/81870.html, consulté le 29/09/2026.
- 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.
- 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.
- 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.
- 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.
- Secure use reference, GitHub Docs, https://docs.github.com/en/actions/reference/security/secure-use, consulté le 29/09/2026.
- 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.
- Workflow syntax for GitHub Actions, GitHub Docs, https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax, consulté le 29/09/2026.
- actions/dependency-review-action, README, GitHub, https://github.com/actions/dependency-review-action, consulté le 29/09/2026.
- 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.
- 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.
- ImageValidatingPolicy, documentation Kyverno, https://kyverno.io/docs/policy-types/image-validating-policy/, consulté le 29/09/2026.
- Announcing Kyverno Release 1.19, Kyverno, https://kyverno.io/blog/2026/08/20/announcing-kyverno-release-1.19/, consulté le 29/09/2026.
- Policy Controller overview, documentation Sigstore, https://docs.sigstore.dev/policy-controller/overview/, consulté le 29/09/2026.
- Resource group, documentation GitLab, https://docs.gitlab.com/ci/resource_groups/, consulté le 29/09/2026.
- Deployment safety, documentation GitLab, https://docs.gitlab.com/ci/environments/deployment_safety/, consulté le 29/09/2026.
- 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.