Retour au cours

infra / github-actions

Secrets et variables d'environnement

Leçon 71 exercice

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 logsOui, automatiquementNon
Usage prévuMots de passe, clés d'API, jetonsURL publique, nom d'environnement
Portée possibleDépôt, organisation, environmentDé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

yaml
# 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
bash
# 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
yaml
# 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 }}
bash
# 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 set permettent de scripter la configuration des pipelines.

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →