{"id":518,"date":"2026-03-04T19:20:46","date_gmt":"2026-03-04T18:20:46","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=518"},"modified":"2026-03-24T12:56:56","modified_gmt":"2026-03-24T11:56:56","slug":"lab-signing-verifying-container-images-cosign-github-actions","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-signing-verifying-container-images-cosign-github-actions\/","title":{"rendered":"Lab : Signature et V\u00e9rification d&rsquo;Images Container avec Cosign dans GitHub Actions"},"content":{"rendered":"<h2>Pr\u00e9sentation<\/h2>\n<p>Chaque image container produite par votre pipeline CI\/CD devrait \u00eatre sign\u00e9e cryptographiquement avant d&rsquo;atteindre un quelconque environnement. Les images non sign\u00e9es constituent un angle mort \u2014 vous n&rsquo;avez aucune preuve qu&rsquo;elles proviennent de votre pipeline, aucune garantie qu&rsquo;elles n&rsquo;ont pas \u00e9t\u00e9 alt\u00e9r\u00e9es en transit, et aucun m\u00e9canisme de politique pour bloquer les d\u00e9ploiements non autoris\u00e9s.<\/p>\n<p>Dans ce lab pratique, vous allez :<\/p>\n<ul>\n<li>Signer une image container localement \u00e0 l&rsquo;aide d&rsquo;une paire de cl\u00e9s Cosign.<\/li>\n<li>Configurer la signature keyless dans GitHub Actions en utilisant l&rsquo;infrastructure Fulcio et Rekor de Sigstore.<\/li>\n<li>V\u00e9rifier les signatures localement avec des contr\u00f4les de certificats bas\u00e9s sur l&rsquo;identit\u00e9.<\/li>\n<li>Appliquer la v\u00e9rification des signatures au moment de l&rsquo;admission dans Kubernetes \u00e0 l&rsquo;aide de Kyverno.<\/li>\n<li>Attacher et v\u00e9rifier une attestation SBOM avec Cosign et Syft.<\/li>\n<\/ul>\n<p>\u00c0 la fin de ce lab, vous disposerez d&rsquo;un workflow GitHub Actions complet qui construit, pousse, signe et atteste chaque image \u2014 ainsi qu&rsquo;une politique Kubernetes qui rejette tout ce qui n&rsquo;est pas sign\u00e9.<\/p>\n<h2>Pr\u00e9requis<\/h2>\n<p>Avant de commencer, assurez-vous d&rsquo;avoir les \u00e9l\u00e9ments suivants :<\/p>\n<ul>\n<li><strong>Compte GitHub<\/strong> avec les permissions n\u00e9cessaires pour cr\u00e9er des d\u00e9p\u00f4ts et activer GitHub Actions.<\/li>\n<li><strong>Compte de registre de conteneurs<\/strong> \u2014 ce lab utilise GitHub Container Registry (GHCR), mais Docker Hub fonctionne \u00e9galement.<\/li>\n<li><strong>Docker<\/strong> install\u00e9 et fonctionnel localement.<\/li>\n<li><strong>CLI Cosign<\/strong> install\u00e9 localement :<\/li>\n<\/ul>\n<pre><code># macOS (Homebrew)\nbrew install cosign\n\n# Ou installation depuis les sources avec Go\ngo install github.com\/sigstore\/cosign\/v2\/cmd\/cosign@latest\n\n# V\u00e9rifier l'installation\ncosign version<\/code><\/pre>\n<ul>\n<li><strong>kubectl<\/strong> et <strong>Helm<\/strong> install\u00e9s (pour l&rsquo;exercice Kyverno).<\/li>\n<li><strong>Syft<\/strong> install\u00e9 (pour l&rsquo;exercice SBOM) :<\/li>\n<\/ul>\n<pre><code>brew install syft<\/code><\/pre>\n<p>Vous aurez \u00e9galement besoin d&rsquo;une application simple \u00e0 conteneuriser. Voici une application Go minimale et son Dockerfile que nous utiliserons tout au long du lab.<\/p>\n<p><strong>main.go<\/strong><\/p>\n<pre><code>package main\n\nimport (\n\t\"fmt\"\n\t\"net\/http\"\n)\n\nfunc main() {\n\thttp.HandleFunc(\"\/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"Hello from a signed container!\")\n\t})\n\thttp.ListenAndServe(\":8080\", nil)\n}<\/code><\/pre>\n<p><strong>Dockerfile<\/strong><\/p>\n<pre><code>FROM golang:1.22-alpine AS builder\nWORKDIR \/app\nCOPY main.go .\nRUN go build -o server main.go\n\nFROM alpine:3.19\nCOPY --from=builder \/app\/server \/server\nEXPOSE 8080\nENTRYPOINT [\"\/server\"]<\/code><\/pre>\n<h2>Configuration de l&rsquo;environnement<\/h2>\n<p>Commencez par cr\u00e9er un d\u00e9p\u00f4t de test et pousser le code de l&rsquo;application.<\/p>\n<h3>\u00c9tape 1 \u2014 Cr\u00e9er le d\u00e9p\u00f4t<\/h3>\n<pre><code># Cr\u00e9er un nouveau r\u00e9pertoire et initialiser un d\u00e9p\u00f4t Git\nmkdir cosign-lab && cd cosign-lab\ngit init\n\n# Cr\u00e9er l'application Go et le Dockerfile \u00e0 partir des pr\u00e9requis ci-dessus\n# Puis pousser vers GitHub\ngit add .\ngit commit -m \"Initial commit: simple Go app\"\ngh repo create cosign-lab --public --source=. --push<\/code><\/pre>\n<h3>\u00c9tape 2 \u2014 Cr\u00e9er le workflow sans signature<\/h3>\n<p>Avant d&rsquo;ajouter la signature, cr\u00e9ez un workflow de base qui ne fait que construire et pousser l&rsquo;image. Cela vous donne un point de comparaison pour la suite.<\/p>\n<p>Cr\u00e9ez <code>.github\/workflows\/build.yml<\/code> :<\/p>\n<pre><code>name: Build and Push (Unsigned)\n\non:\n  push:\n    tags:\n      - 'v*'\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v4\n\n      - name: Log in to GHCR\n        uses: docker\/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata\n        id: meta\n        uses: docker\/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}\n\n      - name: Build and push\n        uses: docker\/build-push-action@v5\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}<\/code><\/pre>\n<p>Committez et poussez ce workflow. Cr\u00e9ez un tag pour le d\u00e9clencher :<\/p>\n<pre><code>git add .\ngit commit -m \"Add unsigned build workflow\"\ngit push origin main\ngit tag v0.1.0\ngit push origin v0.1.0<\/code><\/pre>\n<p>Une fois le workflow termin\u00e9, votre image se trouve dans GHCR \u2014 mais elle ne porte aucune signature cryptographique. Quiconque dispose d&rsquo;un acc\u00e8s en \u00e9criture au registre pourrait la remplacer, et rien en aval ne le remarquerait.<\/p>\n<h2>Exercice 1 : Signature locale avec une paire de cl\u00e9s<\/h2>\n<p>Avant de passer \u00e0 la signature keyless en CI, il est utile de comprendre les fondamentaux en signant une image localement avec une paire de cl\u00e9s explicite.<\/p>\n<h3>\u00c9tape 1 \u2014 G\u00e9n\u00e9rer une paire de cl\u00e9s Cosign<\/h3>\n<pre><code>cosign generate-key-pair<\/code><\/pre>\n<p>Cela cr\u00e9e deux fichiers dans votre r\u00e9pertoire courant :<\/p>\n<ul>\n<li><code>cosign.key<\/code> \u2014 la cl\u00e9 priv\u00e9e (chiffr\u00e9e avec une phrase secr\u00e8te de votre choix).<\/li>\n<li><code>cosign.pub<\/code> \u2014 la cl\u00e9 publique que vous distribuez aux v\u00e9rificateurs.<\/li>\n<\/ul>\n<h3>\u00c9tape 2 \u2014 Construire, pousser et signer l&rsquo;image<\/h3>\n<pre><code># Construire l'image\ndocker build -t ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1 .\n\n# Pousser vers GHCR\ndocker push ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1\n\n# Signer l'image avec votre cl\u00e9 priv\u00e9e\ncosign sign --key cosign.key ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1<\/code><\/pre>\n<p>Remplacez <code>&lt;your-username&gt;<\/code> par votre nom d&rsquo;utilisateur GitHub. Cosign vous demandera la phrase secr\u00e8te d\u00e9finie lors de la g\u00e9n\u00e9ration de la cl\u00e9.<\/p>\n<h3>\u00c9tape 3 \u2014 V\u00e9rifier la signature<\/h3>\n<pre><code>cosign verify --key cosign.pub ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1<\/code><\/pre>\n<p>Vous devriez obtenir une sortie similaire \u00e0 :<\/p>\n<pre><code>Verification for ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1 --\nThe following checks were performed on each of these signatures:\n  - The cosign claims were validated\n  - The signatures were verified against the specified public key\n\n[{\"critical\":{\"identity\":{\"docker-reference\":\"ghcr.io\/&lt;your-username&gt;\/cosign-lab\"},\"image\":{\"docker-manifest-digest\":\"sha256:abc123...\"},\"type\":\"cosign container image signature\"},\"optional\":null}]<\/code><\/pre>\n<h3>O\u00f9 est stock\u00e9e la signature ?<\/h3>\n<p>Cosign stocke les signatures sous forme d&rsquo;artefacts OCI dans le m\u00eame registre, \u00e0 c\u00f4t\u00e9 de l&rsquo;image. Pour une image tagu\u00e9e <code>sha256:abc123<\/code>, Cosign pousse la signature vers un tag d\u00e9riv\u00e9 de ce digest \u2014 <code>sha256-abc123.sig<\/code>. Cela signifie :<\/p>\n<ul>\n<li>Aucune infrastructure de stockage de signatures s\u00e9par\u00e9e n&rsquo;est n\u00e9cessaire.<\/li>\n<li>Les signatures accompagnent l&rsquo;image lorsque vous dupliquez ou r\u00e9pliquez des registres.<\/li>\n<li>Les contr\u00f4les d&rsquo;acc\u00e8s au registre s&rsquo;appliquent aux signatures de la m\u00eame mani\u00e8re qu&rsquo;aux images.<\/li>\n<\/ul>\n<p>La signature par paire de cl\u00e9s fonctionne, mais elle introduit une charge de gestion des cl\u00e9s : vous devez prot\u00e9ger la cl\u00e9 priv\u00e9e, la faire tourner p\u00e9riodiquement et distribuer la cl\u00e9 publique \u00e0 chaque v\u00e9rificateur. Dans l&rsquo;exercice suivant, nous \u00e9liminons enti\u00e8rement cette charge avec la signature keyless.<\/p>\n<h2>Exercice 2 : Signature keyless dans GitHub Actions<\/h2>\n<p>La signature keyless supprime la n\u00e9cessit\u00e9 de g\u00e9n\u00e9rer, stocker ou faire tourner des cl\u00e9s de signature. Elle repose \u00e0 la place sur des certificats \u00e0 dur\u00e9e de vie courte \u00e9mis par <strong>Fulcio<\/strong> et enregistr\u00e9s dans le journal de transparence <strong>Rekor<\/strong>.<\/p>\n<h3>Comment fonctionne la signature keyless<\/h3>\n<ol>\n<li><strong>Jeton OIDC<\/strong> \u2014 GitHub Actions \u00e9met un jeton d&rsquo;identit\u00e9 OIDC qui prouve l&rsquo;identit\u00e9 du workflow (d\u00e9p\u00f4t, fichier de workflow, r\u00e9f\u00e9rence, et plus encore).<\/li>\n<li><strong>Certificat Fulcio<\/strong> \u2014 Cosign envoie ce jeton OIDC \u00e0 Fulcio, qui \u00e9met un certificat de signature X.509 \u00e0 dur\u00e9e de vie courte li\u00e9 \u00e0 l&rsquo;identit\u00e9 du workflow.<\/li>\n<li><strong>Signature<\/strong> \u2014 Cosign signe le digest de l&rsquo;image avec la cl\u00e9 priv\u00e9e \u00e9ph\u00e9m\u00e8re correspondant au certificat Fulcio.<\/li>\n<li><strong>Journal de transparence Rekor<\/strong> \u2014 La signature et le certificat sont enregistr\u00e9s dans Rekor afin que quiconque puisse auditer quand et par qui une image a \u00e9t\u00e9 sign\u00e9e.<\/li>\n<li><strong>Destruction de la cl\u00e9<\/strong> \u2014 La cl\u00e9 priv\u00e9e \u00e9ph\u00e9m\u00e8re est imm\u00e9diatement d\u00e9truite. La v\u00e9rification utilise le certificat et l&rsquo;entr\u00e9e Rekor, et non une cl\u00e9 publique \u00e0 longue dur\u00e9e de vie.<\/li>\n<\/ol>\n<h3>\u00c9tape 1 \u2014 Cr\u00e9er le workflow de signature<\/h3>\n<p>Cr\u00e9ez <code>.github\/workflows\/sign.yml<\/code> :<\/p>\n<pre><code>name: Build, Push, and Sign\n\non:\n  push:\n    tags:\n      - 'v*'\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build-and-sign:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n      id-token: write   # Required for keyless signing via OIDC\n\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v4\n\n      - name: Install Cosign\n        uses: sigstore\/cosign-installer@v3\n\n      - name: Log in to GHCR\n        uses: docker\/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata\n        id: meta\n        uses: docker\/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}\n\n      - name: Build and push\n        id: build\n        uses: docker\/build-push-action@v5\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n\n      - name: Sign the image\n        run: |\n          cosign sign --yes \\\n            ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}<\/code><\/pre>\n<h3>D\u00e9tails importants de ce workflow<\/h3>\n<ul>\n<li><code>id-token: write<\/code> \u2014 cette permission autorise le runner \u00e0 demander un jeton OIDC \u00e0 GitHub, que Fulcio utilise pour \u00e9mettre le certificat de signature.<\/li>\n<li><code>packages: write<\/code> \u2014 n\u00e9cessaire pour pousser l&rsquo;image et sa signature vers GHCR.<\/li>\n<li><code>cosign sign --yes<\/code> \u2014 le flag <code>--yes<\/code> confirme le mode non interactif (pas d&rsquo;invite pour le consentement keyless). L&rsquo;absence de flag <code>--key<\/code> signifie que Cosign utilise automatiquement la signature keyless.<\/li>\n<li>Nous signons par digest (<code>@sha256:...<\/code>) plut\u00f4t que par tag pour garantir que nous signons exactement l&rsquo;image que nous venons de construire.<\/li>\n<\/ul>\n<h3>\u00c9tape 2 \u2014 Pousser et d\u00e9clencher le workflow<\/h3>\n<pre><code>git add .github\/workflows\/sign.yml\ngit commit -m \"Add keyless signing workflow\"\ngit push origin main\ngit tag v1.0.0\ngit push origin v1.0.0<\/code><\/pre>\n<h3>\u00c9tape 3 \u2014 Examiner les logs Actions<\/h3>\n<p>Dans l&rsquo;\u00e9tape \u00ab Sign the image \u00bb, vous verrez une sortie similaire \u00e0 :<\/p>\n<pre><code>Generating ephemeral keys...\nRetrieving signed certificate...\n\n        The sigstore community wants to hear from you! Connect with us at\n        https:\/\/links.sigstore.dev\/slack-invite\n\nSuccessfully verified SCT...\ntlog entry created with index: 45678901\nPushing signature to: ghcr.io\/&lt;your-username&gt;\/cosign-lab:sha256-a1b2c3d4.sig<\/code><\/pre>\n<p>L&rsquo;image est d\u00e9sormais sign\u00e9e avec un certificat qui la lie cryptographiquement \u00e0 l&rsquo;identit\u00e9 de votre workflow GitHub Actions. La signature et le certificat sont enregistr\u00e9s de mani\u00e8re permanente dans le journal de transparence Rekor.<\/p>\n<h2>Exercice 3 : V\u00e9rification locale des signatures<\/h2>\n<p>La v\u00e9rification d&rsquo;une image sign\u00e9e en mode keyless n\u00e9cessite deux informations : l&rsquo;<strong>identit\u00e9 du certificat<\/strong> (qui a sign\u00e9) et l&rsquo;<strong>\u00e9metteur OIDC<\/strong> (qui a attest\u00e9 cette identit\u00e9).<\/p>\n<h3>\u00c9tape 1 \u2014 V\u00e9rifier l&rsquo;image sign\u00e9e<\/h3>\n<pre><code>cosign verify \\\n  --certificate-identity \"https:\/\/github.com\/&lt;your-username&gt;\/cosign-lab\/.github\/workflows\/sign.yml@refs\/tags\/v1.0.0\" \\\n  --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n  ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0<\/code><\/pre>\n<p>Sortie en cas de succ\u00e8s :<\/p>\n<pre><code>Verification for ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0 --\nThe following checks were performed on each of these signatures:\n  - The cosign claims were validated\n  - Existence of the claims in the transparency log was verified offline\n  - The code-signing certificate was verified using trusted certificate authority\n  - The signatures were verified against the specified public key\n  - The signature was verified against a valid Fulcio certificate\n\n[{\"critical\":{\"identity\":{\"docker-reference\":\"ghcr.io\/&lt;your-username&gt;\/cosign-lab\"},\"image\":{\"docker-manifest-digest\":\"sha256:a1b2c3d4...\"},\"type\":\"cosign container image signature\"},\"optional\":{...}}]<\/code><\/pre>\n<h3>\u00c9tape 2 \u2014 V\u00e9rifier avec une identit\u00e9 incorrecte (\u00e9chec attendu)<\/h3>\n<pre><code>cosign verify \\\n  --certificate-identity \"https:\/\/github.com\/attacker\/malicious-repo\/.github\/workflows\/build.yml@refs\/tags\/v1.0.0\" \\\n  --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n  ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0<\/code><\/pre>\n<p>Sortie :<\/p>\n<pre><code>Error: no matching signatures:\nnone of the expected identities matched what was in the certificate<\/code><\/pre>\n<p>Cela confirme que les signatures sont li\u00e9es \u00e0 l&rsquo;identit\u00e9. M\u00eame si quelqu&rsquo;un parvient \u00e0 pousser une signature, elle ne passera pas la v\u00e9rification \u00e0 moins d&rsquo;avoir \u00e9t\u00e9 sign\u00e9e par le workflow exact que vous sp\u00e9cifiez.<\/p>\n<h3>\u00c9tape 3 \u2014 V\u00e9rifier une image non sign\u00e9e (\u00e9chec attendu)<\/h3>\n<pre><code>cosign verify \\\n  --certificate-identity \"https:\/\/github.com\/&lt;your-username&gt;\/cosign-lab\/.github\/workflows\/sign.yml@refs\/tags\/v0.1.0\" \\\n  --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n  ghcr.io\/&lt;your-username&gt;\/cosign-lab:v0.1.0<\/code><\/pre>\n<p>Sortie :<\/p>\n<pre><code>Error: no matching signatures\nno signatures found for image<\/code><\/pre>\n<p>L&rsquo;image v0.1.0 a \u00e9t\u00e9 construite avec le workflow sans signature de la section Configuration de l&rsquo;environnement, donc aucune signature n&rsquo;existe.<\/p>\n<h3>Comprendre les champs du certificat<\/h3>\n<p>Lorsque vous v\u00e9rifiez une signature keyless, Cosign v\u00e9rifie plusieurs champs int\u00e9gr\u00e9s dans le certificat Fulcio :<\/p>\n<ul>\n<li><strong>\u00c9metteur<\/strong> (<code>certificate-oidc-issuer<\/code>) \u2014 le fournisseur OIDC qui a authentifi\u00e9 le signataire. Pour GitHub Actions, c&rsquo;est toujours <code>https:\/\/token.actions.githubusercontent.com<\/code>.<\/li>\n<li><strong>Sujet \/ Identit\u00e9<\/strong> (<code>certificate-identity<\/code>) \u2014 la r\u00e9f\u00e9rence compl\u00e8te du workflow incluant le d\u00e9p\u00f4t, le chemin du fichier de workflow et la r\u00e9f\u00e9rence Git. Cela lie la signature \u00e0 un workflow sp\u00e9cifique \u00e0 un commit ou tag sp\u00e9cifique.<\/li>\n<li><strong>Extensions GitHub Workflow<\/strong> \u2014 le certificat contient \u00e9galement des extensions OID personnalis\u00e9es pour le d\u00e9p\u00f4t, le SHA du workflow, l&rsquo;\u00e9v\u00e9nement d\u00e9clencheur et l&rsquo;environnement du runner. Celles-ci permettent des politiques de v\u00e9rification granulaires.<\/li>\n<\/ul>\n<p>Vous pouvez \u00e9galement utiliser la correspondance par expression r\u00e9guli\u00e8re pour des politiques plus flexibles :<\/p>\n<pre><code>cosign verify \\\n  --certificate-identity-regexp \"https:\/\/github.com\/&lt;your-username&gt;\/cosign-lab\/.*\" \\\n  --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n  ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0<\/code><\/pre>\n<p>C&rsquo;est utile lorsque vous souhaitez accepter les signatures de n&rsquo;importe quel workflow d&rsquo;un d\u00e9p\u00f4t, ou de n&rsquo;importe quel tag.<\/p>\n<h2>Exercice 4 : V\u00e9rification dans Kubernetes avec Kyverno<\/h2>\n<p>La v\u00e9rification locale est utile pour le d\u00e9bogage, mais les clusters de production ont besoin d&rsquo;une application automatis\u00e9e. Kyverno est un contr\u00f4leur d&rsquo;admission Kubernetes qui peut v\u00e9rifier les signatures Cosign \u00e0 chaque requ\u00eate d&rsquo;admission de pod.<\/p>\n<h3>\u00c9tape 1 \u2014 Installer Kyverno<\/h3>\n<pre><code>helm repo add kyverno https:\/\/kyverno.github.io\/kyverno\/\nhelm repo update\nhelm install kyverno kyverno\/kyverno -n kyverno --create-namespace<\/code><\/pre>\n<p>Attendez que les pods Kyverno soient pr\u00eats :<\/p>\n<pre><code>kubectl wait --for=condition=ready pod -l app.kubernetes.io\/instance=kyverno -n kyverno --timeout=120s<\/code><\/pre>\n<h3>\u00c9tape 2 \u2014 Cr\u00e9er la politique de v\u00e9rification d&rsquo;images<\/h3>\n<p>Enregistrez le contenu suivant sous le nom <code>require-signed-images.yml<\/code> :<\/p>\n<pre><code>apiVersion: kyverno.io\/v1\nkind: ClusterPolicy\nmetadata:\n  name: require-cosign-signature\nspec:\n  validationFailureAction: Enforce\n  background: false\n  rules:\n    - name: verify-cosign-signature\n      match:\n        any:\n          - resources:\n              kinds:\n                - Pod\n      verifyImages:\n        - imageReferences:\n            - \"ghcr.io\/&lt;your-username&gt;\/cosign-lab:*\"\n          attestors:\n            - entries:\n                - keyless:\n                    subject: \"https:\/\/github.com\/&lt;your-username&gt;\/cosign-lab\/.github\/workflows\/sign.yml@refs\/tags\/*\"\n                    issuer: \"https:\/\/token.actions.githubusercontent.com\"\n                    rekor:\n                      url: \"https:\/\/rekor.sigstore.dev\"<\/code><\/pre>\n<p>Appliquez la politique :<\/p>\n<pre><code>kubectl apply -f require-signed-images.yml<\/code><\/pre>\n<h3>\u00c9tape 3 \u2014 Tester avec une image sign\u00e9e (devrait r\u00e9ussir)<\/h3>\n<pre><code>kubectl run signed-app \\\n  --image=ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0 \\\n  --restart=Never<\/code><\/pre>\n<p>Sortie attendue :<\/p>\n<pre><code>pod\/signed-app created<\/code><\/pre>\n<h3>\u00c9tape 4 \u2014 Tester avec une image non sign\u00e9e (devrait \u00e9chouer)<\/h3>\n<pre><code>kubectl run unsigned-app \\\n  --image=ghcr.io\/&lt;your-username&gt;\/cosign-lab:v0.1.0 \\\n  --restart=Never<\/code><\/pre>\n<p>Sortie attendue :<\/p>\n<pre><code>Error from server: admission webhook \"mutate.kyverno.svc-fail\" denied the request:\nresource Pod\/default\/unsigned-app was blocked due to the following policies:\n\nrequire-cosign-signature:\n  verify-cosign-signature: |\n    image verification failed for ghcr.io\/&lt;your-username&gt;\/cosign-lab:v0.1.0:\n    no matching signatures found<\/code><\/pre>\n<p>C&rsquo;est exactement la boucle d&rsquo;application que vous souhaitez : seules les images sign\u00e9es par votre workflow GitHub Actions de confiance sont autoris\u00e9es dans le cluster.<\/p>\n<h2>Exercice 5 : Attacher un SBOM<\/h2>\n<p>Une signature prouve qui a construit l&rsquo;image. Une attestation SBOM prouve ce qu&rsquo;elle contient. La combinaison des deux vous donne une cha\u00eene de confiance compl\u00e8te : identit\u00e9, int\u00e9grit\u00e9 et transparence du contenu.<\/p>\n<h3>\u00c9tape 1 \u2014 G\u00e9n\u00e9rer le SBOM avec Syft<\/h3>\n<pre><code>syft ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0 -o spdx-json > sbom.spdx.json<\/code><\/pre>\n<p>Cela analyse les couches de l&rsquo;image et produit un document JSON au format SPDX listant chaque paquet, biblioth\u00e8que et d\u00e9pendance \u00e0 l&rsquo;int\u00e9rieur de l&rsquo;image.<\/p>\n<h3>\u00c9tape 2 \u2014 Attacher le SBOM en tant qu&rsquo;attestation<\/h3>\n<pre><code>cosign attest \\\n  --predicate sbom.spdx.json \\\n  --type spdxjson \\\n  --yes \\\n  ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0<\/code><\/pre>\n<p>Comme pour la signature keyless, cela utilise l&rsquo;identit\u00e9 bas\u00e9e sur OIDC lorsque c&rsquo;est ex\u00e9cut\u00e9 dans GitHub Actions ou demande une authentification interactive en local. L&rsquo;attestation est stock\u00e9e en tant qu&rsquo;artefact OCI \u00e0 c\u00f4t\u00e9 de l&rsquo;image.<\/p>\n<h3>\u00c9tape 3 \u2014 V\u00e9rifier l&rsquo;attestation<\/h3>\n<pre><code>cosign verify-attestation \\\n  --type spdxjson \\\n  --certificate-identity \"https:\/\/github.com\/&lt;your-username&gt;\/cosign-lab\/.github\/workflows\/sign.yml@refs\/tags\/v1.0.0\" \\\n  --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n  ghcr.io\/&lt;your-username&gt;\/cosign-lab:v1.0.0<\/code><\/pre>\n<p>La v\u00e9rification r\u00e9ussie confirme que le SBOM a \u00e9t\u00e9 g\u00e9n\u00e9r\u00e9 et attach\u00e9 par votre workflow de confiance, et qu&rsquo;il n&rsquo;a pas \u00e9t\u00e9 alt\u00e9r\u00e9 depuis.<\/p>\n<h2>Le pipeline de signature complet<\/h2>\n<p>Voici le workflow final qui combine tout : construction, push, signature, g\u00e9n\u00e9ration d&rsquo;un SBOM et attestation. Enregistrez-le sous <code>.github\/workflows\/sign-and-attest.yml<\/code> :<\/p>\n<pre><code>name: Build, Sign, and Attest\n\non:\n  push:\n    tags:\n      - 'v*'\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build-sign-attest:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n      id-token: write\n\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v4\n\n      - name: Install Cosign\n        uses: sigstore\/cosign-installer@v3\n\n      - name: Install Syft\n        uses: anchore\/sbom-action\/download-syft@v0\n\n      - name: Log in to GHCR\n        uses: docker\/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata\n        id: meta\n        uses: docker\/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}\n\n      - name: Build and push\n        id: build\n        uses: docker\/build-push-action@v5\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n\n      - name: Sign the image\n        run: |\n          cosign sign --yes \\\n            ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}\n\n      - name: Generate SBOM\n        run: |\n          syft ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} \\\n            -o spdx-json > sbom.spdx.json\n\n      - name: Attest SBOM\n        run: |\n          cosign attest --yes \\\n            --predicate sbom.spdx.json \\\n            --type spdxjson \\\n            ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}\n\n      - name: Verify signature\n        run: |\n          cosign verify \\\n            --certificate-identity-regexp \"https:\/\/github.com\/${{ github.repository }}\/.*\" \\\n            --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n            ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}\n\n      - name: Verify SBOM attestation\n        run: |\n          cosign verify-attestation \\\n            --type spdxjson \\\n            --certificate-identity-regexp \"https:\/\/github.com\/${{ github.repository }}\/.*\" \\\n            --certificate-oidc-issuer \"https:\/\/token.actions.githubusercontent.com\" \\\n            ${{ env.REGISTRY }}\/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}<\/code><\/pre>\n<p>Ce workflow vous offre une cha\u00eene de confiance compl\u00e8te pour chaque release tagu\u00e9e : l&rsquo;image est sign\u00e9e, son contenu est document\u00e9 dans un SBOM, et le SBOM est attest\u00e9 cryptographiquement \u2014 le tout sans g\u00e9rer une seule cl\u00e9 \u00e0 longue dur\u00e9e de vie.<\/p>\n<h2>Nettoyage<\/h2>\n<p>Lorsque vous avez termin\u00e9 le lab, nettoyez les ressources que vous avez cr\u00e9\u00e9es.<\/p>\n<h3>Supprimer les images de test de GHCR<\/h3>\n<p>Naviguez vers <code>https:\/\/github.com\/&lt;your-username&gt;?tab=packages<\/code> et supprimez le paquet <code>cosign-lab<\/code>, ou utilisez le CLI GitHub :<\/p>\n<pre><code># Lister les versions du paquet\ngh api user\/packages\/container\/cosign-lab\/versions | jq '.[].id'\n\n# Supprimer chaque version\ngh api --method DELETE user\/packages\/container\/cosign-lab\/versions\/&lt;version-id&gt;<\/code><\/pre>\n<h3>Supprimer Kyverno<\/h3>\n<pre><code>kubectl delete clusterpolicy require-cosign-signature\nhelm uninstall kyverno -n kyverno\nkubectl delete namespace kyverno<\/code><\/pre>\n<h3>Supprimer le d\u00e9p\u00f4t de test<\/h3>\n<pre><code>gh repo delete &lt;your-username&gt;\/cosign-lab --yes<\/code><\/pre>\n<h3>Supprimer les fichiers locaux<\/h3>\n<pre><code>cd .. && rm -rf cosign-lab\nrm -f cosign.key cosign.pub<\/code><\/pre>\n<h2>Points cl\u00e9s \u00e0 retenir<\/h2>\n<ul>\n<li><strong>La signature keyless \u00e9limine la gestion des cl\u00e9s.<\/strong> En utilisant les jetons d&rsquo;identit\u00e9 OIDC de GitHub Actions et les certificats Fulcio \u00e0 dur\u00e9e de vie courte, vous \u00e9vitez la charge op\u00e9rationnelle de g\u00e9n\u00e9ration, stockage, rotation et distribution des cl\u00e9s de signature.<\/li>\n<li><strong>Les signatures sont li\u00e9es \u00e0 l&rsquo;identit\u00e9, pas \u00e0 une cl\u00e9.<\/strong> La v\u00e9rification contr\u00f4le qui a sign\u00e9 l&rsquo;image (quel workflow, dans quel d\u00e9p\u00f4t, \u00e0 quelle r\u00e9f\u00e9rence) plut\u00f4t que quelle cl\u00e9 a \u00e9t\u00e9 utilis\u00e9e. Cela rend les politiques plus intuitives et auditables.<\/li>\n<li><strong>Le journal de transparence Rekor fournit une piste d&rsquo;audit inviolable.<\/strong> Chaque signature est enregistr\u00e9e publiquement, de sorte que vous pouvez prouver quand une image a \u00e9t\u00e9 sign\u00e9e et d\u00e9tecter toute tentative d&rsquo;antidater ou de supprimer des signatures.<\/li>\n<li><strong>Les contr\u00f4leurs d&rsquo;admission appliquent les politiques de signature au moment du d\u00e9ploiement.<\/strong> Kyverno (ou des alternatives comme Connaisseur ou Sigstore Policy Controller) garantit que les images non sign\u00e9es ou incorrectement sign\u00e9es ne s&rsquo;ex\u00e9cutent jamais dans votre cluster.<\/li>\n<li><strong>Les attestations SBOM \u00e9tendent la cha\u00eene de confiance.<\/strong> La signature prouve qui a construit l&rsquo;image ; l&rsquo;attachement d&rsquo;un SBOM sign\u00e9 prouve ce qu&rsquo;elle contient. Ensemble, ils fournissent une provenance compl\u00e8te de la source \u00e0 l&rsquo;ex\u00e9cution.<\/li>\n<li><strong>Signez par digest, pas par tag.<\/strong> Les tags sont mutables \u2014 quelqu&rsquo;un peut d\u00e9placer un tag vers une image diff\u00e9rente. Les digests sont des adresses de contenu immuables, donc signer par digest garantit que vous avez sign\u00e9 exactement l&rsquo;image que vous avez construite.<\/li>\n<\/ul>\n<h2>Prochaines \u00e9tapes<\/h2>\n<p>Continuez \u00e0 d\u00e9velopper vos connaissances en s\u00e9curit\u00e9 de la cha\u00eene d&rsquo;approvisionnement avec ces guides connexes :<\/p>\n<ul>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/signing-verifying-container-images-sigstore-cosign\/\">Signature et v\u00e9rification d&rsquo;images container avec Sigstore et Cosign<\/a> \u2014 un guide complet couvrant l&rsquo;architecture de Cosign, les mod\u00e8les de v\u00e9rification avanc\u00e9s et l&rsquo;int\u00e9gration avec diff\u00e9rents registres et syst\u00e8mes CI.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/artifact-provenance-attestations-slsa-in-toto-2\/\">Provenance des artefacts et attestations : de SLSA \u00e0 in-toto<\/a> \u2014 comprendre l&rsquo;\u00e9cosyst\u00e8me de provenance plus large, y compris les niveaux SLSA, les layouts in-toto, et comment les attestations s&rsquo;int\u00e8grent dans une strat\u00e9gie compl\u00e8te de s\u00e9curit\u00e9 de la cha\u00eene d&rsquo;approvisionnement.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Pr\u00e9sentation Chaque image container produite par votre pipeline CI\/CD devrait \u00eatre sign\u00e9e cryptographiquement avant d&rsquo;atteindre un quelconque environnement. Les images non sign\u00e9es constituent un angle mort \u2014 vous n&rsquo;avez aucune preuve qu&rsquo;elles proviennent de votre pipeline, aucune garantie qu&rsquo;elles n&rsquo;ont pas \u00e9t\u00e9 alt\u00e9r\u00e9es en transit, et aucun m\u00e9canisme de politique pour bloquer les d\u00e9ploiements non &#8230; <a title=\"Lab : Signature et V\u00e9rification d&rsquo;Images Container avec Cosign dans GitHub Actions\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-signing-verifying-container-images-cosign-github-actions\/\" aria-label=\"En savoir plus sur Lab : Signature et V\u00e9rification d&rsquo;Images Container avec Cosign dans GitHub Actions\">Lire la suite<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,52,50],"tags":[],"post_folder":[],"class_list":["post-518","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-github-actions","category-software-supply-chain"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/518","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/comments?post=518"}],"version-history":[{"count":2,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/518\/revisions"}],"predecessor-version":[{"id":575,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/518\/revisions\/575"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/media?parent=518"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/categories?post=518"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/tags?post=518"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/post_folder?post=518"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}