Aller au contenu

La gestion des secrets dans Kubernetes

Romain Boulanger
Auteur
Romain Boulanger
Architecte Infra/Cloud avec une touche de DevSecOps
Sommaire

Un cas de figure récurrent
#

Si vous avez déjà déployé des applications dans Kubernetes, vous avez forcément été confronté à cette question : “Mais où est-ce que je stocke mon mot de passe de base de données ou ma clé d’API ?”.

Cette problématique récurrente fait partie de la gestion des informations sensibles lors d’une phase de déploiement d’un outil ou d’une application. Sur le papier et de manière native, Kubernetes semble apporter une solution mais est-ce réellement efficace ?

Dans cet article, je vais vous détailler pourquoi la gestion native des secrets pose problème, particulièrement dans un contexte GitOps, et comment certains outils parviennent à construire une chaîne de confiance pour la récupération de vos informations sensibles.

Prêt ? C’est parti !

Le base64 en trompe l’œil
#

Toute application moderne a besoin d’informations sensibles pour fonctionner : des mots de passe, des tokens d’authentification, des certificats TLS, etc.

Nativement, Kubernetes propose l’objet Secret pour stocker ces données et les injecter au sein de Pods utilisant différents mécanismes comme des variables d’environnement ou des volumes.

apiVersion: v1
kind: Secret
metadata:
  name: mon-secret
type: Opaque
data:
  password: c3VwZXItc2VjcmV0 # Correspond à "super-secret" en base64

Exemple de fichier YAML pour définir un secret

Sur le papier, c’est très pratique et facile à mettre en place. Mais en réalité, cette solution pose un problème de sécurité de la donnée.

En effet, l’objet Secret ne chiffre absolument rien. Il se contente d’encoder les valeurs en Base64. Autrement dit, n’importe qui sachant utiliser la commande base64 -d peut lire vos mots de passe en clair.

Si vous avez adopté une approche GitOps avec des outils comme Argo CD ou Flux CD par exemple, l’objectif est de stocker l’intégralité de vos fichiers YAML, Kustomize, ou charts Helm dans un dépôt Git. Néanmoins, commiter du Base64 dans Git, que votre dépôt soit public ou privé, est une pratique à proscrire d’un point de vue sécurité.

Non seulement car la valeur est uniquement masquée et peut être lue très facilement, mais aussi car cela ne permet pas de bénéficier de l’ensemble des fonctionnalités quand on parle d’informations sensibles : gestion de la rotation, gestion des accès, chiffrement, audit, etc.

Vous l’aurez compris, ce mécanisme natif ne permet pas de répondre aux exigences de sécurité de ce type d’information quel que soit le type d’environnement…

External Secrets à la rescousse
#

Présentation
#

C’est là que l’écosystème Cloud Native entre en jeu, notamment du côté de la CNCF (Cloud Native Computing Foundation) qui référence plusieurs outils dans la catégorie Security and Compliance dont External Secrets.

External Secrets (CNCF)

Évidemment, ce n’est pas la seule solution en termes d’outils, mais je trouve personnellement qu’elle remplit bien des critères que je vais vous exposer juste après.

External Secrets Operator (ESO) de son nom complet, est un outil à installer dans Kubernetes sous forme de chart Helm qui permet d’interagir avec des coffres-forts distants en ayant pour objectif de gérer les informations sensibles et de les distribuer, par la suite, aux outils et applications qui en ont besoin.

Entré initialement en mode sandbox en 2022, il a très vite été adopté massivement en production. Après une longue (mais déjà très robuste) période en version 0.x, l’opérateur a récemment passé un cap de maturité majeur en atteignant la version 1.0 il y a quelques mois. Aujourd’hui, en écrivant cet article, External Secrets vient tout juste de passer en version 2.0 qui apporte une fonctionnalité que je vais décrire plus bas.

Cet opérateur écrit en Go, permet de réagir à la création d’objets personnalisés sous forme de CRD (Custom Resource Definition) appelée ExternalSecret :

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: database-credentials
spec:
  refreshInterval: 1h0m0s
  secretStoreRef:
    kind: SecretStore
    name: azure-keyvault
  target:
    name: database-credentials
    creationPolicy: Owner
  data:
  - secretKey: database-username
    remoteRef:
      key: database-username
      property: database-username

Exemple ici pour récupérer un Secret dans l’Azure Key Vault

