OVHcloud Secret Manager + External Secrets Operator : gérer ses secrets Kubernetes
Introduction
Stocker des secrets directement dans un cluster Kubernetes (dans des Secret gérés à la main, ou pire, commités dans un dépôt Git) est une mauvaise pratique bien connue. Ce qu’on fait habituellement c’est chiffrer ou externaliser ces secrets dans un gestionnaire dédié et les laisser se synchroniser tout seuls dans le cluster via un opérateur dédié (Sealed-Secret si chiffré, et External-Secret Operator pour l’externalisation).
Si vous avez déjà vu mon repo GitOps, vous avez vu que j’utilise beaucoup OpenBao (couplé à ExternalSecret et ArgoCD Vault Plugin, j’ai entamé une migration sur Bank-Vault récémment à cause de Batleforc qui m’en a fait la démo).
Même si OpenBao s’accompagne de Tooling pour la phase du unseal et pour d’autres taches de maintenance, je le trouve très pénible à héberger pour les clusters minimalistes (e.g. le temps d’un live). C’est pourquoi je me suis récemment intéressé vers une solution managé bien plus simple et moins coûteuse à maintenir : le KMS OVH.
Voici ce qu’on va mettre en place dans cet article :
- OVHcloud Secret Manager : un service managé qui expose une API compatible HashiCorp Vault KV v2. Vos secrets sont chiffrés au repos, versionnés, et accessibles via des tokens IAM scopés.
- External Secrets Operator : un opérateur Kubernetes (CNCF sandbox) qui lit les secrets depuis une source externe et crée les
SecretKubernetes correspondants, automatiquement et périodiquement.
Avant de rentrer dans le vif du sujet, réglons tout de suite la question qui fâche : combien ça coûte ?
Combien ça coûte ?
Seul le stockage est facturé : environ 0,03 € par secret et par mois, avec de légères variations selon la région où le secret est stocké. Pour une poignée de dizaines de secrets synchronisés par ESO, la facture mensuelle tourne donc autour de quelques centimes à quelques euros, sans la peine de devoir mettre en place un Vault, gérer son unseal automatique, etc.
Un secret correspond à une entrée versionnée dans l’OKMS, indépendamment du nombre de clés qu’il contient. Regrouper plusieurs valeurs (
db_password,api_key…) sous un même chemin (app/mocha) permet donc de limiter le nombre de secrets facturés.
Le budget étant clarifié, on peut passer à la suite.
Installer la CLI ovhcloud
Avant de toucher à Kubernetes, on va se donner de quoi discuter avec OVH depuis le terminal. La CLI est disponible sur Github et installable via Brew, curl ou go. Moi je vais passer par Mise. 😇
mise use -g "github:ovh/ovhcloud-cli[exe=ovhcloud]"
Une fois le binaire en place, il faut s’authentifier, le workflow est assez simple via la commande suivante :
ovhcloud login
Voilà pour la partie OVH. Passons maintenant au cluster, où il nous manque l’opérateur qui va faire tout le travail.
Installer External Secrets Operator
C’est ESO qui va lire les secrets côté OVH et les matérialiser en Secret Kubernetes. S’il n’est pas encore présent sur le cluster, un chart Helm suffit :
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets \
-n external-secrets \
--create-namespace \
--set installCRDs=true
La partie technique coté cluster est quasiment terminé : ESO va attendre qu’on créé notre ClusterSecretStore, étape qu’on réalisera un peu plus tard.
Récupérer les informations de l’OKMS
Un OKMS se crée le plus simplement depuis la WebUI, il n’existe pas de méthode pour le faire en CLI

