Stocker des secrets Kubernetes dans des dépôts Git sans chiffrement est l’une des erreurs de sécurité les plus courantes dans les déploiements cloud-native. Bien que les Secrets Kubernetes utilisent l’encodage base64, cela ne fournit aucune protection cryptographique — toute personne ayant accès au dépôt peut les décoder instantanément. SOPS (Secrets OPerationS) combiné avec le chiffrement Age résout ce problème élégamment, vous permettant de stocker des secrets chiffrés directement dans Git tout en maintenant une compatibilité totale avec les workflows GitOps comme ArgoCD et FluxCD.
Prérequis
- Un cluster Kubernetes fonctionnel (v1.24+) avec
kubectlconfiguré sopsv3.8+ installéagev1.1+ installé- Familiarité de base avec les Secrets Kubernetes et les manifestes YAML
- Dépôt Git pour vos manifestes Kubernetes
- Optionnel : ArgoCD ou FluxCD pour l’intégration GitOps
Comprendre les Risques des Secrets Kubernetes
Les Secrets Kubernetes sont souvent mal compris comme un mécanisme de stockage sécurisé. En réalité, ils ont des limitations significatives que vous devez comprendre avant de concevoir votre stratégie de secrets.
Base64 N’est Pas du Chiffrement
Un Secret Kubernetes standard stocke les valeurs comme des chaînes encodées en base64 :
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4=
password: cDRzc3cwcmQxMjM=
Décoder ces valeurs est trivial :
echo "cDRzc3cwcmQxMjM=" | base64 -d
# Sortie : p4ssw0rd123
Toute personne qui clone votre dépôt ou obtient un accès en lecture à etcd peut extraire chaque secret de votre cluster.
Préoccupations de Stockage etcd
Par défaut, Kubernetes stocke les Secrets sans chiffrement dans etcd. Bien que vous puissiez activer le chiffrement au repos avec EncryptionConfiguration, cela ne protège que les fichiers de données etcd — pas les réponses du serveur API ni les manifestes stockés dans Git. Vous avez besoin d’une solution qui chiffre les secrets avant qu’ils n’entrent dans votre système de contrôle de versions.
Le Problème Git
GitOps exige que l’état souhaité de votre cluster vive dans Git. Mais commiter des secrets en texte clair dans Git signifie :
- Chaque développeur ayant accès au dépôt voit les identifiants de production
- L’historique des secrets persiste dans Git pour toujours, même après suppression
- Les dépôts divulgués exposent chaque secret jamais commité
- Les cadres de conformité (SOC 2, PCI-DSS) interdisent les identifiants en texte clair dans le contrôle de code source
Installation de SOPS et Age
Linux
# Installer Age
sudo apt-get install age
# Ou depuis les releases GitHub
curl -LO https://github.com/FiloSottile/age/releases/download/v1.2.0/age-v1.2.0-linux-amd64.tar.gz
tar xzf age-v1.2.0-linux-amd64.tar.gz
sudo mv age/age age/age-keygen /usr/local/bin/
# Installer SOPS
curl -LO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64
sudo mv sops-v3.9.4.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
macOS
brew install sops age
Vérifiez les deux installations :
sops --version
# sops 3.9.4
age --version
# v1.2.0
Chiffrement des Secrets avec SOPS et Age
Générer une Paire de Clés Age
age-keygen -o age-key.txt
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Stockez la clé privée en sécurité. La clé publique peut être partagée et commitée dans votre dépôt :
# Stocker la clé où SOPS peut la trouver
mkdir -p ~/.config/sops/age
mv age-key.txt ~/.config/sops/age/keys.txt
# Ou définir la variable d'environnement
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
Créer la Configuration .sops.yaml
Créez un fichier .sops.yaml à la racine de votre dépôt pour définir les règles de chiffrement :
creation_rules:
# Chiffrer tous les fichiers dans le répertoire secrets/
- path_regex: secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Chiffrer les secrets de staging avec une clé différente
- path_regex: envs/staging/secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1lzd99uca0lqtmgahfmxj4gvr2fcswcaxmxnz30fwcmm22hjvrzrqsnqxsl
# Clé différente pour la production
- path_regex: envs/production/secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1an5quvgyl8uny5mkmgkzpnpe9wuufrl2vvmqsht4xp3k96s608q0eamcl
Plusieurs destinataires (séparés par des virgules) permettent l’accès d’équipe — n’importe quelle clé listée peut déchiffrer le fichier.
Chiffrer un Secret Kubernetes
Commencez avec votre manifeste de secret en texte clair :
# secrets/db-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: admin
password: "s3cur3-pr0d-p@ssw0rd"
connection-string: "postgresql://admin:s3cur3-pr0d-p@ssw0rd@db.internal:5432/myapp"
Chiffrez-le avec SOPS :
sops --encrypt --in-place secrets/db-credentials.yaml
Le fichier chiffré préserve la structure YAML mais chiffre les valeurs :
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: ENC[AES256_GCM,data:k8mN3w==,iv:abc...,tag:def...,type:str]
password: ENC[AES256_GCM,data:dGhpcyBpcyBl...,iv:ghi...,tag:jkl...,type:str]
connection-string: ENC[AES256_GCM,data:bG9uZ2VyIHN0cmluZw==...,iv:mno...,tag:pqr...,type:str]
sops:
age:
- recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-17T10:00:00Z"
version: 3.9.4
Les clés (apiVersion, kind, metadata) restent lisibles tandis que les valeurs sont chiffrées. Cela signifie que git diff montre quels secrets ont changé sans révéler les valeurs réelles.
Déchiffrer et Appliquer
# Déchiffrer et appliquer en une seule commande
sops --decrypt secrets/db-credentials.yaml | kubectl apply -f -
# Ou déchiffrer vers un fichier temporaire
sops --decrypt secrets/db-credentials.yaml > /tmp/secret.yaml
kubectl apply -f /tmp/secret.yaml
rm -f /tmp/secret.yaml
Intégration avec les Workflows GitOps
ArgoCD avec KSOPS
KSOPS est un plugin Kustomize qui déchiffre les fichiers chiffrés avec SOPS pendant les opérations de synchronisation ArgoCD.
Installez KSOPS dans votre serveur de dépôt ArgoCD :
# Patch argocd-repo-server
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
namespace: argocd
spec:
template:
spec:
containers:
- name: argocd-repo-server
env:
- name: SOPS_AGE_KEY
valueFrom:
secretKeyRef:
name: sops-age-key
key: age-key.txt
- name: XDG_CONFIG_HOME
value: /.config
volumeMounts:
- mountPath: /.config/kustomize/plugin/viaduct.ai/v1/ksops
name: custom-tools
initContainers:
- name: install-ksops
image: viaductoss/ksops:v4.3.2
command: ["/bin/sh", "-c"]
args:
- cp /usr/local/bin/ksops /.config/kustomize/plugin/viaduct.ai/v1/ksops/ksops
volumeMounts:
- mountPath: /.config/kustomize/plugin/viaduct.ai/v1/ksops
name: custom-tools
volumes:
- name: custom-tools
emptyDir: {}
Créez un générateur KSOPS dans votre overlay Kustomize :
# secret-generator.yaml
apiVersion: viaduct.ai/v1
kind: ksops
metadata:
name: secret-generator
files:
- secrets/db-credentials.yaml
- secrets/api-keys.yaml
Intégration Native FluxCD
FluxCD a un support natif de SOPS. Créez un secret de déchiffrement et configurez votre Kustomization :
# Créer le secret de clé Age dans le namespace flux-system
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=~/.config/sops/age/keys.txt
# flux-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 10m
path: ./envs/production
prune: true
sourceRef:
kind: GitRepository
name: my-app
decryption:
provider: sops
secretRef:
name: sops-age
FluxCD déchiffre automatiquement tout fichier chiffré avec SOPS dans le chemin spécifié lors de la réconciliation.
SOPS+Age vs Sealed Secrets vs Vault vs External Secrets
| Fonctionnalité | SOPS + Age | Sealed Secrets | HashiCorp Vault | External Secrets |
|---|---|---|---|---|
| Lieu du chiffrement | Côté client | Côté contrôleur | Côté serveur | Côté serveur |
| Compatible Git | Oui (YAML chiffré) | Oui (ressource custom) | Non (références seules) | Non (références seules) |
| Infrastructure requise | Aucune | Contrôleur dans le cluster | Serveur Vault | Opérateur + backend |
| Gestion des clés | Fichiers de clé Age | Certificat du cluster | Politiques Vault | Varie selon backend |
| Multi-environnement | Règles .sops.yaml | Certificats par cluster | Namespaces/politiques | Multiples stores |
| Intégration GitOps | Plugins ArgoCD/Flux | K8s natif | Driver CSI/injector | Sync opérateur |
| Rotation | Re-chiffrer avec SOPS | Re-sceller requis | Secrets dynamiques | Dépend du backend |
| Complexité | Faible | Faible | Élevée | Moyenne |
| Fonctionne hors ligne | Oui | Non | Non | Non |
| Idéal pour | Petites-moyennes équipes | Clusters individuels | Entreprise/conformité | Multi-cloud |
SOPS+Age excelle quand vous voulez la simplicité, la capacité hors ligne et un vrai GitOps sans infrastructure supplémentaire.
Scénario Réel
Vous gérez un déploiement Kubernetes multi-environnement sur des clusters de développement, staging et production. Votre équipe de huit ingénieurs utilise ArgoCD pour le GitOps, et vous avez besoin d’une solution de secrets qui :
- Permette aux développeurs de créer et mettre à jour des secrets sans accès au cluster
- Garde les secrets chiffrés dans Git pour l’auditabilité
- Utilise différentes clés de chiffrement par environnement
- Permette la rotation des clés sans redéployer chaque secret
Voici comment structurer votre dépôt :
k8s-manifests/
├── .sops.yaml
├── base/
│ ├── deployment.yaml
│ └── service.yaml
├── envs/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ ├── secret-generator.yaml
│ │ └── secrets/
│ │ └── app-secrets.yaml # chiffré avec clé dev
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ ├── secret-generator.yaml
│ │ └── secrets/
│ │ └── app-secrets.yaml # chiffré avec clé staging
│ └── production/
│ ├── kustomization.yaml
│ ├── secret-generator.yaml
│ └── secrets/
│ └── app-secrets.yaml # chiffré avec clé prod
Chaque environnement a sa propre paire de clés Age. Les développeurs détiennent la clé dev, les chefs d’équipe détiennent dev+staging, et seul le pipeline CI/CD détient la clé de production. Le fichier .sops.yaml dirige le chiffrement vers la bonne clé en fonction du chemin du fichier.
Pièges et Cas Limites
La rotation des clés nécessite un re-chiffrement. Quand vous faites tourner une clé Age, vous devez déchiffrer chaque fichier avec l’ancienne clé et re-chiffrer avec la nouvelle. SOPS fournit sops updatekeys pour cela, mais testez d’abord :
# Mettre à jour les clés pour un seul fichier (utilise les règles .sops.yaml)
sops updatekeys secrets/db-credentials.yaml
# Re-chiffrer tous les secrets en lot
find . -name "*.yaml" -path "*/secrets/*" -exec sops updatekeys {} \;
Chiffrement partiel avec encrypted_regex. Par défaut, SOPS chiffre toutes les valeurs. Utilisez encrypted_regex dans .sops.yaml pour chiffrer uniquement des clés spécifiques :
creation_rules:
- path_regex: secrets/.*\.yaml$
encrypted_regex: "^(data|stringData)$"
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Les secrets binaires nécessitent d’abord un encodage base64. SOPS opère sur des fichiers texte. Pour les secrets binaires (certificats TLS, keystores), encodez-les en base64 avant de chiffrer :
# Encoder le certificat, puis laisser SOPS chiffrer la chaîne base64
cat tls.crt | base64 -w0 > tls.crt.b64
Ne commitez jamais la clé privée Age. Ajoutez-la au .gitignore immédiatement :
echo "age-key.txt" >> .gitignore
echo "*.agekey" >> .gitignore
La section métadonnées SOPS grandit avec les destinataires. Chaque destinataire Age supplémentaire ajoute ~300 octets de métadonnées. Avec beaucoup de destinataires, envisagez d’utiliser une clé d’équipe partagée distribuée via un canal sécurisé plutôt que des clés individuelles.
Résolution de Problèmes
Erreur : could not find common decryption key
La clé privée Age n’est pas disponible. Vérifiez l’emplacement du fichier de clé :
# Vérifier si le fichier de clé existe
ls -la ~/.config/sops/age/keys.txt
# Ou vérifier la variable d'environnement
echo $SOPS_AGE_KEY_FILE
# Vérifier que la clé correspond au destinataire
grep "public key" ~/.config/sops/age/keys.txt
Erreur : failed to decrypt
Le fichier a été chiffré avec une clé Age différente. Vérifiez quelle clé a été utilisée :
sops --decrypt --verbose secrets/db-credentials.yaml 2>&1 | grep "recipient"
La synchronisation ArgoCD échoue avec des erreurs KSOPS Assurez-vous que le conteneur init KSOPS s’est terminé avec succès et que le secret de clé Age existe :
kubectl logs deployment/argocd-repo-server -n argocd -c install-ksops
kubectl get secret sops-age-key -n argocd
FluxCD Kustomization bloqué en Not Ready
Vérifiez les logs du contrôleur Kustomize :
kubectl logs deployment/kustomize-controller -n flux-system | grep -i sops
Cause courante : le secret sops-age est dans le mauvais namespace ou a le mauvais nom de clé (doit être age.agekey).
Le fichier chiffré affiche une erreur mac mismatch
Le fichier a été modifié après le chiffrement sans utiliser sops --set ou sops edit. Re-chiffrez depuis la source en texte clair :
sops --decrypt secrets/db-credentials.yaml > /tmp/plain.yaml
sops --encrypt /tmp/plain.yaml > secrets/db-credentials.yaml
rm -f /tmp/plain.yaml
Résumé
- Les Secrets Kubernetes utilisent l’encodage base64, pas le chiffrement — ils ne sont pas sécurisés par défaut
- SOPS chiffre les valeurs YAML tout en gardant les clés lisibles, permettant des diffs Git significatifs
- Age fournit une gestion de clés plus simple que PGP sans surcharge de configuration
- Le fichier
.sops.yamldéfinit des règles de chiffrement par chemin pour les configurations multi-environnement - ArgoCD s’intègre via le plugin KSOPS ; FluxCD a un support natif de déchiffrement SOPS
- La rotation des clés nécessite de re-chiffrer tous les fichiers affectés avec
sops updatekeys - Stockez toujours les clés privées Age en dehors du dépôt et dans le
.gitignore - Pour les environnements d’entreprise nécessitant des secrets dynamiques, envisagez HashiCorp Vault