Tutoriel: ajouter un "user" dans Kubernetes avec CFSSL

Dans cet article nous verrons comment ajouter un “user” dans Kubernetes. Pour cela nous allons utiliser le service managé de Google Cloud Platform, mais il est possible de faire ces commandes dans tous les clusters.

Ce tutoriel a été réalisé sur la version “v1.11.2” de Kubernetes.

A la fin de ce tutoriel, vous saurez créer un utilisateur dans Kubernetes et lui attribuer des droits spécifiques.

Gestion des identités dans Kubernetes

Il existe deux types d’identités dans le Role Based Access Control (RBAC) de Kubernetes.

L’identité “ServiceAccount” est une identité pouvant être prise par un Pod au sein du cluster ou être configurée comme un utilisateur technique permettant une connexion. Le ServiceAccount est un objet Kubernetes, il peut donc être créé via un YAML.

L’identité “User” est une configuration créée à l’extérieur du cluster qui doit être uniquement validée par le cluster. C’est un objet externe qui ne peut pas être créé directement dans le cluster.

Ces identités sont ensuite liées à un Role pour attribuer des droits au sein d’un namespace ou d’un ClusterRole pour des droits sur tout ou partie des namespaces du cluster. Pour cela, il faudra utiliser un RoleBinding ou un ClusterRoleBinding.

Le Role ou le ClusterRole sont des objets permettant de lister des règles. Chaque règle est appliquée à une liste d’ApiGroup, à des ressources listées si nécessaire et auxquelles s’appliquent des verbes de changement d’état.

Ci-dessous un exemple de ClusterRole pour des droits “administrateurs”.

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
 name: local-admin
rules:
- apiGroups:
 - '*'
 resources:
 - '*'
 verbs:
 - '*'
- nonResourceURLs:
 - '*'
 verbs:
 - '*'

Puis le ClusterRoleBinding qui lie le ClusterRole à une identité - ici un ServiceAccount. Il est possible de lier un ClusterRole à un seul namespace en utilisant un RoleBinding :

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
 name: local-admin
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: ClusterRole
 name: local-admin
subjects:
- kind: ServiceAccount
 name: local-admin
 namespace: default

On retrouve la même syntaxe pour lier le ClusterRole à un User :

...
subjects:
- apiGroup: rbac.authorization.k8s.io
 kind: User
 name: local-admin

Enfin, il est également possible de lier un Role ou un ClusterRole à un groupe d’utilisateurs si la méthode de création de l’utilisateur le permet :

...
subjects:
- kind: Group
 name: admin
 apiGroup: rbac.authorization.k8s.io

Creation d’un ServiceAccount

La création d’un ServiceAccount est aussi simple qu’un YAML :

apiVersion: v1
kind: ServiceAccount
metadata:
 name: local-admin

Kubernetes automatise la création d’un Secret pour la gestion du token de connexion :

$ kubectl get secret
NAME                                 TYPE                                                 
default-token-xqbzn            kubernetes.io/service-account-token        local-admin-token-xclwg     kubernetes.io/service-account-token

Pour récupérer les certificats :

$ kubectl get secret local-admin-token-xclwg -o yaml
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
data:
  ca.crt: L...K
  namespace: ZGVmYXVsdA==
  token: Z...3
metadata:
  annotations:
    kubernetes.io/service-account.name: local-admin
  creationTimestamp: 2018-12-12T17:58:28Z
  name: local-admin-token-xclwg
  namespace: default

Et pour un user ?

Un User est un objet extérieur à Kubernetes, il est possible de lui créer des Users selon trois méthodes.

Via OpenID Connect : c’est le protocole standard pour lier le référentiel d’identité de l’entreprise à une application. Cette méthode vous permettra de ne pas dépendre d’un nouveau référentiel. Il est possible de lier votre LDAP traditionnel à ce protocole via un outil comme Keycloak.

Via une configuration de l’api-server et un fichier CSV : il existe deux configurations via “--token-auth-file=SOMEFILE” et “--basic-auth-file=SOMEFILE”
Dans les deux cas vous devez définir une liste d’utilisateurs liée à vos masters. Cette solution n’est pas compatible avec un changement régulier d’utilisateurs.

Via des certificats X509: c‘est cette solution que nous détaillons dans ce tutoriel.
Les étapes que nous verrons plus bas sont :

  • création d’un cluster avec Terraform et récupération des certificats
  • création d’une demande de signature avec CFSSL
  • signature des certificats avec Kubernetes

kubeconfig

Ce fichier est une configuration locale permettant une connexion à un ou plusieurs clusters Kubernetes. Il est utilisé par des outils comme kubectl qui permet la gestion d’un cluster à distance ou encore par Helm.

Par défaut on trouve ce fichier dans “~/.kube/config” où il est possible de surcharger la configuration en utilisant la variable d’environnement “KUBECONFIG”.

Dans ce fichier on retrouve 3 grandes parties : la configuration des clusters distants “clusters”, les configurations de l’authentification “users” et la création d’un contexte “contexts”.

Enfin il faut renseigner la clef “current-context” avec le contexte créé pour le sélectionner.

apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
    certificate-authority-data: L...=
    server: https://mesmasters
  name: mon-kubernetes
users:
- name: mon-user
  user: mon-user
contexts:
- context:
    cluster: mon-kubernetes
    user: mon-user
  name: mon-contexte
current-context: mon-contexte

Création des certificats

Dans ce chapitre nous verrons comment créer et faire valider les certificats pour identifier un utilisateur.

Les fichiers de certificats sont stockés avec différentes extensions, dans ce tutoriel vous trouverez des .PEM ou .CRT.

Un certificat est la preuve d’une identité numérique. Il est validé par une autorité de certification et contient les informations permettant de vérifier qu’un utilisateur est bien celui qu’il prétend être.

Vous trouverez également des fichiers CSR pour “CertificateSigningRequest”. C’est une normalisation permettant de demander un certificat à une autorité, ici Kubernetes.

Extraction des certificats depuis GKE

Nous utilisons Terraform, de la société HashiCorp pour créer notre cluster dans GCP via GKE.

Lors de la création du cluster par Terraform, l’utilisation de chaînes de caractères vides pour les clefs username et password permet de désactiver l’authentification de base.
Le block “client_certificate_config” permet d’activer l’utilisation de certificats clients.

resource "google_container_cluster" "training-cluster" {
...
 min_master_version = "1.11.2-gke.18"
 node_version       = "1.11.2-gke.18"

...
 master_auth {
   username = ""
   password = ""

   client_certificate_config {
     issue_client_certificate = true
   }
 }
}

Pour la suite des opérations, nous devons récupérer le certificat pour la création des clients du cluster et la clef associée au certificat mais également le certificat d’authentification du cluster me permettant d’identifier celui-ci dans ma configuration kubeconfig.

resource "local_file" "client_certificate" {
 content  = "${google_container_cluster.training-cluster.master_auth.0.client_certificate}"
 filename = "${path.cwd}/client.crt"
}

resource "local_file" "client_key" {
 content  = "${google_container_cluster.training-cluster.master_auth.0.client_key"
 filename = "${path.cwd}/client.key"
}

resource "local_file" "cluster_ca_certificate" {
 content  = "${google_container_cluster.training-cluster.master_auth.0.cluster_ca_certificate}"
 filename = "${path.cwd}/ca.crt"}

Toujours dans l’optique de génération de notre kubeconfig, nous déclarons un output permettant la récupération du endpoint - c’est à dire l’adresse de connexion vers nos masters.

output "cluster-endpoint" {
 value = "${google_container_cluster.training-cluster.endpoint}"
}

Enfin, on décode les certificats qui sont téléchargés en base64 par défaut par Terraform.

cat client.crt | base64 --decode > client.crt
cat client.key | base64 --decode > client.key
cat ca.crt | base64 --decode > ca.crt

Création d’une demande de certificat avec CFSSL

L’objectif de ce chapitre est d’automatiser la création d’un CSR.

CFSSL késako ?

CFSSL pour CloudFlare SSL. Cet outil permet de simplifier la création de certificats via une configuration en JSON.

CFSSLJSON est un outil permettant de générer des fichiers séparés à partir de la sortie de CFSSL.

Vous pouvez télécharger ces deux outils sur ce site: https://pkg.cfssl.org.

Procédure

La procédure commence par la création d’une configuration “standard” pour expliciter le CA de Kubernetes.

{
   "signing": {
     "default": {
       "expiry": "8760h"
     },
     "profiles": {
       "kubernetes": {
         "usages": [
           "signing",
           "key encipherment",
           "server auth",
           "client auth"
         ],
         "expiry": "8760h"
       }
     }
   }
 }

Puis je configure ma demande de certificat via un CSR. Ici je crée mon User local-admin et je l’ajoute dans la liste des administrateurs de Kubernetes dans le groupe “system:masters”.

{
 "CN": "local-admin",
 "hosts": [""],
 "key": {
   "algo": "rsa",
   "size": 2048
 },
 "names": [
   {
     "C": "France",
     "L": "Paris",
     "O": "system:masters",
     "OU": "Kubernetes-Advanced",
     "ST": "Oregon"
   }
 ]
}

Pour lancer la création de mon CSR j’utilise CFSSL avec la syntaxe suivante :

