Oui, je committe mes secrets dans git (avec SOPS)
Des secrets chiffrés dans git qui fonctionnent vraiment. Partagez vos clés API sans Vault ni gestionnaires de secrets.
Les secrets n’ont pas leur place dans git. Sauf que si!
J’ai vu des gens m’envoyer des mots de passe de prod en clair sur Teams. Des fichiers .env dans des messages Slack qui disparaissent après 90 jours. Des credentials partagés dans des Google Docs
Maintenant je stocke mes clés API de prod, mes mots de passe de BDD et mes identifiants de dashboards dans mes repos. Chiffrés avec SOPS, versionnés avec le code, accessibles à toute l’équipe
Pas de gestionnaire de secrets, pas de cluster Vault, pas d’infra séparée. Juste git!

Pourquoi ça marche
La plupart des équipes gèrent leurs secrets n’importe comment. Vault nécessite toute une infra. AWS Secrets Manager coûte cher et t’enferme chez AWS. Les fichiers .env chiffrés dans Slack finissent par se perdre
SOPS chiffre avec age ou GPG. Chaque dev a sa clé. Le fichier chiffré part dans git. Ceux qui ont une clé peuvent déchiffrer. Quelqu’un part? On vire sa clé!
Simple, auditable, marche hors ligne
Le setup prend 5 minutes
Installez SOPS et age:
# Debian/Ubuntu
apt install age
go install github.com/getsops/sops/v3/cmd/sops@latest
# Arch
yay -S sops age Générez votre clé:
age-keygen -o ~/.config/sops/age/keys.txt Ça affiche ta clé publique. Tu la partages avec ton équipe. La clé privée reste secrète
Ajouter quelqu’un dans l’équipe
Un nouveau dev arrive. Il génère sa clé age et t’envoie la clé publique. Tu l’ajoutes dans .sops.yaml:
creation_rules:
- path_regex: secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1qlmn8m9x7xj8p6vfz2k5h3r8w6t4y9q2v5x8c3n7b4m6a1d9e2f5g8h1j4k
encrypted_regex: '^(data|stringData|password|api_key|secret)$' Chaque ligne c’est la clé publique de quelqu’un. Tu ajoutes celle du nouveau, tu commites
Tu re-chiffres tous les secrets avec la nouvelle clé:
sops updatekeys secrets/**/*.yaml
git add secrets/
git commit -m 'add key for new developer' Il peut maintenant tout déchiffrer. Ça prend 2 minutes!
Créer des secrets
J’ai construit une commande de task runner pour ça. task sops:create secrets/api-keys.yaml ouvre vim avec un fichier chiffré:
# Makefile ou Taskfile.yml
sops:create:
cmds:
- |
if [ ! -f {{.CLI_ARGS}} ]; then
echo 'Creating new encrypted file: {{.CLI_ARGS}}'
sops {{.CLI_ARGS}}
else
echo 'File exists, editing: {{.CLI_ARGS}}'
sops {{.CLI_ARGS}}
fi Tu édites le fichier dans ton éditeur. SOPS gère le chiffrement quand tu sauvegardes. Tu le commites:
task sops:create secrets/prod/database.yaml
# Tu édites dans vim, tu ajoutes les credentials
git add secrets/prod/database.yaml
git commit -m 'add production database credentials' Le fichier committé est chiffré. Seulement ceux qui ont une clé peuvent le lire
Utiliser les secrets en CI/CD
GitHub Actions a besoin de la clé privée age. Tu l’ajoutes comme secret de repository nommé SOPS_AGE_KEY. Puis tu déchiffres dans ton workflow:
- name: Decrypt secrets
env:
SOPS_AGE_KEY: \$\{\{ secrets.SOPS_AGE_KEY \}\}
run: |
sops -d secrets/prod/api-keys.yaml > api-keys.yaml
export API_KEY=$(yq '.api_key' api-keys.yaml) Point sensible: La CI détient la clé privée. C’est le maillon faible du système. Si tu utilises un runner self-hosted mal isolé ou si quelqu’un peut exécuter du code arbitraire dans ta CI, il peut extraire SOPS_AGE_KEY et déchiffrer tout
Chez nous on a verrouillé ça millimétriquement:
- Seuls les admins peuvent modifier les workflows (protection de branche sur
.github/workflows/) - Les secrets GitHub ne sont accessibles qu’aux admins du repo
- Pas de PRs externes qui triggent des workflows automatiquement (prevent pwn requests)
- Les GitHub-hosted runners uniquement, jamais de self-hosted pour la prod
- Branch protection: impossible de push direct sur main, même pour les admins
Résultat: pour voler SOPS_AGE_KEY, il faut être admin du repo ET réussir à merger un workflow malveillant. Avec CODEOWNERS et la review obligatoire, c’est quasi impossible
Pareil pour GitLab CI, CircleCI, n’importe quelle CI. Tu définis la variable d’env, tu déchiffres le fichier
Pour Kubernetes, tu déchiffres et tu appliques:
sops -d secrets/prod/k8s-secrets.yaml | kubectl apply -f - Workflow d’approbation pour les accès
Tu utilises la protection de branches et CODEOWNERS. Seulement les devs seniors peuvent approuver les changements sur .sops.yaml:
# CODEOWNERS
.sops.yaml @senior-team
secrets/** @senior-team Un nouveau dev veut un accès? Il ouvre une PR avec sa clé publique. Un senior review, approuve, merge. Le re-chiffrement se fait auto en CI:
# .github/workflows/reencrypt.yml
name: Re-encrypt secrets
on:
push:
paths:
- '.sops.yaml'
jobs:
reencrypt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install SOPS
run: |
wget https://github.com/getsops/sops/releases/latest/download/sops-linux-amd64
sudo mv sops-linux-amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
- name: Re-encrypt all secrets
env:
SOPS_AGE_KEY: \$\{\{ secrets.SOPS_AGE_KEY \}\}
run: |
find secrets -name '*.yaml' -exec sops updatekeys {} \;
- name: Commit changes
run: |
git config user.name 'GitHub Actions'
git config user.email 'actions@github.com'
git add secrets/
git diff --quiet && git diff --staged --quiet || git commit -m 're-encrypt secrets with new recipients'
git push À chaque fois que .sops.yaml change, la CI re-chiffre tout. Le nouveau a l’accès direct. L’ancien le perd quand tu retires sa clé
Un dev quitte l’équipe
Tu retires sa clé publique de .sops.yaml. Tu commites et tu push. La CI re-chiffre tout sans sa clé. Il ne peut plus déchiffrer les nouveaux secrets
Attention: Retirer la clé l’empêche de lire les nouveaux secrets, mais il garde tout l’historique git. Il a encore accès à tous les secrets qu’il a déchiffrés avant son départ. C’est pour ça qu’il faut faire tourner les vrais secrets:
# Tu mets à jour les vrais passwords/clés dans tes services
# Puis tu mets à jour les fichiers chiffrés
sops secrets/prod/database.yaml
# Tu changes les passwords
git commit -m 'rotate production credentials' C’est la seule étape manuelle. Le reste est auto
Structure de fichiers qui scale
secrets/
dev/
api-keys.yaml
database.yaml
staging/
api-keys.yaml
database.yaml
prod/
api-keys.yaml
database.yaml
terraform.yaml
.sops.yaml Des clés différentes par environnement. Les devs ont dev et staging. Les ops ont tout:
creation_rules:
- path_regex: secrets/dev/.*\.yaml$
age: age1dev1...,age1dev2...,age1ops1...
- path_regex: secrets/staging/.*\.yaml$
age: age1dev1...,age1dev2...,age1ops1...
- path_regex: secrets/prod/.*\.yaml$
age: age1ops1...,age1ops2... Contrôle d’accès fin, sans infra
Comparé aux autres solutions
Vault: Il faut toute une infra. SOPS a besoin de rien. Les fichiers sont juste dans git
AWS Secrets Manager: Vendor lock-in cloud. Ça coûte cher. SOPS marche partout
.env chiffré dans Slack: Ça se perd. Pas d’historique. SOPS a tout l’historique git et l’audit trail
1Password/Bitwarden en équipe: C’est un système séparé. SOPS vit avec le code. Le déploiement utilise le même SHA git pour le code et les secrets
SOPS gagne quand tu veux versionner les secrets avec le code
Problèmes courants
Clé privée perdue? T’es bloqué! Sauvegarde ton ~/.config/sops/age/keys.txt. Mets-le dans ton gestionnaire de mots de passe. Pas de backup, pas de récupération possible. Tous tes secrets deviennent inaccessibles
Conflits de merge dans les fichiers chiffrés? Tu déchiffres les deux versions, tu merges à la main, tu re-chiffres:
sops -d secrets/file.yaml > file-yours.yaml
git checkout main
sops -d secrets/file.yaml > file-theirs.yaml
# Tu merges manuellement file-yours.yaml et file-theirs.yaml
sops secrets/file.yaml # Tu édites avec le contenu mergé Quelqu’un a committé un secret en clair? Tu utilises BFG Repo-Cleaner pour le virer de l’historique git. Tu fais tourner le secret tout de suite! Mais sache que si le repo est public ou si quelqu’un a déjà pull, c’est trop tard. Le secret est grillé. GitHub scanne les commits publics pour les secrets, tu auras un alert en quelques minutes
Workflow réel
Un nouveau dev a besoin d’accès. Je lui dis de lancer une commande:
task sops:onboarding C’est tout! La commande installe les dépendances, génère sa clé age, et m’envoie sa clé publique auto. J’approuve la PR, je merge, la CI re-chiffre tout. Il pull et a accès à tous les secrets
Sous le capot, la task fait ça:
sops:onboarding:
cmds:
- |
# Détecte le gestionnaire de paquets et installe
if command -v yay &> /dev/null; then
yay -S --noconfirm sops age
elif command -v apt &> /dev/null; then
apt update && apt install -y age
go install github.com/getsops/sops/v3/cmd/sops@latest
fi
- mkdir -p ~/.config/sops/age
- age-keygen -o ~/.config/sops/age/keys.txt
- |
PUBLIC_KEY=$(grep 'public key:' ~/.config/sops/age/keys.txt | awk '{print $4}')
echo 'Votre clé publique: $PUBLIC_KEY'
echo 'Envoyez-la à votre lead pour obtenir l'accès aux secrets' Il pourrait le faire à la main. Installer SOPS et age, générer une clé, grep la clé publique, l’envoyer. Mais pourquoi leur faire mémoriser des commandes quand tu peux automatiser?
Pareil pour moi quand j’ajoute leur clé. J’ai task sops:add-key PUBLIC_KEY=age1... qui met à jour .sops.yaml, committe, push. Ça prend 5 secondes!
Créer un nouveau secret:
task sops:create secrets/staging/new-api.yaml
# Tu édites le fichier, tu ajoutes les credentials
git add secrets/staging/new-api.yaml
git commit -m 'add staging API credentials'
git push Tu l’utilises en déploiement:
sops -d secrets/staging/new-api.yaml | kubectl apply -f - Pas de systèmes séparés. Pas d’UI à cliquer. Tout est code et git
Avant j’évitais de mettre des secrets dans git. Maintenant je peux plus imaginer les gérer autrement! Versionnés, auditables, accessibles, chiffrés. SOPS a rendu la gestion des secrets chiante à nouveau, et c’est exactement ce qu’elle devrait être
La réalité est souvent plus nuancée. Moi, la nuance ça m'ennuie. Je préfère la clarté.