Une fois le serveur provisionné, on repasse à la CLI pour le reste. Première chose à faire : retrouver l’instance et noter ses identifiants, car on va les réutiliser dans à peu près toutes les étapes suivantes (endpoint, policy IAM, ClusterSecretStore).
Commençons par lister nos instances Secret Manager :
ovhcloud okms list
┌──────────────────────────────────────┬─────────────┐
│ id │ region │
├──────────────────────────────────────┼─────────────┤
│ 336c214e-60b4-4efd-b101-52f31b633523 │ eu-west-par │
└──────────────────────────────────────┴─────────────┘
L’id et la region ne sont pas là pour décorer : ce sont eux qui composent l’endpoint que le ClusterSecretStore consommera plus loin (on le retrouve aussi tel quel dans l’UI) :
https://<region>.okms.ovh.net/api/<id>
Tant qu’on est sur cette instance, récupérons aussi son URN. On en aura besoin dans deux étapes, au moment d’écrire la policy IAM pour dire exactement sur quelle ressource les droits s’appliquent :
ovhcloud okms get <id> -o json | grep urn
# "urn": "urn:v1:eu:resource:okms:<id>"
On a l’URN, on a l’URL, on sait à quel serveru s’adresser !
Créer un compte de service IAM en moindre privilège
Le réflexe facile serait de réutiliser le login actuel, qu’est admin du compte OVH. Mais c’est un peu risqué, on va créer un utilisateur dédié qui aura moins de permissions.
1. Créer l’utilisateur
ovhcloud iam user create \
--login "secretmanager-eso" \
--description "Service account ESO OKMS" \
--email "quentinj@une-pause-cafe.fr" \
--password 'Il1k3ART1STPR00F' \
--group UNPRIVILEGED \
--type USER -o json
Détaillons le choix du UNPRIVILEGED : il garantit qu’aucun droit global n’est accordé par défaut. À ce stade, notre utilisateur existe mais ne peut littéralement rien faire.
2. Récupérer l’URN de l’utilisateur
Comme pour l’OKMS, l’utilisateur a lui aussi son URN. C’est l’identifiant qu’on va utiliser dans la policy, pour relier qui peut agir sur quoi :
ovhcloud iam user get secretmanager-eso -o json | grep urn
"urn": "urn:v1:eu:identity:user:jq4381-ovh/secretmanager-eso"
On tient donc les deux bouts : l’URN de l’utilisateur et celui de l’OKMS récupéré plus haut. Il ne reste plus qu’à les mettre en relation, parce qu’un utilisateur sans droits ne va pas bien loin.
3. Créer la policy IAM (moindre privilège)
Les 3 actions nécessaires à ESO sur l’OKMS sont : créer des secrets, récupérer leurs différentes versions (génération) et, bien évidemment, les lire. On n’autorise que ces trois-là, et on les cantonne à la seule ressource identifiée par son URN.
ovhcloud iam policy create \
--name "eso-okms" \
--description "ESO lecture secrets OKMS" \
--identity "urn:v1:eu:identity:user:jq4381-ovh/secretmanager-eso" \
--resource "urn:v1:eu:resource:okms:<id>" \
--allow "okms:apikms:secret/version/getData" \
--allow "okms:apikms:secret/create" \
--allow "okms:apiovh:secret/get"
L’utilisateur a maintenant exactement les droits qu’il faut, ni plus ni moins. Mais une policy ne s’authentifie pas toute seule : il nous faut un jeton concret à confier à ESO. On génère donc un PAT, qui hérite des permissions définies juste au-dessus :
ovhcloud iam user token create "secretmanager-eso" \
--name "pat-eso" \
--description "ESO -> OKMS Secret Manager"
Configurer External Secrets Operator
On arrive au moment où toutes les pièces se rejoignent. Bonne nouvelle : comme l’OVHcloud Secret Manager expose une API compatible Vault KV v2, on n’a rien de spécifique à écrire côté ESO, on réutilise simplement son provider vault. Il nous faut deux objets : le token, puis le store qui l’utilise.
Ce PAT est la clé qui déverrouille tout le reste, et c’est aussi la seule information sensible à faire transiter jusqu’au cluster. On l’injecte donc dans Kubernetes, sous forme de Secret, pour que l’opérateur puisse le lire :
PAT="eyJ..." # le PAT généré juste avant
kubectl create secret generic ovhcloud-vault-token \
-n external-secrets \
--from-literal=token="$(printf '%s' "$PAT" | base64)"
Le token est dans le cluster, mais ESO ne sait pas encore où le chercher ni à quel serveur s’adresser. C’est le rôle du ClusterSecretStore : il fait le lien entre le Secret qu’on vient de créer, l’endpoint de l’OKMS (souvenez-vous de l’id et de la region du début) et le provider vault :
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: ovh-secret-manager
spec:
provider:
vault:
server: "https://<region>.okms.ovh.net/api/<id>"
path: "secret"
version: "v2"
auth:
tokenSecretRef:
name: ovhcloud-vault-token
key: token
namespace: external-secrets
kubectl apply -f clustersecretstore.yaml
kubectl get clustersecretstore ovh-secret-manager
NAME AGE STATUS CAPABILITIES READY
ovh-secret-manager 10s Valid ReadWrite True
READY: True et CAPABILITIES: ReadWrite : le token est valide et la policy IAM répond comme prévu. On est ready coté ESO.
Synchroniser un secret
Il ne reste plus qu’à créer la donnée et la récupérer.
Pousser un secret dans l’OKMS
Pour commencer, on écrit un secret dans l’OKMS via l’API compatible Vault KV v2 (ici avec un token admin, mais la CLI Vault ou l’UI font tout aussi bien l’affaire) :
curl -XPOST \
"https://<region>.okms.ovh.net/api/<id>/v1/secret/data/app/mocha" \
-H "Authorization: Bearer $PAT_ADMIN" \
-H "Content-Type: application/json" \
-d '{"data": {"db_password": "s3cr3t", "api_key": "abc123"}}'
Pour récupérer ce secret coté Kubernetes, on peut maintenant créer un ExternalSecret comme ceci :
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mocha-app-secret
namespace: default
spec:
refreshInterval: 30m
secretStoreRef:
name: ovh-secret-manager
kind: ClusterSecretStore
target:
name: mocha-app-secret
creationPolicy: Owner
data:
- secretKey: db_password
remoteRef:
key: app/mocha
property: db_password
- secretKey: api_key
remoteRef:
key: app/mocha
property: api_key
kubectl apply -f externalsecret.yaml
kubectl get externalsecret mocha-app-secret
NAME STORETYPE STORE STATUS READY
mocha-app-secret ClusterSecretStore ovh-secret-manager SecretSynced True
Et voila, on a un cluster qui utilise les secrets sur le KMS managé, sans devoir setup de OpenBao et gérer les secrets, volumes et l’unseal que ça implique.