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.
Anatomie d’un pipeline sécurisé
Section intitulée « Anatomie d’un pipeline sécurisé »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 :
- Accès aux secrets : Le pipeline a des tokens pour déployer en production, des clés API, des credentials AWS/Azure/GCP.
- Privilèges élevés : Il peut modifier le code, publier des artefacts, déployer des infrastructures.
- 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.
Les quatre piliers de la défense
Section intitulée « Les quatre piliers de la défense »Voici les fondations non négociables d’un pipeline sécurisé :
| Pilier | Objectif | Menace adressée |
|---|---|---|
| Security gates | Bloquer le code vulnérable avant déploiement | Code malveillant, vulnérabilités connues |
| Gestion des secrets | Protéger les credentials et tokens | Fuite de secrets, accès non autorisé |
| Signature d’artefacts | Garantir l’intégrité et la provenance | Tampering, supply chain attack |
| Isolation | Limiter l’impact d’une compromission | Mouvement 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.
Security gates : les points de contrôle
Section intitulée « Security gates : les points de contrôle »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.
Architecture des gates : défense en profondeur
Section intitulée « Architecture des gates : défense en profondeur »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 :
| Gate | Position | Ce qu’il vérifie | Action si échec |
|---|---|---|---|
| Pre-commit | Avant le commit | Secrets, formatage | Bloque le commit |
| PR Gate | À l’ouverture de PR | SAST, SCA, tests | Bloque le merge |
| Build Gate | Après compilation | Scan d’image, signature | Bloque la publication |
| Deploy Gate | Avant déploiement | Approbation, checks finaux | Bloque 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.
| Gate | Position | Ce qu’il vérifie | Action si échec |
|---|---|---|---|
| Pre-commit | Avant le commit | Secrets, formatage | Bloque le commit |
| PR Gate | À l’ouverture de PR | SAST, SCA, tests | Bloque le merge |
| Build Gate | Après compilation | Scan d’image, signature | Bloque la publication |
| Deploy Gate | Avant déploiement | Approbation, checks finaux | Bloque le déploiement |
Stratégie de seuils : bloquer intelligemment
Section intitulée « Stratégie de seuils : bloquer intelligemment »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 :
- Quelle sévérité bloque ? CRITICAL toujours, HIGH parfois, MEDIUM jamais (sauf exceptions).
- Quel gate bloque quoi ? Pre-commit bloque peu (feedback rapide), deploy gate bloque beaucoup (dernière chance).
- 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 seuilssecurity_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 complexeExemple 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.
Implémentation GitHub Actions
Section intitulée « Implémentation GitHub Actions »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"Gestion des secrets
Section intitulée « Gestion des secrets »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 :
| Erreur | Risque | Fréquence | Exemple concret |
|---|---|---|---|
| Secret en variable d’environnement non masquée | Visible dans les logs | Très courante | echo $DATABASE_PASSWORD → apparaît dans les logs CI, indexé par Google |
| Secret hardcodé dans le code | Exposé dans l’historique Git | Courante | const API_KEY = "sk_live_abc123" → même après suppression, reste dans l’historique Git |
| Secret partagé entre environnements | Compromission transversale | Courante | Même token AWS pour dev/prod → compromission dev = compromission prod |
| Secret sans rotation | Exploitation prolongée après fuite | Très courante | Clé API créée en 2019, jamais changée → si elle fuite aujourd’hui, valide indéfiniment |
| Secret avec privilèges excessifs | Impact amplifié | Courante | Token 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 :
-
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.
# ❌ JAMAISenv:DATABASE_PASSWORD: "SuperSecret123!"# ✅ TOUJOURSenv:DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }} -
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
adminpermet de tout faire : supprimer des repos, modifier des secrets, créer des backdoors. Un token avecread-onlypermet 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, pasadmin. 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 permissifpermissions:contents: write # Peut modifier le codepackages: writeactions: write # Peut modifier les workflows# ✅ Minimalpermissions:contents: read # Juste lire le codepackages: write # Uniquement publier des packages -
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: developmentsteps:- name: Deployenv:API_KEY: ${{ secrets.DEV_API_KEY }} # Token avec accès au sandboxdeploy-prod:environment: production # Environnement GitHub avec validation manuellesteps:- name: Deployenv:API_KEY: ${{ secrets.PROD_API_KEY }} # Token prod, jamais accessible en devBonus : Dans GitHub, les environnements
productionpeuvent exiger une approbation manuelle avant déploiement. Double sécurité. -
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 secret Fréquence de rotation Justification API keys 90 jours Compromis entre sécurité et complexité opérationnelle Tokens d’accès 24h (préférer tokens éphémères) Minimiser l’impact d’une fuite, forcer la régénération quotidienne Certificats Avant expiration, automatisé Let’s Encrypt renouvelle tous les 60j, automatisé via Cert-Manager Passwords de service 180 jours Plus 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.
-
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é :
| Solution | Cas d’usage | Complexité | Quand l’utiliser |
|---|---|---|---|
| GitHub Secrets | Projets simples, GitHub-centric | Faible | Petite équipe, infrastructure sur GitHub Actions uniquement |
| SOPS | Secrets versionnés avec le code (chiffrés) | Moyenne | Besoin de versionner les secrets avec le code (GitOps), multi-cloud |
| HashiCorp Vault | Enterprise, multi-cloud, rotation auto | Élevée | Grande organisation, conformité stricte, rotation automatique essentielle |
| Infisical | Alternative open source à Vault | Moyenne | Budget 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).
Le problème de la confiance
Section intitulée « Le problème de la confiance »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 : l’écosystème de signature moderne
Section intitulée « Sigstore : l’écosystème de signature moderne »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 :
| Composant | Rôle | Analogie | Ce qu’il fait concrètement |
|---|---|---|---|
| Cosign | Signer et vérifier les images/artefacts | Le stylo qui signe | CLI que vous utilisez : cosign sign, cosign verify |
| Fulcio | CA qui émet des certificats éphémères | Le notaire qui certifie l’identité | Vérifie votre identité OIDC, émet un certificat X.509 valide 10 min |
| Rekor | Log de transparence (audit trail) | Le registre public des signatures | Enregistre 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.
Workflow de signature
Section intitulée « Workflow de signature »-
Build : créer l’artefact
Fenêtre de terminal docker build -t myapp:v1.0.0 . -
Push : publier dans le registry
Fenêtre de terminal docker push registry.example.com/myapp:v1.0.0 -
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 ! -
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
Intégration GitHub Actions
Section intitulée « Intégration GitHub Actions »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.
Principes d’isolation : les quatre niveaux
Section intitulée « Principes d’isolation : les quatre niveaux »Voici comment cloisonner un pipeline pour minimiser l’impact d’une compromission :
| Niveau | Mesure | Bénéfice | Exemple concret |
|---|---|---|---|
| Réseau | Runners dans un réseau isolé | Pas d’accès aux ressources internes | Runner dans un VPC dédié, pas de route vers le réseau de prod |
| Compute | Runners éphémères (destroyed après usage) | Pas de persistance d’un compromis | Container détruit après chaque job → malware ne persiste pas |
| Permissions | Token avec moindre privilège | Limite les actions possibles | Token avec read au lieu de write → vol du token = lecture seule |
| Environnements | Séparation dev/staging/prod | Compromis dev ≠ compromis prod | Clusters 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.
Runners éphémères : effacer les traces
Section intitulée « Runners éphémères : effacer les traces »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èresAstuce : 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).
Permissions GitHub Actions
Section intitulée « Permissions GitHub Actions »Réduisez les permissions au strict minimum :
# Permissions par défaut restrictivespermissions: contents: read # Lecture du code seulement
jobs: build: # Permissions spécifiques au job si nécessaire permissions: contents: read packages: write # Uniquement pour ce jobAttestations 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.
| Niveau | Exigences | Ce que ça prouve | Difficulté de falsification |
|---|---|---|---|
| SLSA 1 | Provenance documentée | Qui a construit, quand | Facile (humain peut mentir) |
| SLSA 2 | Provenance générée par le service de build | Build reproductible, automatisé | Moyen (nécessite compromission CI) |
| SLSA 3 | Build isolé, provenance non falsifiable | Intégrité garantie même si développeur malveillant | Difficile (nécessite compromission plateforme CI) |
| SLSA 4 | Hermétique, reproductible | Vérifiable par tous, builds identiques | Trè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).
Génération d’attestations
Section intitulée « Génération d’attestations »- 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 }}Checklist pipeline sécurisé
Section intitulée « Checklist pipeline sécurisé »Une checklist pour évaluer la maturité de votre pipeline :
Basique (à avoir immédiatement)
Section intitulée « Basique (à avoir immédiatement) »- Secrets dans un gestionnaire (pas dans le code)
- Scan de secrets en pre-commit
- SAST sur les PR
- SCA sur les dépendances
Intermédiaire (à planifier)
Section intitulée « Intermédiaire (à planifier) »- Runners éphémères
- Permissions minimales
- Environnements séparés (dev/staging/prod)
- Signature des images
Avancé (maturité DevSecOps)
Section intitulée « Avancé (maturité DevSecOps) »- Attestations SLSA
- SBOM pour tous les artefacts
- Vérification des signatures avant déploiement
- Rotation automatique des secrets
À retenir
Section intitulée « À retenir »- 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