Un pipeline CI/CD dispose souvent d’un accès privilégié à des ressources sensibles : code source, registres, identités de déploiement, secrets applicatifs ou accès cloud temporaires. C’est la cible parfaite pour une attaque supply chain. Et pourtant, c’est souvent le dernier endroit qu’on pense à sécuriser.
Le hardening (durcissement) de l’environnement de build consiste à réduire la surface d’attaque de vos pipelines CI/CD : runners, secrets, réseau, cache, et processus de build. C’est un prérequis pratique pour progresser vers SLSA, en particulier à partir du moment où l’on vise une plateforme de build maîtrisée et, plus encore, des hardened builds de niveau 3.
Ce que vos allez apprendre
Section intitulée « Ce que vos allez apprendre »- Les vecteurs d’attaque sur les environnements de build
- L’architecture défensive : runners éphémères, isolation, egress control
- La gestion des secrets : OIDC, tokens courts, rotation automatique
- Le durcissement pas à pas de GitHub Actions et GitLab CI
Prérequis
Section intitulée « Prérequis »- Connaissance de base des pipelines CI/CD (GitHub Actions ou GitLab CI)
- Lecture recommandée : Attaques sur les pipelines
Pourquoi sécuriser l’environnement de build ?
Section intitulée « Pourquoi sécuriser l’environnement de build ? »Le pipeline : une cible de choix
Section intitulée « Le pipeline : une cible de choix »L’environnement de build est un actif critique car il :
| Caractéristique | Risque associé |
|---|---|
| A accès aux secrets de production | Exfiltration de credentials |
| Peut modifier le code (merge, tag) | Injection de backdoors |
| Signe les artefacts | Signature d’artefacts compromis |
| Publie vers les registres | Distribution de malware |
| Exécute du code tiers (actions, packages) | Exécution de code malveillant |
La surface d’attaque d’un pipeline
Section intitulée « La surface d’attaque d’un pipeline »┌─────────────────────────────────────────────────────────────────────────────┐│ SURFACE D'ATTAQUE D'UN PIPELINE CI/CD │├─────────────────────────────────────────────────────────────────────────────┤│ ││ ┌───────────────────┐ ┌───────────────────┐ ┌──────────────────┐ ││ │ CODE SOURCE │ │ RUNNER │ │ ARTEFACTS │ ││ │ │ │ │ │ │ ││ │ - Repo compromis │───▶│ - VM persistante │───▶│ - Image signée │ ││ │ - PR malveillante │ │ - Secrets exposés │ │ - Registre public│ ││ │ - Workflow modifié│ │ - Réseau ouvert │ │ - Cache empoisonné│ ││ └───────────────────┘ └───────────────────┘ └──────────────────┘ ││ │ ││ ▼ ││ ┌───────────────────┐ ┌───────────────────┐ ┌──────────────────┐ ││ │ DÉPENDANCES │ │ ACTIONS/JOBS │ │ RÉSEAU │ ││ │ │ │ │ │ │ ││ │ - npm/pip/maven │───▶│ - Actions tierces │───▶│ - Egress libre │ ││ │ - Images base │ │ - Plugins non vér.│ │ - C2 possible │ ││ │ - Cache compromis │ │ - Permissions trop│ │ - Exfiltration │ ││ └───────────────────┘ └───────────────────┘ └──────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Exemples d’attaques réelles
Section intitulée « Exemples d’attaques réelles »| Attaque | Vecteur | Impact |
|---|---|---|
| Codecov (2021) | Script bash modifié dans l’image de build | Exfiltration de secrets CI vers l’attaquant |
| Event-stream (2018) | Mainteneur compromis → code malveillant | Vol de Bitcoin via dépendance npm |
| SolarWinds (2020) | Compromission du build system | Backdoor dans 18 000 organisations |
| tj-actions (2025) | Action GitHub compromise | Exfiltration de secrets |
| xz-utils (2024) | Build scripts modifiés | Backdoor SSH dans binaires |
Architecture défensive
Section intitulée « Architecture défensive »Les 7 piliers du hardening
Section intitulée « Les 7 piliers du hardening »┌─────────────────────────────────────────────────────────────────────────────┐│ ARCHITECTURE DÉFENSIVE CI/CD │├─────────────────────────────────────────────────────────────────────────────┤│ ││ 1. ÉPHÉMÉRITÉ 2. ISOLATION 3. LEAST PRIVILEGE ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ Runner détruit│ │ Pas de réseau│ │ Permissions │ ││ │ après chaque │ │ entre jobs │ │ minimales │ ││ │ job │ │ │ │ │ ││ └──────────────┘ └──────────────┘ └──────────────┘ ││ ││ 4. EGRESS CONTROL 5. SECRETS COURTS 6. VÉRIFICATION ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ Whitelist des │ │ OIDC, tokens │ │ Code review │ ││ │ destinations │ │ éphémères │ │ des workflows│ ││ │ réseau │ │ │ │ │ ││ └──────────────┘ └──────────────┘ └──────────────┘ ││ ││ 7. IMMUTABILITÉ DES INPUTS ││ ┌──────────────────────────────────────────────────────┐ ││ │ Actions pinées SHA, images digest, lockfiles figés │ ││ └──────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Pilier 1 : Runners éphémères
Section intitulée « Pilier 1 : Runners éphémères »Principe : Chaque job s’exécute sur une machine vierge, détruite après exécution.
| Mode | Sécurité | Performance | Coût |
|---|---|---|---|
| Runner persistant | ⚠️ Faible | ⭐⭐⭐ Rapide | 💰 Bas |
| Runner éphémère VM | ✅ Très bon isolement de base | ⭐⭐ Moyen | 💰💰 Moyen |
| Runner éphémère conteneur | ⚠️ Bon niveau possible, dépend de la config | ⭐⭐⭐ Rapide | 💰 Bas |
| GitHub-hosted | ✅ Élevée | ⭐⭐ Moyen | 💰💰💰 Élevé |
Pourquoi c’est crucial :
- Pas de persistance d’un attaquant entre les jobs
- Pas de credentials résiduels sur la machine
- Pas de malware persistant installé par un job précédent
Avec Actions Runner Controller (ARC), configurez des runners éphémères sur Kubernetes. Cet exemple est conceptuel — consultez la documentation ARC officielle pour le CRD exact selon votre version :
# Exemple conceptuel - vérifiez la doc ARC pour les runner scale setsapiVersion: actions.summerwind.dev/v1alpha1kind: RunnerDeploymentmetadata: name: ephemeral-runnersspec: replicas: 3 template: spec: ephemeral: true # Détruit après chaque job repository: votre-org/votre-repo labels: - ephemeral - linuxConfigurez des runners Docker avec hygiène renforcée (réduction du cache et de la persistance, sans être de vrais runners éphémères au sens infrastructurel) :
# config.toml - Runner Docker à faible persistance[[runners]] name = "docker-hygiene-renforcee" executor = "docker"
[runners.docker] image = "debian:bookworm-slim" privileged = false disable_cache = true # Pas de cache partagé pull_policy = "always"
# Nettoyage automatique cleanup_grace_period_seconds = 30Pilier 2 : Isolation entre jobs
Section intitulée « Pilier 2 : Isolation entre jobs »Principe : Un job ne doit pas pouvoir accéder aux ressources d’un autre job.
Il faut distinguer deux types d’isolation :
| Type | Description | Mécanismes |
|---|---|---|
| Isolation d’exécution | Séparation technique (processus, réseau, filesystem) | Runners éphémères, network policies, conteneurs/VMs dédiés |
| Séparation logique | Contrôle d’accès aux ressources (secrets, déploiements) | Environments, permissions, approbations |
jobs: build: runs-on: ubuntu-latest # Isolation via environment environment: production
# Permissions minimales par job permissions: contents: read packages: write
steps: - uses: actions/checkout@v4
# Pas d'accès aux secrets d'autres environments - name: Build env: API_KEY: ${{ secrets.PROD_API_KEY }} run: make buildbuild: stage: build # Isolation via environment environment: name: production
# Variables scoped à cet environment variables: API_KEY: $PROD_API_KEY
# Runner dédié avec tag tags: - production - isolated
script: - make buildPilier 3 : Least Privilege (permissions minimales)
Section intitulée « Pilier 3 : Least Privilege (permissions minimales) »Principe : Chaque job n’a accès qu’aux ressources strictement nécessaires.
GitHub Actions : Permissions explicites
Section intitulée « GitHub Actions : Permissions explicites »# Désactiver les permissions par défautpermissions: {}
jobs: lint: runs-on: ubuntu-latest permissions: contents: read # Lecture du code uniquement steps: - uses: actions/checkout@v4 - run: npm run lint
deploy: runs-on: ubuntu-latest needs: lint permissions: contents: read id-token: write # Pour OIDC packages: write # Pour publier steps: - uses: actions/checkout@v4 - name: Deploy run: make deployMatrice des permissions GitHub Actions
Section intitulée « Matrice des permissions GitHub Actions »| Permission | Quand l’utiliser | Risque si trop large |
|---|---|---|
contents: read | Checkout du code | Permet de lire le code (risque si donné à du code non fiable) |
contents: write | Push, merge, tags | Modification du dépôt, tags, releases — injection de code |
packages: write | Publication d’images/packages | Distribution de malware via registre |
id-token: write | OIDC vers cloud providers | Accès cloud si le trust côté provider est mal configuré |
actions: write | Gestion des workflows | Modification/annulation de workflows sensibles |
security-events: write | Upload de résultats de scan | Potentiel masquage de vulnérabilités |
Pilier 4 : Contrôle de l’egress (sortie réseau)
Section intitulée « Pilier 4 : Contrôle de l’egress (sortie réseau) »Principe : Limiter les destinations réseau autorisées pour empêcher l’exfiltration.
Pourquoi c’est critique :
- Un runner compromis peut envoyer des secrets vers un serveur C2
- Les attaques comme Codecov exfiltrent les données via HTTP
Implémentation avec Harden-Runner (GitHub Actions)
Section intitulée « Implémentation avec Harden-Runner (GitHub Actions) »jobs: build: runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@v2 with: # Mode audit (recommandé pour commencer) egress-policy: audit
- uses: actions/checkout@v4 - run: npm ci - run: npm run buildAprès quelques runs, passez en mode blocage :
- name: Harden Runner uses: step-security/harden-runner@v2 with: egress-policy: block allowed-endpoints: > github.com:443 registry.npmjs.org:443 nodejs.org:443Network policies Kubernetes (self-hosted)
Section intitulée « Network policies Kubernetes (self-hosted) »Cet exemple montre une restriction de port (443 uniquement), pas une vraie whitelist de destinations :
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: runner-egress-port-restrictionspec: podSelector: matchLabels: app: github-runner policyTypes: - Egress egress: # Restriction de PORT, pas de destination - to: - ipBlock: cidr: 0.0.0.0/0 ports: - port: 443 protocol: TCP # Ceci autorise tout Internet en 443, pas une whitelistPilier 5 : Secrets courte durée (OIDC)
Section intitulée « Pilier 5 : Secrets courte durée (OIDC) »Principe : Remplacer les secrets statiques par des tokens éphémères via OIDC.
Le problème des secrets statiques
Section intitulée « Le problème des secrets statiques »| Secret statique | Risque |
|---|---|
| AWS Access Key | Valide indéfiniment, exfiltrable |
| Docker Hub token | Permet de publier des images |
| NPM token | Permet de publier des packages |
| Kubeconfig | Accès complet au cluster |
La solution : OIDC (OpenID Connect)
Section intitulée « La solution : OIDC (OpenID Connect) »┌────────────────┐ ┌────────────────┐ ┌────────────────┐│ GitHub Actions │────────▶│ AWS / GCP │────────▶│ Ressource ││ │ Token │ IAM / WIF │ Accès │ Cloud ││ │ OIDC │ │ temp. │ │└────────────────┘ └────────────────┘ └────────────────┘
1. GitHub génère un token OIDC (JWT) avec l'identité du workflow2. Le cloud provider vérifie le token et émet des credentials temporaires3. Les credentials expirent après quelques minutes/heuresjobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read
steps: - uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC) uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/github-actions aws-region: eu-west-1 # Pas de secrets ! Le token OIDC suffit
- name: Deploy to S3 run: aws s3 sync ./dist s3://my-bucketjobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read
steps: - uses: actions/checkout@v4
- name: Authenticate to Google Cloud (OIDC) uses: google-github-actions/auth@v2 with: workload_identity_provider: projects/123/locations/global/workloadIdentityPools/my-pool/providers/github service_account: deploy@my-project.iam.gserviceaccount.com
- name: Deploy run: gcloud run deploy my-service --image=...jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read
steps: - uses: actions/checkout@v4
- name: Azure Login (OIDC) uses: azure/login@v2 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} # Pas de client secret, OIDC est utilisé
- name: Deploy run: az webapp deploy ...Pilier 6 : Vérification des inputs
Section intitulée « Pilier 6 : Vérification des inputs »Principe : Valider et restreindre ce qui peut déclencher et modifier le pipeline.
Protections sur les pull requests
Section intitulée « Protections sur les pull requests »on: pull_request: # Ne pas exécuter sur les PRs des forks (par défaut) # Utiliser pull_request_target avec précaution
jobs: build: runs-on: ubuntu-latest # Vérifier que le workflow n'a pas été modifié dans la PR if: github.event.pull_request.head.repo.full_name == github.repository
steps: - uses: actions/checkout@v4 with: # Checkout la base, pas la PR (pour les workflows sensibles) ref: ${{ github.base_ref }}Bloquer les modifications de fichiers sensibles
Section intitulée « Bloquer les modifications de fichiers sensibles »jobs: check-files: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0
- name: Check for sensitive file changes run: | SENSITIVE_FILES=".github/workflows/ Dockerfile Makefile" CHANGED=$(git diff --name-only origin/main...HEAD)
for file in $SENSITIVE_FILES; do if echo "$CHANGED" | grep -q "$file"; then echo "::error::Modification of $file requires special approval" exit 1 fi donePilier 7 : Immutabilité des inputs
Section intitulée « Pilier 7 : Immutabilité des inputs »Principe : Figer les entrées de build pour éviter les modifications silencieuses.
| Input | Risque si non figé | Bonne pratique |
|---|---|---|
| Actions GitHub | Tag mutable peut être modifié | Pinner par SHA |
| Images Docker | Tag latest ou version peut changer | Pinner par digest |
| Dépendances | Résolution dynamique peut changer | Lockfiles obligatoires |
| Scripts téléchargés | Contenu peut être modifié | Interdire `curl |
# Actions pinées par SHA- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Images pinées par digestFROM python:3.12-slim@sha256:abc123...Hardening pas à pas
Section intitulée « Hardening pas à pas »Checklist de hardening GitHub Actions
Section intitulée « Checklist de hardening GitHub Actions »-
Permissions minimales globales
# Au niveau du workflowpermissions:contents: read -
Pinning des actions par SHA
# ❌ Mauvais- uses: actions/checkout@v4# ✅ Bon- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2Utilisez pin-github-action pour automatiser.
-
OIDC pour tous les déploiements cloud
Remplacez les secrets statiques AWS/GCP/Azure par OIDC.
-
Harden-Runner pour le contrôle egress
- uses: step-security/harden-runner@v2with:egress-policy: audit # puis block -
Environments pour les secrets sensibles
Configurez des environments avec approbation pour la production.
-
Code review obligatoire pour les workflows
Ajoutez les fichiers
.github/workflows/**dans les CODEOWNERS. -
Audit régulier des permissions
Utilisez StepSecurity Secure Repo pour auditer.
Checklist de hardening GitLab CI
Section intitulée « Checklist de hardening GitLab CI »-
Variables protégées et masquées (côté GitLab UI)
Les variables protégées et masquées se configurent dans GitLab (Settings → CI/CD → Variables), pas dans le YAML :
- Protected : uniquement disponibles sur branches/tags protégés
- Masked : masquées dans les logs de pipeline
Dans le
.gitlab-ci.yml, vous accédez simplement à la variable :deploy_prod:script:- echo "Deploying with key..."- deploy --api-key=$PROD_API_KEY # Configuré côté GitLabrules:- if: $CI_COMMIT_REF_PROTECTED == "true" -
Runners dédiés par environment
deploy_prod:tags:- production- secureenvironment:name: production -
Review apps isolées
review:environment:name: review/$CI_COMMIT_REF_SLUGauto_stop_in: 1 week -
Protected branches + merge rules
- Exiger 2 approbations pour
main - Pipeline réussi obligatoire
- Pas de push direct
- Exiger 2 approbations pour
-
Secrets via OIDC + Vault (recommandé)
Utilisez les ID tokens OIDC pour authentification sans secrets statiques :
deploy_prod:id_tokens:VAULT_ID_TOKEN:aud: https://vault.example.comsecrets:DATABASE_PASSWORD:vault:engine:name: kv-v2path: productionpath: dbfield: passwordtoken: $VAULT_ID_TOKENscript:- deploy --db-password=$DATABASE_PASSWORDLe job reçoit un token OIDC que Vault vérifie avant d’émettre le secret.
-
Audit des jobs avec
needsdeploy:needs:- test- security_scanwhen: on_success # Uniquement si les jobs précédents réussissent
Architecture cible alignée SLSA
Section intitulée « Architecture cible alignée SLSA »Vue d’ensemble
Section intitulée « Vue d’ensemble »Cette architecture représente une cible d’inspiration SLSA niveau 3, pas une conformité automatique. Les exigences SLSA sont précises et dépendent des détails d’implémentation.
┌─────────────────────────────────────────────────────────────────────────────┐│ ARCHITECTURE CIBLE POUR BUILDS DURCIS (type SLSA L3) │├─────────────────────────────────────────────────────────────────────────────┤│ ││ SOURCE BUILDER ARTEFACTS ││ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ │ │ │ │ │ ││ │ Git + │────────▶│ Runner │──────▶│ Image + │ ││ │ Signed │ │ Éphémère │ │ SBOM + │ ││ │ Commits │ │ │ │ Provenance │ ││ │ │ │ Hermetic │ │ Signée │ ││ └──────────┘ │ Build │ └──────────────┘ ││ │ │ │ │ ││ │ └──────────────┘ │ ││ │ │ │ ││ ▼ ▼ ▼ ││ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ Signature│ │ Pas d'accès │ │ Vérification │ ││ │ GPG/SSH │ │ réseau sauf │ │ avant │ ││ │ │ │ whitelist │ │ déploiement │ ││ └──────────┘ └──────────────┘ └──────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Mapping SLSA ↔ piliers de hardening
Section intitulée « Mapping SLSA ↔ piliers de hardening »Ce tableau est une lecture opérationnelle simplifiée pour relier le hardening du build à la progression SLSA ; il ne remplace pas la lecture des exigences officielles SLSA.
| Niveau SLSA | Exigences | Piliers couverts |
|---|---|---|
| Level 1 | Provenance basique | Éphémérité (partiel), Immutabilité |
| Level 2 | Provenance signée, build service | Éphémérité, Isolation, Vérification, Immutabilité |
| Level 3 | Isolation complète, provenance non-falsifiable | Tous les piliers |
Monitoring et détection
Section intitulée « Monitoring et détection »Signaux d’alerte à surveiller
Section intitulée « Signaux d’alerte à surveiller »| Signal | Ce qu’il indique | Action |
|---|---|---|
| Job anormalement long | Exfiltration, crypto mining | Alerter + investiguer |
| Connexions réseau inattendues | C2, exfiltration | Bloquer + alerter |
| Modification de workflow sans PR | Compromission | Bloquer + révoquer |
| Échecs de signature répétés | Tentative de contournement | Alerter |
| Nouveau mainteneur sur action utilisée | Supply chain risk | Revoir l’action |
Outils de monitoring
Section intitulée « Outils de monitoring »| Outil | Fonction |
|---|---|
| Harden-Runner | Log des connexions réseau depuis les runners |
| Dependency Review | Alertes sur nouvelles dépendances vulnérables |
| GitHub Audit Log | Toutes les actions sur le repo/org |
| Falco | Runtime security pour runners Kubernetes |
| Datadog CI Visibility | Métriques et traces des pipelines |
Ce que ce guide ne couvre pas
Section intitulée « Ce que ce guide ne couvre pas »Le hardening du build est une pièce essentielle, mais pas suffisante seule :
| Sujet | Où le traiter |
|---|---|
| Sécurité du code source | Revue de code, SAST, policies de contribution |
| Sécurité des dépendances | SBOM, SCA, veille CVE, lockfiles |
| Signature et provenance des artefacts | Sigstore, SLSA |
| Admission policy côté cluster | Kyverno, OPA Gatekeeper, vérification provenance |
| Gestion des mainteneurs tiers | Due diligence, audit des actions/packages utilisés |
Priorisation réaliste
Section intitulée « Priorisation réaliste »Si vous partez de zéro, voici l’ordre recommandé :
-
Permissions minimales — impact immédiat, facile à implémenter
-
Pinning des actions et images — protège contre les modifications silencieuses
-
OIDC pour les déploiements cloud — élimine les secrets statiques
-
Runners éphémères — élimine la persistance
-
Audit egress (mode observation) — comprendre le trafic réseau
-
Blocage egress — après avoir validé les destinations légitimes
-
Architecture hermétique complète — objectif long terme
Compromis coût / sécurité / opérabilité
Section intitulée « Compromis coût / sécurité / opérabilité »Le hardening a un coût. Assumez ces compromis plutôt que de les subir :
| Mesure | Bénéfice sécurité | Coût / friction |
|---|---|---|
| Runners VM éphémères | Isolation maximale | Plus cher, plus lent que conteneurs |
| Egress strict | Bloque exfiltration | Nécessite maintenance de la whitelist |
| OIDC | Plus de secrets statiques | Paramétrage IAM initial |
| Revue obligatoire des workflows | Bloque les modifications malveillantes | Ralentit légèrement le développement |
| Pinning par SHA | Prévient les modifications furtives | Maintenance des mises à jour |
À retenir
Section intitulée « À retenir »Les 6 points essentiels de ce guide :
- Le pipeline CI/CD est une cible critique — il dispose d’accès privilégiés aux ressources sensibles
- 7 piliers de défense : éphémérité, isolation, least privilege, egress control, secrets courts (OIDC), vérification des inputs, immutabilité des inputs
- Les runners éphémères sont la base — mais un conteneur n’est pas automatiquement aussi isolé qu’une VM
- OIDC remplace les secrets statiques — mais le niveau de sécurité dépend du trust configuré côté cloud
- Le hardening est un prérequis pratique pour progresser vers SLSA — pas une garantie automatique de conformité
- Priorisez : permissions, pinning, OIDC d’abord ; egress strict et architecture hermétique ensuite
Prochaines étapes
Section intitulée « Prochaines étapes »SLSA
Comprenez les niveaux SLSA et comment les atteindre Lire le guide SLSA
Attaques GitHub Actions
Techniques offensives et défensives sur les pipelines Lire le guide
Sigstore
Signez vos artefacts avec Cosign et Fulcio Lire le guide Sigstore
SBOM
Générez un inventaire de vos composants à chaque build Lire le guide SBOM