Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

SOPS : chiffrer vos secrets dans Git

Par mis à jour le

Améliorer cette page

SOPS chiffre les valeurs d'un fichier YAML, JSON, ENV ou INI tout en laissant ses clés lisibles, ce qui permet de versionner des secrets dans Git et de relire un diff. Cette leçon s'adresse au DevOps qui doit garder des secrets dans un dépôt, typiquement en GitOps, sans les y laisser en clair. Elle couvre le chiffrement avec age, l'injection sans fichier, le travail en équipe et l'intégration en CI/CD.

Tout est mesuré sur SOPS 3.13.3 et age 1.3.2, dans un conteneur Alpine. Trois résultats changent la façon de s'en servir. Par défaut, toutes les valeurs et même les commentaires sont chiffrés, mais les noms de clés, les types et la longueur des valeurs restent visibles. Retirer un membre avec updatekeys ne change pas la data key : avec une ancienne version du fichier tirée de Git, il déchiffre encore les nouvelles valeurs, jusqu'au sops rotate. Et les clés age post-quantiques fonctionnent déjà.

  • Installer SOPS et age en vérifiant signature et empreinte
  • Chiffrer et déchiffrer des fichiers YAML, JSON, ENV et binaires avec age
  • Mesurer ce qu'un fichier chiffré laisse voir : clés, types, longueurs, destinataires
  • Injecter des secrets avec exec-env et exec-file, sans fichier en clair
  • Partager les secrets en équipe : plusieurs clés, updatekeys, rotate et seuil de Shamir
  • Répondre à l'exigence SOCLE-SEC-STO-2 : aucun secret en clair dans les fichiers du projet

SOPS (Secrets OPerationS) est un éditeur de fichiers chiffrés créé par Mozilla, maintenu aujourd'hui par la communauté sous getsops/sops. Il gère les formats YAML, JSON, ENV, INI et binaire, et délègue la protection de sa clé de chiffrement à un backend : age, PGP, AWS KMS, GCP KMS, Azure Key Vault ou un serveur Vault/OpenBao. La version mesurée ici est la 3.13.3, publiée le 23 juillet 2026.

Le choix se joue sur un arbitrage : acceptez-vous d'exploiter un service centralisé pour vos secrets, ou préférez-vous qu'ils vivent dans le dépôt Git avec le code ? SOPS occupe la case « pas d'infrastructure » tout en gardant le multi-destinataires et des diffs où la structure reste lisible.

CritèreSOPS + ageAnsible VaultHashiCorp Vault / OpenBao
StockageFichiers GitFichiers GitService centralisé
Diffs lisiblesOui, clés en clairNon pour un fichier entier ; encrypt_string chiffre une variableSans objet
InfrastructureAucuneAucuneServeur, haute disponibilité
Plusieurs destinatairesNatif, une clé par personneMots de passe distincts par vault IDPolitiques d'accès
Audit des lecturesNon (sauf journal du KMS)NonOui

La dernière ligne est la limite à retenir : SOPS protège le fichier au repos, mais ne dit pas qui l'a déchiffré. Un coffre comme OpenBao trace chaque lecture ; avec SOPS, seule une clé KMS apporte un journal (CloudTrail chez AWS).

SOPS pratique le chiffrement d'enveloppe. Une data key propre au fichier chiffre chaque valeur en AES-256-GCM ; cette data key est ensuite chiffrée séparément pour chaque destinataire (une clé age, une clé KMS), et les enveloppes sont rangées dans un bloc sops: avec un MAC qui détecte toute modification. Le schéma suit ce double niveau, la data key en rouge au centre.

Chiffrement d'enveloppe de SOPS : une data key unique par fichier chiffre les valeurs en AES-256-GCM (ENC[AES256_GCM,...]) ; la même data key est chiffrée pour chaque destinataire, enveloppe age d'Alice, enveloppe age de Bob, enveloppe AWS KMS, toutes rangées dans le bloc sops: avec le MAC.AES-256-GCMchiffrée pourchiffrée pourchiffrée pourValeurs du fichier(clés en clair)Data key(une par fichier)Valeurs chiffréesENC[AES256_GCM,...]Enveloppe ageAliceEnveloppe ageBobEnveloppeAWS KMSBloc sops:enveloppes + MAC
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Ajouter un destinataire n'alourdit donc presque pas le fichier : on ajoute une enveloppe, pas une copie des valeurs. Et retirer un destinataire ne change rien aux valeurs chiffrées tant que la data key n'est pas renouvelée, point mesuré plus bas.

SOPS est un binaire statique unique, sans service à démarrer. L'installation se résume à récupérer ce binaire, vérifier son empreinte et le placer dans le PATH. Il faut ensuite installer le backend choisi, age dans cette leçon, car SOPS ne protège pas sa data key tout seul.

