cyber / cybersecurite-fondamentale
Gestion des secrets et vault
Explication
Ce que vous allez apprendre
- Expliquer pourquoi un secret committé dans git reste exposé même après suppression du fichier
- Sortir les secrets du code vers des variables d'environnement chargées via un fichier
.envnon versionné - Comprendre la différence entre un secret statique et un secret dynamique généré par un vault
- Intégrer un scanner comme gitleaks en CI pour bloquer un secret avant qu'il n'atteigne la branche principale
- Réagir correctement à une fuite de secret : révocation immédiate, jamais une simple suppression
Dans quel contexte ?
Un développeur commit par erreur un fichier .env contenant la clé API de production Stripe dans un dépôt GitHub, initialement privé puis rendu public quelques mois plus tard lors d'une réorganisation. Même si le fichier est supprimé du dernier commit, la clé reste parfaitement lisible dans l'historique git, accessible à quiconque clone le dépôt. Seule la révocation immédiate de la clé chez Stripe, pas la suppression du fichier, referme réellement la faille.
Le secret le plus mal gardé : celui dans le code source
Un "secret" désigne toute information sensible qui donne un accès (mot de passe de base de données, clé d'API, clé de chiffrement). Le réflexe naturel d'un débutant est de l'écrire directement dans le code — mais le code finit presque toujours dans un dépôt Git, partagé, historisé, parfois public. Une fois committé, un secret reste visible dans l'historique même après suppression du fichier, sauf réécriture complète de l'historique.
Pourquoi les variables d'environnement sont une première étape, pas la solution finale
Sortir les secrets du code vers des variables d'environnement évite déjà l'erreur la plus commune. Mais ces variables restent statiques, souvent partagées entre développeurs par des canaux peu sûrs (chat, fichier .env envoyé par email), et ne changent jamais automatiquement même après un départ d'employé.
Le saut conceptuel vers un vault
Un vault (coffre-fort de secrets, type HashiCorp Vault) va plus loin : il peut générer des identifiants DYNAMIQUES, valables seulement quelques minutes ou heures, créés à la demande pour chaque service qui en a besoin.
| Approche | Durée de vie du secret | Exposition en cas de fuite |
|---|---|---|
| Codé en dur dans le code | Indéfinie | Maximale, visible dans tout l'historique git |
| Variable d'environnement (.env) | Statique, changée manuellement | Réduite mais toujours durable |
| Secret dynamique (vault) | Quelques minutes à quelques heures | Minimale, expire de lui-même |
C'est un changement de philosophie : au lieu de protéger un secret statique, on réduit la fenêtre de temps pendant laquelle un secret volé reste exploitable.
Le rôle de gitleaks et de la détection automatique en CI
Même avec les meilleures intentions, une erreur humaine peut committer un secret par accident. Un scanner comme gitleaks intégré à la chaîne d'intégration continue (CI) bloque ce commit avant qu'il n'atteigne la branche principale — une protection automatique qui ne dépend pas de la vigilance de chaque développeur.
Le réflexe indispensable en cas de fuite
Piège fréquent
Supprimer un secret d'un fichier ne suffit jamais : tant qu'il a été exposé une seule fois, il doit être considéré comme compromis et RÉVOQUÉ (remplacé par un nouveau), pas simplement caché. La réécriture de l'historique git ne referme pas la faille si le dépôt a déjà pu être cloné ou indexé.
Commandes & code
Gestion des secrets et vault
Un secret (clé API, mot de passe DB, clé de chiffrement) ne doit jamais être codé en dur dans le code source ni versionné dans git.
# VULNÉRABLE : secret codé en dur dans le code, versionné dans git pour toujours (même après suppression)
DATABASE_URL = "postgresql://admin:S3cr3tP@ssw0rd@db.technologik.local:5432/prod" # NE JAMAIS FAIRE ÇA
STRIPE_API_KEY = "sk_live_51H8x..."# CORRECT : chargement depuis des variables d'environnement, injectées par l'environnement d'exécution
import os
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
stripe_api_key: str
jwt_secret: str
class Config:
env_file = ".env" # .env DOIT être dans .gitignore, jamais commité
settings = Settings() # lève une erreur explicite si une variable requise est absente# .gitignore - s'assurer qu'aucun secret ne peut être commité par accident
cat <<'EOF' >> .gitignore
.env
.env.*
!.env.example
*.pem
*.key
secrets/
EOF
# .env.example versionné : documente les variables attendues SANS valeurs réelles
cat <<'EOF' > .env.example
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
STRIPE_API_KEY=sk_test_xxx
JWT_SECRET=change_me
EOF# Détecter des secrets déjà commités par erreur avec gitleaks (scan de l'historique complet)
gitleaks detect --source . --verbose --report-path gitleaks-report.json
# Si un secret a fuité dans l'historique git : il faut le RÉVOQUER immédiatement (pas juste le supprimer du repo)
# La réécriture d'historique (git filter-repo / BFG) ne suffit pas seule si le repo a déjà été cloné/publié# HashiCorp Vault : coffre-fort de secrets centralisé, avec bail (lease) et rotation automatique
1. L'application s'authentifie auprès de Vault (AppRole, Kubernetes auth, etc.) - PAS de secret statique
2. Vault génère un secret dynamique à durée de vie limitée (ex: identifiants DB temporaires)
3. Le secret expire automatiquement (lease) -> réduit drastiquement la fenêtre d'exploitation en cas de fuite
4. Toute lecture de secret est journalisée (audit log) -> traçabilité complète# Exemple d'utilisation de Vault en CLI (environnement de lab)
vault kv put secret/technologik/database url="postgresql://..." password="..."
vault kv get secret/technologik/database
# Secrets dynamiques pour PostgreSQL : Vault crée un utilisateur DB temporaire à la demande
vault write database/roles/technologik-app db_name=postgresql creation_statements="CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO "{{name}}";" default_ttl="1h" max_ttl="24h"
vault read database/creds/technologik-app # génère des identifiants valides 1h seulement# Récupération d'un secret Vault au démarrage de l'application (au lieu d'une variable d'env statique)
import hvac
client = hvac.Client(url="https://vault.technologik.local:8200")
client.auth.approle.login(role_id=os.environ["VAULT_ROLE_ID"], secret_id=os.environ["VAULT_SECRET_ID"])
secret = client.secrets.kv.v2.read_secret_version(path="technologik/database")
database_password = secret["data"]["data"]["password"] # jamais loggé, jamais persisté sur disque en clair# Rotation des secrets : politique recommandée
Clés API tierces (Stripe, SendGrid...) : rotation tous les 90 jours ou immédiatement en cas de suspicion
Mots de passe DB / secrets dynamiques : via Vault, TTL courts (1h à 24h), rotation automatique
Clés de chiffrement long terme : rotation annuelle avec ré-chiffrement progressif (voir leçon crypto symétrique)
Clés SSH : rotation à chaque changement d'équipe, désactivation immédiate au départRésumé
- Aucun secret en dur dans le code ni dans git : variables d'environnement au minimum, vault au mieux.
- gitleaks en CI détecte les fuites avant qu'elles n'atteignent la branche principale.
- Un secret qui a fuité doit être révoqué, pas seulement supprimé de l'historique.
- Vault permet des secrets dynamiques à durée de vie limitée, réduisant l'impact d'une fuite.
Exercices pratiques
Mission : la clé Stripe retrouvée dans l'historique git
Objectif : Réagir correctement à une fuite de secret déjà commitée et distinguer les mesures qui referment vraiment la faille de celles qui sont insuffisantes.
Contexte
Un développeur de Technologik découvre, six mois après coup, qu'un ancien commit sur la branche main contient un fichier .env avec STRIPE_API_KEY=sk_live_51H8x... en clair. Le fichier a depuis été supprimé du code actuel, et le dépôt était privé à l'époque du commit, mais est devenu public il y a deux mois lors d'une réorganisation d'équipe.