Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Injecter des secrets depuis AWS, GCP ou Vault dans un Pod Kubernetes

By Ami MahloofMar 23, 20208 min read

Cette page est également disponible en English, Deutsch, Español, Italiano, 日本語 et Português.

1 pylutmoqk4ivd vequwuda

Dans l'univers Kubernetes, on cherche à automatiser et à limiter la duplication de code. Consommer des secrets depuis un gestionnaire devrait suivre la même logique. Voici la marche à suivre.

Cet article fait suite à mon précédent billet consacré à l'automatisation de l'injection de secrets dans les Pods Kubernetes lorsqu'on utilise Vault.

Sur Kubernetes, les secrets restent du texte en clair encodé en base64.

Vous pouvez certes chiffrer les secrets au repos, mais ils ne le sont pas sur le Pod lui-même, et la clé de chiffrement est globale pour Etcd (où les secrets sont stockés). D'où un risque dans un cluster mutualisé, puisque tous les secrets sont chiffrés avec la même clé globale.

1 pylutmoqk4ivd vequwudaInjecter en toute sécurité des secrets AWS, GCP ou Vault dans un Pod Kubernetes

GCP KMS permet de créer une clé applicative de type enveloppe pour pallier ce problème. Mais au bout du compte, le Pod consomme toujours les secrets en clair.

Outre Hashicorp Vault, il existe les gestionnaires de secrets d'AWS et de GCP.

AWS et GCP ne proposent certes pas toutes les fonctionnalités de Vault — backends d'authentification, audit d'accès aux secrets, révocation, voire secrets dynamiques — mais ils offrent un mode similaire de stockage des secrets avec gestion de versions.

Le défi : consommer des secrets dans des Pods Kubernetes

Consommer ces secrets dans un Pod Kubernetes pose un défi : il faut écrire un script wrapper sur mesure pour gérer l'authentification et la récupération des secrets sous forme de variables d'environnement. Vous devez ensuite remplacer votre script wrapper par le processus wrapper afin qu'il hérite de toutes les variables d'environnement et devienne le PID 1, ce qui est essentiel pour traiter correctement les signaux de terminaison dans les conteneurs.

Dans l'univers Kubernetes, on cherche à automatiser et à réduire au minimum la duplication de code nécessaire à la gestion de ce type de tâches annexes (autre exemple : l'automatisation du DNS et des certificats). La consommation de secrets depuis un gestionnaire devrait suivre la même logique.

Certaines entreprises préfèrent ne pas gérer une installation Vault, et pour cette seule raison, elles peuvent se tourner vers les gestionnaires de secrets d'AWS ou de GCP.

Les webhooks Kubernetes incontournables

Kubernetes intègre nativement un admission controller capable d'intercepter les requêtes adressées au serveur API Kubernetes avant la persistance de l'objet, mais après l'authentification et l'autorisation de la requête.

Deux contrôleurs spécifiques sont intégrés au binaire kube-apiserver :

  • MutatingAdmissionWebhook
  • ValidatingAdmissionWebhook

Ils exécutent la mutation et la validation des requêtes après l'authentification et l'autorisation.

Les contrôleurs de mutation peuvent modifier les objets qu'ils admettent, tandis que les contrôleurs de validation décident ou non d'admettre un objet auprès du serveur API.