SOPS ne fabrique pas de clé : il chiffre sa data key avec un backend, à choisir avant la première commande. La documentation officielle recommande age plutôt que PGP quand c'est possible ; les KMS cloud apportent contrôle par IAM et journal des accès.

BackendUsage typiqueAvantage
ageÉquipes, projets personnelsSimple, hors ligne, pas de service
AWS KMSProjets AWSContrôle IAM, journal CloudTrail
GCP KMSProjets GCPContrôle IAM
Azure Key VaultProjets AzureContrôle Entra ID
Vault / OpenBao (Transit)Coffre déjà en placeChiffrement délégué au coffre

Le projet publie un binaire par plateforme, un fichier de sommes sops-v3.13.3.checksums.txt et la signature de ce fichier, produite par cosign avec l'identité OIDC du workflow de publication. Vérifier la signature prouve l'origine des sommes ; vérifier la somme prouve l'intégrité du binaire.

Fenêtre de terminal
# Binaire, fichier de sommes et signature du fichier de sommes
curl -sLO https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.linux.amd64
curl -sLO https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.checksums.txt
curl -sLO https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.checksums.sigstore.json
# 1. Le fichier de sommes vient bien du dépôt getsops (cosign 3.1.1)
cosign verify-blob sops-v3.13.3.checksums.txt \
--bundle sops-v3.13.3.checksums.sigstore.json \
--certificate-identity-regexp=https://github.com/getsops \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com
# Verified OK
# 2. Le binaire correspond à sa somme
sha256sum --check --ignore-missing sops-v3.13.3.checksums.txt
# sops-v3.13.3.linux.amd64: OK
sudo install -m 0755 sops-v3.13.3.linux.amd64 /usr/local/bin/sops
sops --version --disable-version-check
# sops 3.13.3

Sans option, sops --version interroge GitHub pour signaler une version plus récente, et SOPS 3.13.3 affiche un avertissement : ce comportement par défaut est déprécié. L'option --disable-version-check, ou la variable SOPS_DISABLE_VERSION_CHECK=true, évite cet appel réseau, utile en CI.

L'archive Linux d'age 1.3.2 contient age, age-keygen, age-inspect et trois plugins de compatibilité. Elle n'est pas accompagnée d'un fichier de sommes : l'empreinte SHA-256 de chaque artefact est affichée sur la page de la release GitHub, et c'est elle que la commande compare.

Fenêtre de terminal
curl -sLO https://github.com/FiloSottile/age/releases/download/v1.3.2/age-v1.3.2-linux-amd64.tar.gz
# Empreinte publiée sur la release GitHub v1.3.2
echo "cbe24006683f8eb669266162894b9a522a1af52f2665fbc63a4bb032ed26ac10 age-v1.3.2-linux-amd64.tar.gz" \
| sha256sum --check
# age-v1.3.2-linux-amd64.tar.gz: OK
tar xzf age-v1.3.2-linux-amd64.tar.gz
sudo install -m 0755 age/age age/age-keygen /usr/local/bin/
age --version
# v1.3.2

age 1.3.0 a introduit des clés post-quantiques hybrides (ML-KEM-768 et X25519), qui commencent par age1pq1. SOPS les gère depuis sa version 3.12.0 (« Age: support hybrid post-quantum identities »). Mesuré avec SOPS 3.13.3 : age-keygen -pq produit une clé publique de 1959 caractères, et le chiffrement comme le déchiffrement fonctionnent.

Le prix est la taille : le même fichier YAML pèse 5792 octets chiffré pour une clé post-quantique, contre 1548 octets pour une clé age classique. Pour un dépôt qui doit rester lisible, réservez ces clés aux secrets dont la confidentialité doit durer des années.

Le cycle tient en trois commandes : générer une paire de clés, chiffrer avec la clé publique, déchiffrer avec la clé privée. La clé publique age1... se partage sans risque et se commite dans .sops.yaml ; la clé privée reste sur le poste. Aucun service n'intervient, le workflow fonctionne hors ligne.

age-keygen écrit la clé privée dans le fichier indiqué, avec les droits 0600, et affiche la clé publique. SOPS cherche ses identités dans ~/.config/sops/age/keys.txt sous Linux, d'où le chemin retenu.

  1. Créer le répertoire des clés

    Fenêtre de terminal
    mkdir -p ~/.config/sops/age
  2. Générer une clé

    Fenêtre de terminal
    age-keygen -o ~/.config/sops/age/keys.txt
    # Public key: age1rxzcv6lwdvh0k9xyr442v70j4jpkmldchf4xpx5rqeq0elx44y3sa6thtl
  3. Noter la clé publique

    La clé publique (age1...) sert à chiffrer ; age-keygen -y keys.txt la réaffiche à tout moment. La clé privée reste dans keys.txt.

