Aller au contenu
Culture DevOps high

Pipeline CI/CD sécurisé

27 min de lecture

En 2020, l’attaque SolarWinds a compromis 18 000 organisations en injectant du code malveillant dans le processus de build d’un logiciel légitime. Les attaquants n’ont pas exploité une vulnérabilité applicative — ils ont compromis le pipeline lui-même. Cette attaque a redéfini notre compréhension des risques : le pipeline CI/CD n’est plus un outil de développement, c’est une surface d’attaque critique.

Un pipeline non sécurisé est une porte ouverte. Il a accès aux secrets de production, peut déployer du code en production, et s’exécute souvent avec des privilèges élevés. C’est exactement ce que recherchent les attaquants.

Analogie : Imaginez une chaîne de production automobile. Vous ne laissez pas n’importe qui entrer dans l’usine (isolation). Vous vérifiez chaque pièce avant assemblage (security gates). Vous protégez les plans secrets (gestion des secrets). Et vous apposez une plaque VIN unique pour prouver l’origine de chaque voiture (signature). Si un seul maillon est défaillant, toute la chaîne est compromise.

Un pipeline CI/CD est votre usine logicielle. Il transforme du code source en artefacts déployés en production. Chaque étape est une opportunité pour un attaquant : injecter du code malveillant, voler des credentials, substituer un artefact, ou pivoter vers vos systèmes internes.

Pourquoi le pipeline est une cible privilégiée ? Parce qu’il concentre trois caractéristiques irrésistibles pour un attaquant :

  1. Accès aux secrets : Le pipeline a des tokens pour déployer en production, des clés API, des credentials AWS/Azure/GCP.
  2. Privilèges élevés : Il peut modifier le code, publier des artefacts, déployer des infrastructures.
  3. Exécution automatique : Compromettez le pipeline, et chaque commit devient un vecteur d’attaque automatisé.

C’est exactement ce qui s’est passé avec SolarWinds : les attaquants ont compromis le pipeline de build, et chaque client a téléchargé une version backdoorée du logiciel. 18 000 organisations compromises via un seul pipeline.

Voici les fondations non négociables d’un pipeline sécurisé :

PilierObjectifMenace adressée
Security gatesBloquer le code vulnérable avant déploiementCode malveillant, vulnérabilités connues
Gestion des secretsProtéger les credentials et tokensFuite de secrets, accès non autorisé
Signature d’artefactsGarantir l’intégrité et la provenanceTampering, supply chain attack
IsolationLimiter l’impact d’une compromissionMouvement latéral, élévation de privilèges

Pourquoi ces quatre piliers sont indissociables ? Imaginez un pipeline avec des security gates parfaits mais qui expose ses secrets en clair dans les logs. Les gates détecteront les CVE, mais l’attaquant aura déjà volé vos clés AWS. Ou un pipeline qui signe ses artefacts mais n’isole pas ses runners : l’attaquant persiste sur le runner et signe des artefacts malveillants avec vos credentials légitimes. La sécurité est une chaîne : le maillon le plus faible détermine la solidité de l’ensemble.

Analogie : Dans un aéroport, vous passez plusieurs contrôles avant d’embarquer : vérification d’identité au comptoir, contrôle de sécurité, vérification à la porte. Si vous échouez à n’importe quelle étape, vous ne montez pas dans l’avion. Chaque contrôle est un “gate” qui bloque la progression si les critères ne sont pas remplis.

Les security gates appliquent ce principe au pipeline CI/CD. Ce sont des points de décision automatisés : si le code contient des vulnérabilités critiques, des secrets exposés, ou échoue aux tests de sécurité, le pipeline s’arrête. Pas de déploiement en production.

Le principe du “fail fast” : Pourquoi bloquer tôt plutôt que de corriger en production ? Parce que corriger une vulnérabilité en prod coûte 100x plus cher qu’en développement (rollback, incident response, communication client, perte de confiance). Un gate qui bloque un commit vulnérable vous fait économiser des semaines de travail.

