Gestion des secrets DevOps GitOps : 6 motifs de production de Sealed Secrets a External Secrets Operator
Committer des secrets dans Git ? Vous etes assis sur une bombe a retardement
GitOps tourne autour de "Git comme source unique de verite", mais les Secrets Kubernetes sont simplement encodes en Base64 — pas chiffres. Pousser des Secrets encodes en Base64 dans un depot Git equivaut a stocker des mots de passe de base de donnees, des cles API et des certificats TLS en clair. Une fois le depot fuite (mauvaise configuration interne, violation de la chaine d'approvisionnement tierce, nettoyage oublie dans un fork), tous les secrets sont instantanement exposes.
En 2026, la gestion des secrets DevOps GitOps n'est plus optionnelle — c'est une exigence incontournable pour les deploiements en production. Cet article couvre 6 motifs de gestion des secrets de niveau production, du stockage chiffre avec Sealed Secrets a l'injection dynamique avec External Secrets Operator, vous aidant a resoudre completement la securite des secrets dans les flux de travail GitOps.
Points cles
- Comprendre 3 motifs architecturaux pour la gestion des secrets GitOps : stockage chiffre, injection externe et hybride
- Maitriser le deploiement et la configuration complets de Sealed Secrets, External Secrets Operator et SOPS
- Implementer la rotation automatique des secrets et la synchronisation multi-cluster
- Eviter les 5 pieges les plus courants en production et 10 erreurs frequentes
- Obtenir une matrice de decision comparative pour la selection d'outils
Table des matieres
- Concepts fondamentaux de la gestion des secrets GitOps
- Motif 1 : Stockage chiffre avec Sealed Secrets
- Motif 2 : External Secrets Operator + Vault
- Motif 3 : Chiffrement SOPS + Age/GPG
- Motif 4 : Rotation automatique des secrets
- Motif 5 : Synchronisation multi-cluster des secrets
- Motif 6 : Audit et conformite
- 5 Pieges courants et solutions
- 10 Solutions aux erreurs courantes
- Conseils avances d'optimisation
- Analyse comparative
- Outils en ligne recommandes
Concepts fondamentaux de la gestion des secrets GitOps
Trois motifs architecturaux
┌─────────────────────────────────────────────────────────────┐
│ Gestion des secrets GitOps : 3 motifs │
├─────────────────┬──────────────────┬────────────────────────┤
│ Stockage │ Injection │ Hybride │
│ chiffre │ externe │ (SOPS + ESO) │
│ (Sealed Secrets)│ (ESO + Vault) │ │
├─────────────────┼──────────────────┼────────────────────────┤
│ Git stocke │ Git stocke │ Git stocke des valeurs│
│ du texte chiffre│ seulement des │ chiffrees + des │
│ Dechiffrement │ references │ references │
│ dans le cluster │ Le cluster │ Dechiffrement dans le │
│ Audit hors ligne│ recupere le │ cluster ou recuperation│
│ Re-chiffrer pour│ texte clair │ Combination flexible │
│ rotation │ Deps externes │ Support partiel de │
│ │ Support natif │ rotation │
│ │ de rotation │ │
└─────────────────┴──────────────────┴────────────────────────┘
Le probleme fondamental avec les Secrets K8s
# Creer un Secret standard
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='SuperSecret123!'
# Le "chiffrement" n'est qu'un encodage Base64 — n'importe qui peut decoder
kubectl get secret db-credentials -o yaml
# Le decodage ne prend qu'une commande
kubectl get secret db-credentials \
-o jsonpath='{.data.password}' | base64 -d
# Sortie : SuperSecret123!
# Verifier avec l'encodeur/decodeur Base64 de ToolsKu
# https://toolsku.com/fr/encode/base64
Principes fondamentaux de la gestion des secrets DevOps GitOps
| Principe | Description | Consequence de la violation |
|---|---|---|
| Zero texte clair | Ne jamais stocker de secrets en clair dans Git | Fuite du depot = compromission totale |
| Moindre privilege | Chaque application n'accede qu'a ses propres secrets | Fuite laterale de secrets |
| Rotation automatique | Les secrets sont mis a jour automatiquement periodiquement | Cles de longue duree forcees par force brute |
| Auditable | Chaque acces aux secrets est enregistre | Impossible de tracer la source de la fuite |
| Recuperable | Les secrets peuvent etre rapidement restaures apres perte | Interruption de l'activite |
Motif 1 : Stockage chiffre avec Sealed Secrets
Sealed Secrets est une solution de gestion des secrets GitOps open source de Bitnami. L'idee centrale : chiffrer les Secrets localement avec kubeseal, generer des ressources SealedSecret commitees dans Git, et le Sealed Secrets Controller dans le cluster les dechiffre automatiquement en K8s Secrets.
Architecture
┌──────────────────────────────────────────────────────┐
│ Poste de travail du developpeur │
│ ┌────────────┐ ┌────────────┐ │
│ │ secret.yaml │───▶│ kubeseal │ │
│ │ (texte clair,│ │ (outil de │ │
│ │ ne pas │ │ chiffrement)│ │
│ │ committer) │ │ │ │
│ └────────────┘ └─────┬──────┘ │
│ │ chiffrer │
│ ┌─────▼──────┐ │
│ │sealed-secret│ │
│ │.yaml(chiffre)│ │
│ └─────┬──────┘ │
└──────────────────────────┼───────────────────────────┘
│ git push
┌──────────────────────────▼───────────────────────────┐
│ Depot Git │
│ (stocke uniquement les SealedSecrets chiffres) │
└──────────────────────────┬───────────────────────────┘
│ git pull (ArgoCD/Flux)
┌──────────────────────────▼───────────────────────────┐
│ Cluster K8s │
│ ┌──────────────────────────────┐ │
│ │ Sealed Secrets Controller │ │
│ │ (dechiffre avec la cle │ │
│ │ privee) │ │
│ └──────────────┬───────────────┘ │
│ │ dechiffrer │
│ ┌─────▼──────┐ │
│ │ K8s Secret │ │
│ │ (texte clair,│ │
│ │ dans le │ │
│ │ cluster) │ │
│ └────────────┘ │
└───────────────────────────────────────────────────────┘
Installation de Sealed Secrets
# Installer le Controller dans le cluster
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.0/controller.yaml
# Attendre que le Controller soit pret
kubectl wait --for=condition=available --timeout=120s \
deployment/sealed-secrets-controller -n kube-system
# Installer le CLI kubeseal
KUBESEAL_VERSION=0.27.0
curl -sLO "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-linux-amd64"
chmod +x kubeseal-linux-amd64
sudo mv kubeseal-linux-amd64 /usr/local/bin/kubeseal
# Verifier l'installation
kubeseal --version
Chiffrement des secrets
# Chiffrer a partir d'un fichier Secret en texte clair
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='SuperSecret123!' \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-secret.yaml
# Creer a partir de fichiers
kubectl create secret generic tls-cert \
--from-file=tls.crt=server.crt \
--from-file=tls.key=server.key \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-tls.yaml
# Specifier le namespace et le nom (mode strict)
kubectl create secret generic api-key \
--from-literal=key=abc123xyz \
--namespace production \
--dry-run=client -o yaml | \
kubeseal --format yaml \
--scope namespace-wide > sealed-api-key.yaml
Exemple de ressource SealedSecret
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: production
spec:
encryptedData:
username: AgBfj8k2mN3pQ7sT9vWxYz...
password: AgCdH5lM6nO8qR0tU2wXyZa...
template:
metadata:
name: db-credentials
namespace: production
type: Opaque
Controle de la portee de chiffrement
# strict : ne peut etre dechiffre qu'avec le meme namespace et nom (par defaut)
kubeseal --scope strict
# namespace-wide : peut etre renomme dans le meme namespace
kubeseal --scope namespace-wide
# cluster-wide : utilisable dans n'importe quel namespace du cluster
kubeseal --scope cluster-wide
| Portee | Niveau de securite | Flexibilite | Cas d'utilisation |
|---|---|---|---|
| strict | Le plus eleve | La plus faible | Secrets de production |
| namespace-wide | Moyen | Moyen | Multi-application meme namespace |
| cluster-wide | Le plus faible | La plus elevee | Ressources partagees comme les certificats |
Sauvegarde et restauration de la cle maitresse
# Sauvegarde de la cle maitresse (tous les SealedSecrets deviennent indechiffrables si perdue !)
kubectl get secret -n kube-system \
sealed-secrets-key -o yaml > sealed-secrets-key-backup.yaml
# Restaurer la cle maitresse dans un nouveau cluster
kubectl apply -f sealed-secrets-key-backup.yaml
# Rotater la cle maitresse
kubectl delete secret -n kube-system sealed-secrets-key
# Le Controller genere automatiquement une nouvelle cle, les anciens SealedSecrets fonctionnent toujours
Motif 2 : External Secrets Operator + Vault
External Secrets Operator (ESO) integre les systemes externes de gestion des secrets (Vault, AWS Secrets Manager, GCP Secret Manager, etc.) avec Kubernetes. Les depots Git ne stockent que des references SecretStore, et le ESO Controller dans le cluster recupere les secrets des systemes externes et cree des K8s Secrets.
Architecture
┌──────────────────────────────────────────────────────┐
│ Depot Git │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ SecretStore │ │ ExternalSecret│ │
│ │ (config de │ │ (mappage de │ │
│ │ connexion │ │ ref secret) │ │
│ │ Vault) │ │ │ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────┬───────────────────────────────┘
│ git pull
┌──────────────────────▼───────────────────────────────┐
│ Cluster K8s │
│ ┌──────────────────────────────┐ │
│ │ External Secrets Operator │ │
│ │ (surveille les ExternalSecrets)│ │
│ └──────────────┬───────────────┘ │
│ │ recuperer les secrets │
│ ┌────────────▼────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ K8s │ │ HashiCorp│ │
│ │ Secret │ │ Vault │ │
│ └──────────┘ └──────────┘ │
└───────────────────────────────────────────────────────┘
Installation d'ESO
# Installer avec Helm
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--set installCRDs=true \
--set replicaCount=2 \
--set leaderElect=true
# Verifier
kubectl get pods -n external-secrets
Configuration du HashiCorp Vault SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-role"
serviceAccountRef:
name: "external-secrets-sa"
Configuration de la politique Vault
# Politique Vault : restreindre ESO a des chemins specifiques
path "secret/data/production/*" {
capabilities = ["read"]
}
path "secret/data/production/database/*" {
capabilities = ["read", "list"]
}
# Refuser l'acces aux autres environnements
path "secret/data/staging/*" {
capabilities = ["deny"]
}
path "secret/data/development/*" {
capabilities = ["deny"]
}
Ressource ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
template:
type: Opaque
data:
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@db.internal:5432/mydb"
data:
- secretKey: username
remoteRef:
key: secret/data/production/database
property: username
- secretKey: password
remoteRef:
key: secret/data/production/database
property: password
Multi-source : ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-aws-sa
namespace: external-secrets
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: aws-api-keys
namespace: production
spec:
refreshInterval: 5m
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: aws-api-keys
dataFrom:
- extract:
key: production/api-keys
Motif 3 : Chiffrement SOPS + Age/GPG
SOPS (Secrets OPerationS) est un outil de chiffrement de secrets developpe par Mozilla, supportant plusieurs backends de chiffrement dont AES, PGP et Age. Contrairement a Sealed Secrets, SOPS chiffre le fichier lui-meme — il peut chiffrer les formats YAML/JSON/ENV et ne chiffre que les valeurs, pas les cles. Cela signifie que vous pouvez utiliser Git diff pour voir les changements structurels dans les secrets.
Architecture
┌──────────────────────────────────────────────────────┐
│ Poste de travail du developpeur │
│ ┌────────────┐ ┌────────────┐ │
│ │ secret.yaml │───▶│ SOPS │ │
│ │ (texte clair,│ │ + Age/GPG │ │
│ │ ne pas │ │ │ │
│ │ committer) │ │ │ │
│ └────────────┘ └─────┬──────┘ │
│ │ chiffrer │
│ ┌─────▼──────┐ │
│ │secret.enc. │ │
│ │yaml(chiffre)│ │
│ └─────┬──────┘ │
└──────────────────────────┼───────────────────────────┘
│ git push
┌──────────────────────────▼───────────────────────────┐
│ Depot Git │
│ (stocke des fichiers chiffres, cles visibles, │
│ valeurs chiffrees) │
└──────────────────────────┬───────────────────────────┘
│ git pull
┌──────────────────────────▼───────────────────────────┐
│ Pipeline CI/CD │
│ ┌──────────────────────────────┐ │
│ │ sops --decrypt + kubectl apply│ │
│ │ ou integration Flux │ │
│ │ Kustomization SOPS │ │
│ └──────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
Installation de SOPS et Age
# Installer SOPS
curl -sLO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64
chmod +x sops-v3.9.0.linux.amd64
sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops
# Installer Age (backend de chiffrement recommande, plus simple que GPG)
curl -sLO 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 /usr/local/bin/
sudo mv age/age-keygen /usr/local/bin/
# Generer une paire de cles Age
age-keygen -o age.key
# Cle publique : age1abc123xyz...
# Verifier
sops --version
age --version
Chiffrement des fichiers de secrets
# secret.yaml (avant chiffrement)
apiVersion: v1
kind: Secret
metadata:
name: app-config
namespace: production
type: Opaque
stringData:
DB_HOST: "db.internal.example.com"
DB_PASSWORD: "SuperSecret123!"
API_KEY: "sk-proj-abc123xyz456"
REDIS_URL: "redis://redis.internal:6379"
# Chiffrer avec la cle publique Age
sops --encrypt \
--age age1abc123xyz456... \
--encrypted-regex '^(DB_PASSWORD|API_KEY)$' \
--in-place secret.yaml
# secret.yaml (apres chiffrement)
apiVersion: v1
kind: Secret
metadata:
name: app-config
namespace: production
type: Opaque
stringData:
DB_HOST: "db.internal.example.com"
DB_PASSWORD: ENC[AES256_GCM,data:Wk5kPQ==,tag:abc123==,type:str]
API_KEY: ENC[AES256_GCM,data:Zm9vYmFy,tag:def456==,type:str]
REDIS_URL: "redis://redis.internal:6379"
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1abc123xyz456...
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-06-16T10:00:00Z"
mac: ENC[AES256_GCM,data:abc==,tag:xyz==,type:str]
pgp: []
encrypted_regex: ^(DB_PASSWORD|API_KEY)$
version: 3.9.0
Integration SOPS avec Flux CD
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app-secrets
namespace: flux-system
spec:
interval: 10m
path: ./clusters/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
decryption:
provider: sops
secretRef:
name: sops-age-key
# Creer un Secret avec la cle privee Age pour le dechiffrement Flux
kubectl create secret generic sops-age-key \
--namespace flux-system \
--from-file=age.agekey=age.key \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-sops-key.yaml
Chiffrement multi-cles (collaboration d'equipe)
# Fichier de configuration .sops.yaml — configuration de chiffrement au niveau du projet
cat > .sops.yaml << 'EOF'
creation_rules:
- path_regex: ^clusters/production/.*\.yaml$
key_groups:
- age:
- age1abc123xyz456 # cle de production
- age1def789uvw012 # cle de l'equipe SRE
- path_regex: ^clusters/staging/.*\.yaml$
key_groups:
- age:
- age1ghi345rst678 # cle de staging
- path_regex: ^clusters/.*\.yaml$
key_groups:
- age:
- age1abc123xyz456 # cle par defaut
EOF
Motif 4 : Rotation automatique des secrets
La rotation des secrets est l'aspect le plus neglige de la gestion des secrets DevOps GitOps. Les secrets statiques qui ne changent jamais creent une fenetre d'attaque infinie une fois fuites. La meilleure pratique 2026 : rotater tous les secrets de production tous les 90 jours, et les secrets a haute sensibilite tous les 7 jours.
Architecture de rotation
┌──────────────────────────────────────────────────────┐
│ Architecture d'auto-rotation des secrets │
│ │
│ ┌──────────┐ declencheur ┌──────────────┐ │
│ │ CronJob │─────────────▶│ Vault Rotate │ │
│ │ (planifi- │ │ (generer │ │
│ │ cation) │ │ nouveau) │ │
│ └──────────┘ └──────┬───────┘ │
│ │ nouveau secret │
│ ┌──────▼───────┐ │
│ │ ExternalSecret│ │
│ │ (auto refresh)│ │
│ └──────┬───────┘ │
│ │ mettre a jour │
│ ┌──────▼───────┐ │
│ │ K8s Secret │ │
│ │ (auto mise a │ │
│ │ jour) │ │
│ └──────┬───────┘ │
│ │ rolling restart │
│ ┌──────▼───────┐ │
│ │ Pods │ │
│ │ (lire la │ │
│ │ nouvelle cle)│ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────┘
Configuration d'auto-rotation ESO
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: rotating-api-key
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: api-key
creationPolicy: Owner
template:
type: Opaque
metadata:
annotations:
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
data:
- secretKey: api-key
remoteRef:
key: secret/data/production/api
property: key
Secrets dynamiques Vault
# Configuration des identifiants dynamiques de base de donnees Vault
resource "vault_database_secret_backend_connection" "postgresql" {
backend = "database"
name = "postgresql-production"
allowed_roles = ["app-readonly", "app-readwrite"]
postgresql {
connection_url = "postgresql://{{username}}:{{password}}@db.internal:5432/mydb?sslmode=require"
username = "vault_admin"
password = "VaultAdminPassword123!"
}
}
resource "vault_database_secret_backend_role" "app_readwrite" {
backend = "database"
name = "app-readwrite"
db_name = vault_database_secret_backend_connection.postgresql.name
default_ttl = 3600
max_ttl = 86400
creation_statements = [
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
"GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
]
}
Detection automatique des changements de secrets dans les applications
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
annotations:
secret.reloader.stakater.com/reload: "api-key,db-credentials"
spec:
containers:
- name: api-server
image: registry.example.com/api-server:v2.1.0
envFrom:
- secretRef:
name: api-key
- secretRef:
name: db-credentials
# Installer Reloader — redemarrage rolling automatique des Pods lors des changements de Secrets
helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update
helm install reloader stakater/reloader \
--namespace reloader \
--create-namespace \
--set reloader.watchGlobally=false
Motif 5 : Synchronisation multi-cluster des secrets
Dans les scenarios multi-cluster, les secrets doivent etre synchronises en toute securite entre les clusters. La gestion des secrets DevOps GitOps exige : chaque cluster a son propre cycle de vie de secrets tout en maintenant la coherence.
Architecture multi-cluster
┌──────────────────────────────────────────────────────┐
│ Architecture de synchronisation multi-cluster │
│ des secrets │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ HashiCorp Vault (Central) │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │prod-east/│ │prod-west/│ │ │
│ │ │ secrets │ │ secrets │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └──────────┬───────────────┬──────────┘ │
│ │ │ │
│ ┌────────▼──────┐ ┌──────▼────────┐ │
│ │ Cluster East │ │ Cluster West │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ ESO │ │ │ │ ESO │ │ │
│ │ │Controller│ │ │ │Controller│ │ │
│ │ └────┬─────┘ │ │ └────┬─────┘ │ │
│ │ ▼ │ │ ▼ │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ Secrets │ │ │ │ Secrets │ │ │
│ │ └──────────┘ │ │ └──────────┘ │ │
│ └───────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────┘
SecretStore independant par cluster
# Cluster East
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-east
namespace: production
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes/east"
role: "eso-east-role"
serviceAccountRef:
name: "external-secrets-sa"
# Cluster West
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-west
namespace: production
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes/west"
role: "eso-west-role"
serviceAccountRef:
name: "external-secrets-sa"
Distribution multi-cluster des certificats TLS
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: wildcard-tls
namespace: ingress-nginx
spec:
refreshInterval: 24h
secretStoreRef:
name: vault-east
kind: SecretStore
target:
name: wildcard-tls
creationPolicy: Owner
template:
type: kubernetes.io/tls
data:
- secretKey: tls.crt
remoteRef:
key: secret/data/shared/tls/wildcard
property: cert
- secretKey: tls.key
remoteRef:
key: secret/data/shared/tls/wildcard
property: key
Synchronisation de la cle maitresse Sealed Secrets multi-cluster
# Exporter la cle maitresse
kubectl get secret -n kube-system sealed-secrets-key \
-o yaml > sealed-secrets-master-key.yaml
# Importer dans le cluster cible
kubectl apply -f sealed-secrets-master-key.yaml -n kube-system
# Redemarrer le Controller pour charger la cle
kubectl rollout restart deployment/sealed-secrets-controller -n kube-system
Motif 6 : Audit et conformite
La gestion des secrets DevOps GitOps doit satisfaire aux exigences d'audit : qui a accede a quel secret et quand. Les normes de conformite 2026 (SOC2, ISO 27001, MLPS 2.0) exigent toutes un acces aux secrets traçable.
Journaux d'audit Vault
# Activer la journalisation d'audit Vault
audit {
type = "file"
options = {
file_path = "/vault/audit/audit.log"
mode = "0600"
}
}
# Activer l'audit Syslog
audit {
type = "syslog"
options = {
facility = "AUTH"
tag = "vault"
address = "syslog.internal:514"
}
}
Journalisation des acces ESO
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: audited-vault
annotations:
audit.external-secrets.io/enabled: "true"
audit.external-secrets.io/log-access: "true"
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-audited-role"
serviceAccountRef:
name: "external-secrets-sa"
Politique d'audit Kubernetes
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
namespaces: ["production", "staging"]
omitStages:
- RequestReceived
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
namespaces: ["default"]
omitStages:
- RequestReceived
- level: RequestResponse
resources:
- group: "external-secrets.io"
resources: ["externalsecrets", "secretstores"]
omitStages:
- RequestReceived
Surveillance des acces aux secrets
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-secret-access
spec:
validationFailureAction: Audit
background: true
rules:
- name: block-default-sa-secret-access
match:
resources:
kinds: [Pod]
validate:
message: "L'acces aux secrets avec le ServiceAccount par defaut est interdit"
pattern:
spec:
serviceAccountName: "!default"
- name: require-secret-annotations
match:
resources:
kinds: [Secret]
names: ["db-*", "api-*", "tls-*"]
validate:
message: "Les secrets de production doivent avoir des annotations de proprietaire et d'expiration"
pattern:
metadata:
annotations:
owner: "?*"
expiry: "?*"
5 Pieges courants et solutions
Piege 1 : Perte de la cle maitresse Sealed Secrets
Symptome : Apres reconstruction du cluster, tous les SealedSecrets ne peuvent pas etre dechiffres. Le Controller signale failed to unseal.
Cause racine : Sealed Secrets utilise le chiffrement asymetrique. La cle privee n'existe que dans le cluster. Detruire le cluster perd la cle privee.
Solution :
# 1. Sauvegarde reguliere de la cle maitresse (automatisee)
kubectl get secret -n kube-system \
sealed-secrets-key -o yaml | \
sops --encrypt --age age1abc123xyz456... \
/dev/stdin > sealed-secrets-key.enc.yaml
# 2. Stocker la cle chiffree dans un autre depot Git
git add sealed-secrets-key.enc.yaml
git commit -m "backup: sealed secrets master key $(date +%Y%m%d)"
# 3. Processus de recuperation
sops --decrypt sealed-secrets-key.enc.yaml | \
kubectl apply -f -
Piege 2 : Retard de rafraichissement ExternalSecret causant l'echec de demarrage des Pods
Symptome : Les Pods nouvellement deployes echouent au demarrage car le Secret n'existe pas encore — ESO ne l'a pas encore recupere de Vault.
Solution :
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
refreshInterval: 5m
target:
creationPolicy: Owner
template:
type: Opaque
dataFrom:
- extract:
key: production/app-secrets
# S'assurer qu'ESO a synchronise avant de deployer l'application
# Methode 1 : Utiliser les ArgoCD Sync Hooks
# Methode 2 : Attendre que le Secret existe dans le CI
kubectl wait --for=condition=Ready \
externalsecret/app-secrets -n production --timeout=120s
Piege 3 : La rotation des cles SOPS casse le dechiffrement des anciens textes chiffres
Symptome : Apres que l'equipe a rotate les cles Age, les anciens fichiers chiffres ne peuvent pas etre dechiffres.
Solution :
# Utiliser le chiffrement multi-cles — coexistence des anciennes et nouvelles cles
sops --encrypt \
--age age1NEWKEY...,age1OLDKEY... \
--in-place secret.yaml
# Ou gerer les groupes de cles via .sops.yaml
# Apres la mise a jour de la cle, dechiffrer avec l'ancienne cle puis rechiffrer avec la nouvelle
sops --decrypt --age age1OLDKEY... secret.yaml | \
sops --encrypt --age age1NEWKEY... \
--filename-override secret.yaml /dev/stdin > secret_new.yaml
Piege 4 : Impossible de revoquer rapidement les secrets fuites
Symptome : Une cle API a fuite, mais SealedSecret necessite un rechiffrement et un commit — la fenetre de revocation est trop longue.
Solution :
# Approche ESO : desactiver directement l'ancienne cle dans Vault
vault kv metadata put -delete-version-after=0s \
secret/data/production/api-key
# Approche Sealed Secrets : suppression d'urgence via le Controller
kubectl delete secret api-key -n production
# Rechiffrer immediatement et commiter le nouveau SealedSecret
# Approche generique : blocage au niveau reseau
kubectl apply -f - <<EOF
apiVersion: networkpolicies.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-compromised
namespace: production
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- compromised-api.example.com/32
EOF
Piege 5 : Les secrets partages entre equipes menent au chaos du controle d'acces
Symptome : Plusieurs equipes utilisent le meme Token Vault pour acceder aux secrets, rendant impossible de distinguer qui a fait quoi.
Solution :
# Creer des politiques Vault par equipe
path "secret/data/team-a/*" {
capabilities = ["read", "list"]
}
path "secret/data/team-b/*" {
capabilities = ["read", "list"]
}
# Deni explicite pour l'acces croise
path "secret/data/team-a/*" {
capabilities = ["deny"]
}
# Lier au role team-b
# Utiliser les espaces de noms Vault pour l'isolation
namespace "team-a" {
path "secret/*" {
capabilities = ["read", "list", "create", "update"]
}
}
10 Solutions aux erreurs courantes
1. failed to unseal: no private key found
# Verifier si le Controller detient la cle maitresse
kubectl get secrets -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key
# Restaurer la cle maitresse
kubectl apply -f sealed-secrets-key-backup.yaml -n kube-system
kubectl rollout restart deployment/sealed-secrets-controller -n kube-system
2. ExternalSecret "not ready": could not get secret data
# Verifier la connexion SecretStore
kubectl describe secretstore vault-backend -n production
# Verifier l'authentification Vault
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets
# Verifier les permissions du ServiceAccount
kubectl auth can-i get secret -n production \
--as=system:serviceaccount:production:external-secrets-sa
3. sops error decrypting: could not find a matching key
# Verifier si la cle Age est correcte
age-keygen -y age.key
# Verifier la configuration des cles dans .sops.yaml
cat .sops.yaml
# Essayer de specifier la cle pour le dechiffrement
sops --decrypt --age age1YOURKEY... secret.yaml
4. SealedSecret "sealed-secrets" is invalid: metadata.name
# Verifier le namespace et le nom specifies lors du chiffrement
# Le mode strict exige une correspondance exacte
kubeseal --scope strict --namespace production \
--name db-credentials < secret.yaml > sealed.yaml
5. Vault seal status: sealed
# Verifier le statut de Vault
vault status
# Sceller Vault (necessite 2 des 3 cles de descellement)
vault operator unseal <key1>
vault operator unseal <key2>
# Configuration auto-unseal (recommandee pour la production)
# Utiliser AWS KMS / GCP KMS / Azure Key Vault pour l'auto-unseal
6. refreshInterval: cannot unmarshal
# S'assurer que le format de refreshInterval est correct
# Correct : "15m", "1h", "24h"
# Incorrect : 15, "15", "15minutes"
spec:
refreshInterval: 15m
7. kubeseal: error: failed to get certificate
# Verifier si le Controller est en cours d'execution
kubectl get deployment sealed-secrets-controller -n kube-system
# Verifier le Secret du certificat
kubectl get secret -n kube-system sealed-secrets-key
# Recuperer la cle publique du Controller
kubeseal --fetch-cert > sealed-secrets-cert.pem
kubeseal --cert sealed-secrets-cert.pem < secret.yaml
8. ExternalSecret status: SecretSyncedError
# Voir l'erreur detaillee
kubectl describe externalsecret db-credentials -n production
# Cause courante : le chemin Vault n'existe pas
# Verifier les chemins dans Vault
vault kv list secret/production/
# Cause courante : permissions insuffisantes
vault policy read eso-role
9. SOPS: mac mismatch: file has been modified
# Le fichier a ete modifie manuellement apres chiffrement, la verification MAC a echoue
# Approche sure : rechiffrer
sops --decrypt secret.yaml > secret_plain.yaml
# Modifier puis rechiffrer
sops --encrypt --age age1YOURKEY... secret_plain.yaml > secret.yaml
10. Kubernetes: secret "xxx" not found
# ESO n'a pas encore termine la synchronisation
kubectl get externalsecret -n production
# Verifier les logs du Controller ESO
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets \
--tail=100
# Declencher manuellement la synchronisation
kubectl annotate externalsecret db-credentials \
force-sync=$(date +%s) -n production \
--overwrite
Conseils avances d'optimisation
1. Hook pre-commit Git pour empecher les commits de secrets en texte clair
#!/bin/bash
# .git/hooks/pre-commit
# Scanner les fichiers stages pour les secrets suspects
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.yaml$|\.yml$|\.env$')
for FILE in $STAGED_FILES; do
# Detecter un Secret K8s encode en Base64
if grep -qE 'kind:\s*Secret' "$FILE" 2>/dev/null; then
if ! grep -qE 'kind:\s*SealedSecret|encryptedData|sops:' "$FILE" 2>/dev/null; then
echo "ERREUR : Fichier Secret non chiffre trouve : $FILE"
echo "Veuillez chiffrer avec kubeseal ou sops avant de committer"
exit 1
fi
fi
# Detecter les motifs de secrets courants
if grep -qiE '(password|secret|api.key|token)\s*[:=]\s*["\x27]?[A-Za-z0-9+/=]{16,}' "$FILE" 2>/dev/null; then
if ! grep -qE 'ENC\[|sops:|encryptedData' "$FILE" 2>/dev/null; then
echo "AVERTISSEMENT : Secret en texte clair suspect dans : $FILE"
echo "Veuillez confirmer le chiffrement ou utiliser la gestion externe des secrets"
fi
fi
done
exit 0
2. Integration de la gestion des secrets avec ArgoCD
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-with-secrets
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/gitops-manifests.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
3. Moteur de modeles de secrets
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-config
namespace: production
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-config
template:
type: Opaque
engineVersion: v2
data:
DATABASE_URL: "postgresql://{{ .db_user }}:{{ .db_pass }}@{{ .db_host }}:5432/{{ .db_name }}?sslmode=require"
REDIS_URL: "redis://:{{ .redis_pass }}@{{ .redis_host }}:6379/0"
JWT_SECRET: "{{ .jwt_secret }}"
CONFIG_JSON: |
{
"database": {
"host": "{{ .db_host }}",
"port": 5432,
"name": "{{ .db_name }}"
},
"redis": {
"host": "{{ .redis_host }}"
}
}
data:
- secretKey: db_user
remoteRef:
key: secret/data/production/database
property: username
- secretKey: db_pass
remoteRef:
key: secret/data/production/database
property: password
- secretKey: db_host
remoteRef:
key: secret/data/production/database
property: host
- secretKey: db_name
remoteRef:
key: secret/data/production/database
property: dbname
- secretKey: redis_pass
remoteRef:
key: secret/data/production/redis
property: password
- secretKey: redis_host
remoteRef:
key: secret/data/production/redis
property: host
- secretKey: jwt_secret
remoteRef:
key: secret/data/production/auth
property: jwt_secret
4. Verification de sante des secrets
apiVersion: batch/v1
kind: CronJob
metadata:
name: secret-health-check
namespace: security
spec:
schedule: "0 8 * * 1"
jobTemplate:
spec:
template:
spec:
serviceAccountName: secret-checker
containers:
- name: checker
image: bitnami/kubectl:1.30
command:
- /bin/bash
- -c
- |
echo "=== Verification de sante des secrets $(date) ==="
echo ""
echo "--- Verification des certificats TLS arrivant a expiration ---"
kubectl get secrets --all-namespaces \
-o json | jq -r '.items[] |
select(.type=="kubernetes.io/tls") |
"\(.metadata.namespace)/\(.metadata.name)"' | \
while read secret; do
ns=$(echo $secret | cut -d/ -f1)
name=$(echo $secret | cut -d/ -f2)
cert=$(kubectl get secret $name -n $ns \
-o jsonpath='{.data.tls\.crt}' | base64 -d)
expiry=$(echo "$cert" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$expiry" ]; then
days=$(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 ))
if [ $days -lt 30 ]; then
echo "AVERTISSEMENT : Le certificat $secret expire dans ${days} jours"
fi
fi
done
echo ""
echo "--- Verification du statut de synchronisation ExternalSecret ---"
kubectl get externalsecrets --all-namespaces \
-o json | jq -r '.items[] |
select(.status.conditions[]?.type=="Ready" and
.status.conditions[]?.status!="True") |
"\(.metadata.namespace)/\(.metadata.name): PAS PRET"'
restartPolicy: OnFailure
Comparaison : Sealed Secrets vs External Secrets vs SOPS
| Dimension | Sealed Secrets | External Secrets Operator | SOPS |
|---|---|---|---|
| Chiffrement | Asymetrique (cle publique du cluster) | Pas de chiffrement du contenu Git | Symetrique/Asymetrique |
| Contenu Git | Texte chiffre (SealedSecret) | References (ExternalSecret) | Texte chiffre (valeurs chiffrees) |
| Deps externes | Aucune (autonome) | Vault/AWS/GCP etc. | Aucune (autonome) |
| Rotation des secrets | Rechiffrement requis | Support natif | Rechiffrement requis |
| Multi-cluster | Synchroniser la cle maitresse | SecretStore par cluster | Partager la cle de chiffrement |
| Compatible Git Diff | Non (texte chiffre illisible) | Oui (references lisibles) | Oui (cles lisibles, valeurs chiffrees) |
| Dechiffrement hors ligne | Non (necessite cle privee du cluster) | Non (necessite service externe) | Oui (cle locale suffisante) |
| Courbe d'apprentissage | Faible | Moyenne | Moyenne |
| Complexite operationnelle | Faible | Elevee (Vault necessaire) | Faible |
| Capacite d'audit | Historique Git | Journaux d'audit Vault | Historique Git |
| Secrets dynamiques | Non supporte | Supporte (identifiants dynamiques Vault) | Non supporte |
| Echelle adaptee | Petites-moyennes equipes | Moyennes-grandes entreprises | Toute echelle |
| Mieux pour | GitOps simple | Gestion des secrets entreprise | Chiffrement multi-format |
Arbre de decision de selection
Avez-vous deja Vault/AWS Secrets Manager ?
├── Oui → External Secrets Operator
│ └── Besoin de secrets dynamiques ? → ESO + identifiants dynamiques Vault
└── Non → Besoin de stocker du texte chiffre dans Git ?
├── Oui → Besoin de compatibilite Git Diff ?
│ ├── Oui → SOPS + Age
│ └── Non → Sealed Secrets
└── Non → Besoin de secrets dynamiques ?
├── Oui → Deployer Vault + ESO
└── Non → Sealed Secrets
Outils en ligne recommandes
- Encodeur/Decodeur Base64 : /fr/encode/base64 — Encoder/decoder les donnees K8s Secret et SealedSecret
- Generateur de cles RSA : /fr/encode/rsa — Generer des paires de cles RSA pour le chiffrement SOPS GPG
- Calculateur de hash : /fr/encode/hash — Calculer les empreintes de secrets et les sommes de controle
Resume : Il n'y a pas de balle d'argent pour la gestion des secrets DevOps GitOps, mais il existe un chemin de meilleures pratiques. Les petites equipes commencent avec Sealed Secrets — zero dependances externes pour la securite des secrets GitOps. Les moyennes et grandes entreprises choisissent External Secrets Operator + Vault, avec rotation native des secrets et identifiants dynamiques. Lorsque vous avez besoin de chiffrement multi-format et de compatibilite Git Diff, SOPS + Age est le meilleur choix. Les principes fondamentaux 2026 : zero stockage en texte clair, rotation automatique, auditable et traçable. Rappelez-vous — les fuites de secrets ne sont pas une question de "si" mais de "quand".
Articles connexes :
- GitOps avec ArgoCD en production — Guide complet d'automatisation du deploiement ArgoCD
- GitOps avec Flux CD en production — Livraison continue Flux CD en pratique
- Durcissement de securite des conteneurs Docker — Systeme de defense de conteneurs a 8 couches
References externes :
Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →