Aller au contenu

Secrets applicatifs

Le service Vault fourni par Caascad est configuré de manière à ce que les applications Kubernetes puissent y lire des secrets.

Les applications peuvent s'authentifier sur Vault avec le token Kubernetes associé à leur ServiceAccount et y lire des secrets. Pour les applications qui ne supportent pas nativement Vault il est possible d'utiliser le service Vault injector (outil déployé optionnellement dans les clusters par Caascad).

Secrets Kubernetes vs secrets Vault

Pour fournir des secrets aux applications, Kubernetes fourni nativement une API de secrets qui permet aux utilisateurs de définir des secrets et d'utiliser leur contenu dans l'environnement du Pod de l'application, sous forme de fichier ou de variable d'environnement.

Les secrets Kubernetes sont simples à utiliser en raison de leur intégration native à l'écosystème Kubernetes, mais présentent cependant quelques désavantages :

  • fréquemment, les définitions des ressources à déployer dans le cluster sont stockées et versionnées dans des dépôts de code. Les secrets, de par leur nature confidentielle, doivent être chiffrés sur ces mêmes dépôts de codes et déchiffrés au moment du déploiement dans le cluster, ce qui nécessite la mise en place d'outils supplémentaires adaptés
  • l'accès aux secrets Kubernetes par les applications est impossible en dehors du namespace de l'application. Si plusieurs applications dans des namespaces différents doivent accéder au même secret, celui-ci doit être provisionné dans chaque namespace applicatif
  • les secrets sont statiques
  • il est difficile de configurer finement l'accès aux secrets par les utilisateurs avec les RBAC Kubernetes
  • suivant la configuration du control plane Kubernetes, les secrets et autres ressources sauvegardés sur disque par le serveur d'API Kubernetes peuvent ne pas être chiffrés

L'utilisation de Vault permet de s'affranchir complètement de ces différents problèmes :

  • les secrets ne sont pas stockés sous forme de fichiers et sont écrits directement dans Vault
  • la politique d'accès aux secrets dans Vault peut être configurée de manière fine
  • pas de notion de namespace, plusieurs applications peuvent lire le même secret, quelle que soit la méthode de déploiement employée: application Kubernetes, VM, etc...
  • l'utilisation de Vault ne se limite pas aux applications Kubernetes
  • Vault peut fournir des secrets dynamiques (AK/SK AWS temporaire, certificats...)
  • la base de données de Vault est chiffrée sur le disque

L'intégration de Vault dans l'univers Kubernetes n'étant pas native, l'interfaçage entre Vault et l'application cible est nécessaire. Les méthodes suivantes sont à considérer :

  • si l'application est capable de communiquer nativement avec Vault, la connexion à Vault se fait directement depuis l'application
  • si l'application n'est pas capable de communiquer nativement avec Vault, l'utilisation d'une solution tierce comme l'agent Vault permet d'assurer la récupération des secrets avant et/ou pendant l'exécution de l'application principale.

Hiérarchie des secrets

L'intégration de Vault dans Caascad propose un modèle d'organisation des secrets générique afin de couvrir un maximum de cas d'usage.

Les secrets à destination des applications Kubernetes sont placés dans le KV store à des endroits précis suivant l'utilisation souhaitée :

  • kv/k8s/global/all/: accès par toutes les applications sur tous les clusters
  • kv/k8s/global/<NAMESPACE>: accès par les applications d'un namespace particulier sur tous les clusters
  • kv/k8s/<CLUSTER_NAME>/all: accès par toutes les applications sur un cluster en particulier
  • kv/k8s/<CLUSTER_NAME>/<NAMESPACE>: accès par les applications d'un namespace particulier sur un cluster en particulier

Note

Par défaut vous ne verrez aucun de ces chemins tant qu'aucun secret n'a été provisionné.

Lors de la création d'un secret, assurez vous de l'écrire dans un chemin valide comme décrit plus haut.

Pour écrire des secrets dans kv/k8s/global vous devez avoir le rôle caascad-devops (ou caascad-vault-k8s-secrets qui est inclu dans caascad-devops). Les utilisateurs qui ont le rôle caascad-devops-<CLUSTER_NAME> (ou caascad-vault-k8s-secrets-<CLUSTER_NAME>) ne peuvent écrire des secrets que dans kv/k8s/<CLUSTER_NAME>.

Intégration native des applications avec Vault

Si vous développez vous-même vos applications c'est la méthode recommandée car les secrets ne seront pas visibles dans le cluster (même via un kubectl exec dans le Pod).

Des librairies sont disponibles pour la plupart des languages et leur utilisation est simple.

Exemple d'authentification Kubernetes et de lecture d'un secret avec la librarie HVAC (python):