cfssl gencert \
 -ca=client.crt \
 -ca-key=client.key \
 -config=ca-config.json \
 -profile=kubernetes \
 admin-csr.json | cfssljson -bare local-admin

Ce qui me génère les fichiers suivants. Je retrouve bien un certificat, non validé par Kubernetes pour l’instant, avec la clef et la demande de signature du certificat.

local-admin-key.pem
local-admin.csr
local-admin.pem

Pour vérifier la bonne création de ce certificat nous pouvons utiliser OpenSSL.

$ openssl x509 -in local-admin.pem -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 6e:..:08
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=client
        Validity
            Not Before: Dec 13 10:01:00 2018 GMT
            Not After : Dec 13 10:01:00 2019 GMT
        Subject: C=France, ST=Oregon, L=Paris, O=system:masters, OU=Kubernetes-Advanced, CN=local-admin
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:...:95
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                8C:...:1D
            X509v3 Subject Alternative Name:
                DNS:
    Signature Algorithm: sha256WithRSAEncryption
         65:..:fb

Approbation par Kubernetes

Il faut maintenant que Kubernetes approuve ce certificat. Pour cela, j’utilise un objet Kubernetes dans lequel je copie le CSR créé avec CFSSL.

cat <<EOF | kubectl create -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
 name: local-admin-csr
spec:
 groups:
 - system:masters
 request: $(cat local-admin.csr| base64 | tr -d '\n')
 usages:
 - digital signature
 - key encipherment
 - server auth
EOF

Je constate ensuite avec la commande ci-dessous que mon certificat est en attente d’approbation.

$ kubectl describe csr local-admin-csr
Name:               local-admin-csr
CreationTimestamp:  Thu, 13 Dec 2018 11:20:23 +0100
Requesting User:    sebastien.lavayssiere@wescale.fr
Status:             Pending
Subject:
  Common Name:          local-admin
  Organization:         system:masters
  Organizational Unit:  Kubernetes-Advanced
...

En utilisant un User administrateur je peux approuver cette demande et récupérer le certificat.

kubectl certificate approve local-admin-csr
kubectl get csr local-admin-csr -o jsonpath='{.status.certificate}' \
   | base64 --decode > local-admin.crt

Pour visualiser le certificat :

$ openssl x509 -in local-admin.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            c1:...:36
    Signature Algorithm: sha256WithRSAEncryption
        **Issuer: CN=ace6c217-e651-4d8c-a0b9-c13d8e751969**
        Validity
            Not Before: Dec 13 10:25:13 2018 GMT
            Not After : Dec 12 10:25:13 2023 GMT
        Subject: C=France, ST=Oregon, L=Paris, O=system:masters, OU=Kubernetes-Advanced, CN=local-admin
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:...:95
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Alternative Name:
                DNS:
    Signature Algorithm: sha256WithRSAEncryption
         00:...:18

Génération du fichier de Config

Pour commencer, je récupère le endpoint du master via l’output Terraform défini dans mon IaC.

ENDPOINT=$(terraform output cluster-endpoint)

Il faut ensuite créer le cluster dans un nouveau fichier Config via l’outil kubectl.

kubectl config set-cluster training-cluster-0 --server="https://${ENDPOINT}" --certificate-authority="ca.crt" --embed-certs=true --kubeconfig="test-kubecfg"    

Puis j’ajoute le certificat récupéré via Kubernetes et la clef que j’ai généré avec CFSSL.

kubectl config set-credentials local-admin --client-certificate="local-admin.crt"  --client-key="local-admin-key.pem" --embed-certs=true --kubeconfig="test-kubecfg"

Je crée un nouveau context qui lie mon cluster et mon utilisateur. Ici, je configure également le namespace par défaut.

kubectl config set-context local-admin --cluster=training-cluster-0 --namespace=default --user=local-admin --kubeconfig="test-kubecfg"

Enfin, j’indique utiliser le nouveau contexte comme contexte par défaut.

kubectl config use-context local-admin  --kubeconfig="test-kubecfg"

Test

Pour tester ce nouveau fichier, je peux lister l’ensemble des Pods de mon cluster ou faire n’importe quelle commande administrateur.

KUBECONFIG="test-kubecfg" kubectl get pods --all-namespaces

Conclusion

Durant ce tutoriel, nous avons pu créer un User avec les droits administrateurs dans un Kubernetes installé sur GKE.

Il vous suffira de changer le groupe de l’utilisateur dans le CSR configuré par CFSSL ou le Role associé à cet utilisateur dans le RBAC pour créer des Users avec des droits particuliers.

Cette méthode est simple une fois automatisée et vous permettra de gérer vos utilisateurs facilement sans recourir à une configuration particulière de votre api-server.