infra / github-actions
Secrets et variables d'environnement
Explication
Ce que vous allez apprendre
- Distinguer les secrets (
secrets.*, masqués) des variables (vars.*, visibles) - Comprendre comment GitHub masque automatiquement un secret dans les logs d'exécution
- Reconnaître le piège des valeurs dérivées qui échappent au masquage automatique
- Choisir la bonne portée pour un secret (dépôt, organisation, ou environment)
- Appliquer le principe du moindre privilège à la portée des secrets
Dans quel contexte ?
Un développeur ajoute une clé d'API de paiement directement dans le fichier deploy.yml, en clair, pour "aller vite" avant une démo. Ce fichier est versionné dans Git : la clé se retrouve visible dans l'historique du dépôt, potentiellement accessible à quiconque a un accès en lecture, même après suppression du fichier (l'historique Git garde la trace). Cette leçon montre le mécanisme que GitHub Actions propose précisément pour éviter ce genre d'erreur : les secrets, stockés séparément et masqués automatiquement des logs.
Le problème : un pipeline manipule des valeurs confidentielles
D'abord, déployer une application demande souvent des informations confidentielles : un mot de passe de base de données, une clé d'API, un jeton d'authentification.
Le risque de les écrire en clair
Les écrire en clair dans le fichier de workflow serait catastrophique, ce fichier est versionné dans le dépôt Git, potentiellement visible par tout le monde.
La distinction que GitHub Actions propose
GitHub Actions distingue justement deux catégories de valeurs configurables : les secrets, masqués, et les variables, visibles.
secrets.* | vars.* | |
|---|---|---|
| Masqué dans les logs | Oui, automatiquement | Non |
| Usage prévu | Mots de passe, clés d'API, jetons | URL publique, nom d'environnement |
| Portée possible | Dépôt, organisation, environment | Dépôt, organisation, environment |
Ce que fait un secret dans les logs
Une valeur enregistrée comme "secret", secrets.*, est automatiquement masquée dans les journaux d'exécution : si le job l'affiche par erreur, GitHub la remplace par des astérisques.
Ce qui distingue une variable
Une "variable", vars.*, n'a pas cette protection, car elle est censée ne rien contenir de sensible, comme une URL publique ou un nom d'environnement.
Piège fréquent
Le masquage automatique de GitHub repère la valeur EXACTE du secret dans les logs. Si ce secret est transformé avant d'être affiché (encodé en base64, tronqué, concaténé à autre chose), la valeur affichée ne correspond plus au secret original, et le masquage peut échouer à la reconnaître — une fuite silencieuse possible qu'il faut parfois masquer manuellement avec ::add-mask::.
L'erreur à éviter dans les deux sens
Utiliser un secret pour une donnée non sensible, ou une variable pour une donnée sensible, est une erreur de configuration à éviter.
Un piège que le masquage automatique ne couvre pas
Le masquage de GitHub fonctionne en repérant la valeur exacte du secret dans les logs. Si le secret est transformé avant d'être affiché, encodé en base64, tronqué, concaténé, la valeur affichée ne correspond plus exactement au secret original.
La conséquence de ce piège
Le masquage automatique peut alors échouer à le reconnaître, une fuite silencieuse possible, ce qui oblige parfois à masquer manuellement une valeur dérivée.
Bonne pratique
Applique le principe du moindre privilège à la portée d'un secret : préfère un secret d'"environment" (portée la plus fine, vue à la leçon suivante) à un secret d'organisation accessible par défaut à tous les dépôts, dès que c'est possible.
La portée : où un secret est-il disponible
Un secret peut être défini au niveau du dépôt, de l'organisation entière avec restriction sur quels dépôts y ont accès, ou d'un "environment" spécifique, la portée la plus fine, vue dans la leçon suivante.
Le principe qui guide ce choix de portée
Plus la portée est étroite, moins un secret risque d'être exposé à un job qui n'en a pas réellement besoin : donner accès seulement à ce qui est strictement nécessaire.
La leçon suivante approfondit justement cette portée la plus fine : les environments, avec leurs propres règles d'approbation.
Commandes & code
Secrets & variables
# Settings > Secrets and variables > Actions
# Secrets : valeurs masquées dans les logs (tokens, mots de passe, clés API)
# Variables : valeurs non sensibles, visibles en clair (URL d'API publique, nom d'environnement)
name: Deploy
on: push
env:
APP_ENV: production # variable d'environnement au niveau du WORKFLOW entier
jobs:
deploy:
runs-on: ubuntu-latest
env:
LOG_LEVEL: info # variable au niveau du JOB
steps:
- uses: actions/checkout@v4
- name: Déployer
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }} # secret injecté juste pour ce step
API_KEY: ${{ secrets.STRIPE_API_KEY }}
FEATURE_FLAG: ${{ vars.NEW_CHECKOUT_ENABLED }} # "variable" (non secrète)
run: |
echo "Déploiement en $APP_ENV avec log level $LOG_LEVEL"
./scripts/deploy.sh# GitHub masque automatiquement toute valeur de secret qui apparaîtrait dans les logs
# MAIS un secret imprimé via une transformation (base64, substring...) peut échapper au masquage
echo "::add-mask::$(echo $CUSTOM_TOKEN | base64)" # masquer manuellement une valeur dérivée# Secrets au niveau organisation (partagés entre plusieurs repos) vs niveau repo
# Settings organisation > Secrets and variables > Actions
# -> "Repository access" limite quels dépôts peuvent lire un secret d'org
# Secrets par environment (voir leçon dédiée) : encore plus restreints
jobs:
deploy-prod:
environment: production
runs-on: ubuntu-latest
steps:
- run: echo "Utilise le secret scoping à l'environnement 'production'"
env:
PROD_DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}# Gérer les secrets via gh CLI (utile en scripts d'onboarding/CI-as-code)
gh secret set DATABASE_URL --body "postgres://..."
gh secret set STRIPE_API_KEY < ./local-only-key.txt
gh secret list
gh variable set NEW_CHECKOUT_ENABLED --body "true"Résumé
secrets.*= masqué dans les logs,vars.*= variable non sensible, visible en clair.- Portée : repo, organisation (avec accès restreint par dépôt), ou environment (la plus fine).
- Un secret transformé (encodage, sous-chaîne) peut échapper au masquage automatique.
gh secret set/gh variable setpermettent de scripter la configuration des pipelines.
Exercices pratiques
Mission : une fuite de secret qui passe sous le radar du masquage
Objectif : Diagnostiquer un step qui affiche une version transformée d'un secret sans être masquée, puis sécuriser le pipeline.
Contexte
Un step encode secrets.STRIPE_API_KEY en base64 pour l'inclure dans un header HTTP, puis affiche cette valeur encodée dans les logs avec echo, « pour du débogage ». En relisant les logs de la dernière exécution, tu remarques que cette valeur encodée apparaît en clair, non masquée, alors que STRIPE_API_KEY est pourtant bien déclaré comme secret côté GitHub.