Secret management GitOps avec SOPS
Déchiffrement SOPS/Age à la volée dans ArgoCD, via un plugin custom (CMP) côté repo-server.
Le problème
En déclenchant un rollout ArgoCD via un appel curl à l'API, un values.yaml entièrement déchiffré, avec des clés API et des mots de passe en clair, est apparu dans les logs du pipeline CI côté GitHub Actions.
Le secret n'était donc jamais réellement protégé une fois le pipeline exécuté, même si le fichier source restait chiffré dans Git.
Options envisagées et écartées
Deux approches ont été construites et comparées en parallèle pour ce problème : SOPS/Age (cette page) et Vault self-hosted avec Vault Secrets Operator (page dédiée).
SOPS chiffre les secrets directement dans Git avec Age, sans infrastructure externe à opérer. La contrepartie est une rotation manuelle des clés et l'absence d'audit centralisé.
Vault héberge les secrets hors Git, avec rotation dynamique et audit, mais ajoute un composant supplémentaire à opérer et à sécuriser.
Le choix de SOPS pour ce POC répond au besoin le plus direct : ne plus jamais avoir de secret déchiffré visible en clair dans un log, sans ajouter de service supplémentaire à la charge opérationnelle.
Architecture
Git repo ArgoCD repo-server
(values.yaml +--------------------------------+
chiffre SOPS/Age) --> | CMP sidecar |
| argocd-cmp-helm-secrets:v1.0.0 |
| dechiffrement SOPS/Age |
+----------------+----------------+
|
v (manifeste dechiffre,
jamais expose via l'API)
Kubernetes API
|
v
Cluster cible
Décisions difficiles
La mise en œuvre n'a pas nécessité de contournement majeur. La configuration du CMP sidecar et la gestion des clés Age se sont appuyées sur la documentation ArgoCD et SOPS existante.
Vérification
Vérification faite en interrogeant directement l'API ArgoCD après déploiement : les réponses ne contiennent aucun secret en clair, seul le manifeste déchiffré appliqué au cluster y a accès.
Ce que je ferais autrement
L'approche resterait la même. Le compromis de SOPS (pas d'infrastructure supplémentaire, rotation manuelle des clés) correspond au besoin initial.