Comprendre les secrets Kubernetes
Met en évidence certaines connaissances essentielles sur Kubernetes qui nous permettront de nous sentir à l’aise lors de la gestion d’objets secrets pour les charges de travail de nos applications.
Vue d'ensemble
- L'objet « secret » de Kubernetes peut être utilisé pour stocker des informations sensibles
- Les informations sensibles pourraient ensuite être transférées en toute sécurité vers des pods ou directement utilisées par d'autres ressources Kubernetes
- Les informations sensibles à l'intérieur de l'objet « secret » sont par défaut « codées en base64 »
- Les données stockées dans des ressources secrètes ne sont jamais écrites sur le disque. Elles résident dans la RAM et sont chiffrées lors du transfert entre les nœuds du cluster.
- Pour d'autres informations et précautions, voir Les secrets Kubernetes
Types de secrets
Demander de l'aide pour la création d'un objet « secret » indique 3 types de secrets :
$ kubectl create secret -h
Create a secret using specified subcommand.
Available Commands:
docker-registry Create a secret for use with a Docker registry
generic Create a secret from a local file, directory, or literal
value
tls Create a TLS secret
- Nous utilisons le secret de type « docker-registry » pour créer des secrets permettant de s'authentifier auprès de registres de conteneurs de différents fournisseurs, pas seulement de Docker. Un cas d'utilisation typique de ce type de secret est l'extraction/le transfert d'images privées de conteneurs depuis/vers des registres de conteneurs. Pour créer un secret de ce type, vous avez besoin de :
- l'adresse du registre de conteneurs
- le nom de l'utilisateur qui s'authentifie
- le mot de passe associé à l'utilisateur qui s'authentifie
- Le type de secret « generic » sera utilisé pour stocker des données sous forme de « clé : valeur ». Les données peuvent ensuite être récupérées depuis les pods sous forme de fichiers ou de variables d'environnement
- Le type de secret « tls » sera utilisé pour stocker les certificats et les clés TLS. Le certificat et les données de clé privée associées doivent être encodés au format « PEM ». Cela pourrait être utilisé par exemple avec des contrôleurs d'ingress (ingress controllers) pour rendre nos applications accessibles en HTTPS
Créer des secrets
Docker-registry
Voici la commande de création :
$ kubectl create secret docker-registry my-docker-registry-secret --docker-server=my-registry.example.com --docker-username=myuser --docker-password=mypassword
Jetons un œil au secret créé :
$ kubectl get secret my-docker-registry-secret
NAME TYPE DATA AGE
my-docker-registry-secret kubernetes.io/dockerconfigjson 1 12s
$ kubectl get secret my-docker-registry-secret -o yaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJteS1yZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6Im15dXNlciIsInBhc3N3b3JkIjoibXlwYXNzd29yZCIsImF1dGgiOiJiWGwxYzJWeU9tMTVjR0Z6YzNkdmNtUT0ifX19
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-docker-registry-secret
namespace: juiceshop
resourceVersion: "546844"
uid: fd824b18-aacb-4341-a056-c3beb24b1162
type: kubernetes.io/dockerconfigjson
Voyons la valeur décodée « base64 » de la clé de données « .dockerconfigjson » :
$ echo "eyJhdXRocyI6eyJteS1yZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6Im15dXNlciIsInBhc3N3b3JkIjoibXlwYXNzd29yZCIsImF1dGgiOiJiWGwxYzJWeU9tMTVjR0Z6YzNkdmNtUT0ifX19" | base64 -d | jq
{
"auths": {
"my-registry.example.com": {
"username": "myuser",
"password": "mypassword",
"auth": "bXl1c2VyOm15cGFzc3dvcmQ="
}
}
}
Le cas du registre de conteneurs de Google
Pour Google Container Registry, nous ne pouvons pas générer un nom d'utilisateur/mot de passe pour l'authentification. À la place, nous devons créer un « compte de service » Google et donner à ce « compte de service » l'autorisation de lire les données de GCS (Google Cloud Storage).
Nous pouvons ensuite utiliser un jeton d'authentification du « compte de service » pour créer le secret « docker-registry ». En supposant que le fichier « ~/gcr-image-pull-token.json » contienne le jeton d'authentification du « compte de service », nous utiliserons les valeurs suivantes pour le nom d'utilisateur et le mot de passe du registre dans la commande de création du secret « docker-registry » :
--docker-username=_json_key--docker-password="$(cat ~/gcr-image-pull-token.json)"
TLS
Voici la commande de création :
kubectl create secret tls my-tls-secret --key=tls-key.pem --cert=tls-cert.pem
Voici une sortie tronquée du contenu du certificat TLS et des fichiers de clé privée que nous avons utilisés :
tls-cert.pem
-----BEGIN CERTIFICATE-----
MIIDzDCCArSgAwIBAgIUOiPOFvdtyD/wOCrgaoGNsV6CuIEwDQYJKoZIhvcNAQEL
BQAwYTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
(...)
jJBYxC4Hi6ZNnb2/UUliumVmmQZvLLcyNK+rcbGGPtP0wB+QoXhDUrKAsrM/McTK
f1IAy/XYLdk9UUcLxpN2Iw==
-----END CERTIFICATE-----
tls-key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2OmXD24qOvorrGqB3GOdeNdHF1v6QIb3CSA4tNfqH3fqQ22F
iEsXHt8s2dfHlSqwZ4J5bwwzjp9eXqwwNRPDwWIaFzesWh1qsWxJIvh9Z2b/9TUD
(...)
5C9jvJzMZRAqQ2dWd0yO00a6kf/Uyi/dzVpx96iwf0nwj0R0dEndudHkH0awBAwi
JZvsLB7gYCceCXs+S/OROtFh0322pBTHwmBTSyUARRF2VOfC3bQj
-----END RSA PRIVATE KEY-----
Regardons maintenant le secret créé :
$ kubectl get secret my-tls-secret
NAME TYPE DATA AGE
my-tls-secret kubernetes.io/tls 2 12m
$ kubectl get secret my-tls-secret -o yaml
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR6RENDQXJTZ0F3SUJBZ0lVT2lQT0Z2ZHR5RC93T0
(...truncated)
hoRFVyS0Fzck0vTWNUSwpmMUlBeS9YWUxkazlVVWNMeHBOMkl3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMk9tWEQyNHFPdm9yck
(...truncated)
Z1lDY2VDWHMrUy9PUk90RmgwMzIycEJUSHdtQlRTeVVBUlJGMlZPZkMzYlFqCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-tls-secret
namespace: juiceshop
resourceVersion: "651996"
uid: b5913af1-5fab-45b2-be2d-252b7a829ea7
type: kubernetes.io/tls
Générique (generic)
La commande « kubectl create secret generic -h » montre des exemples de création de secrets génériques. Essayons-en quelques-uns et voyons le résultat.
- Créez un nouveau secret nommé « my-secret » avec « key1=supersecret » et « key2=topsecret »
$ kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
key1: c3VwZXJzZWNyZXQ=
key2: dG9wc2VjcmV0
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "655183"
uid: 4bec3a1f-f7b9-4d5f-bd5e-f218e94a218f
type: Opaque
- Créez un nouveau secret nommé « my-secret » en utilisant une combinaison d'un fichier et d'un littéral
$ cat bar/db_password
superpass
$ kubectl create secret generic my-secret --from-file=bar/db_password --from-literal=passphrase=topsecret
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
db_password: c3VwZXJwYXNzCg==
passphrase: dG9wc2VjcmV0
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "655590"
uid: 5fe8546b-489c-4986-a711-69a4155e7f11
type: Opaque
- Créez un nouveau secret nommé « my-secret » à partir d'un fichier, avec un nom de clé spécifique. Dans l'exemple précédent, nous allons définir le nom de clé « database_password » au lieu du nom par défaut « db_password » du fichier.
$ cat bar/db_password
superpass
$ kubectl create secret generic my-secret --from-file=database_password=bar/db_password
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
database_password: c3VwZXJwYXNzCg==
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "655654"
uid: 362c9267-7e35-4101-a659-ae7e56c0e420
type: Opaque
- Créez un nouveau secret nommé « my-secret » à partir d'un fichier contenant des lignes avec des paires « clé = valeur »
$ cat app.env
db_user=user
db_password=superpass
$ kubectl create secret generic my-secret --from-env-file=app.env
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
db_password: c3VwZXJwYXNz
db_user: dXNlcg==
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "653254"
uid: 0297bafd-fffa-4af2-adbf-97cd3147de1f
type: Opaque
- Créez un nouveau secret nommé « my-secret » avec des clés extraites de chaque nom de fichier se trouvant dans le dossier bar et des valeurs provenant du contenu des fichiers associés
$ ls bar/
db_password db_user
$ cat bar/db_user
user
$ cat bar/db_password
superpass
$ kubectl create secret generic my-secret --from-file=bar/
secret/my-secret created
$ kubectl get secret my-secret -o yaml
apiVersion: v1
data:
db_password: c3VwZXJwYXNzCg==
db_user: dXNlcgo=
kind: Secret
metadata:
creationTimestamp: "****-**-**T**:**:**Z"
name: my-secret
namespace: juiceshop
resourceVersion: "654889"
uid: ea7d7829-240c-4cbe-a3ec-9bb6750377ea
type: Opaque
Utilisation des secrets
Depuis les pods en tant que fichiers
Voici comment nous rendons les secrets Kubernetes existants disponibles à l'intérieur des pods sous forme de fichiers :
- Dans nos manifestes Kubernetes utilisés pour créer des pods, nous devons déclarer un « volume » dans la section « spec » des pods, qui sera alimenté avec un ou plusieurs de nos secrets existants
- Selon la documentation de Kubernetes, les volumes contenant des secrets utilisent tmpfs (un système de fichiers utilisant la RAM) afin qu'ils ne soient jamais écrits sur un stockage non volatil
- Nous devons ensuite déclarer un « volumeMounts » dans la section « spec.containers » des pods, afin de monter les secrets sous forme de fichiers dans des chemins spécifiques au sein des conteneurs des pods. Voici un exemple :
apiVersion: apps/v1
kind: Deployment
(...)
spec:
(...)
template:
(...)
spec:
containers:
- name: db
image: mariadb
env:
(...)
- name: MYSQL_PASSWORD_FILE
value: "/run/secrets/mysql/user_password"
- name: MYSQL_ROOT_PASSWORD_FILE
value: "/run/secrets/mysql/root_password"
volumeMounts:
(...)
- name: secret-volume
mountPath: /run/secrets/mysql
(...)
volumes:
(...)
- name: secret-volume
secret:
secretName: mysql-secret
- Les données du secret Kubernetes existant nommé « mysql-secret » seront mises à la disposition des pods dans le répertoire « /run/secrets/mysql »
- Toutes les clés de données du secret « mysql-secret » seront les noms des fichiers à l'intérieur du répertoire « /run/secrets/mysql » et les valeurs de clé associées au contenu des fichiers
Voici un exemple. Les paires clé/valeur suivantes proviennent du secret « mysql-secret » (décodage en base64) :
user_password: O3hndfn!dSZu
root_password: kHj483iYh?d2
seront disponibles à l'intérieur des conteneurs des pods sous forme des fichiers suivants :
/run/secrets/mysql/user_passwordcomprenantO3hndfn!dSZu/run/secrets/mysql/root_passwordcomprenantkHj483iYh?d2
Nous pouvons également spécifier un chemin d'accès spécifique au fichier à partir duquel nous souhaitons que des données secrètes spécifiques soient disponibles. Ce chemin est relatif au chemin spécifié dans le champ 'mountPath' pour le volume du secret ('/run/secrets/mysql' dans ce cas).
Voici un exemple. Si nous avions voulu rendre le secret 'user_password' disponible à partir de '/run/secrets/mysql/other_dir/other_user_password_filename' au lieu de '/run/secrets/mysql/user_password', nous aurions utilisé :
(...)
volumeMounts:
- name: secret-volume
mountPath: /run/secrets/mysql
(...)
volumes:
- name: secret-volume
secret:
secretName: mysql-secret
items:
- key: user_password
path: other_dir/other_user_password_filename
Depuis les pods en tant que variables d'environnement
- Le secret générique Kubernetes nommé « mysecret » existe et a été créé avec l'option « --from-env-file » de la commande « kubectl create secret generic », à partir du contenu du fichier suivant :
username=myusername
password=mypassword
- Le manifeste de ressources Kubernetes « Déploiement » suivant rendra les données « nom d'utilisateur » et « mot de passe » de « mysecret » disponibles pour les conteneurs de pod en tant que variables d'environnement :
apiVersion: apps/v1
kind: Deployment
(...)
spec:
(...)
template:
(...)
spec:
containers:
- name: my-container-name
image: my-container-image
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
- Les variables d'environnement créées à l'intérieur des conteneurs des pod sont :
USERNAME=myusername
PASSWORD=mypassword
Pull des images de conteneurs privés
Utilisez simplement « spec.imagePullSecrets » pour spécifier le nom du secret « docker-registry » à utiliser pour l'authentification lors du pull d'images de conteneurs privés définies dans les fichiers manifestes Kubernetes. Voici un exemple :
apiVersion: apps/v1
kind: Deployment
(...)
spec:
(...)
template:
(...)
spec:
imagePullSecrets:
- name: my-docker-registry-secret
containers:
- name: my-container-name
image: myregistry.example.com/my-private-container-image
(...)