Ces objets webhook s'installent comme n'importe quel objet Kubernetes via kubectl (ils contiennent l'adresse https de la logique webhook proprement dite).

Une fois installé, chaque requête passera par cette logique webhook.

Webhook de mutation Secrets Consumer

Le code et les principes qui suivent s'appuient largement sur l'excellent travail de Banzai Cloud, à qui revient tout le mérite (allez lire leur blog, il est excellent !).

Voici les principales différences entre les webhooks de Banzai Cloud et les miens :

  1. Prise en charge des gestionnaires de secrets AWS, GCP et Vault.
  2. Suppression de consulTemplates et de la fonctionnalité de secrets dynamiques du webhook Banzai Cloud.
  3. Authentification de Vault possible non seulement via un backend Kubernetes, mais aussi via un backend GCP.
  4. Utilisation de secrets explicites depuis le gestionnaire, ou récupération de tous les secrets.
  5. Utilisation du chemin du secret comme répertoire pour récupérer tous les secrets en dessous (limité au premier niveau).
  6. Utilisation des noms de secrets comme clés lorsque le chemin se termine par / et que chaque valeur est unique :

L'objectif est de mieux gérer le contenu des secrets — réduire le risque d'erreur lors de l'ajout ou de la modification d'un secret, ainsi que le nombre d'étapes nécessaires (lire, ajouter, mettre à jour). 7. Utilisation d'un chemin de secret avec joker. Par exemple, si le chemin se termine par / et qu'il existe plusieurs secrets commençant par db_, le chemin du secret peut être /secret/path/db_*. 8. Détection automatique du type KV1 ou KV2 (plus besoin de préfixer /data dans votre chemin de secret).

Fonctionnement du webhook de mutation

Chaque nouvelle requête vers le serveur API passe par ce webhook.

Le webhook vérifie si l'objet est un Pod et ne le mute que s'il contient des annotations Pod spécifiques, par exemple :

https://gist.github.com/innovia/3e58eeb38668c9f4da4f1f76a736fbca

Lorsqu'il détecte ces annotations, il modifie l'objet Pod ainsi :

  • Ajout d'un volume partagé en mémoire
  • Ajout d'un init container avec le binaire vault-env et une commande pour copier vault-env vers ce volume partagé
  • Modification de la commande du Pod en vault-env
  • Ajout des variables d'environnement Vault (ROLE, CA_PATH, SECRET_PATH) pour faciliter les opérations Vault.

Point important :

Le webhook n'ajoute pas les secrets en variables d'environnement sur le Pod. Aucune donnée confidentielle n'est jamais persistée sur le disque, même temporairement, ni ailleurs, et elles ne sont pas non plus visibles sur l'objet Pod modifié.

Tous les secrets sont stockés en mémoire et ne sont visibles que par le processus qui les demande.

Utiliser des Charts sans container.command ni container.args explicites

Le webhook est désormais capable de déterminer l'ENTRYPOINT et la CMD du conteneur à partir des métadonnées d'image récupérées depuis le registre. Ces données sont mises en cache jusqu'au redémarrage du Pod du webhook. Si le registre est accessible publiquement (sans authentification), vous n'avez rien à faire ; en revanche, s'il requiert une authentification, les identifiants doivent figurer dans la section imagePullSecrets du Pod.

Quasiment tous les charts permettent de définir vos podAnnotations. Vous trouverez les annotations disponibles sur la page README du webhook.

Consommer des secrets en dehors de Kubernetes

L'outil secrets-consumer-env s'exécute en définissant quelques variables d'environnement — consultez le README.md pour découvrir toutes les options disponibles.

Installation du webhook

Avant d'installer ce chart, vous devez créer un namespace dédié. Cela tient à l'ordre dans lequel les ressources des charts sont appliquées (Helm collecte toutes les ressources d'un chart donné et de ses dépendances, les regroupe par type de ressource, puis les installe dans un ordre prédéfini ; voir ici — Helm 2.10).

La MutatingWebhookConfiguration est créée avant le Pod backend qui sert effectivement de webhook. Kubernetes voudrait également muter ce Pod, mais celui-ci n'est pas encore prêt à l'être (récursion infinie dans la logique).

export WEBHOOK_NS=`<namepsace>`
WEBHOOK_NS=${WEBHOOK_NS:-vault-secrets-webhook}
kubectl create namespace "${WEBHOOK_NS}"
kubectl label ns "${WEBHOOK_NS}" name="${WEBHOOK_NS}"

Récupérer le chart :

git clone https://github.com/innovia/secrets-consumer-webhook.git

Installer le chart :

helm upgrade --namespace ${WEBHOOK_NS} --install secrets-consumer-webhook secrets-consumer-webhook --wait

REMARQUE : --wait est nécessaire en raison de problèmes de timing avec Helm ; voir ce ticket.

À propos des clusters privés GKE

Lorsque Google configure le control plane des clusters privés, il met automatiquement en place un peering VPC entre le réseau de votre cluster Kubernetes et un projet géré par Google séparé.

Les règles générées automatiquement n'ouvrent que les ports 10250 et 443 entre les masters et les nodes. Pour utiliser le composant webhook avec un cluster privé GKE, vous devez donc configurer une règle de pare-feu supplémentaire afin d'autoriser le CIDR de vos masters à accéder à votre Pod webhook via le port 8443.

Pour en savoir plus sur l'ajout de règles de pare-feu pour les nodes du control plane GKE, consultez la documentation GKE.

Secrets explicites ou non explicites (tout récupérer)

Vous pouvez choisir les secrets à exposer à votre processus, ou récupérer tous les secrets pour un chemin/nom donné.

Pour sélectionner explicitement des secrets depuis le gestionnaire, ajoutez une variable d'environnement à votre Pod selon la convention suivante :

env:
- name: <variable name to export>
value: vault:<vault key name from secret>

Configurer l'authentification du backend Kubernetes avec Vault

Vault peut s'authentifier auprès de Kubernetes via un service account Kubernetes.

Cela passe par un autre service account nommé vault-reviewer doté de la permission auth-delegator, qui lui permet de transmettre le token d'un autre service account pour s'authentifier auprès du master Kubernetes.

Une fois l'authentification réussie, Vault retourne un client token utilisable pour se connecter à Vault. Vault vérifie alors la correspondance entre un rôle Vault, un service account, un namespace et la policy pour autoriser ou refuser l'accès.

Ce token de service account vault-reviewer sera configuré à l'intérieur de Vault via la CLI Vault.

Créons le service account dédié à vault-reviewer.

https://gist.github.com/innovia/5435f2336e4dd0045dbb5842880b3334

Lien vers le gist original

À noter : si vous avez installé Vault dans un autre namespace, pensez à mettre à jour ce fichier en conséquence.

kubectl apply -f vault-reviewer.yaml

Activez le backend d'authentification Kubernetes :

$ vault login
Token (will be hidden):
$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

Configurer Vault avec le token et le CA de Vault-reviewer :

À noter : si vous avez installé Vault dans un autre namespace, ajoutez le flag -n après chaque commande kubectl.

$ VAULT_SA_TOKEN_NAME=$(kubectl get sa vault-reviewer -o jsonpath="{.secrets[*]['name']}")
$ SA_JWT_TOKEN=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data.token}" | base64 --decode; echo)
$ SA_CA_CRT=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)
$ vault write auth/kubernetes/config token_reviewer_jwt="$SA_JWT_TOKEN" kubernetes_host=https://kubernetes.default kubernetes_ca_cert="$SA_CA_CRT"
Success! Data written to: auth/kubernetes/config