Un pipeline robuste place des gates à chaque transition, comme des sas de sécurité successifs. Si un gate est contourné (erreur de config, bug de l’outil), le gate suivant rattrape l’erreur.

Voici l’architecture de référence :

GatePositionCe qu’il vérifieAction si échec
Pre-commitAvant le commitSecrets, formatageBloque le commit
PR GateÀ l’ouverture de PRSAST, SCA, testsBloque le merge
Build GateAprès compilationScan d’image, signatureBloque la publication
Deploy GateAvant déploiementApprobation, checks finauxBloque le déploiement

Pourquoi quatre niveaux ? Chaque gate a un rôle spécifique dans le cycle de vie :

  • Pre-commit : Feedback immédiat au développeur (2 secondes). Corrige avant même de polluer l’historique Git.
  • PR Gate : Validation collaborative. L’équipe voit les problèmes avant le merge. Protège la branche main.
  • Build Gate : Scan des artefacts finaux (image Docker). Certaines vulnérabilités n’apparaissent qu’après compilation.
  • Deploy Gate : Dernière ligne de défense. Vérification manuelle si nécessaire, validation par un lead.
GatePositionCe qu’il vérifieAction si échec
Pre-commitAvant le commitSecrets, formatageBloque le commit
PR GateÀ l’ouverture de PRSAST, SCA, testsBloque le merge
Build GateAprès compilationScan d’image, signatureBloque la publication
Deploy GateAvant déploiementApprobation, checks finauxBloque le déploiement

Le dilemme : Si vous bloquez le pipeline pour chaque alerte (y compris les faux positifs et les vulnérabilités mineures), les développeurs perdront confiance et chercheront à contourner les gates. Si vous ne bloquez rien, les gates sont inutiles. Il faut trouver l’équilibre.

La solution : Une stratégie de seuils graduée qui répond à trois questions :

  1. Quelle sévérité bloque ? CRITICAL toujours, HIGH parfois, MEDIUM jamais (sauf exceptions).
  2. Quel gate bloque quoi ? Pre-commit bloque peu (feedback rapide), deploy gate bloque beaucoup (dernière chance).
  3. Quelle tolérance au faux positif ? Les secrets sont binaires (présent = bloqué), les CVE nécessitent du contexte (exploitable ou pas ?).

Voici une stratégie pragmatique qui équilibre sécurité et vélocité :

# Exemple de stratégie de seuils
security_gates:
pre-commit:
block_on: [secrets] # Seuls les secrets bloquent (feedback en 2s)
warn_on: [high_cvss] # Avertir, mais ne pas bloquer (trop tôt)
pr_gate:
block_on: [critical_cvss, secrets, high_cvss_with_exploit]
warn_on: [high_cvss, medium_cvss] # Visible dans la PR, pas bloquant
deploy_gate:
block_on: [any_critical, missing_signature] # Tolérance zéro
require_approval_for: [high_cvss] # Humain décide si contexte complexe

Exemple concret : Une CVE HIGH est détectée sur une dépendance Node.js. Le PR gate avertit (commentaire dans la PR : “lodash 4.17.20 a une CVE HIGH, mettre à jour vers 4.17.21”). Le développeur peut merger si urgent, mais le deploy gate bloquera le déploiement tant que la correction n’est pas mergée. Résultat : pas de friction inutile, mais garantie que la prod est saine.

name: Security Gates
on:
pull_request:
branches: [main]
jobs:
# Gate 1 : Secrets
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Historique complet pour scanner les commits
- name: Detect secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Gate 2 : SAST
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SAST scan
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
# Gate 3 : SCA
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Dependency scan
run: |
trivy fs --severity CRITICAL --exit-code 1 .
# Gate final : tous les checks doivent passer
security-gate:
needs: [secrets-scan, sast, sca]
runs-on: ubuntu-latest
steps:
- run: echo "All security gates passed"