sops encrypt écrit le résultat sur la sortie standard : sans redirection, le fichier chiffré s'affiche et l'original reste en clair sur le disque. Supprimez l'original une fois le fichier chiffré commité, et ajoutez-le au .gitignore.

Fenêtre de terminal
cat > secrets.yaml <<'EOF'
# Base de production
database:
host: db.example.com
port: 5432
username: admin
password: super-secret-123 # à renouveler tous les 90 jours
replicas:
- db1.example.com
api:
token: sk-1234567890abcdef
enabled: true
EOF
sops encrypt --age age1rxzcv6lwdvh0k9xyr442v70j4jpkmldchf4xpx5rqeq0elx44y3sa6thtl \
secrets.yaml > secrets.enc.yaml

Le résultat mesuré, abrégé : toutes les valeurs sont chiffrées, host et port compris, et même les commentaires. Le bloc sops: porte l'enveloppe age, le destinataire, la date et le MAC.

#ENC[AES256_GCM,data:uyZcSv8N3RJXMbKWMyL+qpFRYA==,iv:...,tag:...,type:comment]
database:
host: ENC[AES256_GCM,data:UlSv5JPrwd6nBmILQ+U=,iv:...,tag:...,type:str]
port: ENC[AES256_GCM,data:fNHrgw==,iv:...,tag:...,type:int]
username: ENC[AES256_GCM,data:lI/85LQ=,iv:...,tag:...,type:str]
password: ENC[AES256_GCM,data:+31UjqiqVmVp3iNSk06O1w==,iv:...,type:str] #ENC[...,type:comment]
replicas:
- ENC[AES256_GCM,data:BdeCuyKGCsL6FeSOmnoD,iv:...,tag:...,type:str]
api:
token: ENC[AES256_GCM,data:JdaMriEP4rBaiSAWZFNWoZvMyw==,iv:...,tag:...,type:str]
enabled: ENC[AES256_GCM,data:9uAflw==,iv:...,tag:...,type:bool]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
recipient: age1rxzcv6lwdvh0k9xyr442v70j4jpkmldchf4xpx5rqeq0elx44y3sa6thtl
lastmodified: "2026-10-01T13:31:56Z"
mac: ENC[AES256_GCM,data:...,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.3

Un fichier SOPS n'est pas une boîte opaque, et c'est voulu : la structure reste lisible pour la revue. Mais cela signifie que le fichier révèle plusieurs informations à quiconque lit le dépôt. La mesure en relève six, à prendre en compte avant de publier un dépôt.

Ce qui reste visibleExemple mesuré
Noms des clés et hiérarchiedatabase.password, api.token
Type de chaque valeurtype:str, type:int, type:bool, type:comment
Nombre d'éléments d'une listereplicas contient une entrée
Longueur de chaque valeur1 caractère donne data:dg==, 40 caractères donnent 40 octets chiffrés
DestinatairesClés publiques age, ARN des clés KMS
MétadonnéesDate lastmodified, version de SOPS

La longueur est la fuite la plus sous-estimée : AES-GCM ne remplit pas les valeurs, et un mot de passe de 8 caractères se distingue d'une clé d'API de 40. Les noms de clés comptent aussi : stripe_live_key dit à un attaquant ce qu'il cherche. Choisissez des noms neutres pour les dépôts publics.

Aucune option de clé n'est nécessaire au déchiffrement : la liste des destinataires est dans le bloc sops:, et SOPS essaie les identités qu'il trouve. Il les cherche notamment, comme le montrent ses messages d'erreur, dans SOPS_AGE_SSH_PRIVATE_KEY_FILE, ~/.ssh/id_ed25519, SOPS_AGE_KEY, SOPS_AGE_KEY_FILE, SOPS_AGE_KEY_CMD et ~/.config/sops/age/keys.txt.

Fenêtre de terminal
# Avec la clé à l'emplacement par défaut
sops decrypt secrets.enc.yaml
# Avec un fichier de clé ailleurs
SOPS_AGE_KEY_FILE=~/keys/ma-cle.txt sops decrypt secrets.enc.yaml
# Avec le contenu de la clé dans une variable (cas d'une CI)
SOPS_AGE_KEY="$(grep AGE-SECRET-KEY ~/keys/ma-cle.txt)" sops decrypt secrets.enc.yaml

Sans aucune clé, la commande sort en code 128 avec Failed to get the data key required to decrypt the SOPS file et la liste des emplacements essayés. Avec une clé qui n'est pas destinataire, le message devient no identity matched any of the recipients.

sops edit est la commande du quotidien : le contenu déchiffré n'existe que dans un fichier temporaire, supprimé à la fermeture de l'éditeur, et SOPS rechiffre à l'enregistrement. L'éditeur se choisit avec SOPS_EDITOR, depuis SOPS 3.10, ou à défaut avec EDITOR.

Fenêtre de terminal
# Ouvre le fichier déchiffré dans l'éditeur, rechiffre à l'enregistrement
SOPS_EDITOR=vim sops edit secrets.enc.yaml

SOPS détecte le format par l'extension du fichier. Le format ENV chiffre chaque ligne CLÉ=valeur et range ses métadonnées dans des lignes sops_*. Toute extension inconnue, .pem compris, est traitée comme binaire : le contenu entier devient une seule valeur data dans un JSON.

Fenêtre de terminal
cat > secrets.env <<'EOF'
DB_HOST=db.example.com
DB_PASSWORD=super-secret-123
API_TOKEN=sk-1234567890abcdef
EOF
sops encrypt --age age1... secrets.env > secrets.enc.env
# DB_PASSWORD=ENC[AES256_GCM,data:neYlNO1YUTm9+4rwjQoHZw==,iv:...,type:str]
# sops_age__list_0__map_recipient=age1...
# sops_version=3.13.3

Chiffrement sur place, entrée standard et fichier de sortie

Section intitulée « Chiffrement sur place, entrée standard et fichier de sortie »

L'option -i (--in-place) remplace le fichier par sa version chiffrée, ce qui suppose une copie dans Git avant de la lancer. Depuis SOPS 3.10, SOPS lit aussi l'entrée standard, à condition de lui dire quel fichier il traite avec --filename-override, faute de quoi il refuse.

Fenêtre de terminal
# Chiffrer sur place
sops encrypt -i --age age1... secrets.yaml
# Entrée standard : --filename-override fixe le format et la règle .sops.yaml
echo '{"secret": "from-stdin"}' | sops encrypt --filename-override secret.json --age age1...
# Sans l'option : Error: must specify --filename-override when reading from stdin
# Déchiffrer vers un fichier
sops decrypt secrets.enc.yaml --output secrets.yaml

Le fichier écrit par --output prend les droits par défaut du système : mesuré avec un umask de 0022, il est créé en 0644, donc lisible par tous les utilisateurs de la machine. Préférez les commandes exec-env et exec-file, qui évitent ce fichier en clair.

Par défaut, SOPS chiffre toutes les valeurs. Le chiffrement sélectif garde lisibles celles qui ne sont pas sensibles (nom d'hôte, port, URL publique) pour faciliter la revue. Le revers est réel : chaque valeur laissée en clair est une information de plus pour un attaquant, et une erreur d'expression régulière laisse un secret en clair sans prévenir.

L'option --encrypted-regex ne chiffre que les clés dont le nom correspond à l'expression régulière. Mesuré : host, port, username et replicas restent en clair, et le commentaire attaché à password aussi, puisque seule la valeur correspondante est chiffrée.

Fenêtre de terminal
sops encrypt --age age1... \
--encrypted-regex '^(password|token)$' \
secrets.yaml > secrets.enc.yaml
# Base de production
database:
host: db.example.com
port: 5432
username: admin
password: ENC[AES256_GCM,data:FzSPf3Eu2kjxJpJSjUnNkg==,iv:...,type:str] # à renouveler tous les 90 jours
replicas:
- db1.example.com
api:
token: ENC[AES256_GCM,data:qmzPg5zuWmBv6SITwoVemljqiA==,iv:...,type:str]
enabled: true

L'option inverse, --unencrypted-regex, chiffre tout sauf les clés désignées. Le filtrage par suffixe existe aussi, et SOPS en applique un par défaut : toute clé terminée par _unencrypted reste en clair, comme le rappelle la ligne unencrypted_suffix du bloc sops:.

Fenêtre de terminal
# Tout chiffrer sauf host, username et port
sops encrypt --age age1... --unencrypted-regex '^(host|username|port)$' secrets.yaml
# Ne chiffrer que les clés terminées par _secret
sops encrypt --age age1... --encrypted-suffix '_secret' config.yaml
# db_secret: ENC[AES256_GCM,...]
# host: x

Pour ne pas répéter ces options, déclarez-les dans .sops.yaml, à la racine du dépôt. SOPS applique la première règle dont path_regex correspond au chemin du fichier ; aucune règle ne correspond, et la commande échoue avec no matching creation rules found.

creation_rules:
# Production : ne chiffrer que les secrets
- path_regex: prod/.*
age: age1abc...
encrypted_regex: '^(password|token|secret|key)$'
# Tout le reste : tout chiffrer
- path_regex: .*
age: age1abc...

sops decrypt > secrets.yaml laisse un fichier en clair sur le disque, en 0644 comme mesuré plus haut. SOPS propose deux commandes qui l'évitent : exec-env et exec-file. Le secret n'existe alors que dans la mémoire ou dans un tube nommé du processus lancé.

sops exec-env déchiffre un fichier plat et lance la commande avec chaque clé comme variable d'environnement. La commande est passée entre apostrophes, sinon votre shell remplacerait $DB_PASSWORD avant que SOPS ne la lance.

Fenêtre de terminal
sops exec-env secrets.enc.env 'echo DB_PASSWORD=$DB_PASSWORD'
# DB_PASSWORD=super-secret-123

Un YAML imbriqué est refusé, mesuré : cannot use complex value in environment; offending key database, avec un code de sortie 1. Réservez exec-env aux fichiers .env ou aux YAML plats.

L'option --pristine lance la commande sans l'environnement du parent : seuls les secrets déchiffrés sont transmis. Mesuré, HOME est vide ; PATH vaut /sbin:/usr/sbin:/bin:/usr/bin, valeur que le shell lancé fixe lui-même faute d'héritage.

Fenêtre de terminal
sops exec-env --pristine secrets.enc.env 'echo HOME=$HOME PATH=$PATH DB_PASSWORD=$DB_PASSWORD'
# HOME= PATH=/sbin:/usr/sbin:/bin:/usr/bin DB_PASSWORD=super-secret-123

L'intérêt est de ne pas transmettre à la commande les variables de votre session, comme un AWS_SECRET_ACCESS_KEY exporté plus tôt. Cela protège contre une fuite accidentelle, pas contre un script malveillant qui lirait d'autres sources.

sops exec-file rend le contenu déchiffré disponible à la commande via {}. Par défaut, mesuré, {} est un tube nommé (FIFO, droits prw-------) : rien n'est écrit sur le disque. L'option --no-fifo le remplace par un fichier temporaire en 0600, supprimé à la fin, pour les outils qui relisent leur fichier.

Fenêtre de terminal
sops exec-file secrets.enc.yaml 'ls -l {}; head -3 {}'
# prw------- 1 root root 0 ... /tmp/.sops544678192/tmp-file
# # Base de production
# database:
# host: db.example.com
# Exemple : appliquer un manifeste Kubernetes chiffré
sops exec-file k8s-secrets.enc.yaml 'kubectl apply -f {}'

exec-file accepte tous les formats, YAML imbriqué compris. Un tube ne se lit qu'une fois : un outil qui ouvre deux fois son fichier de configuration demande --no-fifo.

Les commandes set, unset et decrypt --extract manipulent un fichier chiffré par programme, sans éditeur. Les chemins suivent une syntaxe JSON : '["database"]["password"]', et les valeurs aussi, d'où les guillemets doubles autour d'une chaîne.

sops set modifie le fichier sur place ; une clé absente est créée. La valeur doit être du JSON valide : une chaîne s'écrit '"new-password-456"', un nombre 5433.

Fenêtre de terminal
sops set secrets.enc.yaml '["database"]["password"]' '"new-password-456"'
sops decrypt --extract '["database"]["password"]' secrets.enc.yaml
# new-password-456

Depuis SOPS 3.11, --value-stdin et --value-file évitent que la valeur passe dans l'historique du shell. La règle du JSON vaut aussi pour eux, mesuré : une valeur brute est refusée avec Value for --set is not valid JSON.

Fenêtre de terminal
# ❌ Valeur brute : refusée
printf 'new-password' | sops set --value-stdin secrets.enc.yaml '["database"]["password"]'
# Value for --set is not valid JSON
# Valeur JSON : acceptée
printf '"new-password"' | sops set --value-stdin secrets.enc.yaml '["database"]["password"]'

sops unset retire une clé et sa valeur, sur place. Le parent n'est pas nettoyé : supprimer les deux clés de api laisse un objet vide, api: {}, visible dans la sortie mesurée.

Fenêtre de terminal
sops unset secrets.enc.yaml '["api"]["token"]'
sops unset secrets.enc.yaml '["api"]["enabled"]'
sops decrypt secrets.enc.yaml | tail -2
# - db1.example.com
# api: {}

decrypt --extract ne renvoie que la valeur ou la sous-arborescence demandée, ce qui convient aux scripts. Une sous-arborescence est rendue avec ses commentaires déchiffrés.

Fenêtre de terminal
sops decrypt --extract '["database"]["password"]' secrets.enc.yaml
# super-secret-123
sops decrypt --extract '["database"]' secrets.enc.yaml
# host: db.example.com
# port: 5432
# username: admin
# # à renouveler tous les 90 jours
# password: super-secret-123
# ...

sops filestatus indique si un fichier est chiffré par SOPS, en JSON. C'est la base d'un hook de pré-commit qui refuse un fichier *.enc.yaml resté en clair, l'erreur la plus fréquente après un sops decrypt -i oublié.

Fenêtre de terminal
sops filestatus secrets.enc.yaml
# {"encrypted":true}
sops filestatus secrets.yaml
# {"encrypted":false}
#!/usr/bin/env bash
# .git/hooks/pre-commit : refuse un *.enc.yaml non chiffré
set -euo pipefail
while IFS= read -r file; do
if ! sops filestatus "$file" | grep -q '"encrypted":true'; then
echo "ERREUR : $file n'est pas chiffré" >&2
exit 1
fi
done < <(git diff --cached --name-only --diff-filter=ACM -- '*.enc.yaml')

Pour savoir qui peut déchiffrer un fichier, inutile de le déchiffrer : les destinataires sont en clair dans le bloc sops:. Un grep recipient secrets.enc.yaml liste les clés publiques age, et les ARN KMS apparaissent sous la clé kms. L'option --show-all-keys qu'on trouve dans d'anciens tutoriels n'existe pas : SOPS 3.13.3 répond flag provided but not defined.

Pour une équipe, plusieurs personnes doivent pouvoir déchiffrer. SOPS chiffre la data key pour chaque destinataire : chacun déchiffre avec sa propre clé privée, et chacun peut être retiré sans toucher aux autres.

Les destinataires se séparent par une virgule, sans espace. Mesuré : le fichier contient alors deux enveloppes, et chacune des deux clés privées déchiffre seule.

Fenêtre de terminal
sops encrypt --age age1alice...,age1bob... secrets.yaml > secrets.enc.yaml

Pour ne pas répéter les clés, déclarez les destinataires dans .sops.yaml par chemin. Un key group réunit des clés alternatives : n'importe laquelle suffit à retrouver la data key. Les comptes AWS ont 12 chiffres dans les ARN.

.sops.yaml
creation_rules:
# Production : équipe ops, ou clé KMS
- path_regex: secrets/prod/.*\.yaml$
key_groups:
- age:
- age1abc... # Alice (ops)
- age1def... # Bob (ops)
kms:
- arn: arn:aws:kms:eu-west-3:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab
# Développement : équipe dev
- path_regex: secrets/dev/.*\.yaml$
age: age1ghi...,age1jkl...
Fenêtre de terminal
# SOPS applique la règle qui correspond au chemin
sops encrypt secrets/prod/database.yaml > secrets/prod/database.enc.yaml

Quand quelqu'un quitte l'équipe, on retire sa clé de .sops.yaml, puis sops updatekeys synchronise le fichier. La sortie mesurée montre le retrait. Mais la data key ne change pas, et c'est tout le problème.

Fenêtre de terminal
$ sops updatekeys -y team.enc.yaml
Syncing keys for file /w/team.enc.yaml
The following changes will be made to the file's groups:
Group 1
age1akfm23rhvguzrnezcw40m3kt6fyhftpgzzsu9fqxt6dhay6g7vxq7djamd
--- age1m06ql5n62vepz5ycvp8w3rl5gs7trygldqg5yu6qmxrlwj4sdslstlx9y0
File /w/team.enc.yaml synced with new keys

Après cette commande, Bob ne déchiffre plus le fichier à jour (code 128). Mais les valeurs chiffrées sont identiques octet pour octet, et Bob possède toujours l'ancienne version du fichier dans l'historique Git. Mesuré : en recollant l'ancien bloc sops:, qui contient son enveloppe, sur les nouvelles valeurs, Bob retrouve la data key et déchiffre le nouveau fichier.

Fenêtre de terminal
# Ancien bloc sops: (avec l'enveloppe de Bob) + valeurs du fichier mis à jour
SOPS_AGE_KEY_FILE=bob.txt sops decrypt --ignore-mac hybride.yaml | grep password
# password: super-secret-123

La parade est sops rotate, qui génère une nouvelle data key et rechiffre toutes les valeurs. Mesuré : après sops rotate -i, les valeurs chiffrées changent, et la même manipulation échoue (cipher: message authentication failed). La documentation SOPS donne l'ordre : retirer la clé, puis renouveler la data key, puis changer les secrets eux-mêmes, que Bob a pu lire tant qu'il était destinataire.

Fenêtre de terminal
# 1. Retirer la clé de .sops.yaml, puis synchroniser
sops updatekeys -y secrets.enc.yaml
# 2. Nouvelle data key, toutes les valeurs rechiffrées
sops rotate -i secrets.enc.yaml
# 3. Changer les mots de passe et jetons chez leurs fournisseurs

sops rotate accepte aussi --add-age et --rm-age pour ajouter ou retirer un destinataire et changer la data key en une seule commande.

Pour les secrets critiques, SOPS découpe la data key avec le partage de secret de Shamir : avec shamir_threshold: 2 et trois key groups, il faut réussir à déchiffrer avec au moins deux groupes sur trois. Aucune personne seule ne peut alors lire le fichier.

.sops.yaml
creation_rules:
- path_regex: critical/.*
shamir_threshold: 2
key_groups:
- age:
- age1abc... # Équipe ops
- age:
- age1def... # Équipe sécurité
- age:
- age1ghi... # Sauvegarde hors ligne

Mesuré avec une seule clé disponible, le déchiffrement échoue en code 128 :

Group 0: SUCCESS
Group 1: FAILED
Group 2: FAILED
Recovery failed because the file was encrypted with a Shamir threshold of
2, but only 1 part(s) were successfully recovered, one for each successful
key group. In order for SOPS to recover the file, at least 2 groups have to
be successful.

Avec les clés de deux groupes, le fichier se déchiffre. Le seuil protège contre une personne isolée, au prix d'une coordination à chaque lecture : réservez Shamir aux secrets racine.

Sur un projet AWS, KMS évite de distribuer des clés privées : le droit de déchiffrer devient une politique IAM, révocable sans toucher aux fichiers, et chaque appel Decrypt est tracé dans CloudTrail, ce qu'age ne fournit pas. Cette section suit la documentation SOPS et AWS ; elle n'a pas été rejouée, faute de compte AWS dans le laboratoire.

  1. Créer une clé KMS

    key/...
    aws kms create-key --description "SOPS encryption key" \
    --tags TagKey=Purpose,TagValue=SOPS
  2. Chiffrer avec KMS

    Fenêtre de terminal
    sops encrypt --kms arn:aws:kms:eu-west-3:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab \
    secrets.yaml > secrets.enc.yaml
  3. Déchiffrer : SOPS utilise les identifiants AWS de la session

    Fenêtre de terminal
    sops decrypt secrets.enc.yaml

La contrepartie est la dépendance réseau : un poste hors ligne ou une région indisponible bloque le déchiffrement. Placer une clé age hors ligne dans le même key group que la clé KMS, comme dans le .sops.yaml d'équipe plus haut, garde une voie de secours.

SOPS n'est utile que s'il s'insère dans les outils qui consomment les secrets. Le principe reste le même : le fichier chiffré est versionné, et seule la clé privée est fournie à l'exécution, par variable d'environnement ou par rôle IAM. Quatre intégrations couvrent les cas fréquents.

La collection community.sops déchiffre les fichiers SOPS dans les playbooks. Toute tâche qui manipule la valeur porte no_log: true, sinon Ansible l'écrit dans sa sortie en mode verbeux ou en cas d'échec.

Fenêtre de terminal
ansible-galaxy collection install community.sops
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
---
- name: Déployer avec secrets SOPS
hosts: all
vars:
secrets: "{{ lookup('community.sops.sops', 'secrets.enc.yaml') | from_yaml }}"
tasks:
- name: Écrire la configuration de connexion
ansible.builtin.template:
src: db.conf.j2
dest: /etc/app/db.conf
owner: app
group: app
mode: "0600"
no_log: true

L'opérateur sops-secrets-operator transforme un objet SopsSecret, chiffré par SOPS, en Secret Kubernetes. Sa version 0.21.2 sert l'API isindir.github.com/v1alpha3. La ressource entière est chiffrée par sops encrypt, bloc sops: compris, et l'opérateur détient la clé de déchiffrement dans le cluster.

apiVersion: isindir.github.com/v1alpha3
kind: SopsSecret
metadata:
name: app-secrets
spec:
secretTemplates:
- name: db-credentials
stringData:
username: ENC[AES256_GCM,data:...,type:str]
password: ENC[AES256_GCM,data:...,type:str]
# sops:
# (bloc ajouté par sops encrypt)

Un runner n'a pas de clé dans ~/.config : la clé privée arrive par la variable SOPS_AGE_KEY, qui contient directement le texte de la clé. Les deux exemples installent SOPS avec vérification d'empreinte, puis lancent le déploiement avec sops exec-env, sans fichier déchiffré sur le disque.

.github/workflows/deploy.yml
name: deploy
on:
push:
branches: [main]
permissions: {}
jobs:
deploy:
runs-on: ubuntu-24.04
environment: production
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Installer SOPS (empreinte vérifiée)
run: |
curl -sLO https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.linux.amd64
echo "e5bec3346a873ae91d871550f3e698c1aad962aff462a080e40f25fde17fef6b sops-v3.13.3.linux.amd64" | sha256sum --check
sudo install -m 0755 sops-v3.13.3.linux.amd64 /usr/local/bin/sops
sops --version --disable-version-check
- name: Déployer avec les secrets déchiffrés en mémoire
env:
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
run: sops exec-env secrets/prod.enc.env './deploy.sh'

Ce workflow passe actionlint, zizmor 1.30.0 et poutine 1.1.6 sans constat. Le secret SOPS_AGE_KEY est rangé dans l'environnement production, que l'on protège par une règle de revue.

La somme e5bec334... vient du fichier sops-v3.13.3.checksums.txt dont la signature cosign a été vérifiée plus haut. L'écrire dans le pipeline, plutôt que retélécharger le fichier de sommes, fige le binaire attendu : une release modifiée après coup ferait échouer le job au lieu de passer.

Git peut afficher les valeurs déchiffrées dans ses diffs grâce à un textconv. Mesuré : après changement du port, git diff montre - port: 5432 et + port: 5433 au lieu de deux blocs ENC[...].

  1. Déclarer le filtre dans .gitattributes

    *.enc.yaml diff=sopsdiffer
    *.enc.json diff=sopsdiffer
  2. Configurer Git

    Fenêtre de terminal
    git config diff.sopsdiffer.textconv "sops decrypt"
  3. Vérifier : git diff affiche les valeurs en clair pour ces fichiers.

Ce réglage déchiffre les fichiers sur la machine qui lance le diff. Ne l'activez que sur un poste qui détient la clé privée, jamais sur un runner partagé : la revue de la structure n'a pas besoin des valeurs.

La plupart des erreurs SOPS viennent de trois causes : une clé privée introuvable, un destinataire absent du fichier, ou une modification qui casse le MAC. Le tableau associe chaque message réel, relevé avec SOPS 3.13.3, à sa cause.

Message mesuréCauseSolution
Failed to get the data key required to decrypt the SOPS fileAucune clé privée trouvéeVérifier SOPS_AGE_KEY_FILE, SOPS_AGE_KEY ou ~/.config/sops/age/keys.txt
no identity matched any of the recipientsVotre clé n'est pas destinataireSe faire ajouter, puis sops updatekeys
MAC mismatch. File has ..., computed ...Clé ou ligne retirée à la mainRestaurer depuis Git, modifier avec sops edit ou sops set
cipher: message authentication failedValeur chiffrée altérée ou déplacéeRestaurer depuis Git
cannot use complex value in environmentYAML imbriqué avec exec-envFichier .env, YAML plat, ou exec-file
no matching creation rules foundAucune path_regex ne correspondCorriger .sops.yaml ou le chemin du fichier
Recovery failed because the file was encrypted with a Shamir threshold of 2Pas assez de key groups disponiblesRéunir les clés d'au moins shamir_threshold groupes
Value for --set is not valid JSONValeur brute passée à setEntourer la chaîne de guillemets doubles
must specify --filename-override when reading from stdinEntrée standard sans nom de fichierAjouter --filename-override

Ces règles règlent les problèmes qui apparaissent après quelques mois en équipe : le départ d'un collègue, la perte d'un poste, la divergence des conventions. Les appliquer dès le début coûte quelques minutes ; les rattraper impose de rechiffrer tous les fichiers et de changer les secrets.

  • Une clé par personne : chaque membre a sa clé age, ce qui permet de retirer une personne sans toucher aux autres
  • Retrait = updatekeys + rotate + nouveaux secrets : l'historique Git garde l'ancienne enveloppe
  • Sauvegarder les clés privées : une clé perdue rend les fichiers irrécupérables
  • .sops.yaml versionné : toute l'équipe applique les mêmes règles
  • Noms de clés neutres dans les dépôts publics : ils restent lisibles
  • KMS en production : contrôle par IAM et journal des déchiffrements, avec une clé age de secours

Séparer dev/ et prod/ par répertoire permet d'écrire des path_regex distincts dans .sops.yaml, donc des destinataires différents par environnement. Le suffixe .enc.yaml uniformise le filtrage dans les hooks Git et dans .gitattributes.

  • Répertoireproject/
    • .sops.yaml configuration des destinataires
    • Répertoiresecrets/
      • Répertoiredev/
        • database.enc.yaml
      • Répertoireprod/
        • database.enc.yaml
    • README.md instructions pour l'équipe

Ces documentations officielles complètent les mesures de la leçon. La documentation de SOPS a quitté le README du dépôt pour getsops.io depuis la version 3.13.1 ; les notes de version signalent les changements de comportement d'une version à l'autre.

  • SOPS chiffre les valeurs avec une data key par fichier, elle-même chiffrée pour chaque destinataire (age, KMS).
  • Par défaut, tout est chiffré, commentaires compris ; restent visibles les noms de clés, les types, la longueur des valeurs et les destinataires.
  • On installe SOPS en vérifiant la signature cosign du fichier de sommes, puis la somme du binaire.
  • age est recommandé par la documentation SOPS ; ses clés post-quantiques fonctionnent depuis SOPS 3.12.0, au prix de fichiers presque quatre fois plus gros.
  • exec-env et exec-file (tube nommé par défaut) évitent le fichier en clair que --output crée en 0644.
  • updatekeys ne change pas la data key : pour retirer un membre, enchaîner updatekeys, sops rotate, puis changer les secrets.
  • Un seuil de Shamir exige plusieurs key groups ; une seule clé donne Recovery failed.
  • La preuve attendue par SOCLE-SEC-STO-2 est une inspection montrant qu'aucun secret n'est en clair dans les fichiers du projet ; SOPS ne trace pas les lectures, ce que STO-4 demande.

Vérifiez que l'essentiel de cette leçon est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn