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 clusterskv/k8s/global/<NAMESPACE>
: accès par les applications d'un namespace particulier sur tous les clusterskv/k8s/<CLUSTER_NAME>/all
: accès par toutes les applications sur un cluster en particulierkv/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é auPod
dans/vault/secrets/<unique_name>
(où<unique_name>
est déterminé à partir du suffixe de l'annotationvault.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
Où <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¶
-
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"]
-
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
-
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 }}
-
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"