Scénario réel : En 2022, un développeur a committé par erreur une clé API AWS dans un repo GitHub public. En 4 minutes, un bot automatisé a détecté la clé, l’a utilisée pour créer des instances EC2 de minage de cryptomonnaie, et a généré une facture de 50 000€ en 12 heures. Le développeur a découvert le problème en recevant un email d’AWS : “Votre compte a dépassé le budget mensuel de 500€. Facture actuelle : 47 382€.”

Les secrets (API keys, tokens, passwords, certificats) sont le nerf de la guerre du pipeline. Ils donnent accès à vos systèmes de production, vos bases de données, vos clouds providers. Un seul secret exposé = compromission complète.

Pourquoi les secrets fuient si souvent ? Parce qu’ils sont partout : dans le code (hardcodés), dans les variables d’environnement (loggées), dans les fichiers de config (versionnés), dans les containers (visibles avec docker inspect). Chaque endroit est une opportunité de fuite.

Les erreurs classiques (et comment elles se produisent)

Section intitulée « Les erreurs classiques (et comment elles se produisent) »

Voici les patterns d’erreur les plus fréquents, avec des exemples concrets :

ErreurRisqueFréquenceExemple concret
Secret en variable d’environnement non masquéeVisible dans les logsTrès couranteecho $DATABASE_PASSWORD → apparaît dans les logs CI, indexé par Google
Secret hardcodé dans le codeExposé dans l’historique GitCouranteconst API_KEY = "sk_live_abc123" → même après suppression, reste dans l’historique Git
Secret partagé entre environnementsCompromission transversaleCouranteMême token AWS pour dev/prod → compromission dev = compromission prod
Secret sans rotationExploitation prolongée après fuiteTrès couranteClé API créée en 2019, jamais changée → si elle fuite aujourd’hui, valide indéfiniment
Secret avec privilèges excessifsImpact amplifiéCouranteToken avec admin au lieu de read-only → vol du token = contrôle total

Pourquoi ces erreurs persistent ? Parce que les secrets sont invisibles jusqu’à ce qu’ils fuient. Un développeur qui hardcode une clé API “temporairement” oublie souvent de la retirer. Une variable d’environnement non masquée passe inaperçue jusqu’au jour où un attaquant fouille vos logs publics. La sécurité des secrets nécessite de la discipline et de l’automatisation.

Principes de gestion sécurisée : les 5 règles d’or

Section intitulée « Principes de gestion sécurisée : les 5 règles d’or »

Prémisse : La gestion des secrets repose sur une règle absolue : le code ne doit jamais contenir de valeurs secrètes, seulement des références à des secrets stockés ailleurs. Un secret dans le code est une bombe à retardement.

Voici les cinq principes non négociables, avec la justification de chacun :

  1. Jamais de secrets dans le code : externaliser systématiquement

    Pourquoi ? Parce que le code est versionné dans Git. Même si vous supprimez le secret dans un commit ultérieur, il reste dans l’historique. N’importe qui avec accès au repo (y compris les anciens employés, les forks publics, les mirrors) peut fouiller l’historique et extraire le secret.

    Utilisez toujours un gestionnaire de secrets externe. Le code ne doit contenir que des références.

    # ❌ JAMAIS
    env:
    DATABASE_PASSWORD: "SuperSecret123!"
    # ✅ TOUJOURS
    env:
    DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
  2. Moindre privilège : limiter le blast radius

    Pourquoi ? Si un secret fuite (et ça arrivera), l’impact doit être minimal. Un token avec des privilèges admin permet de tout faire : supprimer des repos, modifier des secrets, créer des backdoors. Un token avec read-only permet juste de lire du code.

    Exemple concret : Votre pipeline a besoin de publier un package npm. Créez un token avec uniquement la permission publish, pas admin. Si le token fuite, l’attaquant peut publier des versions malveillantes (détectable), mais pas supprimer votre compte npm (irréversible).

    Chaque secret ne doit avoir que les permissions strictement nécessaires :

    # ❌ Trop permissif
    permissions:
    contents: write # Peut modifier le code
    packages: write
    actions: write # Peut modifier les workflows
    # ✅ Minimal
    permissions:
    contents: read # Juste lire le code
    packages: write # Uniquement publier des packages
  3. Isolation par environnement : cloisonner les contextes

    Pourquoi ? Un développeur qui teste localement ou en dev ne devrait jamais avoir besoin des secrets de production. Si vous partagez les mêmes secrets, une erreur en dev (logger le secret, commit accidentel) compromet la prod.

    Scénario catastrophe : Un développeur utilise le token AWS de prod pour tester en local. Son laptop est volé. L’attaquant a maintenant les clés de votre prod. Si vous aviez utilisé un token dev (avec accès à un compte AWS sandbox), l’impact serait nul.

    Des secrets différents pour dev, staging et production. Jamais de secret prod en dev :

    jobs:
    deploy-dev:
    environment: development
    steps:
    - name: Deploy
    env:
    API_KEY: ${{ secrets.DEV_API_KEY }} # Token avec accès au sandbox
    deploy-prod:
    environment: production # Environnement GitHub avec validation manuelle
    steps:
    - name: Deploy
    env:
    API_KEY: ${{ secrets.PROD_API_KEY }} # Token prod, jamais accessible en dev

    Bonus : Dans GitHub, les environnements production peuvent exiger une approbation manuelle avant déploiement. Double sécurité.

  4. Rotation automatique : limiter la fenêtre d’exploitation

    Pourquoi ? Parce que vous ne saurez jamais quand un secret fuite. Peut-être qu’un ancien employé l’a copié avant de partir. Peut-être qu’un log de 2022 contenant le secret est indexé dans un moteur de recherche public. Peut-être qu’un attaquant l’a volé il y a 6 mois et attend le bon moment pour l’utiliser.

    La rotation réduit la fenêtre d’exploitation : Si vous changez vos secrets tous les 90 jours, un secret volé il y a 91 jours est inutilisable. L’attaquant a 90 jours maximum pour exploiter, pas 5 ans.

    Les secrets doivent être renouvelés régulièrement. Idéalement, automatiquement (via Vault, AWS Secrets Manager, etc.) :

    Type de secretFréquence de rotationJustification
    API keys90 joursCompromis entre sécurité et complexité opérationnelle
    Tokens d’accès24h (préférer tokens éphémères)Minimiser l’impact d’une fuite, forcer la régénération quotidienne
    CertificatsAvant expiration, automatiséLet’s Encrypt renouvelle tous les 60j, automatisé via Cert-Manager
    Passwords de service180 joursPlus long car changement manuel complexe, à automatiser progressivement

    Astuce : Préférez les tokens éphémères générés à la demande (OIDC tokens, IAM Roles for Service Accounts) plutôt que des secrets statiques. GitHub Actions peut s’authentifier sur AWS/Azure/GCP via OIDC sans aucun secret statique.

  5. Audit et monitoring

    Chaque accès à un secret doit être loggé et auditable.

Solutions de gestion de secrets : choisir selon la maturité

Section intitulée « Solutions de gestion de secrets : choisir selon la maturité »

Le dilemme : Il existe des dizaines d’outils de gestion de secrets. Comment choisir ? Réponse : Commencez simple, complexifiez au besoin. Ne déployez pas Vault (complexe, coûteux) si vous avez 2 secrets et 1 pipeline GitHub.

Voici un guide de sélection pragmatique, du plus simple au plus sophistiqué :

SolutionCas d’usageComplexitéQuand l’utiliser
GitHub SecretsProjets simples, GitHub-centricFaiblePetite équipe, infrastructure sur GitHub Actions uniquement
SOPSSecrets versionnés avec le code (chiffrés)MoyenneBesoin de versionner les secrets avec le code (GitOps), multi-cloud
HashiCorp VaultEnterprise, multi-cloud, rotation autoÉlevéeGrande organisation, conformité stricte, rotation automatique essentielle
InfisicalAlternative open source à VaultMoyenneBudget limité, besoin de features Vault sans le coût

Comment décider ? :

  • < 10 secrets, 1 plateforme CI → GitHub Secrets / GitLab CI Variables (simple, gratuit, intégré)
  • Secrets versionnés, GitOps → SOPS (chiffrement transparent, versionnement Git)
  • Multi-cloud, rotation, audit → Vault (gold standard, mais coûteux en temps/argent)
  • Budget limité, features avancées → Infisical (Vault open source sans les complications)

Signature d’artefacts : prouver l’origine et l’intégrité

Section intitulée « Signature d’artefacts : prouver l’origine et l’intégrité »

Analogie : Quand vous achetez un médicament, vous vérifiez le sceau inviolable du bouchon. Ce sceau prouve deux choses : personne n’a ouvert le flacon (intégrité), et il provient bien du laboratoire officiel (provenance). Si le sceau est brisé ou absent, vous ne consommez pas le produit.

La signature cryptographique d’artefacts logiciels repose sur le même principe. Elle garantit deux propriétés essentielles pour la sécurité de la supply chain :

  • Intégrité : L’artefact n’a pas été modifié depuis sa création (pas de tampering)
  • Provenance : L’artefact provient bien du pipeline autorisé, pas d’un attaquant

Scénario d’attaque sans signature : Vous buildez une image Docker myapp:v1.0.0 et la publiez sur Docker Hub. Un attaquant compromet votre compte Docker Hub (mot de passe faible, pas de 2FA). Il remplace votre image par une version identique en apparence, mais avec un backdoor. Votre pipeline déploie l’image en production. Vous venez de déployer le malware de l’attaquant.

Avec une signature, ce scénario est impossible : le pipeline vérifie la signature avant déploiement. L’image substituée n’est pas signée avec votre clé → déploiement refusé.

Pourquoi la signature n’est pas encore généralisée ? Historiquement, parce que c’était compliqué (gérer des clés privées, distribuer des clés publiques, infrastructure PKI). Sigstore a changé la donne en 2021 avec la signature keyless : plus de clé privée à gérer, juste votre identité OIDC (GitHub, GitLab, Google).

Dans une architecture moderne, les artefacts traversent de nombreuses étapes :

Code → Build → Registry → Deploy → Production

À chaque transition, un attaquant pourrait intercepter et modifier l’artefact. La signature crée une chaîne de confiance vérifiable à chaque étape.

Sigstore est devenu le standard de facto pour la signature d’artefacts dans l’écosystème cloud-native (adopté par Kubernetes, Google, Red Hat, GitHub). Il résout le problème historique de la signature : la gestion des clés privées.

Avant Sigstore : Pour signer, vous deviez générer une paire de clés GPG/RSA, stocker la clé privée de manière sécurisée (HSM, KMS), distribuer la clé publique, gérer la rotation, révoquer si compromise. Résultat : 95% des projets ne signaient rien.

Avec Sigstore : Vous vous authentifiez via OIDC (GitHub Actions, GitLab CI), Sigstore émet un certificat éphémère valide 10 minutes, vous signez, le certificat expire. Pas de clé privée à gérer, mais une traçabilité complète via un log de transparence public (Rekor).

Voici les trois composants de l’écosystème :

ComposantRôleAnalogieCe qu’il fait concrètement
CosignSigner et vérifier les images/artefactsLe stylo qui signeCLI que vous utilisez : cosign sign, cosign verify
FulcioCA qui émet des certificats éphémèresLe notaire qui certifie l’identitéVérifie votre identité OIDC, émet un certificat X.509 valide 10 min
RekorLog de transparence (audit trail)Le registre public des signaturesEnregistre chaque signature dans un ledger immuable, vérifiable par tous

