Aller au contenu
Sécurité medium

Rendre une CLI conforme CRA : SBOM, signature, provenance

15 min de lecture

Le Cyber Resilience Act vous demande des preuves : SBOM, signature, traçabilité, canal advisory, politique de support. Ce guide vous montre comment produire toutes ces preuves automatiquement pour une CLI Python distribuée, via un pipeline GitHub Actions complet, validé par quatre outils de sécurité, et publiant chaque release avec l'ensemble des artefacts vérifiables.

Le code complet est inspiré d'un lab fonctionnel qu'on construit ensemble : une CLI minimaliste cracli (qui calcule un SHA256), avec une chaîne de distribution conforme à l'esprit du règlement. La valeur n'est pas dans la commande, elle est dans la rigueur du pipeline autour.

  • Pinner vos dépendances Python et vos actions GitHub par version exacte / SHA
  • Documenter un canal de remontée de vulnérabilités conforme dans SECURITY.md
  • Valider en continu la conformité de votre pipeline (actionlint, zizmor, poutine, plumber)
  • Générer un SBOM CycloneDX à chaque release avec Syft
  • Signer les artefacts en mode keyless avec cosign et l'OIDC GitHub Actions
  • Publier la release avec SBOM + signatures + certificats + checksums
  • Vérifier côté consommateur l'authenticité d'une release en deux commandes
  • Une CLI à distribuer, exemple ici en Python 3.10+ (la démarche se transpose à Go, Node, Rust)
  • Un dépôt GitHub (les workflows utilisent l'OIDC GitHub pour cosign keyless)
  • Le CLI gh configuré localement pour les validations en pré-publication
  • Lecture conseillée : guide CRA complet, Syft pour le SBOM, SLSA

1. Le squelette de la CLI avec dépendances épinglées

Section intitulée « 1. Le squelette de la CLI avec dépendances épinglées »

La règle absolue : toute version est explicite. Pas de >=, pas de ~=, pas de plage. Le CRA vous demande de pouvoir reproduire bit-à-bit ce que vous avez livré.

pyproject.toml — extrait significatif :

[build-system]
requires = ["hatchling==1.27.0"]
build-backend = "hatchling.build"
[project]
name = "cracli-demo"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"typer==0.15.1",
]
[project.scripts]
cracli = "cracli_demo.cli:app"

Chaque dépendance porte un ==X.Y.Z. Pour une CLI réelle, vous compléterez avec pip-compile ou uv pip compile pour figer aussi les sous-dépendances dans un requirements.lock. Sans verrouillage transitif, vous ne pouvez pas démontrer ce qui a réellement été livré.

La CLI elle-même est triviale (trois sous-commandes Typer : hash, verify, version). Le détail importe peu pour ce guide — l'objet est la chaîne autour.

2. SECURITY.md : la pièce maîtresse côté consommateur

Section intitulée « 2. SECURITY.md : la pièce maîtresse côté consommateur »

Le CRA exige un canal de divulgation responsable documenté et publiquement accessible. Sans SECURITY.md, vous ne pouvez pas démontrer ce point d'audit. Le fichier doit couvrir au moins quatre sections : versions supportées, canal de remontée, délais d'engagement, vérification d'authenticité.

Versions supportées — un tableau lisible, et une durée minimale documentée :

| Version | Statut | Fin de support |
| -------- | ------------------ | -------------- |
| `0.1.x` | Maintenue (active) | À définir |
La durée minimale de support est de **5 ans** à compter de la mise sur
le marché de la version majeure, conformément à l'esprit du CRA. Toute
modification est annoncée dans le CHANGELOG.md avec un préavis de 6 mois.

Canal de remontée — privilégier les Private Security Advisories GitHub (traçabilité native + workflow CVE intégré) avec un fallback email :

