Aller au contenu
Signer et vérifier ses images

Signer et vérifier ses images

300 Concevoir ⏱ 1 h 05 dockercosignsigstore

À la fin, vous saurez

  • Expliquer ce qu'une signature apporte qu'une empreinte n'apporte pas
  • Signer une image par son empreinte avec une paire de clés Cosign, et vérifier la signature
  • Signer et vérifier une attestation (SBOM) attachée à une image
  • Expliquer la signature sans clé de Sigstore (OIDC, Fulcio, Rekor) et vérifier une image publique ainsi signée
  • Situer les moyens de faire respecter la vérification au déploiement

Prérequis

Testé avec cosign 3.1.3 docker 29.8.1 registry 3 syft 1.52.0 , vérifié le 1 octobre 2026

Pourquoi

La leçon 7 du cours précédent a établi qu'une empreinte d'image est une identité infalsifiable : une empreinte sha256:... désigne pour toujours les mêmes octets. Mais elle ne répond qu'à la question « est-ce bien cette image ? », pas à « qui l'a produite ? ». Un attaquant qui obtient le droit de pousser dans votre registre pousse sa image, avec sa propre empreinte, tout aussi valide, et met à jour l'étiquette 1.0. Rien dans l'empreinte ne le trahit. Et la provenance de la leçon 8, jointe à l'image, ne le trahit pas davantage : il peut pousser aussi une fausse provenance.

Il manque une preuve d'origine : une signature, produite avec un secret que seul le producteur légitime détient, et vérifiable par n'importe qui. C'est le dernier maillon de la chaîne de confiance : la CI construit (leçon 10), signe l'image et ses attestations ; le cluster refuse tout ce qui n'est pas signé par la CI.

Pour les clients publics de Lyneko, la question est concrète : comment une collectivité peut-elle s'assurer que l'image déployée chez elle est bien celle que Lyneko a construite et testée, et non une image altérée entre-temps, chez Lyneko, chez l'hébergeur ou dans le registre ?

Les concepts

Ce que signe une signature d'image

Une signature d'image porte sur l'empreinte du manifeste, pas sur l'étiquette. La signature dit : « le détenteur de cette clé atteste que l'image sha256:11019f6b... est la sienne ». Conséquences :

  • signer une étiquette n'a pas de sens : on signe toujours une empreinte ;
  • si l'étiquette est déplacée vers une autre image, la signature ne la suit pas : la nouvelle image n'est pas signée ;
  • vérifier, c'est vérifier qu'il existe, pour l'empreinte de l'image que l'on s'apprête à exécuter, une signature valide d'une identité de confiance.

Sigstore et Cosign

Sigstore est un projet de l'OpenSSF qui fournit les outils et l'infrastructure publique de signature d'artefacts. Son outil pour les images est Cosign. Deux modes :

  • Avec une paire de clés : vous générez une clé privée (protégée par une phrase de passe ou rangée dans un service de gestion de clés) et une clé publique que vous distribuez. Simple à comprendre, mais il faut protéger, distribuer et faire tourner la clé.
  • Sans clé (keyless) : pas de clé à long terme. Au moment de signer, Cosign génère une clé éphémère, prouve votre identité auprès d'un fournisseur OIDC (votre compte, ou l'identité de la tâche GitHub Actions), obtient de l'autorité de certification Fulcio un certificat de courte durée (10 minutes) liant la clé éphémère à cette identité, signe, et inscrit la signature dans le journal de transparence Rekor, public et en ajout seul. Vérifier, c'est vérifier la signature, le certificat, l'identité attendue et l'inscription dans le journal.

Où sont stockées les signatures

Dans le registre, à côté de l'image. Cosign 3 produit un bundle Sigstore (signature, certificat ou clé, preuve d'inscription au journal) qu'il rattache à l'image par l'API referrers de la spécification de distribution OCI 1.1. Si le registre ne la propose pas, il utilise le schéma d'étiquettes de repli prévu par la même spécification : une étiquette sha256-<empreinte> qui pointe vers un index des artefacts rattachés.

Et Docker Content Trust ?

