Politique de pinning supply chain
Ce document décrit comment Perf Sentinel maintient l'immutabilité de ses entrées de build. L'objectif est qu'un checkout d'une release taggée résolve les mêmes entrées de build (actions, images, crates) des semaines ou des années plus tard, et qu'un upstream compromis ne puisse pas échanger un tag dans notre dos. Des entrées épinglées ne font pas un build reproductible : aucun job ne reconstruit une release pour la comparer octet par octet.
La politique ci-dessous est déjà appliquée sur l'ensemble du dépôt. Ce document la formalise pour que les futurs contributeurs et les relecteurs puissent appliquer les mêmes règles aux nouveaux workflows, Dockerfile et dépendances.
État
Vérification de conformité au 2026-06-09 :
- GitHub Actions : 100 % des lignes
uses:à travers les 11 workflows de.github/workflows/sont épinglées à un commit SHA de 40 caractères, avec le tag lisible en commentaire de fin de ligne. - Dockerfile : l'image de production est
FROM scratch, sans image de base externe à épingler. La seule action Docker invoquée depuis la CI (zricethezav/gitleaksdansci.yml) est épinglée par digest. - Dépendances Cargo :
Cargo.lockest commité et suivi. Le workspace fait tournercargo auditquotidiennement via.github/workflows/security-audit.yml. Les advisories acquittées avec analyse d'exposition documentée vivent dansaudit.toml. - Permissions : chaque workflow déclare
permissions:au niveau job (par défautcontents: read), avec des scopes plus larges activés uniquement par job quand c'est nécessaire (release, packages, attestations). - Dependabot : configuré pour
github-actionsdans.github/dependabot.yml, planification hebdomadaire le lundi, groupé par propriétaire upstream pour garder le diff cohérent.
Règles de pinning
GitHub Actions
Chaque ligne uses: dans un workflow doit référencer un commit SHA de 40 caractères. Le tag semver va dans un commentaire de fin de ligne pour que les relecteurs puissent lire la version d'un coup d'œil :
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.2Pourquoi SHA et pas tags : les attaques supply chain récentes contre tj-actions/changed-files (mars 2025) et incidents similaires ont tous exploité le fait qu'un tag Git est un pointeur mutable. Un mainteneur ou un attaquant peut déplacer v6 vers un nouveau commit à tout moment, et tous les workflows qui épinglaient @v6 exécutent immédiatement le nouveau code. Un SHA est content-addressable : le réécrire nécessite une collision SHA-1, ce qui n'est à la portée d'aucun attaquant connu.
Images Docker
Quand un Dockerfile ou un workflow référence une image externe, épinglez le digest du contenu :
FROM golang@sha256:abc...def # 1.22-alpineLe Dockerfile de production utilise FROM scratch, il n'y a donc rien à épingler dans l'image elle-même. Le binaire copié dedans (build/linux-${TARGETARCH}/perf-sentinel) est construit depuis ce dépôt même, avec Cargo.lock qui pilote l'ensemble de ses dépendances.
Dépendances Cargo
Cargo.tomldéclare des plages de versions semver comme d'habitude.Cargo.lockest commité et fait foi pour ce que le build compile.cargo audittourne quotidiennement, et sur chaque push ou PR versmainqui toucheCargo.toml,Cargo.lockoudeny.toml.- Les advisories acquittées vivent dans
audit.tomlavec un paragraphe expliquant pourquoi le chemin de code affecté n'est pas exercé. Voir l'entréeRUSTSEC-2026-0097pour le format et la profondeur d'analyse attendus.
Permissions des workflows
Le GITHUB_TOKEN par défaut a des permissions larges. Les workflows les abaissent explicitement à contents: read au niveau job et n'activent des scopes additionnels que lorsque c'est nécessaire :
jobs:
build:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3Les jobs de release qui poussent vers GHCR ou créent une release ajoutent packages: write, contents: write ou attestations: write selon les besoins. Il n'y a aucun permissions: write-all au niveau workflow dans le dépôt.
Configuration Dependabot
L'extrait pertinent de .github/dependabot.yml :
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
groups:
ci-actions:
patterns: ["actions/*", "taiki-e/*", "actions-rust-lang/*"]
docker-actions:
patterns: ["docker/*"]
security-actions:
patterns: ["github/codeql-action", "github/codeql-action/*"]Les dépendances Cargo sont exclues de Dependabot : la combinaison Cargo.lock plus cargo audit quotidien couvre déjà l'angle sécurité, et le volume de mises à jour de patch que Dependabot générerait sur un workspace de 200+ crates est mal rentabilisé pour un projet de cette taille. Les mises à jour Cargo sont gérées manuellement via cargo update quand c'est nécessaire.
Commandes de vérification
Ces commandes auditent la posture de pinning du dépôt à tout moment :
# 1. Trouver toute GitHub Action dont la ref n'est PAS un SHA 40-char. Attendu : 0 hit.
# Matche tout ce qui suit `@` et qui n'est pas 40 caractères hex : tags semver,
# noms de branches, `latest`, `HEAD`, refs custom comme `release-1.2`.
grep -rnE 'uses:[[:space:]]+[^@]+@[^[:space:]#]+' .github/workflows/ \
| grep -vE 'uses:[[:space:]]+[^@]+@[a-f0-9]{40}([[:space:]]|$)'
# 2. Trouver toute ligne FROM dans un Dockerfile qui n'est pas pinnée par digest.
# Attendu : seulement `FROM scratch` et des digests explicites.
grep -rnE '^FROM[[:space:]]+[^@]+:[^@]+$' \
Dockerfile* charts/*/Dockerfile* 2>/dev/null
# 3. Lancer cargo audit. Attendu : seuls les ignores documentés sortent.
cargo audit
# 4. Inspecter les permissions actions du repo. Attendu : enabled et
# `selected` (pas `all`). Nécessite gh CLI authentifié.
gh api repos/Institut-du-Numerique-Responsable/perf-sentinel/actions/permissionsBumper un pin manuellement
Dependabot gère les mises à jour de routine. Pour une mise à jour à la main (correctif de sécurité hors cycle hebdomadaire, ou nouvelle action que Dependabot n'a pas encore prise en charge), résolvez le SHA via l'API GitHub :
# Résoudre le SHA pour un tag semver d'une action publiée.
TAG="v6.0.2"
gh api repos/actions/checkout/git/ref/tags/${TAG} --jq '.object.sha'Puis mettez à jour le workflow :
- uses: actions/checkout@<the-sha-you-just-resolved> # v6.0.2Mettez toujours à jour le commentaire de fin de ligne pour qu'il corresponde au nouveau tag. Un SHA avec un commentaire périmé est pire que pas de commentaire.
Pour les images Docker, résolvez le digest avec docker buildx :
docker buildx imagetools inspect <image>:<tag> --format '{{.Manifest.Digest}}'Procédure de réponse CVE
- Détection :
cargo audittourne quotidiennement et sur les PR qui modifient les fichiers Cargo. GitHub Security Advisories fait remonter les mêmes données, plus les alertes propres à chaque écosystème. Dependabot ouvre des PRs de sécurité automatiquement quand un correctif est disponible. - Triage : lisez l'advisory, lancez
cargo tree -i <crate>pour confirmer si la version affectée est compilée dans le binaire (le paragrapheRUSTSEC-2026-0097dansaudit.tomlest l'exemple canonique de la profondeur d'analyse attendue). - Remédiation : mettez à jour la dépendance dans
Cargo.tomlsi le correctif est upstream, lancezcargo update -p <crate>, vérifiez aveccargo audit, ouvrez une PR avec préfixechore(deps). - Acquittement : si le chemin de code affecté n'est pas exercé, ajoutez une entrée dans
audit.tomlavec un paragraphe expliquant l'analyse d'exposition et les conditions qui doivent déclencher un réexamen. Ne pas ignorer silencieusement. - Divulgation : voir
SECURITY.mdpour le processus complet de divulgation coordonnée et la matrice des versions prises en charge.
Provenance SLSA des binaires
Introduction à Sigstore
Si vous n'avez jamais utilisé Sigstore, cette introduction courte est un préalable pour les références à SLSA, Cosign, Rekor et in-toto qui suivent. Les autres docs Perf Sentinel renvoient ici pour les définitions canoniques, voir Divulgation, Méthodologie, Déploiement Helm, Schéma.
Pourquoi Sigstore. Sigstore est une boîte à outils open source hébergée par l'Open Source Security Foundation (OpenSSF), maintenue par Google, Red Hat, Chainguard, GitHub et la Linux Foundation. C'est le standard de facto pour les signatures d'artefacts vérifiables dans l'écosystème cloud-native (Kubernetes, Helm, la provenance npm, les attestations PyPI s'appuient toutes dessus). Perf Sentinel l'utilise à trois endroits : attestation de la provenance de build des binaires de release officiels (provenance SLSA), signature du chart Helm (signature Cosign vérifiable via cosign verify), signature des rapports de divulgation périodiques (integrity.signature avec preuve d'inclusion Rekor). Trois propriétés motivent ce choix :
- Signature sans clé permanente, aucune clé privée longue durée à gérer ou risquer de divulguer côté signataire.
- Un journal public où toute altération est détectable (Rekor), si bien qu'un tiers peut vérifier de façon indépendante qu'une signature existait à un instant donné.
- Libre, open source, auto-hébergeable, pas de verrouillage propriétaire ni de facturation à la signature.
Les trois composants.
- Cosign est l'outil CLI exécuté localement (ou par GitHub Actions en CI). Il ouvre un flux OIDC dans le navigateur, signe l'artefact, et envoie la signature à Sigstore.
- Fulcio est l'autorité de certification. Il consomme le token OIDC obtenu par cosign (preuve d'identité : email, URL d'un workflow GitHub, ...) et émet un certificat X.509 à durée courte (10 minutes) lié à cette identité. Fulcio ne voit jamais la clé privée du signataire.
- Rekor est le journal de transparence public. Il enregistre la signature à côté du certificat Fulcio, retourne une preuve d'inclusion, et expose l'entrée à un index de journal stable. Les entrées passées ne peuvent pas être réécrites silencieusement.
Qui signe avec quelle clé. Cosign génère un nouveau couple de clés éphémère juste avant la signature. Fulcio émet un certificat de 10 minutes qui lie la moitié publique de ce couple à l'identité OIDC. Une fois la signature envoyée à Rekor, le couple de clés est détruit. Il ne reste que la signature, le certificat et l'entrée Rekor, ce dont un vérifieur a besoin.
L'identité OIDC est le sujet du certificat Fulcio, remonté comme signer_identity + signer_issuer dans tout document qui enregistre la signature. Pour un workflow GitHub Actions de release, l'identité est l'URL du workflow (https://github.com/Institut-du-Numerique-Responsable/perf-sentinel/.github/workflows/release.yml@refs/tags/...) et l'issuer est https://token.actions.githubusercontent.com. Pour un individu qui signe localement avec un compte Google, l'identité est l'email et l'issuer est https://accounts.google.com. Les consommateurs doivent épingler la regex d'identité attendue et l'issuer dans leur politique de vérification.
Limite connue : migration de fournisseur OIDC. L'URL de l'issuer est inscrite dans le certificat, donc enregistrée dans Rekor. Si l'organisation productrice change plus tard de fournisseur d'identité, les signatures passées restent valides mais les nouvelles signatures porteront une valeur signer_issuer différente. Les politiques de vérification qui épinglent un issuer spécifique devront être mises à jour, sinon elles rejetteront les nouvelles signatures comme non fiables. Anticiper la politique d'épinglage en prévision des migrations de fournisseur.
Termes connexes que vous rencontrerez dans les commandes supply-chain de Perf Sentinel. Une ligne par terme seulement, les définitions complètes sont dans les specs liées.
- OIDC (OpenID Connect) est un protocole d'identité posé sur OAuth 2.0. Dans ce workflow, c'est la manière dont cosign prouve "ce signataire est
user@example.org" (ou "c'est le workflow release perf-sentinel sur le tag v0.7.1") à Fulcio. Spec. - in-toto v1 statement est une spécification OpenSSF ouverte pour les attestations de chaîne d'approvisionnement logicielle. Une enveloppe JSON qui apparie le hash d'un artefact avec une claim typée sur celui-ci. La provenance SLSA et l'attestation de divulgation périodique sont toutes deux des statements in-toto en interne. Cosign signe le statement, pas l'artefact brut, ce qui permet aux vérifieurs de chaîner la confiance depuis le hash de l'artefact vers le statement in-toto, puis vers la signature cosign, puis vers le certificat Fulcio. Spec.
- Bundle (
bundle.sig) est le fichier JSON que cosign écrit au moment de la signature. Il rassemble la signature, le certificat Fulcio et la preuve d'inclusion Rekor dans un seul artefact, ce qui permet une vérification totalement hors ligne plus tard (un consommateur valide contre la clé publique Rekor sans avoir à réinterroger Rekor en direct). - SLSA (Supply-chain Levels for Software Artifacts) est un framework OpenSSF séparé qui décrit comment un artefact a été construit (commit source, builder, workflow). Les binaires et charts Helm Perf Sentinel portent des attestations de provenance SLSA produites par
actions/attest-build-provenance. GitHub documente ces attestations, seules, comme du SLSA v1.0 Build niveau 2. Le niveau 3 demande que le build tourne dans un workflow réutilisable isolé du workflow appelant, ce que les workflows de release ne font pas encore. Spec. - SBOM (Software Bill of Materials) est un inventaire structuré des dépendances d'un artefact. Perf Sentinel publie un SBOM au format SPDX attesté sous le prédicat in-toto SPDX, donc les consommateurs le vérifient de la même manière qu'ils vérifient la signature Cosign. Spec SPDX, prédicat in-toto SPDX.
- CT log (Certificate Transparency) est le modèle plus large que Rekor implémente. L'instance Rekor publique Sigstore est sur
rekor.sigstore.dev. Les opérateurs aux exigences plus strictes peuvent faire tourner une instance privée.
Workflow
Depuis v0.7.1, chaque binaire de release officiel Perf Sentinel porte une attestation de provenance SLSA (Build niveau 2). L'attestation est générée par GitHub Actions via actions/attest-build-provenance (maintenu sous l'org GitHub actions/) et stockée dans l'API attestations GitHub associée à ce dépôt. Elle n'est pas publiée comme asset de la release GitHub.
La 0.7.1 migre depuis l'outillage précédent, slsa-framework/slsa-github-generator@v2.1.0, en maintenance de facto depuis le 24 février 2025. Au moment de la migration, il n'avait publié aucune release depuis 15 mois, et toutes ses actions internes étaient encore sur Node.js 20 alors que les runners GitHub-hosted devaient basculer sur Node 24 par défaut le 2 juin 2026. Le nouveau pipeline préserve le contrat SLSA Build Provenance et supprime l'asset release multiple.intoto.jsonl (les attestations vivent désormais dans l'API attestations). Le niveau reste L2. Les versions précédentes de ce document revendiquaient L3 pour le nouveau pipeline, mais GitHub documente les attestations d'artefacts, seules, comme du SLSA v1.0 Build niveau 2, le niveau 3 demandant un workflow réutilisable qui isole le build du workflow appelant. Le workflow de release construit en ligne.
Vérifier un binaire téléchargé :
gh attestation verify perf-sentinel-linux-amd64 \
--repo Institut-du-Numerique-Responsable/perf-sentinelLes binaires de v0.26.2 et antérieures ont été attestés avant le passage du dépôt à l'Institut du Numérique Responsable. Leurs attestations sont restées sous le compte d'origine, elles se vérifient donc par propriétaire, épinglées au dépôt d'origine :
gh attestation verify perf-sentinel-linux-amd64 \
--owner robintra \
--signer-repo robintra/perf-sentinelUne vérification réussie confirme que le binaire vient d'un tag de release de ce dépôt, construit par GitHub Actions, pas par un tiers. Combiner avec la sous-commande verify-hash sur un rapport de divulgation périodique pour vérifier la chaîne complète : source -> SLSA -> binary -> report -> Sigstore signature.
Prérequis : gh CLI 2.49+ côté consommateur (les versions antérieures n'implémentent pas gh attestation verify). La même vérification peut être réalisée via les SDK clients Sigstore directement contre l'API attestations GitHub pour les outils qui ne peuvent pas dépendre de gh.
Note de migration consommateur : un binaire 0.6.x ou 0.7.0 embarque toujours le multiple.intoto.jsonl legacy et se vérifie via slsa-verifier verify-artifact. Le chemin de vérification legacy est conservé sur ces tags existants. Seul 0.7.1+ requiert la nouvelle commande.
SBOM des binaires et données d'audit embarquées
Au-delà de la provenance SLSA, chaque release de binaires porte deux artefacts supplémentaires de chaîne d'approvisionnement, sous la même forme que le SBOM du chart Helm.
Données cargo-auditable embarquées. Chaque binaire de release est construit avec cargo auditable build, donc sa liste de dépendances résolue est embarquée dans le binaire lui-même. Auditez l'artefact livré directement, pas seulement le Cargo.lock du dépôt :
cargo audit bin perf-sentinel-linux-amd64SBOM SPDX. Chaque release publie perf-sentinel-sbom.spdx.json, un SBOM SPDX que Syft dérive de ces données embarquées et atteste sous le prédicat SPDX (https://spdx.dev/Document/v2.3) contre le binaire Linux amd64, le même prédicat que le chart. Vérifiez l'attestation contre le binaire (le SBOM est le prédicat de l'attestation, le binaire en est le sujet) :
gh attestation verify perf-sentinel-linux-amd64 \
--repo Institut-du-Numerique-Responsable/perf-sentinel \
--predicate-type https://spdx.dev/Document/v2.3Pour v0.26.2 et antérieures, remplacez --repo par --owner robintra --signer-repo robintra/perf-sentinel, comme plus haut.
Le SBOM est dérivé du binaire Linux amd64. Les quatre binaires de release partagent le même ensemble de dépendances Rust à quelques crates spécifiques à la plateforme près, donc le SBOM documente la release dans son ensemble.
Images de conteneur
Les images publiées sur GHCR et Docker Hub ne portent aucune signature Cosign, et le workflow de release n'émet aucune attestation pour elles. BuildKit ajoute de lui-même à l'index de l'image un document de provenance non signé, que rien ne relie à l'attestation du binaire émise par GitHub. Avant le push, le workflow de release scanne l'image amd64 avec Trivy et bloque sur les vulnérabilités HIGH ou CRITICAL qui ont un correctif. L'image ne contient que le binaire de release (FROM scratch, UID 65534), mais rien de vérifiable ne relie le digest d'une image tirée à la provenance ou au SBOM du binaire. Pour faire tourner un artefact que vous avez vérifié, vérifiez le binaire avec gh attestation verify et construisez l'image à partir de lui.
Checklist de relecture PR
Lors de la relecture d'une PR qui touche l'infrastructure CI :
- Nouvelle ligne
uses:? Doit épingler un SHA de 40 caractères, tag en commentaire de fin de ligne. - Nouvelle ligne
FROMdans un Dockerfile ? Doit épinglerimage@sha256:<digest>, sauf siFROM scratch. - Nouvelle dépendance Cargo ?
cargo auditdoit passer sur la PR. Si un nouvel advisory est inévitable, le contributeur doit ajouter une entréeaudit.tomlavec la même profondeur d'analyse que les entrées existantes. - Nouveau workflow ? Bloc
permissions:au niveau job, par défautcontents: read, scopes plus larges activés uniquement quand c'est nécessaire. permissions: write-allau niveau workflow ? À rejeter. Utiliser des scopes au niveau job à la place.
Les commandes de vérification ci-dessus peuvent tourner localement avant de pousser pour s'assurer que la PR est propre.