import hvac
# Lecture du token Kubernetes dans le Pod
f = open('/var/run/secrets/kubernetes.io/serviceaccount/token')
jwt = f.read()
# Création du client Vault
client = hvac.Client(url='https://vault.<ZONE_NAME>.caascad.com')
# Login avec le role `caascad-secret-reader` et le token Kubernetes du Pod.
# Suivant le cluster utilisé le `mount_point` est différent
client.auth_kubernetes('caascad-secret-reader', jwt, mount_point='kubernetes-<CLUSTER_NAME>')
# Lecture d'un secret
s = client.secrets.kv.read_secret_version(path='kv/k8s/global/all/my-secret')
print(s['data']['data'])

Note

Dans le cas ou vous utilisez un ServiceAccount dans vos manifests, assurez-vous, dans sa configuration, que le paramètre automountServiceAccountToken n'est pas à false.

Vault injector

L'injector Vault permet d'injecter un init container dans le Pod de l'application. Ce processus d'initialisation ajouté au Pod est responsable de la récupération des secrets Vault. Les secrets récupérés sont ensuite exposés à l'application via des fichiers.

Note

  • Le Vault injector n'est pas déployé par défault. Vous devez demander à Caascad de le déployer dans les clusters si vous en avez besoin.
  • Dans le cas ou vous utilisez un ServiceAccount dans vos manifests, assurez-vous, dans sa configuration, que le paramètre automountServiceAccountToken n'est pas à false.

L'activation de l'injector se fait via l'ajout d'annotations sur un Pod Kubernetes.

Annotations minimales

Exemple d'annotations minimales à utiliser:

annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "caascad-secret-reader"
    vault.hashicorp.com/agent-inject-secret-foo: "kv/data/k8s/global/all/foo"

Note

  • sur n'importe quel cluster client, le role à utiliser est toujours caascad-secret-reader
  • le KV store étant en version 2 le début du chemin est toujours kv/data/...
  • le contenu du secret kv/k8s/global/all/foo sera écrit dans un volume attaché au Pod dans /vault/secrets/<unique_name> (où <unique_name> est déterminé à partir du suffixe de l'annotation vault.hashicorp.com/agent-inject-secret-<unique_name>)
  • il est possible de spécifier plusieurs fois l'annotation vault.hashicorp.com/agent-inject-secret-<unique_name> pour lire plusieurs secrets

Le format de l'annotation pour lire un secret est le suivant :

vault.hashicorp.com/agent-inject-secret-<unique_name>: /path/to/secret

<unique_name> est le nom du fichier qui sera créé dans le dossier /vault/secrets.

Le contenu du fichier résultant aura par défaut la forme suivante (fichier au format json) :

{"data":{"key1":"secret1","key2":"secret2","key3":"secret3"},"metadata":{"created_time":"2021-06-12T09:38:53.854966686Z","custom_metadata":null}}

Templates

Si le format du fichier ne convient pas il est possible de définir un template pour mettre en forme le fichier en ajoutant une annotation supplémentaire :

Exemples

  1. Génération d'un fichier exécutable par l'interpréteur de commande shell :

    vault.hashicorp.com/agent-inject-template-foo: |
      {{- with secret "kv/data/k8s/global/all/foo" -}}
      export KEY1={{ .Data.data.key1 }}
      export KEY2={{ .Data.data.key2 }}
      {{- end }}
    

    Le fichier résultant aura la forme suivante :

    export KEY1=secret1
    export KEY2=secret2
    

    Note

    Ces exemple explique comment générer un fichier permettant d'exposer des variables d'environnement à l'application. Pour se servir de ces variables, il faudra également modifier la commande exécutée au lancement du conteneur :

    apiVersion: v1
    kind: Pod
    metadata:
      name: demo-pod
    annotations:
      <vault-injector annotations...>
    spec:
      containers:
        command: ["/bin/bash", "-c", "source /vault/secrets/foo && exec /bin/app"]
    
  2. Boucler sur l'ensemble des clées d'un secret :

    vault.hashicorp.com/agent-inject-template-foo: |
      {{- with secret "kv/data/k8s/global/all/foo" -}}
      {{- range $k, $v := .Data.data -}}
      {{ $k }}: {{ $v }}
      {{ end -}}
      {{- end -}}
    

    Le fichier résultant aura la forme suivante :

    key1: secret1
    key2: secret2
    key3: secret3
    

  3. Exposer le contenu du secret au format yaml :

    vault.hashicorp.com/agent-inject-template-foo: |
      {{- with secret "kv/data/k8s/global/all/foo" -}}
      {{- .Data.data | toYAML }}
      {{- end }}
    

  4. Pour aller plus loin, vous pouvez consulter la documentation officielle du langage de templating utilisé par Vault.

Sidecar

Par défaut un init container ainsi qu'un Pod sidecar (un container supplémentaire dans le Pod) seront injectés. Le sidecar observe régulièrement le ou les secrets demandés et les met à jour dans le Pod lorsque ceux-ci changent.

Ce sidecar est souvent inutile et il peut être désactivé via l'annotation :

vault.hashicorp.com/agent-pre-populate-only: "true"

Liens