Docker a longtemps proposé son propre mécanisme de signature, Docker Content Trust (fondé sur Notary v1). Il a été retiré du client Docker avec la version 29. Les mécanismes actuels sont Sigstore (Cosign) et Notation (Notary Project v2), qui stockent tous deux leurs signatures comme des artefacts OCI.

En pratique

Note

Les signatures de cette partie sont faites avec une paire de clés sans inscription au journal de transparence public : inscrire des signatures d'essai dans Rekor les publierait de façon permanente. Le registre local de démonstration est présenté sur le port 5000 ; sur la machine de test, il écoutait sur un autre port. Cosign a été utilisé en binaire autonome (version 3.1.3).

Générer une paire de clés

$ export COSIGN_PASSWORD='phrase-de-passe-de-demonstration'
$ cosign generate-key-pair
Private key written to cosign.key
Public key written to cosign.pub
$ ls -l cosign.*
-rw------- 1 vous vous 653 oct.   1 15:13 cosign.key
-rw-r--r-- 1 vous vous 178 oct.   1 15:13 cosign.pub
$ head -1 cosign.key
-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY-----
$ cat cosign.pub
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjhFIWC9OLRTmEAc7GD1qDBCP4P/w
CfEjCkEyoLct2L+4r/d5I8OFsNnST35Erjn86rGQINFVRx+lEmfosjozBQ==
-----END PUBLIC KEY-----

Une clé ECDSA P-256 : la clé privée est chiffrée avec la phrase de passe (lue dans COSIGN_PASSWORD) et ses droits sont restreints au propriétaire ; la clé publique est faite pour être diffusée.

Signer une image, par son empreinte

Poussons l'image scratch de signalements-export (leçon 5) dans le registre, et récupérons l'empreinte de son manifeste :

$ docker buildx build -q -t 127.0.0.1:5000/export:1.0 --push export
$ D=$(docker buildx imagetools inspect 127.0.0.1:5000/export:1.0 --format '{{.Manifest.Digest}}')
$ echo "empreinte : $D"
empreinte : sha256:11019f6b3fd8ead44eaeeaf387f368951f82d1d90308144e8effef852835f678

Premier essai de signature, en demandant de ne pas utiliser le journal public avec l'option qu'on trouve dans la plupart des tutoriels :

$ cosign sign --yes --key cosign.key --tlog-upload=false --allow-http-registry 127.0.0.1:5000/export@$D
Flag --tlog-upload has been deprecated, prefer using a --signing-config file with no transparency log services
Error: --tlog-upload=false is not supported with --signing-config or --use-signing-config. Provide a signing config with --signing-config without a transparency log service, ...

Cosign 3 a changé de modèle : les services utilisés pour signer (autorité de certification, journal de transparence, horodatage) sont décrits dans une configuration de signature. Par défaut, c'est celle de l'infrastructure publique de Sigstore. Pour une signature purement locale, on crée une configuration vide :

$ cosign signing-config create --out signing-config-local.json
$ cat signing-config-local.json
{"mediaType":"application/vnd.dev.sigstore.signingconfig.v0.2+json","rekorTlogConfig":{},"tsaConfig":{}}
$ cosign sign --yes --key cosign.key --signing-config signing-config-local.json --allow-http-registry 127.0.0.1:5000/export@$D
Signing artifact...
Pushing signature to: 127.0.0.1:5000/export
  • On signe export@sha256:..., jamais export:1.0 : Cosign accepte une étiquette, mais il la résout au moment de signer, et l'on ne sait plus exactement ce qui a été signé si l'étiquette a bougé entre la construction et la signature.
  • --allow-http-registry n'est nécessaire que pour ce registre de test sans TLS.

Vérifier

$ cosign verify --key cosign.pub --insecure-ignore-tlog=true --allow-http-registry 127.0.0.1:5000/export:1.0
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the signature.

Verification for 127.0.0.1:5000/export:1.0 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The signatures were verified against the specified public key

[{"critical":{"identity":{"docker-reference":"127.0.0.1:5000/export:1.0"},"image":{"docker-manifest-digest":"sha256:11019f6b3fd8ead44eaeeaf387f368951f82d1d90308144e8effef852835f678"},"type":"https://sigstore.dev/cosign/sign/v1"},"optional":{}}]

La vérification résout l'étiquette en empreinte, trouve une signature pour cette empreinte, et la valide avec la clé publique. Le document signé (payload) affiche l'empreinte du manifeste et la référence. Notez la ligne « Existence of the claims in the transparency log was verified offline » : Cosign l'affiche dans tous les cas, mais ici aucun journal n'a été consulté, puisque la signature n'y a pas été inscrite et que la vérification a été demandée sans lui. L'avertissement, lui, est légitime : sans journal de transparence, personne d'autre ne peut savoir que cette signature existe, ni détecter qu'une clé volée a servi à signer à votre insu.

Les échecs qui protègent

Une étiquette réécrite. Un attaquant pousse une autre image sous l'étiquette 1.0 :

$ docker buildx build -q -f Dockerfile.piege -t 127.0.0.1:5000/export:1.0 --push .
$ cosign verify --key cosign.pub --insecure-ignore-tlog=true --allow-http-registry 127.0.0.1:5000/export:1.0
Error: no signatures found
error during command execution: no signatures found

L'étiquette désigne maintenant une image dont l'empreinte n'a jamais été signée. L'image d'origine, elle, reste vérifiable par son empreinte :

$ cosign verify --key cosign.pub --insecure-ignore-tlog=true --allow-http-registry \
    127.0.0.1:5000/export@sha256:11019f6b3fd8ead44eaeeaf387f368951f82d1d90308144e8effef852835f678
Verification for 127.0.0.1:5000/export@sha256:11019f6b3fd8ead44eaeeaf387f368951f82d1d90308144e8effef852835f678 --
...
  - The signatures were verified against the specified public key

Une autre clé. Une signature n'a de valeur que rapportée à une identité attendue. Vérifions avec une autre clé publique :

$ cosign generate-key-pair --output-key-prefix autre
$ cosign verify --key autre.pub --insecure-ignore-tlog=true --allow-http-registry 127.0.0.1:5000/export@sha256:11019f6b...
Error: no matching attestations: failed to verify signature: could not verify envelope: accepted signatures do not match threshold, Found: 0, Expected 1

Une signature existe, mais aucune n'est valide pour cette clé. (Le message parle d'« attestations » parce que Cosign 3 range même une simple signature dans une enveloppe DSSE, le format des attestations.) C'est ce qui protège d'un attaquant qui signerait sa propre image avec sa propre clé : la politique de vérification ne fait confiance qu'à votre clé.

Signer une attestation

La signature de l'image dit « c'est la mienne ». Une attestation signée dit « j'affirme ceci à propos de cette image » : par exemple, « voici son SBOM ». Produisons le SBOM CycloneDX de l'image avec Syft (leçon 8), directement depuis le registre, puis attestons-le :

$ docker run --rm --network host -v "$PWD":/t anchore/syft:v1.52.0 \
    registry:127.0.0.1:5000/export@sha256:11019f6b... -o cyclonedx-json=/t/export.cdx.json -q
$ cosign attest --yes --key cosign.key --signing-config signing-config-local.json --allow-http-registry \
    --type cyclonedx --predicate export.cdx.json 127.0.0.1:5000/export@sha256:11019f6b...
Using payload from: export.cdx.json
Signing artifact...

La vérification d'une attestation précise le type attendu, et renvoie l'attestation signée (en base64) :

$ cosign verify-attestation --key cosign.pub --insecure-ignore-tlog=true --allow-http-registry \
    --type cyclonedx 127.0.0.1:5000/export@sha256:11019f6b... 2>/dev/null \
  | jq -r '.payload' | base64 -d \
  | jq -c '{predicateType, sujet: .subject[0].digest.sha256[0:12], composants: (.predicate.components | length)}'
{"predicateType":"https://cyclonedx.org/bom","sujet":"11019f6b3fd8","composants":3}

