Table des matières :
- Kubernetes Secrets et External Secrets Operator : sortir du
kubectl create secretsans se mentir - OKMS (OVHcloud KMS) : chiffrement, enveloppe, audit — mais pas un Secrets Manager
- Patron d’intégration réaliste : ESO + backend chiffré + service de déchiffrement OKMS (Webhook)
- Déploiement reproductible : ESO via Helm, ClusterSecretStore Webhook, ExternalSecret (exemples)
- Rotation, versioning et observabilité : éviter la panne silencieuse lors d’un changement de secret
- Durcissement : RBAC, NetworkPolicy, chiffrement etcd, et plan de réponse à incident
- Checklist de mise en production : ce qui évite 80% des erreurs
Kubernetes Secrets et External Secrets Operator : sortir du kubectl create secret sans se mentir
Kubernetes fournit l’objet Secret, mais il ne faut pas le sur-vendre. Un Secret est surtout une API de distribution au runtime (env vars, volumes, CSI) avec des garde-fous RBAC ; ce n’est pas un coffre-fort. La documentation Kubernetes est explicite : « Secrets are intended to hold sensitive information, such as passwords, OAuth tokens, and ssh keys. » (Kubernetes Docs — Secrets).
Deux rappels concrets qui évitent beaucoup de malentendus en production :
- Un
SecretKubernetes n’est pas chiffré par défaut : sa valeur est encodée en Base64 côté YAML (ce qui n’est pas une protection) puis stockée dansetcd. - Un
Secretest optimisé pour être consommé par un workload (pod) ; pas pour être gouverné comme une “source de vérité” (versioning métier, workflow d’approbation, audit “qui a consulté le secret”, rotation orchestrée, etc.).
En pratique, dès que vous avez un parc multi-namespaces, des environnements éphémères et des rotations fréquentes (DB, API, JWT, SMTP), la gestion manuelle devient une source de dérive : secrets oubliés, valeurs incohérentes entre namespaces, “copier/coller” dangereux dans des tickets, ou pire, secrets commités par erreur.
Le point qui fait mal côté “core” : « Kubernetes Secrets are, by default, stored unencrypted in the API server’s underlying data store (etcd). » (Kubernetes Docs, même page). Oui, vous pouvez activer le chiffrement au repos d’etcd, mais ça ne règle ni la rotation, ni la traçabilité fine, ni la gouvernance (qui peut lire quoi et quand) dès que vous sortez du modèle “cluster-admin de confiance”. Et, sur un cluster managé, ce réglage est parfois hors de votre contrôle : vous devez vérifier contractuellement et techniquement ce que fait le provider (chiffrement, sauvegardes, accès opérateur, etc.).
External Secrets Operator (ESO) sert précisément à supprimer la saisie manuelle et à réconcilier un état désiré : une “source externe” → un Secret Kubernetes, avec rafraîchissement périodique, statuts, événements et métriques. La valeur ajoutée n’est pas “magique” : ESO ne rend pas Kubernetes intrinsèquement plus sûr ; il automatise la synchronisation et rend la rotation opérationnelle.
Un mini-scénario typique (très courant en e-commerce) illustre bien le gain :
- vous tournez le mot de passe PostgreSQL côté DBaaS à 02:00 ;
- le backend de secrets est mis à jour (nouvelle version chiffrée) ;
- ESO réconcilie dans les minutes suivantes et met à jour le
SecretKubernetes ; - un rollout contrôlé redémarre les pods consommateurs.
Sans ESO, la même rotation se termine souvent par une intervention manuelle “en urgence” (et une dérive durable : staging/prod qui divergent).
La question clé devient donc : quelle est votre source de vérité et quel est votre root of trust ? Autrement dit : où vit le secret “maître” (ou sa version chiffrée), et qui a le droit de le déchiffrer ?
OKMS (OVHcloud KMS) : chiffrement, enveloppe, audit — mais pas un Secrets Manager
OVHcloud OKMS (souvent présenté comme “KMS”) est un service de gestion de clés (création, rotation, politiques d’usage, opérations cryptographiques côté service). C’est adapté à l’encryption-at-rest (disques, objets, DB) et à l’encryption applicative (enveloppe) : vous chiffrez vos données avec une DEK (data encryption key), elle-même chiffrée/“wrappée” via une KEK stockée dans le KMS. L’API expose typiquement des opérations de type encrypt/decrypt/wrap/unwrap et de la gouvernance autour des clés.
Pour rendre le modèle “enveloppe” très concret, le stockage d’un secret applicatif peut ressembler à ceci (structure logique) :
ciphertext: la donnée chiffrée (le secret)kid: identifiant de la clé OKMS utilisée (utile lors d’une rotation)iv/nonce: paramètre cryptographique selon l’algorithmeaad: données associées (par ex.namespace/name), si vous utilisez un mode AEADcreated_at,version,expires_at: métadonnées de gouvernance
Ça implique une conséquence architecturale : OKMS ne “stocke” pas vos secrets applicatifs (mots de passe, tokens) comme le ferait un Secrets Manager. Il stocke des clés (ou des matériaux cryptographiques), pas des valeurs métier. Donc “synchroniser des secrets Kubernetes avec OKMS” ne peut pas être un simple mapping 1:1 (secret → KMS). Il vous faut un backend de stockage pour les secrets (au minimum des blobs chiffrés) et OKMS devient le mécanisme de déchiffrement contrôlé.
Un pattern robuste consiste à stocker des secrets chiffrés dans un support versionné (Object Storage, base interne, etc.), et à déléguer le déchiffrement à OKMS. En contexte OVHcloud, ça se marie bien avec des briques déjà présentes côté plateforme (Managed Kubernetes + stockage objet, par exemple), tout en gardant une séparation nette : l’objet stocké est inutilisable sans autorisation cryptographique.
Ça colle avec l’esprit Twelve-Factor : « Store config in the environment. » (The Twelve-Factor App, Config : Twelve-Factor — Config) — sauf qu’en prod, “config” inclut des secrets, et les mettre “dans l’environnement” suppose une chaîne de distribution propre (rotation, audit, contrôle d’accès). OKMS est utile pour que le clair n’apparaisse jamais “au repos” hors du cluster ; mais le clair finira quand même en mémoire dans un pod, et souvent dans un Secret Kubernetes si vous utilisez ESO. L’enjeu est donc de réduire la durée de vie du clair, de limiter l’exposition (RBAC, réseau, logs) et de rendre la rotation fiable.
Patron d’intégration réaliste : ESO + backend chiffré + service de déchiffrement OKMS (Webhook)
À l’heure actuelle, ESO supporte nativement des backends type Vault/ASM/GSM/Azure KV, mais pas OVHcloud OKMS en provider direct. Plutôt que d’attendre un provider upstream, l’approche pragmatique est d’utiliser le provider Webhook d’ESO et de mettre une brique d’adaptation : un petit service interne (dans le cluster) qui (1) récupère le secret chiffré dans votre backend, (2) demande à OKMS le déchiffrement (ou l’unwrapping), (3) renvoie la valeur en JSON à ESO.
Le flux typique ressemble à ça :
1) ExternalSecret (namespace app) → ESO controller
2) ESO appelle POST /v1/secret sur okms-decryptor (service interne)
3) okms-decryptor lit un blob chiffré (ex : Object Storage) + appelle l’API OVHcloud OKMS pour decrypt + renvoie { "value": "..." }
4) ESO crée/maj le Secret Kubernetes (Opaque, kubernetes.io/tls, etc.)
Ce pattern a un avantage : vous gardez ESO (observabilité, CRD, reconciliation), et vous encapsulez les détails OVH (auth, endpoints, quotas) dans un micro-service maîtrisé.
Quelques points de conception qui font la différence entre une démo et une prod :
- Timeouts et retries : votre
okms-decryptordoit traiter explicitement les erreurs réseau (API OVHcloud indisponible, latence, quotas) et renvoyer des codes d’erreur propres. ESO réessaiera, mais vous voulez des signaux clairs (erreur “transitoire” vs “ref introuvable”). - Cache mesuré : vous pouvez cacher (en mémoire) des secrets fraîchement déchiffrés pour réduire la charge sur OKMS, mais attention : un cache trop agressif ralentit la rotation. Une approche raisonnable est un TTL court (ex. 30–120s) uniquement pour lisser les bursts.
- Journalisation : interdisez toute log de la valeur en clair (y compris en debug). En incident, c’est souvent le “print du JSON” qui transforme un problème en fuite de secrets.
- Contrôle d’accès logique : n’acceptez pas n’importe quel
ref. Par exemple, restreignezrefà un préfixe (prod/shop/…) et refusez les caractères inattendus. Ce n’est pas du “path traversal” au sens fichiers, mais c’est le même type d’abus logique.
Le point faible est évident : vous introduisez un “bootstrap secret” (les credentials OVH pour joindre l’API KMS et éventuellement l’Object Storage). Ce bootstrap doit être traité comme un root credential : RBAC strict, namespace dédié, NetworkPolicy restrictive, et idéalement un mécanisme de rotation. Sur ce sujet, ne bricolez pas : si vos clés API sont trop larges, vous créez un point de compromission. Pour cadrer la gouvernance côté API (limitation de droits, rotation, débit), reprenez les fondamentaux exposés dans l’article interne : API : sécuriser apikey, limiter le débit et renforcer la conformité.
Déploiement reproductible : ESO via Helm, ClusterSecretStore Webhook, ExternalSecret (exemples)
Contexte versions (à adapter à votre CI/CD) : Kubernetes v1.28+, Helm v3.12+, External Secrets Operator v0.10+ (CRD external-secrets.io/v1beta1). Sur OVHcloud Managed Kubernetes, vous avez généralement un control-plane managé ; vérifiez ce que le provider chiffre au repos et comment sont gérés les logs/audits côté API. Pré-requis côté cluster : un Ingress n’est pas nécessaire si le webhook reste ClusterIP.
Installation ESO (namespace dédié, RBAC standard) :
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
kubectl create namespace external-secrets
helm upgrade --install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--set installCRDs=true
À ce stade, vous devez voir le controller et le webhook server ESO tourner. Vérifiez aussi que les CRD sont bien installées :
kubectl get crd | grep external-secrets
kubectl -n external-secrets get pods
Le service d’adaptation okms-decryptor (exemple minimal) doit exposer un endpoint interne. Le code dépendra de votre stockage (Object Storage, DB) et de l’API OKMS ; OVHcloud fournit un API Explorer et la création d’applications via API Explorer et createApp (utile pour générer application key/secret).
Deux recommandations “plateforme” simples (et souvent oubliées) pour le déchiffreur :
- Fixez des resources requests/limits (CPU/mémoire) : un déchiffreur qui part en OOM peut casser la rotation et donc la prod, même si vos apps vont bien.
- Appliquez des Pod Security Standards (profil baseline/restricted selon votre contexte) :
runAsNonRoot,readOnlyRootFilesystem, capabilities minimales. Un service qui manipule des secrets mérite au moins le niveau de durcissement de vos workloads critiques.
Déployez le service avec un ServiceAccount dédié et montez un Secret Kubernetes contenant les credentials OVH (bootstrap) — oui, c’est un serpent qui se mord la queue, mais c’est la réalité : il faut bien une racine de confiance initiale. Une bonne pratique est de mettre ce bootstrap dans un namespace très verrouillé (et de ne jamais y déployer d’applications métier).
Exemple de ClusterSecretStore utilisant le provider Webhook (le format exact peut varier selon votre version ESO ; validez avec la doc : documentation ESO) :
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: ovh-okms
spec:
provider:
webhook:
url: "http://okms-decryptor.okms.svc.cluster.local:8080/v1/secret"
method: POST
headers:
Content-Type: application/json
# Le body doit permettre de passer une clé logique (chemin) et éventuellement une propriété
body: |
{"ref":"{{ .remoteRef.key }}","property":"{{ .remoteRef.property }}"}
result:
jsonPath: "$.value"
Et un ExternalSecret typique pour injecter un DSN Postgres et un secret Symfony/PrestaShop (exemple volontairement proche d’un stack e-commerce conteneurisé) :
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: prestashop-app-secrets
namespace: shop
spec:
refreshInterval: 1m
secretStoreRef:
kind: ClusterSecretStore
name: ovh-okms
target:
name: prestashop-app-secrets
creationPolicy: Owner
template:
type: Opaque
data:
- secretKey: DATABASE_URL
remoteRef:
key: "prod/shop/database"
property: "url"
- secretKey: APP_SECRET
remoteRef:
key: "prod/shop/symfony"
property: "app_secret"
Trois points opérationnels : (1) Kubernetes ne redémarre pas vos pods quand un Secret change ; si l’app lit le secret au démarrage, vous devez déclencher un rollout (reloader, checksum annotation, ou stratégie applicative). (2) Si vous utilisez des env vars, la rotation est plus dure qu’en volume monté (où les fichiers sont rafraîchis). (3) Ne mettez pas tous vos secrets dans un ClusterSecretStore partagé si vous pouvez faire des SecretStore par namespace : le blast radius doit rester petit.
Une règle empirique utile : plus le secret est critique, plus sa portée doit être petite (namespace dédié, store dédié, RBAC dédié) et plus sa rotation doit être testée (ex. “rotation DB” testée comme un scénario de reprise).
Rotation, versioning et observabilité : éviter la panne silencieuse lors d’un changement de secret
La rotation ne se résume pas à “mettre à jour une valeur”. Avec ESO, la latence de propagation est bornée par refreshInterval + le temps de reconciliation + la latence provider. Si vous mettez refreshInterval: 1h et que vous tournez une clé API à 10:05, votre app peut continuer à tourner avec l’ancienne clé jusqu’à 11:00, puis casser d’un coup au prochain redeploy.
En pratique, vous pouvez calibrer les intervalles selon le type de secret (à ajuster selon vos contraintes de coût et d’API rate-limit) :
| Type de secret | Exemples | Rafraîchissement conseillé |
|---|---|---|
| Haute fréquence / expiration courte | jetons temporaires, certs courts | 30s à 5m |
| Rotation régulière mais non “expirable” | mots de passe DB, APP_SECRET |
5m à 30m |
| Rarement modifié | licences, clés “longue durée” | 30m à 6h (mais surveillé) |
Le piège classique : choisir un intervalle “confort” et oublier de mettre des alertes. Résultat : un incident n’est détecté qu’au moment où l’application redémarre (au pire, pendant un déploiement).
ESO expose des statuts sur ExternalSecret (conditions, erreurs provider, timestamp de dernière sync). Exploitez-les : un secret qui ne se met plus à jour est une dette technique immédiate. Branchez Prometheus sur les métriques ESO et alertez sur : taux d’erreurs, “temps depuis la dernière sync réussie”, et ExternalSecret en état non Ready. Pour le wiring Prometheus sur cluster OVHcloud, vous avez déjà un guide concret : Prometheus sur Kubernetes OVHcloud : déployer kube-prometheus-stack avec Helm.
Côté versioning, ne faites pas “un blob par secret sans historique”. Si vous stockez des secrets chiffrés dans un backend (Object Storage, DB), stockez aussi : version, created_at, expires_at, kid (key id OKMS), et un champ d’audit (qui a écrit). Le déchiffreur peut refuser de servir une version expirée ou une version non conforme (ex. kid révoqué). C’est trivial à implémenter et ça évite le scénario classique : un rollback applicatif redéploie une image qui attend un ancien format de secret, mais vous n’avez plus la donnée.
Enfin, testez une rotation complète (pas seulement “changer la valeur”) :
- rotation du secret (nouvelle version chiffrée dans le backend),
- propagation ESO (statut Ready + secret mis à jour),
- redémarrage contrôlé des consommateurs,
- validation applicative (connexion DB, signature JWT, etc.),
- et, idéalement, révocation de l’ancienne valeur.
Durcissement : RBAC, NetworkPolicy, chiffrement etcd, et plan de réponse à incident
Le pattern Webhook ajoute une surface d’attaque : un service interne qui rend des secrets en clair. Durcissement minimal : namespace isolé (okms), NetworkPolicy default-deny, autoriser uniquement le trafic depuis le namespace external-secrets vers le port du déchiffreur, et interdire tout egress sauf vers l’API OKMS et votre backend de stockage.
Exemple (simplifié) d’approche réseau “zéro confiance” au niveau cluster : vous partez d’un deny all dans okms, puis vous ouvrez uniquement ce qui est nécessaire. Le détail dépend de votre CNI (et de la façon de gérer les FQDN en egress), mais l’intention doit être la même : réduire les chemins réseau qui permettent d’obtenir un secret.
Ajoutez une authentification mTLS interne si votre CNI/service mesh le permet ; sinon, au minimum, un header HMAC statique partagé via Secret (bootstrap) et un contrôle strict de l’input (ref whitelisté, pas de “ref arbitraire”). Gardez en tête qu’un attaquant qui peut appeler le webhook et deviner des références peut exfiltrer des secrets si vous ne mettez pas de garde-fous.
Le RBAC doit être chirurgical : ESO n’a pas besoin d’écrire des secrets partout. Utilisez SecretStore par namespace quand c’est possible, et évitez d’accorder get/list/watch sur tous les secrets à des workloads applicatifs. Pour donner un ordre de priorité :
- limiter qui peut créer/modifier des
ExternalSecret(c’est souvent “équivalent” à donner accès aux secrets), - limiter qui peut lire les
Secretrésultants, - limiter qui peut lister (list/watch) les secrets à large échelle (risque d’inventaire).
Sur le stockage etcd, activez le chiffrement au repos si vous maîtrisez l’API server (cluster self-managed). Sur cluster managé, vérifiez le niveau de chiffrement et surtout : qui a accès aux sauvegardes etcd et aux snapshots.
Pour un durcissement Kubernetes plus général (pod, RBAC, réseau), un bon point de départ (sans dépendre d’un vendor) est le Kubernetes Security Cheat Sheet d’OWASP : Kubernetes Security Cheat Sheet (OWASP)
Enfin, acceptez qu’un incident “secrets” est un incident “prod” (pas un détail DevOps). Si un token de paiement, un secret JWT ou un mot de passe DB fuite, le containment implique rotation, invalidation, audit des accès, et analyse du code/CI/CD (supply chain). Pour structurer une réponse, gardez un runbook clair et actionnable ; l’article interne Sécurité PrestaShop : plan de réponse à incident et containment immédiat est transposable tel quel à un contexte Kubernetes (mêmes étapes : couper l’hémorragie, préserver les preuves, révoquer/rotater, puis durcir).
Checklist de mise en production : ce qui évite 80% des erreurs
Commencez par valider la chaîne cryptographique : (1) le backend ne contient que du chiffré, (2) chaque blob est associé à un kid OKMS, (3) le déchiffrement échoue de manière explicite si la clé est révoquée/rotatée. Testez un scénario de rotation de clé OKMS (nouvelle KEK) et mesurez l’impact sur la disponibilité : si vous dépendez d’un cache local de DEK dans le déchiffreur, documentez la durée de vie et la stratégie d’invalidation.
Validez ensuite la chaîne Kubernetes : un ExternalSecret qui échoue doit être visible en 2 minutes maximum (events + alert). Mesurez le temps entre “écriture d’une nouvelle version” et “secret prêt dans le namespace applicatif”. Faites-le en CI avec un test d’intégration : poussez un secret chiffré “canary”, attendez la sync, lancez un pod qui consomme le secret et vérifiez le hash attendu. C’est basique, mais ça évite le drift entre environnements.
Pour rendre la checklist vraiment actionnable, voici une version “go/no-go” adaptée à ESO + webhook :
- Accès & gouvernance
- [ ] le namespace
okmsest isolé (pas de workloads applicatifs dedans) - [ ] seuls des rôles ciblés peuvent créer/modifier des
ExternalSecret - [ ] les pods applicatifs n’ont pas de droits “larges” sur les
Secret(pas delistglobal) - Réseau
- [ ] NetworkPolicy default-deny sur
okms - [ ] uniquement
external-secrets→okms-decryptorest autorisé - [ ] l’egress de
okms-decryptorest limité au strict nécessaire (API OKMS + backend) - Fiabilité
- [ ]
refreshIntervalaligné avec la réalité métier (expiration, SLA de rotation) - [ ] timeouts et gestion d’erreurs clairs côté
okms-decryptor(pas de “200 avec vide”) - [ ] limites CPU/mémoire définies pour ESO et le déchiffreur
- Observabilité
- [ ] alertes sur échec de sync / absence de sync réussie
- [ ] logs sans secrets (vérifier niveau debug, erreurs HTTP, traces)
- Exploitation
- [ ] mécanisme de rollout lors d’un changement de secret (au moins pour les env vars)
- [ ] runbook de rotation (DB, JWT, API keys) testé en conditions réalistes
Terminez par le volet exploitation : définissez quels secrets nécessitent un rollout applicatif, et automatisez-le. Si votre app lit les secrets uniquement au démarrage (cas fréquent PHP-FPM, workers Symfony, PrestaShop), vous devez coupler la mise à jour du Secret à un redémarrage contrôlé (RollingUpdate, PDB, readiness). Ne laissez pas ça au “bon vouloir” des développeurs : formalisez-le comme une contrainte d’architecture, sinon vous aurez des rotations qui “passent” en staging et cassent en prod.
