Sommaire
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.