Le sujet de l'attestation est l'empreinte de l'image, le prédicat est le SBOM (3 composants pour cette image scratch : le binaire, son module, la bibliothèque standard de Go). Demander un autre type échoue, avec la liste de ce qui existe :

$ cosign verify-attestation --key cosign.pub --insecure-ignore-tlog=true --allow-http-registry \
    --type slsaprovenance 127.0.0.1:5000/export@sha256:11019f6b...
Error: ... none of the attestations matched the predicate type: slsaprovenance, found: https://cyclonedx.org/bom,https://sigstore.dev/cosign/sign/v1

Contrairement au SBOM joint par BuildKit (leçon 8), ce SBOM-ci est signé : on peut s'y fier autant qu'à la clé qui l'a signé.

Ce qui a été stocké

$ cosign tree --allow-http-registry 127.0.0.1:5000/export@sha256:11019f6b...
📦 Supply Chain Security Related artifacts for an image: 127.0.0.1:5000/export@sha256:11019f6b3fd8ead44eaeeaf387f368951f82d1d90308144e8effef852835f678
└── 🔗 https://cyclonedx.org/bom artifacts via OCI referrer: 127.0.0.1:5000/export@sha256:85af786fad6f4e8a4d23a59e41525f8e3609a4bbf122d412930c623cb5666f98
   └── 🍒 sha256:dd1355c6e93f6ba9644ed91cc2df393ddd226fbed4b9beb1ad8d3ba139c3eb3d
└── 🔗 https://sigstore.dev/cosign/sign/v1 artifacts via OCI referrer: 127.0.0.1:5000/export@sha256:c9c4a60cea7543517e29dccde3d705e8e0e0bc32011b034a31b271ebbb9415ed
   └── 🍒 sha256:a28e2a677d8f95aa19699e52b53e958eeca0ea84aa771b12144ed911097900f2
$ curl -s http://127.0.0.1:5000/v2/export/tags/list
{"name":"export","tags":["1.0","sha256-11019f6b3fd8ead44eaeeaf387f368951f82d1d90308144e8effef852835f678"]}
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5000/v2/export/referrers/sha256:11019f6b...
404

Deux artefacts rattachés à l'image : la signature et l'attestation SBOM, chacun sous forme de bundle Sigstore. Le registre de test (registry:3) ne propose pas l'API referrers (réponse 404) : Cosign a utilisé l'étiquette de repli sha256-<empreinte>. Un registre compatible OCI 1.1 (Harbor, Zot, la plupart des registres hébergés récents) l'aurait évitée.

La signature sans clé, vérifiée sur une image publique

Les images distroless de Google sont signées en mode sans clé, depuis leur chaîne de construction. On peut le vérifier sans rien publier : la vérification ne fait que lire le registre et les racines de confiance publiques de Sigstore. Il faut seulement dire qui doit avoir signé :

$ cosign verify gcr.io/distroless/static-debian13:nonroot \
    --certificate-identity=keyless@distroless.iam.gserviceaccount.com \
    --certificate-oidc-issuer=https://accounts.google.com
Verification for gcr.io/distroless/static-debian13:nonroot --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The code-signing certificate was verified using trusted certificate authority certificates
...
$ cosign verify gcr.io/distroless/static-debian13:nonroot \
    --certificate-identity=keyless@distroless.iam.gserviceaccount.com \
    --certificate-oidc-issuer=https://accounts.google.com 2>/dev/null \
  | jq '.[0].optional | {Issuer, Subject, integratedTime: .Bundle.Payload.integratedTime, logIndex: .Bundle.Payload.logIndex}'
{
  "Issuer": "https://accounts.google.com",
  "Subject": "keyless@distroless.iam.gserviceaccount.com",
  "integratedTime": 1789336872,
  "logIndex": 2822616414
}

Il n'y a pas de clé publique à distribuer : la confiance porte sur une identité (le compte de service de construction de distroless, authentifié par Google) et sur un certificat émis par Fulcio pour cette identité. La signature a été inscrite au journal Rekor à l'entrée 2 822 616 414, le 13 septembre 2026 (l'horodatage 1789336872). Une autre identité attendue fait échouer la vérification :

$ cosign verify gcr.io/distroless/static-debian13:nonroot \
    --certificate-identity=attaquant@example.com --certificate-oidc-issuer=https://accounts.google.com
Error: no matching signatures: none of the expected identities matched what was in the certificate, got subjects [keyless@distroless.iam.gserviceaccount.com] with issuer https...

C'est ainsi qu'on vérifie une image de base avant de l'adopter (leçon 1) : non pas « est-elle signée ? », mais « est-elle signée par qui elle doit l'être ? ».

Sous le capot

Ce que contient un bundle Sigstore. Un document JSON (type application/vnd.dev.sigstore.bundle.v0.3+json) qui contient : l'enveloppe signée (au format DSSE, Dead Simple Signing Envelope) avec le document signé, la signature, le matériel de vérification (clé publique ou certificat Fulcio avec sa chaîne), et, si la signature a été inscrite au journal, la preuve d'inclusion Rekor. La preuve permet de vérifier « hors ligne » que l'inscription existe, à partir de la racine de confiance du journal, sans interroger Rekor à chaque vérification.

Pourquoi le journal de transparence compte. Avec une paire de clés seule, si la clé fuit, un attaquant peut signer en secret : personne ne le saura. Avec Rekor, chaque signature est publiquement inscrite et horodatée ; le propriétaire de l'identité peut surveiller le journal et découvrir une signature qu'il n'a pas faite. En mode sans clé, l'horodatage du journal prouve aussi que la signature a été faite pendant les 10 minutes de validité du certificat, ce qui permet de vérifier des années plus tard, bien après l'expiration du certificat.

Comment se fait la vérification sans clé. Cosign télécharge (et met en cache) les racines de confiance de Sigstore, distribuées par un dépôt TUF (The Update Framework) lui-même signé. Il vérifie que le certificat a été émis par Fulcio, que l'identité (Subject) et l'émetteur OIDC correspondent à ceux demandés, que la signature est valide pour la clé du certificat, et que l'inscription au journal est valide et datée pendant la validité du certificat.

Pièges courants

Signer une étiquette. Entre la construction et la signature, l'étiquette a pu bouger. Signez toujours l'empreinte produite par la construction (BuildKit la renvoie dans --metadata-file, la leçon 10 l'exploite).

--tlog-upload=false refusé. Avec Cosign 3, l'option est dépréciée et incompatible avec la configuration de signature par défaut ; il faut une configuration de signature sans journal (cosign signing-config create). Beaucoup de tutoriels et de scripts de CI écrits pour Cosign 2 cassent à cette occasion.

Vérifier sans identité. Une vérification sans clé qui accepterait n'importe quelle identité (--certificate-identity-regexp '.*') ne prouve rien : n'importe qui peut obtenir un certificat Fulcio pour son propre compte et signer n'importe quoi. Fixez l'identité exacte et l'émetteur, et pour une CI, le dépôt et la branche (l'identité d'une tâche GitHub Actions contient le chemin du workflow et la référence Git).

Croire qu'une image signée est sûre. La signature prouve l'origine, pas l'absence de vulnérabilités ni de porte dérobée. Une CI compromise signe des images compromises avec la vraie clé. D'où la combinaison : signature, provenance (qu'est-ce qui a été construit, comment), reproductibilité (leçon 6), et analyses.

Copier une image sans ses signatures. docker pull puis docker push vers un autre registre ne copie pas les artefacts rattachés. Utilisez cosign copy ou un outil qui copie les artefacts référents, sinon la vérification échoue dans le registre de destination avec no signatures found.

Sécurité

  • Protégez la clé privée comme un secret critique. Dans un service de gestion de clés (KMS de votre fournisseur, Vault), jamais en clair dans un dépôt ou une variable de CI longue durée. Cosign sait signer avec une clé KMS sans jamais la voir (--key awskms://, gcpkms://, hashivault://...).
  • Préférez le mode sans clé en CI quand votre plateforme fournit une identité OIDC (GitHub Actions, GitLab CI) : pas de clé à voler, chaque signature attachée à un workflow, une branche, un commit, et inscrite au journal public. Pour des images privées dont l'existence même est confidentielle, une instance Sigstore privée, ou une paire de clés en KMS avec un journal privé, évite d'inscrire leurs empreintes dans le journal public.
  • La vérification doit être automatique et bloquante. Une signature que personne ne vérifie ne protège de rien. Le point de contrôle naturel est l'admission dans le cluster : Kyverno (règles verifyImages) ou le policy-controller de Sigstore refusent tout pod dont l'image n'est pas signée par l'identité attendue, et peuvent exiger aussi des attestations (SBOM, provenance) et en contrôler le contenu. Ces outils sont présentés dans le cours Sécurité de Kubernetes.
  • Signez aussi les attestations de provenance et de SBOM : non signées, elles ne valent qu'autant que les droits d'écriture sur le registre (leçon 8).

En production

  • La CI signe, le cluster vérifie. C'est la séparation des rôles : seule la CI détient le moyen de signer (identité OIDC de la tâche, ou clé en KMS) ; les développeurs n'ont pas de quoi produire une image acceptable à la main. La leçon 10 assemble la signature dans le workflow.
  • Signez l'index et ses variantes. Pour une image multi-architectures, signez l'empreinte de l'index ; Cosign peut aussi signer chaque manifeste (--recursive), ce que demandent certaines politiques qui vérifient la variante effectivement exécutée.
  • Prévoyez la rotation. Pour une paire de clés : une procédure de rotation, la publication de la nouvelle clé publique, une période de recouvrement où les deux sont acceptées. Pour le mode sans clé : la confiance porte sur l'identité, il n'y a rien à faire tourner.
  • Choisissez entre Cosign et Notation selon votre écosystème. Notation (Notary Project, CNCF) signe avec des certificats X.509 classiques, ce qui convient aux organisations dotées d'une infrastructure de clés publiques, et s'intègre aux services de clés des grands clouds. Les deux stockent leurs signatures comme des artefacts OCI référents.

Exercices

1. Le cycle complet. Générez une paire de clés, poussez signalements-export dans un registre local, signez-le par son empreinte avec une configuration de signature sans journal, vérifiez, puis reconstruisez l'image avec une modification et poussez-la sous la même étiquette. Que dit la vérification de l'étiquette ? Et de l'ancienne empreinte ?

Solution

Suivez les étapes de la leçon. Après la reconstruction poussée sous la même étiquette, cosign verify ...:1.0 répond no signatures found (la nouvelle empreinte n'est pas signée), tandis que cosign verify ...@<ancienne empreinte> réussit toujours. C'est exactement la protection recherchée : une politique de déploiement qui exige une signature refuserait la nouvelle image.

2. Vérifier une image de base (niveau 200). Vérifiez la signature sans clé de gcr.io/distroless/python3-debian13:nonroot. Puis essayez avec python:3.14-slim et l'identité de votre choix. Que concluez-vous pour votre politique d'images de base ?

Solution

L'image distroless se vérifie avec la même identité que dans la leçon (keyless@distroless.iam.gserviceaccount.com, émetteur https://accounts.google.com). Pour python:3.14-slim, Cosign répond no signatures found : les images officielles Docker ne sont pas signées de cette façon. La politique d'images de base doit donc prévoir comment établir la confiance au cas par cas : signature vérifiable quand l'éditeur en publie une (distroless, Chainguard, Docker Hardened Images publient des signatures et attestations), sinon recopie dans votre registre après analyse et signature par vous-même, ce qui fait de votre organisation le garant de l'image recopiée.

3. Exiger un SBOM signé (niveau 300). Écrivez le script de vérification qu'exécuterait un déploiement : il doit refuser une image si elle n'est pas signée par votre clé, ou si elle n'a pas d'attestation CycloneDX signée par la même clé, ou si cette attestation ne concerne pas la même empreinte.

Solution
#!/usr/bin/env bash
set -euo pipefail
IMAGE=$1                      # par empreinte : registre/depot@sha256:...
CLE=cosign.pub
OPTS="--key $CLE --insecure-ignore-tlog=true --allow-http-registry"
cosign verify $OPTS "$IMAGE" > /dev/null
sujet=$(cosign verify-attestation $OPTS --type cyclonedx "$IMAGE" 2>/dev/null \
        | jq -r '.payload' | base64 -d | jq -r '.subject[0].digest.sha256')
[ "sha256:$sujet" = "${IMAGE#*@}" ] || { echo "attestation pour une autre image" >&2; exit 1; }
echo "image et SBOM vérifiés"

Essai sur l'image signée, puis sur l'image piégée poussée sous l'étiquette 1.0 :

$ ./verifier-deploiement.sh 127.0.0.1:5000/export@sha256:11019f6b...
...
  - The signatures were verified against the specified public key
image et SBOM vérifiés
$ ./verifier-deploiement.sh 127.0.0.1:5000/export@$(docker buildx imagetools inspect 127.0.0.1:5000/export:1.0 --format '{{.Manifest.Digest}}')
...
error during command execution: no signatures found

(Les lignes de Cosign sont écrites sur la sortie d'erreur et s'affichent donc malgré la redirection.) Cosign vérifie déjà que l'attestation porte sur l'image demandée ; la comparaison explicite rend la règle lisible et testable. En production, on retire --insecure-ignore-tlog et --allow-http-registry, et l'on confie ce contrôle à une politique d'admission (Kyverno, policy-controller) plutôt qu'à un script.

4. Concevoir la confiance (niveau 400). Lyneko livre la même image à trois collectivités, qui l'hébergent chacune sur leur propre cluster. Proposez une architecture de signature et de vérification : qui signe, avec quoi, comment chaque collectivité vérifie, et que se passe-t-il si la clé (ou l'identité) de signature de Lyneko est compromise.

Solution

Signature par la CI de Lyneko, en mode sans clé avec l'identité OIDC du workflow de publication (dépôt, workflow, branche protégée), ou avec une clé en KMS si les images ne doivent pas apparaître dans le journal public. Publication de la politique de vérification à chaque collectivité : identité et émetteur attendus (ou clé publique), types d'attestation exigés (SBOM, provenance). Chaque collectivité recopie l'image et ses artefacts dans son registre (cosign copy) et fait vérifier à l'admission de son cluster. En cas de compromission : révocation de l'identité ou de la clé, surveillance du journal Rekor pour repérer les signatures illégitimes et leur date, communication des empreintes à refuser, re-signature des images légitimes avec la nouvelle identité ou clé, et mise à jour des politiques des trois collectivités. La provenance signée et la reproductibilité (leçons 6 et 8) permettent de distinguer les images légitimes des images forgées.

Récapitulatif

  • Une empreinte prouve quoi, une signature prouve qui. On signe toujours une empreinte, jamais une étiquette.
  • Cosign signe avec une paire de clés ou sans clé (OIDC, certificat Fulcio de 10 minutes, journal de transparence Rekor). Avec Cosign 3, une signature sans journal passe par une configuration de signature sans service.
  • La vérification échoue quand l'étiquette désigne une image non signée, ou quand la signature n'est pas celle de l'identité attendue : c'est ce qui protège.
  • Les attestations se signent aussi (cosign attest, cosign verify-attestation --type) : un SBOM signé vaut la confiance accordée à sa clé.
  • Signatures et attestations vivent dans le registre comme artefacts référents (API referrers d'OCI 1.1, ou étiquette de repli).
  • La vérification doit être automatique et bloquante, à l'admission dans le cluster.

Pour aller plus loin

  • La documentation de Sigstore, en particulier le modèle de sécurité et la signature sans clé dans GitHub Actions.
  • La documentation de Kyverno sur les règles verifyImages, pour exiger signatures et attestations au déploiement.
  • Le projet Notary et l'outil Notation, pour une approche fondée sur X.509.
  • Leçon suivante : assembler toute la chaîne (construction, cache, tests, SBOM, provenance, analyse, signature, publication) dans un workflow de CI.
Voir ma constellation →

Sources