07 - Secrets dans CI/CD : GitLab, GitHub et les bonnes pratiques
Ce que tu vas apprendre
- Configurer les variables CI/CD dans GitLab et les secrets dans GitHub Actions
- Les options de protection et de masquage
- Les erreurs classiques qui font fuiter des secrets dans les logs
Prerequisites
Un pipeline CI/CD fonctionnel. Si tu pars de zero, commence par la serie sur le déploiement avec GitLab CI.
Les secrets dans le pipeline
Ton code a besoin de secrets pour tourner. Cle API, mot de passe de base de donnees, credentials SMTP. En local, tu as un .env. En production, il faut que ces secrets arrivent dans ton conteneur Docker au moment du déploiement, sans passer par git.
C'est le rôle des variables CI/CD : des secrets stockes dans la plateforme (GitLab, GitHub) et injectes dans l'environnement d'exécution du pipeline.
Sur paltemps.fr, j'utilise GitLab CI avec un runner shell sur le VPS. Le déploiement tient en quelques lignes parce que les secrets sont geres par GitLab.
GitLab CI/CD : variables
Tu les trouves dans Settings > CI/CD > Variables. Chaque variable a plusieurs options :
Type : "Variable" (injectee comme variable d'environnement) ou "File" (écrite dans un fichier temporaire, le chemin est dans la variable). Pour un .env complet, le type "Variable" fonctionne bien.
Protected : la variable n'est disponible que sur les branches protegees (typiquement main). Active toujours cette option pour les secrets de production. Ca empeche un merge request malicieux de lire tes secrets depuis une branche de feature.
Masked : la valeur est remplacee par [MASKED] dans les logs du pipeline. Active cette option pour tout ce qui est sensible. Attention : GitLab ne peut masquer que les valeurs qui font au moins 8 caractères et qui ne contiennent pas de retours a la ligne (pour les valeurs multilignes, utilise le type "File").
Environment scope : tu peux limiter une variable a un environnement spécifique (production, staging). Utile si tes secrets différent entre les environnements.
Le setup paltemps.fr
Voici comment je gere les secrets pour paltemps.fr. J'ai une variable GitLab DOTENV qui contient tout le fichier .env :
ANTHROPIC_API_KEY=sk-ant-api03-xxxx
OPENAI_API_KEY=sk-xxxx
UNSPLASH_ACCESS_KEY=xxxx
PEXELS_API_KEY=xxxx
SMTP_USER=no-reply@paltemps.fr
SMTP_PASS=monmotdepasse
ADMIN_PASSWORD=admin123
WHITELISTED_IPS=1.2.3.4,5.6.7.8
Le .gitlab-ci.yml :
yamlstages:
- deploy
deploy:
stage: deploy
script:
- echo "$DOTENV" > .env
- docker compose up -d --build
after_script:
- rm -f .env
only:
- main
tags:
- shell
Le runner shell exécuté ca directement sur le VPS. Le fichier .env existe pendant le build, puis il est supprime. En réalité, Docker Compose le lit et injecte les variables dans les conteneurs, donc le fichier n'a plus besoin d'exister apres le up.
Une alternative plus propre : écrire le .env dans un emplacement que Docker Compose référencé avec env_file, et le supprimer apres :
yamldeploy:
script:
- echo "$DOTENV" > /opt/paltemps/.env
- cd /opt/paltemps && docker compose up -d --build
after_script:
- rm -f /opt/paltemps/.env
GitHub Actions : secrets
Chez GitHub, c'est dans Settings > Secrets and variables > Actions. Trois niveaux :
- Repository secrets : disponibles dans tous les workflows du repo
- Environment secrets : lies a un environnement (production, staging), avec des regles d'approbation possibles
- Organization secrets : partages entre plusieurs repos
Utilisation dans un workflow :
yamlname: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create .env
run: echo "${{ secrets.DOTENV }}" > .env
- name: Deploy
run: |
scp .env user@vps:/opt/app/.env
ssh user@vps "cd /opt/app && docker compose up -d --build"
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
La différence avec GitLab : GitHub Actions tourne sur les serveurs de GitHub (ou un runner self-hosted). Pour déployer sur ton VPS, tu as besoin d'une connexion SSH, ce qui veut dire stocker une clé privee SSH dans les secrets. Avec GitLab et un runner shell sur le VPS, tu n'as pas ce problème.
Les erreurs qui font fuiter des secrets
Ca arrive plus souvent qu'on le pense. Voici les classiques.
Le echo de debug :
yaml# NE FAIS PAS CA
script:
- echo "La cle est $ANTHROPIC_API_KEY"
- echo $DOTENV # Affiche TOUT le .env dans les logs
Meme avec le masquage active, certaines formes d'affichage passent à travers. Le masquage de GitLab fonctionne par correspondance exacte : si la valeur apparaît en parties ou encodee differemment, elle peut ne pas etre masquee.
Le curl verbose :
yaml# NE FAIS PAS CA
script:
- curl -v -H "Authorization: Bearer $API_KEY" https://api.example.com
# Le -v affiche les headers, donc ta cle, dans les logs
Le set -x :
yaml# NE FAIS PAS CA
script:
- set -x # Affiche chaque commande avant execution
- export API_KEY=$SECRET_KEY # Secret visible dans les logs
Le docker build avec des ARG :
dockerfile# NE FAIS PAS CA
ARG API_KEY
RUN echo $API_KEY > /app/.env
# La valeur est dans l'historique des layers Docker
Utilise plutot des secrets Docker ou monte le .env au runtime, pas au build.
GitHub secret scanning
GitHub scanne automatiquement les commits pour détecter des patterns de secrets connus (clés AWS, tokens GitHub, clés API de dizaines de services). Si un secret est détecté, tu recois une alerte.
Active-le dans Settings > Code security and analysis > Secret scanning. C'est gratuit pour les repos publics, et disponible pour les repos prives avec GitHub Advanced Security.
C'est un filet de sécurité, pas une solution. Le secret a deja ete pousse dans l'historique git au moment ou l'alerte arrive. La bonne pratique, c'est de ne jamais committer de secret, pas de compter sur un scanner pour les trouver apres.
GitLab a un équivalent avec Secret Détection dans le pipeline CI.
Bonnes pratiques
Jamais d'echo de secrets dans les logs. Si tu as besoin de vérifier qu'une variable existe, teste sa longueur :
bashif [ -z "$ANTHROPIC_API_KEY" ]; then
echo "ERREUR: ANTHROPIC_API_KEY non definie"
exit 1
fi
echo "ANTHROPIC_API_KEY: definie (${#ANTHROPIC_API_KEY} caracteres)"
Variables protegees sur les branches protegees. Un contributeur externe ne devrait pas pouvoir lire tes secrets de production via un merge request.
Separe prod et staging. Deux jeux de secrets différents, deux environnements différents. Si le staging est compromis, la prod reste intacte.
Fais tourner les secrets periodiquement. Le prochain article détaillé comment faire la rotation sans casser ton déploiement.
Si un secret fuite dans les logs : considéré-le compromis. Regenere-le immédiatement, mets à jour la variable CI/CD, et vérifié les logs d'acces pour voir si quelqu'un l'a exploite.
Combiner avec SOPS
La stratégie que je recommande pour les petites équipes : les secrets chiffres dans git avec SOPS + age pour le versioning et le partage, et la clé privee age comme unique variable CI/CD. Le pipeline dechiffre le fichier au déploiement.
Ca réduit le nombre de variables CI/CD a une seule (la clé age), et tous les secrets sont geres dans git avec un historique propre.
Navigation : Precedent : 06 - SOPS + age | Suivant : 08 - Rotation des secrets
Sources
- GitLab CI/CD variables par GitLab
- GitHub Actions encrypted secrets par GitHub
- GitHub secret scanning par GitHub
- GitLab Secret Détection par GitLab
Retrouve d'autres articles techniques sur paltemps.fr.