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.

Comprendre les secrets Kubernetes

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_password comprenant O3hndfn!dSZu
  • /run/secrets/mysql/root_password comprenant kHj483iYh?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
        (...)