Aller au contenu
Culture DevOps high
🚧 Section en cours de réécriture — Le contenu est en cours de restructuration et peut évoluer.

Pipeline CI/CD sécurisé

15 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.

Un pipeline CI/CD sécurisé repose sur quatre piliers fondamentaux qui forment une défense en profondeur :

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

Ces piliers ne sont pas indépendants : ils se renforcent mutuellement. Un secret bien protégé n’a de valeur que si le pipeline qui l’utilise est isolé. Une signature n’a de sens que si les security gates ont validé le code signé.

Les security gates sont des points de décision dans le pipeline. Si les critères ne sont pas remplis, le pipeline s’arrête. C’est le principe du “fail fast” appliqué à la sécurité.

Un pipeline efficace place des gates à chaque transition :

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

Tous les problèmes ne méritent pas de bloquer le pipeline. Une stratégie efficace distingue plusieurs niveaux :

# Exemple de stratégie de seuils
security_gates:
pre-commit:
block_on: [secrets] # Seuls les secrets bloquent
warn_on: [high_cvss]
pr_gate:
block_on: [critical_cvss, secrets, high_cvss_with_exploit]
warn_on: [high_cvss, medium_cvss]
deploy_gate:
block_on: [any_critical, missing_signature]
require_approval_for: [high_cvss]
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"

Les secrets (API keys, tokens, passwords, certificats) sont le nerf de la guerre. Un secret exposé dans un pipeline compromet potentiellement toute l’infrastructure.

ErreurRisqueFréquence
Secret en variable d’environnement non masquéeVisible dans les logsTrès courante
Secret hardcodé dans le codeExposé dans l’historique GitCourante
Secret partagé entre environnementsCompromission transversaleCourante
Secret sans rotationExploitation prolongée après fuiteTrès courante
Secret avec privilèges excessifsImpact amplifiéCourante
  1. Jamais de secrets dans le code

    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

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

    # Token GitHub avec permissions minimales
    permissions:
    contents: read
    packages: write # Uniquement si nécessaire
  3. Isolation par environnement

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

    jobs:
    deploy:
    environment: production # Environnement GitHub avec ses propres secrets
    steps:
    - name: Deploy
    env:
    API_KEY: ${{ secrets.PROD_API_KEY }} # Secret spécifique à prod
  4. Rotation automatique

    Les secrets doivent être renouvelés régulièrement. Idéalement, automatiquement.

    Type de secretFréquence de rotation
    API keys90 jours
    Tokens d’accès24h (préférer les tokens éphémères)
    CertificatsAvant expiration, automatisé
    Passwords de service180 jours
  5. Audit et monitoring

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

Plusieurs approches existent, du simple au sophistiqué :

SolutionCas d’usageComplexité
GitHub SecretsProjets simples, GitHub-centricFaible
SOPSSecrets versionnés avec le code (chiffrés)Moyenne
HashiCorp VaultEnterprise, multi-cloud, rotation automatiqueÉlevée
InfisicalAlternative open source à VaultMoyenne

La signature cryptographique 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
  • Provenance : L’artefact provient bien du pipeline autorisé

Sans signature, rien ne prouve qu’une image Docker en production est bien celle construite par votre CI. Un attaquant pourrait la remplacer.

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. Il résout les problèmes historiques de la signature (gestion des clés) avec une approche “keyless”.

ComposantRôleAnalogie
CosignSigner et vérifier les images/artefactsLe stylo qui signe
FulcioCA qui émet des certificats éphémèresLe notaire qui certifie l’identité
RekorLog de transparence (audit trail)Le registre public des signatures
  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 }}

Un pipeline compromis ne devrait pas pouvoir compromettre toute l’infrastructure. L’isolation limite le “blast radius” d’une attaque.

NiveauMesureBénéfice
RéseauRunners dans un réseau isoléPas d’accès aux ressources internes
ComputeRunners éphémères (destroyed après usage)Pas de persistance d’un compromis
PermissionsToken avec moindre privilègeLimite les actions possibles
EnvironnementsSéparation dev/staging/prodCompromis dev ≠ compromis prod

Les runners self-hosted permanents sont un risque : un attaquant qui compromet un job peut persister sur le runner et compromettre les jobs suivants.

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

jobs:
build:
runs-on: ubuntu-latest # Runner GitHub hébergé, détruit après le job
# Ou pour self-hosted
sensitive-deploy:
runs-on: [self-hosted, ephemeral] # Label pour runners éphémères

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

Au-delà de la signature, les attestations ajoutent des métadonnées vérifiables sur comment, où et par qui un artefact a été construit.

SLSA (Supply-chain Levels for Software Artifacts) définit des niveaux de maturité pour la sécurité de la supply chain :

NiveauExigencesCe que ça prouve
SLSA 1Provenance documentéeQui a construit, quand
SLSA 2Provenance générée par le service de buildBuild reproductible
SLSA 3Build isolé, provenance non falsifiableIntégrité garantie
SLSA 4Hermétique, reproductibleVérifiable par tous
- 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