Secret management GitOps avec Vault
Vault self-hosted et Vault Secrets Operator, du POC à un déploiement en production avec Keycloak OIDC, Teleport et durcissement réseau.
Le problème
Le même déclencheur que pour l'approche SOPS : un values.yaml déchiffré, avec des clés API et des mots de passe en clair, visible dans les logs du pipeline CI GitHub Actions lors d'un rollout ArgoCD.
Options envisagées et écartées
Vault a été construit et comparé en parallèle de SOPS/Age (page dédiée).
Vault stocke les secrets hors Git, avec rotation dynamique et audit, au prix d'un composant supplémentaire (Vault lui-même) à opérer et à sécuriser.
SOPS reste plus simple à opérer, sans infrastructure externe, mais avec une rotation manuelle des clés et sans audit centralisé.
Les deux approches ont été menées à terme plutôt que de trancher a priori, pour comparer le compromis réel entre simplicité opérationnelle et contrôle fin sur les secrets.
Architecture
Vault (self-hosted)
secrets stockes hors Git
|
| auth Kubernetes (ServiceAccount)
v
Vault Secrets Operator (VSO)
synchronise les secrets
|
v
Kubernetes Secret (namespace applicatif)
|
v
Pod applicatif
(ArgoCD gere le reste du manifeste,
aucun secret ne transite par Git)
Décisions difficiles
Comme pour l'approche SOPS, la mise en œuvre n'a pas nécessité de contournement majeur. La configuration de l'authentification Kubernetes et du Vault Secrets Operator s'est appuyée sur la documentation HashiCorp existante.
Vérification
La vérification a couvert tout le cycle de vie du secret, pas seulement son injection initiale au démarrage :
- Provisionnement : les secrets apparaissent dans les pods applicatifs (nginx et Postgres) sans jamais transiter par Git ni par Helm.
- Preuve d'intégration réelle : un premier test du mot de passe Postgres depuis l'intérieur du pod passait à tort, quel que soit le mot de passe fourni. En cause,
pg_hba.confconfiguré entrustpour les connexions locales, qui ne vérifie aucun mot de passe. Le test valide n'a de sens qu'en connexion réseau depuis un autre pod : un mot de passe correct passe, un mot de passe incorrect échoue. - Exposition API : les manifestes rendus par ArgoCD, en ligne de commande comme via l'API HTTP, ne contiennent aucun secret. Helm ne génère structurellement plus aucun objet Secret.
- Indépendance au déploiement : un patch applicatif via l'API ArgoCD (changement de tag d'image) ne touche pas au secret existant, déjà présent dans le nouveau pod.
- Rotation : un changement de valeur dans Vault se propage au pod, avec redémarrage automatique, en moins de 30 secondes. Zéro commit, zéro synchronisation ArgoCD. C'est précisément ce que SOPS ne sait pas faire : une rotation y demande de rechiffrer, commiter, pousser, puis attendre le sync.
- Révocation : couper l'accès d'une policy Vault empêche bien la lecture d'une nouvelle valeur, mais les tokens déjà émis restent valides jusqu'à expiration (TTL). Avec SOPS, révoquer un accès impose de rechiffrer avec une nouvelle clé et de considérer tout l'historique Git comme compromis.
- Audit : chaque lecture de secret par le Vault Secrets Operator est journalisée (qui, quoi, quand, sur quelle policy), avec les valeurs hashées plutôt qu'en clair. SOPS n'a aucun équivalent : aucune trace de qui a déchiffré quoi.
- Résilience : un arrêt de Vault n'interrompt pas les pods déjà démarrés, le secret restant persisté dans etcd, mais bloque tout nouveau déploiement qui en dépend.
Passage en production
Le POC a été suivi d'un déploiement en production, dans le but de comprendre concrètement les implications de ce passage avant de déployer Vault sur mon propre serveur. Le nœud Vault reste unique à ce stade : l'effort a porté sur l'authentification et le contrôle d'accès plutôt que sur la topologie de stockage.
Authentification humaine via Keycloak (OIDC)
L'accès à l'interface Vault et au CLI par les opérateurs humains passe par Keycloak en OIDC, plutôt que par un token root ou une méthode d'authentification locale.
Accès via Teleport
Teleport sert de bastion pour l'accès à Vault et au cluster Kubernetes : un point d'entrée unique et audité, plutôt qu'un accès direct aux services.
Restriction réseau
Un firewall restreint l'accès réseau à Vault : l'API n'est plus exposée largement, seules les sources autorisées peuvent l'atteindre.
Ce que je ferais autrement
Le POC tournait avec Vault en mode développement sur une seule VM, un point de défaillance unique assumé pour ce périmètre. Le test de résilience le confirme : ça n'interrompt pas ce qui tourne déjà, mais ça bloque tout nouveau déploiement en attendant que Vault revienne. C'est un incident de déploiement, pas un incident de production, sauf en cas de replanification de pods sur du matériel neuf au même moment.
Le déploiement en production reste lui aussi sur un nœud Vault unique : la mise en cluster n'a pas été traitée à ce stade, la priorité ayant été donnée à l'authentification et au contrôle d'accès. Le point de défaillance unique subsiste donc, à traiter dans une itération suivante.