**Canal privé recommandé** : utiliser la fonctionnalité GitHub
[Private Security Advisories](https://github.com/<org>/<repo>/security/advisories/new).
**Canal alternatif** : envoyer un courriel à `security@votreentreprise.com`.
Ne pas ouvrir d'issue publique ni publier un PoC tant que le correctif
n'est pas disponible et qu'un advisory n'a pas été émis.

Délais d'engagement — alignés avec l'article 14 du CRA pour les vulnérabilités activement exploitées :

ÉtapeDélai cible
Accusé de réceptionsous 48 heures ouvrées
Triage initialsous 5 jours ouvrés
Notification autorités CRAsous 24 h (early warning) en cas de vulnérabilité activement exploitée
Notification complètesous 72 h

Vérification d'authenticité — la procédure que vos utilisateurs vont copier (donc à tester avant publication). On y revient en section 7.

3. Validation continue de la conformité du pipeline

Section intitulée « 3. Validation continue de la conformité du pipeline »

Avant même de parler de produit, le CRA suppose que votre pipeline lui-même est auditable. Quatre outils complémentaires se relaient pour le valider, chacun couvrant un angle :

OutilCouvreMode dans le workflow
actionlintSyntaxe YAML, erreurs courantes, scripts shellBinaire téléchargé avec checksum
zizmorAudit sécurité spécifique GitHub ActionsBinaire téléchargé avec checksum
poutineScanner CI/CD multi-plateformes orienté supply chainAction officielle pinnée par SHA
plumberConformité de pipelines, PBOM CycloneDXBinaire téléchargé avec checksum

L'idée : chaque push et chaque pull request déclenche un job validate-pipeline qui exécute les quatre outils. Si l'un fail, la CI bloque. Vous obtenez ainsi un gardien continu : impossible d'introduire silencieusement une régression de sécurité dans le workflow.

Job de validation — squelette pertinent :

jobs:
validate-pipeline:
runs-on: ubuntu-24.04
permissions:
contents: read
env:
ACTIONLINT_VERSION: "1.7.12"
ZIZMOR_VERSION: "1.25.2"
PLUMBER_VERSION: "0.3.50"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: actionlint
run: |
curl -sSL -o /tmp/al.tar.gz \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
# Vérification du checksum, install, exécution...
- name: zizmor
run: zizmor .github/workflows/
- name: poutine
uses: boostsecurityio/poutine-action@e240ebd3eff8b2db5a8e5f6b28f58739d7db2247 # v1.1.4
- name: plumber
run: plumber analyze --pbom-cyclonedx plumber-pbom.cdx.json

Trois détails non négociables dans ce job :

  • permissions: contents: read au niveau job (jamais hériter de permissions implicites)
  • persist-credentials: false sur actions/checkout (sinon le token GitHub reste dans la config Git du workspace — vulnérabilité classique exploitée par plusieurs incidents supply chain récents)
  • Toutes les actions tierces épinglées par SHA 40 caractères, avec le tag en commentaire pour la lisibilité

Plumber génère un PBOM (Pipeline Bill of Materials) au format CycloneDX qui complète votre SBOM applicatif. Vous avez ainsi deux niveaux d'inventaire : ce qui constitue votre produit (SBOM) et ce qui constitue le pipeline qui le produit (PBOM). C'est cette double visibilité que demande implicitement le CRA.

Le SBOM est l'inventaire des composants de votre produit, dans un format machine-lisible. Sans lui, vous ne pouvez pas répondre à la question CRA fondamentale : « si la CVE X tombe sur la dépendance Y, mes produits sont-ils impactés ? ».

Voir le guide Syft pour la théorie. Côté pipeline, l'action anchore/sbom-action génère le SBOM en une étape :

- name: Générer le SBOM CycloneDX (Syft)
uses: anchore/sbom-action@55dc4ee22412511ee8c3142cbea40418e6cec693 # v0.17.8
with:
path: .
format: cyclonedx-json
output-file: sbom.cdx.json
upload-artifact: false
upload-release-assets: false

Le SBOM est généré avant la signature (logique : on signe le SBOM aussi). Format CycloneDX JSON parce qu'il est le standard supply chain le plus largement adopté en 2026 (SPDX reste valide mais a moins de traction dans l'écosystème supply chain security).

Cosign keyless utilise l'OIDC GitHub Actions pour signer sans gérer de clé privée. À la place, chaque signature est liée à un certificat éphémère émis par Sigstore pour la durée du job, prouvant que la signature a bien été produite par votre workflow GitHub à un commit donné.