Exemple de mappage d'un rôle vers un service account et un namespace dans Vault

vault write auth/kubernetes/role/tester
bound_service_account_names=tester
bound_service_account_namespaces=default
policies=test_policy
ttl=1h

Configurer l'authentification du backend GCP avec Vault

Activez la méthode d'authentification Google Cloud :

$ vault auth enable gcp

Configurez les identifiants de la méthode d'authentification :

$ vault write auth/gcp/config \ credentials=@/path/to/credentials.json

Si vous utilisez des identifiants d'instance ou souhaitez les spécifier via une variable d'environnement, vous pouvez sauter cette étape.

Créez un rôle nommé :

Pour un rôle de type iam :

$ vault write auth/gcp/role/my-iam-role \
type="iam" \
policies="dev,prod" \
bound_service_accounts="[email protected]"

Pour un rôle de type gce :

$ vault write auth/gcp/role/my-gce-role \
type="gce" \
policies="dev,prod" \
bound_projects="my-project1,my-project2" \
bound_zones="us-east1-b" \
bound_labels="foo:bar,zip:zap" \
bound_service_accounts="[email protected]"

Permissions GCP requises

Permissions du serveur Vault

Pour les rôles Vault de type iam, on peut attribuer à Vault les rôles suivants :

roles/iam.serviceAccountKeyAdmin

Pour les rôles Vault de type gce, on peut attribuer à Vault les rôles suivants :

roles/compute.viewer

Si vous préférez créer un rôle personnalisé n'incluant que les permissions GCP strictement nécessaires, utilisez la liste suivante :

iam.serviceAccounts.get
iam.serviceAccountKeys.get
compute.instances.get
compute.instanceGroups.list

Permissions pour s'authentifier auprès de Vault

À noter : les permissions précitées sont attribuées aux serveurs Vault. Le service account IAM ou l'instance GCE qui s'authentifie auprès de Vault doit disposer du rôle suivant :

roles/iam.serviceAccountTokenCreator

n'hésitez pas à consulter le code source :

https://github.com/doitintl/secrets-consumer-webhook.git