Comme vous pouvez le constater cet objet comporte des chemins avec le champ property pour récupérer la ou les valeurs sensibles mais aucune information critique. Ce qui permet de le commiter dans Git.

Qui plus est, une fois l’objet créé et déployé dans un cluster Kubernetes, ce dernier créera un objet Secret avec les valeurs correspondantes de l’ExternalSecret comme database-username dans l’exemple.

External Secrets en mode GitOps

Dans les points positifs, External Secrets brille par sa polyvalence :

  • Multi-providers : Que vos secrets soient stockés dans Azure Key Vault, HashiCorp Vault, AWS Secrets Manager, etc. l’opérateur s’interface avec presque tous les acteurs du marché. Vos objets Kubernetes restent les mêmes, seul le backend change ;

Vous pouvez retrouver la liste complète des providers dans la documentation officielle. Si votre coffre-fort n’est pas supporté, sachez qu’il y a toujours le provider Webhook qui permet de faire des merveilles avec les APIs disponibles.

  • Fonctionnalités avancées : Il est capable de récupérer des secrets pour les injecter dans le cluster, mais aussi d’en pousser vers l’extérieur (via des ressources PushSecret), et surtout d’en assurer la rotation.
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: pushsecret-example
spec:
  refreshInterval: 1h0m0s
  secretStoreRefs:
    - name: vault
      kind: SecretStore
  selector:
    secret:
      name: kubeconfig
  data:
    - match:
        secretKey: value
        remoteRef:
          remoteKey: kubernetes/kubeconfig

Exemple de PushSecret avec HashiCorp Vault

L’objet PushSecret est un argument redoutable, notamment pour pousser des secrets générés au sein du cluster. C’est le cas lors de l’utilisation de Cluster API pour créer des clusters Kubernetes à la volée afin de récupérer le kubeconfig et le stocker ou le mettre à jour dans votre coffre-fort.

Enfin, il existe différents types spécifiques de Secret dans Kubernetes : Service account token, TLS, Docker config, etc. Ils peuvent être facilement définis grâce au champ target.template pour personnaliser le contenu du Secret qui sera généré via l’ExternalSecret :

[...]
  target:
    template:
      type: kubernetes.io/tls
[...]

Si vous désirez en savoir plus sur l’ensemble des possibilités, je vous conseille de faire un tour dans la documentation du moteur de templating (version 2), la version 1 étant dépréciée.

Créer des Secrets ? Mais pas seulement…
#

La version 2.0 apporte une fonctionnalité très intéressante grâce au champ target.manifest qui permet de créer des objets comme des ConfigMap ou des ressources personnalisées. Dans quel but ? Eh bien, tout simplement pour directement créer un objet avec des champs sensibles plutôt que de passer par l’objet Secret, une fois l’information récupérée.

Pour ceux travaillant avec Argo CD, il est donc totalement possible de générer une Application si le CRD est présent sur le cluster cible grâce à la clé argocd/applications/frontend qui permettra de créer l’objet via la valeur en paramètre :

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: argocd-app-frontend
spec:
[...]
  target:
    name: frontend
    manifest:
      apiVersion: argoproj.io/v1alpha1
      kind: Application
  dataFrom:
  - extract:
      key: argocd/applications/frontend
[...]

C’est donc une fonctionnalité intéressante notamment pour injecter des informations sur différents types d’objets que l’on ne peut stocker directement dans Git.

Configuration des providers
#

Forcément pour récupérer ou pousser ces secrets, il vous faut un endroit pour gérer la connexion avec le ou les coffres-forts que vous souhaitez utiliser.

C’est là que rentrent en scène les objets SecretStore et ClusterSecretStore, le premier étant dédié à un namespace tandis que l’autre a une portée sur l’ensemble du cluster. L’ensemble des providers disponibles peuvent être utilisés dans ces deux configurations.

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: aws-secretsmanager
  namespace: app
spec:
  provider:
    aws:
      service: SecretsManager
      role: # AWS Role
      region: eu-central-2
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            key: access-key
          secretAccessKeySecretRef:
            name: aws-credentials
            key: secret-access-key

SecretStore avec AWS Secrets Manager pour le namespace app

apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: delinea
spec:
  provider:
    delinea:
      tenant: # Tenant
      tld: # Tld
      clientId:
        value: # Client ID
      clientSecret:
        secretRef:
          name: delinea-credentials
          key: client-secret
  conditions:
    - namespaces:
        - "app"
        - "backend"