- name: Installer cosign
uses: sigstore/cosign-installer@1aa8e0f2454b781fbf0fbf306a4c9533a0c57409 # v3.7.0
with:
cosign-release: 'v2.4.1'
- name: Signer les artefacts (cosign keyless, OIDC)
run: |
set -euo pipefail
for f in dist/* sbom.cdx.json; do
cosign sign-blob --yes "$f" \
--output-signature "${f}.sig" \
--output-certificate "${f}.crt"
done

Permissions requises au niveau du job : id-token: write (pour récupérer le token OIDC GitHub) et contents: write (pour publier la release). Sans id-token: write, cosign keyless échoue avec un message obscur — pensez à le rajouter explicitement.

Pour aller plus loin avec la provenance SLSA niveau 3 (attestation in-toto signée), vous pouvez ajouter le reusable workflow slsa-framework/slsa-github-generator. Hors scope de ce guide pour rester focus, mais c'est l'étape suivante naturelle si votre produit doit être audité par un tiers.

Les checksums SHA256 servent les utilisateurs qui ne veulent pas (ou ne peuvent pas) installer cosign. C'est la couche minimale, sans laquelle vous laissez l'utilisateur sans aucun moyen de vérifier l'intégrité.

- name: Calculer les checksums SHA256
run: |
set -euo pipefail
(cd dist && sha256sum -- *) > checksums.txt
sha256sum sbom.cdx.json >> checksums.txt
- name: Créer la GitHub Release avec toutes les preuves
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ github.ref_name }}
run: |
gh release create "$TAG_NAME" \
--title "$TAG_NAME" \
--generate-notes \
dist/* \
sbom.cdx.json \
sbom.cdx.json.sig \
sbom.cdx.json.crt \
checksums.txt

Pourquoi gh release create plutôt qu'une action tierce ? Le CLI gh est pré-installé sur les runners GitHub-hosted : zéro dépendance externe, zéro surface d'attaque supply chain ajoutée, et ça passe sans warning les scanners de pipeline qui flaggent les « unverified creators ». Sur ce point précis, choisir la solution native gagne sur tous les axes.

C'est ce que vos utilisateurs vont copier-coller depuis votre SECURITY.md. Donc à tester avant chaque release pour ne pas leur donner une procédure cassée.

Vérification minimale (checksums) :

Fenêtre de terminal
sha256sum --check checksums.txt

Vérification cosign keyless (sans clé) :

Fenêtre de terminal
cosign verify-blob \
--signature cracli_demo-0.1.0-py3-none-any.whl.sig \
--certificate cracli_demo-0.1.0-py3-none-any.whl.crt \
--certificate-identity-regexp 'https://github\.com/<org>/<repo>/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
cracli_demo-0.1.0-py3-none-any.whl

Le --certificate-identity-regexp est crucial : il fixe quel workflow est autorisé à signer. Sans cette ancre d'identité, n'importe qui ayant cosign peut produire une signature valide pour Sigstore — vous valideriez la cryptographie sans valider l'origine.

Sept exigences principales du CRA, sept éléments produits par ce pipeline :

Exigence CRAÉlément correspondant
Inventaire des composantsSBOM CycloneDX par release (Syft)
Intégrité des artefacts livrésSignatures cosign keyless + checksums SHA256
Traçabilité de la chaîne d'approvisionnementPipeline auditable + PBOM Plumber + certificats OIDC
Canal de divulgation des vulnérabilitésSECURITY.md + Private Security Advisories GitHub
Politique de support documentéeTableau « Versions supportées » dans SECURITY.md
Sécurité par défaut du pipeline lui-mêmeValidation continue actionlint / zizmor / poutine / plumber
Notification 24 h / 72 h des vulnérabilités exploitéesDélais documentés + canal GHSA en place

Ce que ce pipeline ne couvre pas et qui reste à votre charge :

  • Le triage humain des vulnérabilités remontées (qui décide ? avec quels critères ?)
  • La communication aux utilisateurs (mail list, RSS, GHSA — à choisir)
  • La classification CRA du produit (important, critique, défaut — déclaration manuelle)
  • L'attestation SLSA niveau 3 (étape avancée, demande un reusable workflow supplémentaire)
  • La vérification continue des signatures côté consommateur (admission controller Kubernetes, policy as code)
  • Pinner tout : versions Python ==X.Y.Z, actions GitHub @<SHA>, binaires téléchargés vérifiés par checksum. Sans pinning, pas de reproductibilité, donc pas de conformité démontrable.
  • Valider le pipeline avant le produit : actionlint + zizmor + poutine + plumber sur chaque push. La conformité du produit suppose un pipeline auditable.
  • SBOM, signature, checksums sont indissociables : chacun couvre un angle (inventaire / intégrité / vérification simple). Ils s'utilisent ensemble, pas l'un ou l'autre.
  • Cosign keyless avec OIDC GitHub est le standard 2026 pour une CLI distribuée. Aucune clé à gérer, identité du signataire ancrée sur l'URL du workflow.
  • SECURITY.md est la pièce maîtresse côté consommateur. Sans politique de support et canal de remontée documentés, vous ne pouvez pas démontrer l'engagement de durée de vie qu'exige le CRA.
  • Préférer gh release create aux actions tierces quand c'est possible. Une dépendance externe en moins = une surface d'attaque en moins, et un scan de pipeline qui passe plus facilement.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn