Créer un certificat auto-signé LDAPS pour Pinniped

Afin de simplifier l’authentification des clusters Kubernetes fonctionnant sur différents clouds, VMware a développé le projet Pinniped accessible en Opensource. Pinniped a été intégré par défaut dans l’offre VMware Tanzu Kubernetes Grid (TKG) depuis la version 1.3  en remplacement de l’extension Gangway. Pinniped permet l’authentification à partir de sources OIDC ou LDAP. Dans le cas de source LDAP, Pinniped ne se connecte pas directement à LDAP mais s’appuie pour le moment sur le composant Dex comme le faisait déjà Gangway.

Lorsqu’un utilisateur exécute une commande Kubernetes pour la première fois ou après une certaine période d’inactivité, il est invité à s’authentifier une seule fois avec son ses identifiants d’entreprise et peut ensuite consommer plusieurs cluster Kubernetes.

J’ai voulu tester cette fonctionnalité dans mon lab avec un serveur LDAPS/Active Directory sous Windows 2019 et je me suis vite confronté à l’éternel problème de certificat non signés par une autorité connue. Il fallait donc que je créé un certificat qui soit reconnu par le serveur Active Directory. En cherchant des heures sur Internet, j’ai fini par trouver un article (en Anglais) de Peter Mescalchin qui a fonctionné du premier coup : Enable LDAP over SSL (LDAPS) for Microsoft Active Directory servers. – bl.ocks.org.

Cependant, quand j’ai voulu utiliser cette procédure avec Pinniped, ça n’a pas fonctionné car les informations SAN (Subject Alternative Name) n’étaient pas présentes dans le certificat. En croisant plusieurs articles sur le sujet, j’ai pu adapter la solution de Peter Mescalchin afin que les certificats intègrent les informations SAN. Ca donne ceci :

Création du certificat Root

Via OpenSSL (j’ai utilisé un Linux Ubuntu) créer une clé privée (ca.key dans mon exemple) pour pouvoir ensuite créer le certificat root (ca.crt dans mon exemple). La première commande vous demandera un mot de passe et la seconde des renseignements sur votre organisation.

$ openssl genrsa -aes256 -out ca.key 4096
$ openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

 Importer le certificat Root sur le serveur AD

A partir du serveur AD, tapez la commande certlm ou via Control Pannel, tapez computer certificates dans la barre de recherche :

Attention à bien choisir “Manage computer certificates” et non “Manage user certificates »

Importez le ca.crt précédemment généré dans la partie « Trusted Root Certification Authorities\Certificates »

 

Création du certificat Client

Toujours à partir du serveur Active Directory, créer un fichier, dans notre exemple il porte le nom request.inf. En rouge, j’ai apporté les modifications par rapport à la procédure initiale afin d’y ajouter les informations SAN. Attention à bien mettre dans CN le FQDN du serveur AD.

Les valeurs de _continue_= « dns » et _continue_= « ip-address » correspondent aux valeurs SAN, les autres valeurs possibles pour référencer le serveur AD.

[Version]
Signature=”$Windows NT$”

[NewRequest]
Subject = “CN=ad-server.cpod-velocity.az-fkd.cloud-garage.net
KeySpec = 1
KeyLength = 2048
Exportable = TRUE
MachineKeySet = TRUE
SMIME = FALSE
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = “Microsoft RSA SChannel Cryptographic Provider”
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0

[EnhancedKeyUsageExtension]
OID = 1.3.6.1.5.5.7.3.1 ; Server Authentication

[Extensions]
; SANs can be included in the Extensions section by using the following text format. Note 2.5.29.17 is the OID for a SAN extension.
2.5.29.17 = “{text}”
_continue_ = “dns=ad-server&”
_continue_ = “dns=ad-server.cpod-velocity.az-fkd.cloud-garage.net&”
_continue_ = “dns=cloud-garage.net&”
_continue_ = “ipaddress=172.17.13.9&”

 

Générer le fichier client.csr avec la commande ci-dessous

c:\> certreq -new request.inf client.csr

A partir de la machine Linux :

Créer un fichier d’extension, dans notre exemple, il porte le nom de v3ext.txt. En rouge, j’ai apporté les modifications par rapport à la procédure initiale afin d’y ajouter les informations SAN sous la rubrique v3_ca qui sera référencé dans la prochaine commande.

keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectKeyIdentifier=hash

 # These extensions are added when ‘ca’ signs a request.
[ v3_ca ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = ad-server.cpod-velocity.az-fkd.cloud-garage.net
DNS.2 = ad-server
IP.1 = 172.17.13.9

Toujours à partir de la machine Linux, créer le certificat client.crt à partir des fichiers générés dans les étapes précédentes ca.crt, ca.key, client.csr et v3ext.txt en rouge ce qui a été rajouté par rapport à la commande issue de la procédure initiale

$ openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -extfile v3ext.txt -set_serial 01 -out client.crt -extensions v3_ca

 

Pour vérifier la présence des informations SAN

$ openssl x509 -in client.crt -text
 …..
X509v3 extensions:
      X509v3 Subject Alternative Name:
          DNS:ad-server.cpod-velocity.az-fkd.cloud-garage.net, DNS:ad-server, IP Address:172.17.13.9

 

Importer le certificat Client

A partir du serveur AD

C:\> certreq -accept client.crt

Le certificat devrait ainsi apparaître dans « Personal\Certificates »

Pour que le certificat soit pris en compte, il faut soit redémarrer le serveur AD ou forcer LDAPS à charger le certificat avec la procédure ci-dessous :

Toujours à partir du serveur AD, créer un fichier text, dans notre exemple il se nome ldap-renewservercert.txt avec le contenu ci-dessous (attention la fin du fichier comprend une ligne avec un – (un tiret) :

dn:
changetype: modify
add: renewServerCertificate
renewServerCertificate: 1

Puis tappez la commande ci-dessous :

c:\> ldifde -i -f ldap-renewservercert.txt

Pour tester la prise en compte

Utilisez l’utilitaire ldp.exe en sélectionnant le port 636 (ou un autre s’il est spécifique) et en cochant la case SSL.

Une fois toute la procédure effectuée, il faut récupérer le ca.crt généré à la première étape pour le donner à Pinniped. Cela peut se faire soit au moment de la création du cluster de management TKG ou soit à postériori.

Si le cluster de management n’a pas encore était créé :

$ tanzu management-cluster create –ui
(il y a deux tirets avant l’argument ui mais WordPress n’en n’affiche qu’un seul)

Dans mon test j’ai choisi vSphere comme plate-forme, à l’étape identity manager il faudra copier le certificat dans la partie ROOT CA

 

Si le cluster de management TKG a déjà était créé et que vous souhaitez le mettre à jour :

A partir du contexte Kubernetes du cluster de manager, chiffrez le certificat Root du serveur AD avec la commande base64 et récupérez le résultat :

$ base64 -w 0 ca.crt

Modifier le certificat dans la configmap dex par le résultat de la commande précédente :

$ kubectl edit configmap -n tanzu-system-auth dex
# Please edit the object below. Lines beginning with a ‘#’ will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  config.yaml: |
    issuer: https://172.17.13.10:30167
    frontend:
        theme: tkg
    web:
        https: 0.0.0.0:5556
        tlsCert: /etc/dex/tls/tls.crt
        tlsKey: /etc/dex/tls/tls.key
    expiry:
        signingKeys: 90m
        idTokens: 5m
    logger:
        level: info
        format: json
    staticClients:
        – id: pinniped-client-id
          name: pinniped-client-id
          redirectURIs:
            – https://172.17.13.10:31234/callback
          secret: 089db7e23b19cb628ba841b17cc32ea4
    connectors:
        – type: ldap
          id: ldap
          name: LDAP
          config:
            host: ad-server.cpod-velocity.az-fkd.cloud-garage.net:636
            insecureSkipVerify: false
bindDN: cn=administrator,cn=Users,dc=velocity,dc=local
            bindPW: $BIND_PW_ENV_VAR
            usernamePrompt: LDAP Username
            rootCAData:
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdQekNDQkNlZ0F3SUJBZ0lVU2tQd0JPazVYRVFLRlpydXdwZXBoeTlINndzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dhNHhDek
KQmdOVkJBWVRBa1pTTVF3d0NnWURWUVFJREFOSlJFWXhEakFNQmdOVkJBY01CVkJoY21segpNUlF3RWdZRFZRUUtEQXRNYjJWcGJDMWtkUzFUUlRFTE1Ba0dBMVVFQ3d3Q1UwVXhPRE
EyQmdOVkJBTU1MMkZrCkxYTmxjblpsY2k1amNHOWtMWFpsYkc5amFYUjVMbUY2TFdaclpDNWpiRzkxWkMxbllYSmhaMlV1Ym1WME1TUXcKSWdZSktvWklodmNOQVFrQkZoVm1ZbVZ1Y
21WcVpHRnNRSFp0ZDJGeVpTNWpiMjB3SGhjTk1qRXdOekEzTURjeQpNekl5V2hjTk16RXdOekExTURjeU16SXlXakNCcmpFTE1Ba0dBMVVFQmhNQ1JsSXhEREFLQmdOVkJBZ01BMGxFClJqRU9NQXdHQTFVRUJ3d0ZVR0Z5YVhNeEZEQVNCZ05WQkFvTUMweHZaV2xzTFdSMUxWTkZNUXN3Q1FZRFZRUUwKREFKVFJURTRNRFlHQTFVRUF3d3ZZV1F0YzJWeWRtVnlMbU53YjJ
RdGRtVnNiMk5wZEhrdVlYb3RabXRrTG1OcwpiM1ZrTFdkaGNtRm5aUzV1WlhReEpEQWlCZ2txaGtpRzl3MEJDUUVXRldaaVpXNXlaV3BrWVd4QWRtMTNZWEpsCkxtTnZiVENDQWlJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFMaWx0WE81WkxNOFRzZ0YKMXFxRFFURi9xV1EzTGkvalU2ZFJqM1VMLys2YitRL0VUVjdIb2VLMi9hK09UdHlRbzY4c
k9ySTVJRjNNWlJqKwpzU0JuWC9SejczczYvVjArWXhJTFozSmNNenlWUzZtR1ZhNTZBTmFZRFRqSkErUzF5enZJczFpZXdMWDR2YlFzCnJRdFE2NVphb3NYbFlMSWpxdzZCY01TZUlX
NUlUWitlUVF3emlkN2t5ZFBYNDdTBSSm1vR…..1
            ….

Relancer le pod dex dans le namespace tanzu-system-auth pour qu’il prenne en compte la modification.

 

Une fois le cluster de management avec le bon certificat

A partir de là, créer un cluster de workload :

$ tanzu cluster create my-cluster -f <fichier-environnement>

Importer le Kubeconfig d’administration du cluster de workload créé :

$ tanzu cluster kubeconfig get my-cluster –admin
(il y a deux tirets avant l’argument admin mais WordPress n’en n’affiche qu’un seul)

Se connecter au cluster de workload avec le context admin, en tant qu’admin pas besoin de compte :

$ kubectl use-context my-cluster-admin@my-cluster

Créer un cluster rôle binding avec le rôle qui vous intéresse (ici cluster-admin) pour les utilisateurs souhaités, ça permettra à l’utilisateur d’utiliser ce cluster une fois authentifié :

$ kubectl create clusterrolebinding admin-fbenrejdal  –clusterrole cluster-admin –user fbe@velocity.local
(il y a deux tirets avant les arguments clusterrole et user mais WordPress n’en n’affiche qu’un seul)

Exporter le kubeconfig du cluster de workload, c’est ce kubeconfig qu’il faudra transmettre aux utilisateurs, il n’a pas de context admin et demandera à l’utilisateur une authentification. L’utilisateur consommera ce cluster en fonction des droits définis dans clusterrolebinding de l’étape précédente :

$ tanzu cluster kubeconfig get my-cluster –export-file my-cluster-kubeconfig
(il y a deux tirets avant l’argument export mais WordPress n’en n’affiche qu’un seul)

Lancez une commande kubernetes avec le fichier kubeconfig généré, ce qui lancera le navigateur pour permettre l’authtentification :

$ kubectl get pods -A –kubeconfig my-cluster-kubeconfig
(il y a deux tirets avant l’argument kubeconfig mais WordPress n’en n’affiche qu’un seul)

Vous devriez être redirigé vers un navigateur avec une page web vous demandant votre nom d’utilisateur et votre mot de passe :

Une fois saisies, vous obtiendrez le résultat de votre dernière commande :

Le résultat de la commande précédemment passée devrait s’afficher :


NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system antrea-agent-q9xpg 2/2 Running 0 7d15h
kube-system antrea-agent-qlmj8 2/2 Running 0 7d15h
kube-system antrea-controller-6bb57bd84-6cj58 1/1 Running 0 7d15h
kube-system coredns-68d49685bd-bjcps 1/1 Running 0 7d15h
kube-system coredns-68d49685bd-vttdw 1/1 Running 0 7d15h
kube-system etcd-my-cluster-control-plane-48n9f 1/1 Running 0 7d15h
kube-system kube-apiserver-my-cluster-control-plane-48n9f 1/1 Running 0 7d15h
kube-system kube-controller-manager-my-cluster-control-plane-48n9f 1/1 Running 0 7d15h
kube-system kube-proxy-dntrc 1/1 Running 0 7d15h
kube-system kube-proxy-k5m9g 1/1 Running 0 7d15h
kube-system kube-scheduler-my-cluster-control-plane-48n9f 1/1 Running 0 7d15h
kube-system kube-vip-my-cluster-control-plane-48n9f 1/1 Running 0 7d15h
kube-system metrics-server-66cb4fb659-xlprc 1/1 Running 0 7d15h
kube-system vsphere-cloud-controller-manager-vmfwl 1/1 Running 1 7d15h
kube-system vsphere-csi-controller-bd8b6cc8c-8ljl8 6/6 Running 0 7d15h
kube-system vsphere-csi-node-6xqf5 3/3 Running 0 7d15h
kube-system vsphere-csi-node-vmbmq 3/3 Running 0 7d15h
pinniped-concierge pinniped-concierge-dcd587f97-lk9n5 1/1 Running 0 7d15h
pinniped-concierge pinniped-concierge-dcd587f97-zrnb7 1/1 Running 0 7d15h
pinniped-concierge pinniped-concierge-kube-cert-agent-8a8e3e38 1/1 Running 0 7d15h
pinniped-supervisor pinniped-post-deploy-job-4ldt7 0/1 Completed 0 7d15h
pinniped-supervisor pinniped-post-deploy-job-m74gz 0/1 Error 0 7d15h
tkg-system kapp-controller-69c4d4bbb4-kwk5l 1/1 Running 0 7d15h

Ajouter un self-signed certificat dans Kubernetes avec Containerd

Dans cet article (Déployer Harbor avec type loadBalancer) j’ai expliqué comment déployer Habor et utiliser le certificat self-signed pour que Docker puisse l’utiliser. Si vous utilisez Kubernetes avec docker, vous pouvez aussi suivre cette procédure sur chaque worker node. Sinon vous risquez d’avoir l’erreur suivante :

Unknown desc = failed to pull and unpack image “harbor.cpod-tkg.az-lab.shwrfr.com/memecached/hello-world:latest”: failed to resolve reference “harbor.cpod-tkg.az-lab.shwrfr.com/memecached/hello-world:latest”: failed to do request: Head https://harbor.cpod-tkg.az-lab.shwrfr.com/v2/memecached/hello-world/manifests/latest: x509: certificate signed by unknown authority
Warning Failed 12s (x2 over 24s) kubelet, tkg-utility-md-0-798c695db5-pjgsk Error: ErrImagePull

Si vous utilisez Kubernetes avec Containerd, la procédure est différente. Mon collègue Rob Hardt (https://gist.github.com/rhardt-pivotal/) a développé un script pour ça : https://gist.githubusercontent.com/rhardt-pivotal/4aa09ced6302194561936717262bb203/raw/623c707748925c969c525ade4bb432f95b61cff0/node-ca-updater-daemonset.yaml

Il faut néanmoins modifier les 3 champs en rouge :

apiVersion: v1
data:
  ca.pem: |+
    —–BEGIN CERTIFICATE—–
    Mettre votre certificat
    —–END CERTIFICATE—–

kind: ConfigMap
metadata:
 name: trusted-ca-cm
 namespace: default

   —-

apiVersion: v1
data:
    build-ca.sh: “#!/usr/bin/env bash \nset -euxo pipefail\ntdnf update \ntdnf install -y ca-certificates\ntdnf install -y openssl-c_rehash\necho \”$TRUSTED_CERT\” > /etc/ssl/certs/my-trusted-cert.pem\n/usr/bin/rehash_ca_certificates.sh\ncurl -vv https://<Votre URL HARBOR>\n”
kind: ConfigMap
metadata:
    name: rehash-script
    namespace: default
—   
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: trusted-ca-updater
  namespace: default
  labels:
    k8s-app: trusted-ca-updater
spec:
  selector:
    matchLabels:
      name: trusted-ca-updater
  template:
    metadata:
      labels:
        name: trusted-ca-updater
    spec:
      tolerations:
      # this toleration is to have the daemonset runnable on master nodes
      # remove it if your masters can’t run pods
      – key: node-role.kubernetes.io/master
        effect: NoSchedule
      initContainers:
      – name: script-runner
        image: photon:3.0
        command: [“/bin/sh”, “-c”, “/root/build-ca.sh” ]
        volumeMounts:
        – name: update-trusted-certs-script
          mountPath: /root/
        – name: certs-dir
          mountPath: /etc/ssl/certs
        – name: agg-certs-dir
          mountPath: /etc/pki/tls/certs/
        env:
        – name: TRUSTED_CERT
          valueFrom:
            configMapKeyRef:
              name: trusted-ca-cm
              key: ca.pem   
        resources:
            limits:
              ephemeral-storage: 30G # mettre une plus petite taille
      containers:
      – name: sleepy
        image: photon:3.0
        command: [“/bin/sh”]
        args: [“-c”, “while true; do sleep 3600;done”]
      volumes:
      – name: update-trusted-certs-script
        configMap:
            name: rehash-script
            defaultMode: 0766
      – name: certs-dir
        hostPath:
          path: /etc/ssl/certs
          type: Directory
      – name: agg-certs-dir
        hostPath:
          path: /etc/pki/tls/certs/
          type: Directory

 

Il faut ensuite se connecter sur les workernodes pour relancer containerd. Ci-dessous un exemple pour TKG (Une solution Kubernetes as a Service pour tous les Clouds)

# ssh capv@<ip-YourWokerNode>

capv@YourWokerNode$ sudo systemctl restart containerd