ClusterSecretStore avec Delinea limité aux namespaces app et backend

Comme le champ conditions, il existe beaucoup d’éléments de personnalisation, ce qui montre ici la maturité de l’outil pour correspondre à vos besoins.

Il existe également l’objet ClusterPushSecret pour paramétrer le backend dans lequel vous pouvez pousser vos secrets.

Cependant, il existe toujours un problème qui n’est pas inhérent qu’à External Secrets mais à l’ensemble de ce type de solutions. Eh oui, il faut bien un premier secret pour connecter votre SecretStore ou ClusterSecretStore aux providers définis…

Un secret pour les gouverner tous !
#

Pour qu’External Secrets puisse s’authentifier auprès de votre coffre-fort distant (comme on le voit dans les exemples du dessus), il a lui-même besoin d’un secret, que ce soit un mot de passe, un token, etc. C’est ce qu’on appelle communément le problème du “Secret Zéro”.

External Secrets ne résout pas le souci de ce tout premier secret à injecter au sein du cluster lors de son initialisation. Et comme on l’a vu, il est hors de question de le commiter en clair dans votre dépôt Git, ce qui compromettrait la sécurité du coffre-fort.

Plusieurs solutions existent pour chiffrer ce premier secret. De mon côté, j’utilise très souvent SOPS (Secrets OPerationS), un projet initialement créé par Mozilla puis donné à la CNCF en 2023. Cette solution permet de chiffrer et déchiffrer très rapidement et facilement vos fichiers.

L’idée étant de générer une clé privée et publique (à stocker dans votre coffre-fort), pourquoi pas avec age et la commande age-keygen pour chiffrer le fichier contenant ce premier secret et laisser votre pipeline de déploiement déchiffrer et pousser le secret dans votre cluster Kubernetes.

$ age-keygen
# created: 2026-02-21T07:43:52+01:00
# public key: age1j8salupy7sum88gz37vjwdny6nmqxamhewe7k5h83yc9j08zqaeqt82j6f
AGE-SECRET-KEY-1D658GZYLVEP5D3NTTSKF4QVSV23RAFWPKQ6NZYVQACLP67E2D2TQLRVZ8J

age... étant la clé publique et AGE-SECRET-KEY-... la clé privée à stocker précieusement.

Ce qui fait que pour chiffrer avec des informations sensibles, il suffit de mettre la clé publique dans la variable SOPS_AGE_RECIPIENTS puis d’exécuter la commande :

$ SOPS_AGE_RECIPIENTS=age1j8sal... sops --encrypt --in-place values.secret.yaml
$ cat values.secret.yaml
secretStore:
    clientSecret: ENC[AES256_GCM,data:...,type:str]
sops:
    age:
        - recipient: age...
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            [...]
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2026-02-21T08:30:07Z"
    mac: ENC[AES256_GCM,data:...,type:str]
    unencrypted_suffix: _unencrypted
    version: 3.12.0

Fichier values.secret.yaml pour déployer le premier secret

Pipeline de bootstrap du secret

Pipeline de bootstrap pour injecter le premier secret

Votre pipeline a donc un rôle de bootstrap pour permettre à External Secrets ou tout autre outil de s’exécuter avec les bonnes informations et remplir son rôle.

Bien évidemment la machine qui réalise cette action doit avoir le droit de récupérer la clé privée age, c’est clairement l’histoire de l’œuf et la poule où cette dernière devra disposer d’une identité pour réaliser ces actions que l’on retrouve principalement sur le Cloud ou via des fonctionnalités comme Workload Identity.

Un mot pour conclure
#

La gestion des informations sensibles est toujours un sujet primordial quelle que soit la technologie. Comme vous l’avez vu, Kubernetes apporte une solution native peu adaptée par rapport à la philosophie GitOps mais aussi dans la centralisation et gestion des secrets en général.

Plutôt que de réinventer la roue, External Secrets se positionne comme une interface pour dialoguer avec vos coffres-forts afin de récupérer ou pousser des secrets avec un ensemble de fonctionnalités permettant une personnalisation avancée.

Bien évidemment, il y a toujours un premier secret dont il faut s’occuper, et pour cela, SOPS est un moyen rapide et fiable pour le chiffrer et le déployer.

Articles connexes