Retour aux articles
6 min de lecture

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.

SecurityDevOpsGitSOPS

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!

Dernier jour de stage non payé

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é.

Commentaires