Pourquoi Rekor est crucial ? Parce qu’il rend les attaques détectables. Si un attaquant compromet votre CI et signe un artefact malveillant, la signature est enregistrée publiquement dans Rekor avec l’identité de l’attaquant. Impossible de signer en secret.

  1. Build : créer l’artefact

    Fenêtre de terminal
    docker build -t myapp:v1.0.0 .
  2. Push : publier dans le registry

    Fenêtre de terminal
    docker push registry.example.com/myapp:v1.0.0
  3. Sign : signer avec Cosign

    Fenêtre de terminal
    # Signature keyless (recommandé)
    cosign sign registry.example.com/myapp:v1.0.0
    # Cosign utilise l'identité OIDC (GitHub Actions, GitLab CI)
    # Pas de clé privée à gérer !
  4. Verify : vérifier avant déploiement

    Fenêtre de terminal
    cosign verify \
    --certificate-identity "https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main" \
    --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
    registry.example.com/myapp:v1.0.0
name: Build and Sign
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # Nécessaire pour la signature keyless
steps:
- uses: actions/checkout@v4
- name: Build image
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push image
run: |
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Sign image
run: |
cosign sign --yes ghcr.io/${{ github.repository }}:${{ github.sha }}

Isolation et moindre privilège : limiter le blast radius

Section intitulée « Isolation et moindre privilège : limiter le blast radius »

Le principe du blast radius : En sécurité, on part du principe que tout système sera éventuellement compromis. La question n’est pas “si”, mais “quand”. L’isolation répond à la question : Quand mon pipeline sera compromis, quel sera l’impact ?

Scénario sans isolation : Votre runner GitHub Actions self-hosted tourne sur un serveur avec accès au réseau interne de l’entreprise. Un développeur malveillant injecte du code dans un workflow qui scanne le réseau interne, vole les credentials d’une base de données, exfiltre les données clients. Blast radius : toute l’infrastructure interne.

Scénario avec isolation : Le runner tourne dans un container éphémère, dans un réseau isolé, sans accès au réseau interne. Le workflow ne peut accéder qu’à Internet et aux services explicitement autorisés (GitHub API, Docker Hub). Le développeur malveillant peut scanner… le réseau du container (vide). Blast radius : un container jetable.

L’isolation limite les dégâts : même si un attaquant compromet un job CI, il ne peut pas pivoter vers vos systèmes critiques. C’est une défense en profondeur.

Voici comment cloisonner un pipeline pour minimiser l’impact d’une compromission :

NiveauMesureBénéficeExemple concret
RéseauRunners dans un réseau isoléPas d’accès aux ressources internesRunner dans un VPC dédié, pas de route vers le réseau de prod
ComputeRunners éphémères (destroyed après usage)Pas de persistance d’un compromisContainer détruit après chaque job → malware ne persiste pas
PermissionsToken avec moindre privilègeLimite les actions possiblesToken avec read au lieu de write → vol du token = lecture seule
EnvironnementsSéparation dev/staging/prodCompromis dev ≠ compromis prodClusters Kubernetes séparés, comptes AWS séparés

Pourquoi chaque niveau compte ? Parce qu’ils sont complémentaires. Un runner éphémère avec permissions excessives peut quand même exfiltrer des secrets. Un runner avec permissions minimales mais permanent peut être utilisé comme point d’entrée pour des attaques futures. La combinaison des quatre crée une défense robuste.

Le problème des runners permanents : Un runner GitHub Actions self-hosted qui tourne 24/7 est comme un ordinateur partagé dans un cybercafé. Chaque job laisse des traces : fichiers temporaires, historique shell, credentials en mémoire, malware. Le job suivant hérite de cet environnement pollué.

Scénario d’attaque : Job A (malveillant) installe un keylogger qui capture les secrets du Job B (légitime). Job B se déploie en prod avec ses credentials. Le keylogger envoie les credentials à l’attaquant. L’attaquant a maintenant les clés de la prod.

La solution : runners éphémères : Un runner éphémère est créé pour un seul job, puis détruit complètement. Filesystem effacé, mémoire libérée, aucune persistance. Le job suivant part d’un environnement vierge.

Préférez les runners éphémères :

