Cryptons les certificats SSL avec cert-manager dans Kubernetes



Dans cet article, je vais parler de la façon d'automatiser la commande et le renouvellement des certificats de Let's Encrypt (et pas seulement) pour Ingress in Kubernetes à l'aide du gestionnaire de certificats complémentaire. Mais je vais commencer par une brève introduction à l'essence du problème.

Un peu de programme éducatif


Le protocole HTTP, développé au début des années 90 du siècle dernier, est entré si étroitement dans notre vie quotidienne qu’il est difficile d’imaginer au moins une journée sans l’utiliser. Cependant, en soi, il n'offre même pas un niveau de sécurité minimum lors de l'échange d'informations entre un utilisateur et un serveur Web. Le HTTPS («S» - sécurisé) vient à la rescousse: en utilisant le conditionnement des données transmises en SSL / TLS, ce protocole a fait ses preuves dans la protection des informations contre l'interception et est activement promu par l'industrie.

Par exemple, Google a adhéré à 2014Position "HTTPS partout" et diminue même la priorité des sites sans elle dans les résultats de recherche. Cette «propagande» ne contourne pas non plus les consommateurs ordinaires: les navigateurs modernes avertissent leurs utilisateurs de la présence et de l'exactitude des certificats SSL pour les sites visités.





Le coût d'un certificat pour un site personnel commence à partir de dizaines de dollars. L'acheter n'est pas toujours justifié et approprié. Heureusement, depuis fin 2015, une alternative gratuite sous forme de certificats Let's Encrypt (LE) est disponible . Ce projet à but non lucratif a été créé par des passionnés de Mozilla afin de couvrir la plupart des sites Web avec cryptage.

L'autorité de certification émet des certificats validés par domaine(le plus simple parmi ceux disponibles sur le marché) avec une période de validité de 90 jours, et depuis de nombreuses années, il est possible de délivrer le certificat dit générique pour plusieurs sous-domaines.

Pour obtenir le certificat, le site utilise les algorithmes décrits dans le protocole ACME ( Automated Certificate Management Environment ), créé spécifiquement pour Let's Encrypt. Lors de son utilisation, la confirmation de la propriété du domaine est effectuée par des demandes via le placement d'un code HTTP spécifique (appelé HTTP-01 ) ou l'installation d'enregistrements DNS (DNS-01) - plus d'informations à leur sujet seront données ci-dessous.

Responsable Certification


Cert-manager est un projet spécial pour Kubernetes, qui est un ensemble de CustomResourceDefinitions (d'où la limitation de la version minimale prise en charge de K8s - v1.12) pour la configuration CA (autorités de certification) et la commande directe de certificats. L'installation de CRD dans un cluster est triviale et revient à utiliser un seul fichier YAML:

kubectl create ns cert-manager
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.13.0/cert-manager.yaml

( Il y a aussi la possibilité d'installer à l'aide de Helm.)

Pour lancer la procédure de commande, les ressources des autorités de certification (CA) doivent être déclarées dans le cluster: Issuerou ClusterIssuer, qui sont utilisées pour signer le CSR (demandes d'émission de certificats). La différence entre la première ressource et la seconde est dans la portée:

  • Issuer peut être utilisé dans le même espace de noms,
  • ClusterIssuer est un objet cluster global.

Pratique avec cert-manager


N ° 1. Certificat auto-signé


Commençons par le cas le plus simple - commander un certificat auto-signé. Cette option est assez courante, par exemple, pour les environnements de test dynamiques pour les développeurs ou dans le cas de l'utilisation d'un équilibreur externe qui met fin au trafic SSL.

La ressource Issuerressemblera à ceci:

apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: selfsigned
spec:
  selfSigned: {}

Et pour émettre un certificat, il est nécessaire de décrire la ressource Certificate, où il est indiqué comment l'émettre (voir la section issuerRefci - dessous) et où se trouve la clé privée (champ secretName). Après cela, Ingress devra se référer à cette clé (voir la section tlsc spec):

---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: selfsigned-crt
spec:
  secretName: tls-secret
  issuerRef:
    kind: Issuer
    name: selfsigned
  commonName: "yet-another.website"
  dnsNames:
  - "yet-another.website"
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: app
spec:
  tls:
  - hosts:
    - "yet-another.website"
    secretName: tls-secret
  rules:
  - host: "yet-another.website"
    http:
      paths:
      - path: /
        backend:
          serviceName: app
          servicePort: 8080

Quelques secondes après avoir ajouté ces ressources au cluster, le certificat sera émis. Vous pouvez voir le rapport correspondant dans la sortie de la commande:

kubectl -n app describe  certificate selfsigned-crt
...
  Normal  GeneratedKey  5s    cert-manager  Generated a new private key
  Normal  Requested     5s    cert-manager  Created new CertificateRequest resource "selfsigned-crt-4198958557"
  Normal  Issued        5s    cert-manager  Certificate issued successfully

Si vous regardez la ressource secrète elle-même, elle contient:

  • clé privée tls.key,
  • certificat racine ca.crt,
  • Notre certificat auto-signé tls.crt.

Le contenu de ces fichiers peut être vu en utilisant l'utilitaire openssl, par exemple, comme ceci:

kubectl -n app get secret tls-secret -ojson | jq -r '.data."tls.crt"' | base64 -d | openssl x509 -dates -noout -issuer
notBefore=Feb 10 21:01:59 2020 GMT
notAfter=May 10 21:01:59 2020 GMT
issuer=O = cert-manager, CN = yet-another.website

Il convient de noter que, dans le cas général, le certificat émis à l'aide de celui- ci ne sera pas approuvé par lesIssuer clients connectés . La raison est simple: elle n'a pas de CA (voir la note sur le site du projet ) . Pour éviter cela, vous devez spécifier dans le chemin d'accès au fichier secret où il se trouve . Il peut s'agir d'une organisation d'autorité de certification d'entreprise - pour signer les certificats émis pour Ingress avec une clé déjà utilisée pour les besoins d'autres services de serveur / systèmes d'information.Certificateca.crt

N ° 2. Cryptons le certificat avec la validation HTTP


Pour émettre des certificats LE, comme mentionné précédemment, deux types de confirmation de propriété de domaine sont disponibles : HTTP-01 et DNS-01.

La première approche (HTTP-01) consiste à lancer un petit serveur Web avec un déploiement séparé, qui enverra à Internet via le lien <YOUR_DOMAIN > /. Bien connu / acme-challenge / <TOKEN> certaines données demandées au serveur de certification. Par conséquent, cette méthode implique la disponibilité d'Ingress de l'extérieur sur le port 80 et la résolution de l'enregistrement DNS du domaine en adresses IP publiques.

La deuxième option pour vérifier le certificat émis (DNS-01) provient ded'avoir une API pour le serveur DNS hébergeant les enregistrements de domaine. L'émetteur, à l'aide des jetons indiqués, crée des enregistrements TXT sur le domaine, que le serveur ACME reçoit ensuite lors de la confirmation. Parmi les fournisseurs DNS officiellement pris en charge figurent CloudFlare, AWS Route53, Google CloudDNS et d'autres, y compris sa propre implémentation ( acme-dns ).

Remarque : Let's Encrypt a des limites assez strictes sur les demandes aux serveurs ACME. Afin de ne pas entrer dans une longue interdiction, il est recommandé d'utiliser le type de certificat letsencrypt-staging pour le débogage (la différence ne concerne que le serveur ACME).

Nous décrivons donc les ressources:

apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory 
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - http01:
       ingress:
         class: nginx
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: le-crt
spec:
  secretName: tls-secret
  issuerRef:
    kind: Issuer
    name: letsencrypt
  commonName: yet-another.website
  dnsNames:
  - yet-another.website

Notez que l' adresse du serveur de transfert est spécifiée comme servery acme(c Issuer) . Le remplacer par une bataille sera possible plus tard. En appliquant cette configuration, nous suivons l'intégralité du chemin de commande:



  1. La création a Certificateengendré une nouvelle ressource CertificateRequest:

    kubectl -n app describe certificate le-crt
    ...
    Created new CertificateRequest resource "le-crt-1127528680"
  2. Dans sa description - une marque sur la création Order:

    kubectl -n app describe certificaterequests le-crt-1127528680
    Created Order resource app/le-crt-1127528680-1805948596
  3. Il Orderdécrit avec quels paramètres le test réussit et quel est son état actuel. Cette vérification est effectuée par la ressource Challenge:

    kubectl -n app describe order le-crt-1127528680-1805948596
    Created Challenge resource "le-crt-1127528680-1805948596-1231544594" for domain "yet-another.website"
  4. Enfin, les détails de cette ressource contiennent des informations sur l'état de l'analyse elle-même:

    kubectl -n app describe challenges le-crt-1127528680-1805948596-1231544594
    ...
      Reason:      Successfully authorized domain                                                                                                                                                                      
    ...
      Normal  Started         2m45s  cert-manager  Challenge scheduled for processing
      Normal  Presented       2m45s  cert-manager  Presented challenge using http-01 challenge mechanism
      Normal  DomainVerified  2m22s  cert-manager  Domain "yet-another.website" verified with "http-01" validation

Si toutes les conditions sont remplies (c'est-à-dire que le domaine est accessible de l'extérieur, il n'y a pas d'interdiction du LE ...) - en moins d'une minute, le certificat sera émis. En cas de succès, un describe certificate le-crtenregistrement apparaîtra dans la sortie Certificate issued successfully.

Vous pouvez maintenant modifier en toute sécurité l'adresse du serveur pour combattre ( https://acme-v02.api.letsencrypt.org/directory) et réorganiser les vrais certificats déjà signés non Fake LE Intermediate X1, mais Let's Encrypt Authority X3.

Pour ce faire, vous devez d'abord supprimer la ressource Certificate: sinon, aucune procédure de commande n'est activée, car le certificat existe déjà et il est pertinent. La suppression d'un secret entraînera son retour immédiat avec un message à describe certificate:

  Normal  PrivateKeyLost  44s                   cert-manager  Lost private key for CertificateRequest "le-crt-613810377", deleting old resource

Il reste à appliquer le manifeste «combat» à Issuercelui déjà décrit ci-dessus Certificate(il n'a pas changé):

apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - http01:
       ingress:
         class: nginx

Après avoir reçu le message Certificate issued successfully, describevérifiez-le:

kubectl -n app get secret tls-secret -ojson | jq -r '.data."tls.crt"' | base64 -d | openssl x509 -dates -noout -issuer
notBefore=Feb 10 21:11:48 2020 GMT
notAfter=May 10 21:11:48 2020 GMT
issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3

Numéro 3. Wildcard LE avec validation DNS


Nous compliquerons la tâche en écrivant un certificat immédiatement à tous les sous-domaines du site et en utilisant cette fois la vérification DNS (via CloudFlare).

Tout d'abord, récupérez le jeton dans le panneau de configuration CloudFlare pour travailler via l'API:

  1. Profil → Jetons d'API → Créer un jeton.
  2. Définissez les autorisations comme suit:
    • Autorisations:
      • Zone - DNS - Modifier
      • Zone - Zone - Lire
    • Ressources de zone:
      • Inclure - Toutes les zones
  3. Copiez le jeton reçu après l'enregistrement (par exemple :) y_JNkgQwkroIsflbbYqYmBooyspN6BskXZpsiH4M.

Créez un secret dans lequel ce jeton sera stocké et faites-y référence dans l'émetteur:

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token
type: Opaque
stringData:
  api-token: y_JNkgQwkroIsflbbYqYmBooyspN6BskXZpsiH4M
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory 
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - dns01:
        cloudflare:
          email: my-cloudflare-acc@example.com
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token

---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: le-crt
spec:
  secretName: tls-secret
  issuerRef:
    kind: Issuer
    name: letsencrypt
  commonName: yet-another.website
  dnsNames:
  - "yet-another.website"
  - "*.yet-another.website"

(N'oubliez pas d'utiliser la mise en scène si vous expérimentez!)

Passons en revue le processus de confirmation de la propriété du domaine:

kubectl -n app describe challenges.acme.cert-manager.io le-crt-613810377-1285319347-3806582233
...
Status:
  Presented:   true
  Processing:  true
  Reason:      Waiting for dns-01 challenge propagation: DNS record for "yet-another.website" not yet propagated
  State:       pending
Events:
  Type    Reason     Age   From          Message
  ----    ------     ----  ----          -------
  Normal  Started    54s   cert-manager  Challenge scheduled for processing
  Normal  Presented  53s   cert-manager  Presented challenge using dns-01 challenge mechanism

Un enregistrement TXT apparaîtra dans le panneau:



... et après un certain temps, le statut passera à:

Domain "yet-another.website" verified with "dns-01" validation

Assurez-vous que le certificat est valide pour n'importe quel sous-domaine:

kubectl -n app get secret tls-secret -ojson | jq -r '.data."tls.crt"' | base64 -d | openssl x509 -dates -noout -text |grep DNS:
          DNS:*.yet-another.website, DNS:yet-another.website

La validation par DNS, en règle générale, ne se fait pas rapidement, car la plupart des fournisseurs DNS ont une période de mise à jour des données qui montre combien de temps s'écoule entre le moment où l'enregistrement DNS est modifié et la mise à jour réelle de tous les serveurs DNS du fournisseur. Cependant, la norme ACME fournit également une combinaison de deux options de vérification, qui peuvent être utilisées pour accélérer la réception d'un certificat pour le domaine principal. Dans ce cas, la description Issuersera la suivante:

apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - selector:
        dnsNames:
        - "*.yet-another.website"
      dns01:
        cloudflare:
          email: my-cloudflare-acc@example.com
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token            
    - selector:
        dnsNames:
        - "yet-another.website"
      http01:
        ingress:
          class: nginx

Si vous appliquez cette configuration, deux ressources seront créées Challenge:

kubectl -n app describe orders le-crt-613810377-1285319347
  Normal  Created  3m29s  cert-manager  Created Challenge resource "le-crt-613810377-1285319347-3996324737" for domain "yet-another.website"                 
  Normal  Created  3m29s  cert-manager  Created Challenge resource "le-crt-613810377-1285319347-1443470517" for domain "yet-another.website"

Numéro 4. Utilisation d'annotations spéciales Ingress


En plus du chemin d'accès direct à la création de certificats dans cert-manager, il est possible d'utiliser un composant appelé ingress-shim et de ne pas créer explicitement de ressources Certificate. L'idée est qu'à l'aide d'annotations Ingress spéciales, le certificat sera automatiquement commandé en utilisant celui qui y est indiqué Issuer. Le résultat est à peu près la ressource Ingress suivante:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  tls:
  - hosts:
    - "yet-another.website"
    secretName: tls-secret
  rules:
  - host: "yet-another.website"
    http:
      paths:
      - path: /
        backend:
          serviceName: app
          servicePort: 8080

Pour un fonctionnement correct, seule la présence de l'émetteur suffit ici, c'est-à-dire la création d'une entité de moins.

De plus, il existe une annotation obsolète kube-lego - kubernetes.io/tls-acme: "true", - qui nécessite une tâche Issuerpar défaut lors de l'installation de cert-manager à l'aide des paramètres de Helm (ou dans les options de lancement du conteneur de gestionnaire).

Nous, dans l'entreprise, n'utilisons pas ces options et ne pouvons pas les conseiller en raison de l'opacité des approches utilisées pour commander des certificats SSL (et en même temps de divers problèmes qui surviennent ), mais nous avons tout de même décidé de mentionner dans l'article pour une image plus complète.

Au lieu d'une conclusion


Grâce à de simples manipulations avec CRD, nous avons appris à écrire des certificats SSL auto-renouvelables, auto-signés et gratuits à partir du projet Let's Encrypt pour les domaines de sites Web lancés dans le cadre des clusters Ingresss in Kubernetes.

L'article fournit des exemples de résolution des problèmes les plus courants dans notre pratique. Cependant, les fonctions cert-manager ne sont pas limitées aux fonctionnalités décrites ci-dessus. Sur le site Web de l'utilitaire, vous pouvez trouver des exemples de travail avec d'autres services - par exemple, un bundle avec Vault ou l' utilisation de centres d'émission externes (émetteurs).

PS


Lisez aussi dans notre blog:


All Articles