jobs:
build:
runs-on: ubuntu-latest # Runner GitHub hébergé, détruit après le job (recommandé)
# Ou pour self-hosted (si vous devez gérer vos propres runners)
sensitive-deploy:
runs-on: [self-hosted, ephemeral] # Label pour runners éphémères

Astuce : Les runners GitHub hébergés (ubuntu-latest, windows-latest) sont toujours éphémères. C’est gratuit (dans les limites du plan), sécurisé, et sans gestion d’infrastructure. Préférez-les sauf si vous avez des besoins spécifiques (GPU, architecture ARM, compliance).

Réduisez les permissions au strict minimum :

# Permissions par défaut restrictives
permissions:
contents: read # Lecture du code seulement
jobs:
build:
# Permissions spécifiques au job si nécessaire
permissions:
contents: read
packages: write # Uniquement pour ce job

Attestations et provenance : prouver le processus de build

Section intitulée « Attestations et provenance : prouver le processus de build »

Le problème : Une signature prouve que l’artefact est intègre et provient de votre pipeline. Mais elle ne dit rien sur comment l’artefact a été construit. Quelles dépendances ? Quel commit Git ? Quel runner ? Quelles variables d’environnement ?

Exemple : Votre image Docker est signée. Parfait. Mais un développeur malveillant a modifié le Dockerfile pour installer un backdoor avant le build. L’image signée contient le backdoor. La signature est valide, mais l’artefact est compromis.

Les attestations résolvent ce problème en ajoutant des métadonnées vérifiables sur le processus de build : commit source, dépendances utilisées, runner qui a exécuté le build, timestamp. Ces métadonnées sont signées avec l’artefact. Résultat : vous pouvez vérifier comment l’artefact a été construit, pas juste qu’il a été construit.

SLSA : le framework de référence pour la provenance

Section intitulée « SLSA : le framework de référence pour la provenance »

SLSA (Supply-chain Levels for Software Artifacts, prononcé “salsa”) définit des niveaux de maturité progressifs pour la sécurité de la supply chain. Chaque niveau rajoute des garanties techniques qui rendent la falsification plus difficile.

Pourquoi des niveaux ? Parce que passer directement au niveau 4 (build hermétique reproductible) est irréaliste pour 95% des organisations. SLSA vous permet de progresser étape par étape.

NiveauExigencesCe que ça prouveDifficulté de falsification
SLSA 1Provenance documentéeQui a construit, quandFacile (humain peut mentir)
SLSA 2Provenance générée par le service de buildBuild reproductible, automatiséMoyen (nécessite compromission CI)
SLSA 3Build isolé, provenance non falsifiableIntégrité garantie même si développeur malveillantDifficile (nécessite compromission plateforme CI)
SLSA 4Hermétique, reproductibleVérifiable par tous, builds identiquesTrès difficile (reproductibilité bit-à-bit)

Objectif pragmatique : Visez SLSA 2 minimum (facile avec GitHub Actions, GitLab CI), SLSA 3 pour les applications critiques (nécessite isolation renforcée). SLSA 4 est un objectif à long terme (builds hermétiques type Bazel, Nix).

- name: Generate SLSA provenance
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1
with:
image: ghcr.io/${{ github.repository }}
digest: ${{ steps.build.outputs.digest }}

Une checklist pour évaluer la maturité de votre pipeline :

  • Secrets dans un gestionnaire (pas dans le code)
  • Scan de secrets en pre-commit
  • SAST sur les PR
  • SCA sur les dépendances
  • Runners éphémères
  • Permissions minimales
  • Environnements séparés (dev/staging/prod)
  • Signature des images
  • Attestations SLSA
  • SBOM pour tous les artefacts
  • Vérification des signatures avant déploiement
  • Rotation automatique des secrets
  • Security gates : Points de contrôle automatiques, fail fast
  • Secrets : Jamais dans le code, rotation régulière, moindre privilège
  • Signature : Cosign/Sigstore pour l’intégrité et la provenance
  • Isolation : Runners éphémères, permissions minimales
  • Attestations : SLSA pour prouver comment l’artefact a été construit

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.