Añadir soporte TLS a tu Kubernetes en Azure con Let’s Encrypt

¡Buenas! En este post vamos a ver como añadir soporte TLS a tu clúster de Kubernetes desplegado en ACS o AKS. Hace tiempo escribí un post sobre como añadir certificados de desarrollo a un servicio NGINX que tuvieses en Kubernetes. Aunque lo dicho en aquel post sigue siendo válido, hay una manera mucho más sencilla con la condición de que usemos ingress para exponer nuestros servicios al exterior.

En este post parto de la suposición de que:

  1. Tienes un clúster de Kubernetes en ACS/AKS y kubectl configurado para atacar a él
  2. Tienes el controlador ingress de NGINX instalado en el clúster exponiendo cualquier servicio

Es decir, haciendo http://IP-CLUSTER/servicio obtienes alguna respuesta. Lo que queremos es poder usar https en su lugar.

El controlador ingress de NGINX viene con un certificado SSL preinstalado. De hecho para la gente que lo usa por primera vez le molesta más que otra cosa, porque cualquier petición vía http se redirige a https de forma automática y obtenemos este certificado incorrecto:

Error de certificado

Si observas el issuer es “Kubernetes Ingress Controller Fake Certificate“: este es el certificado que incorpora el controlador ingress de NGINX.

Si has usado el controlador ingress de NGINX seguramente ya lo sabrás, pero por si acaso: Puedes deshabilitar la redirección automática de http a https añadiendo anotaciones al recurso ingress:

annotations:
  ingress.kubernetes.io/ssl-redirect: "false"  
  nginx.ingress.kubernetes.io/ssl-redirect: "false"

De este modo, el controlador NGINX ya no te redireccionará de http a https.

Vale, ahora vayamos a lo que nos interesa, que es añadir soporte https con un certificado válido de Let’s Encrypt.

Para ello vamos a usar cert-manager que es un paquete de Kubernetes que auto-aprovisiona certificados TLS. Es decir, todo el proceso pasará de forma automática, sin que nosotros tengamos que hacer nada especial. Lo primero es instalar helm para desplegar cert-manager en nuestro clúster. Helm se compone de dos partes:

  1. Una CLI en cliente (helm)
  2. Un componente servidor en el clúster (tiller)

Para entender rápidamente que es helm, podríamos decir que es “el nuget de Kubernetes”. Nos permite desplegar paquetes de Kubernetes, donde un paquete es un conjunto de elementos de Kubernetes (controladores, servicios, etc). La instalación de helm es muy sencilla y está muy bien detallada en su página web. Pero básicamente son dos pasos:

  1. Descárgate la CLI
  2. Ejecuta helm init: Eso instalará tiller en el cluster (Si usas una RC de helm añade el parámetro –canary-image). Ojo, que la última RC de helm puede dar errores en AKS, te recomiendo la última estable.

Una vez tengas helm instalado, el siguiente paso es usarlo para instalar cert-manager, lo que tambien es trivial. Basta con teclear:

helm install --name cert-manager --namespace kube-system stable/cert-manager --set rbac.create=false

(El parámetro rbac.create es para que no cree roles RBAC, que AKS no los soporta de momento).

Ahora que ya tenemos cert-manager estamos listos para empezar.

Definiendo el issuer

Cert-manager añade dos tipos de objetos nuevos a la API de Kubernetes: issuercertificate. El primero define una entidad certificadora (tal como Let’s Encrypt) y el segundo define un certificado concreto. Vamos a empezar por el primero. Como todos los objetos de k8s se define mediante un YAML:

apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-staging
  namespace: default
spec:
  acme:
    server: https://acme-staging.api.letsencrypt.org/directory
    email: user@example.com
    privateKeySecretRef:
      name: letsencrypt-staging
    http01: {}

Con eso definimos un issuer llamado letsencrypt-staging. Las entradas importantes ahí son:

  • server: Cual es el servidor a usar. Este servidor debe soportar el protocolo ACME (ya, el nombre suena a broma :P), que es un protocolo que se usa para la gestión automatizada de certificados. El valor apunta al servidor de pruebas de Let’s Encrypt
  • El valor de http01 (un objeto vacío) indica que usaremos el mecanismo de validación HTTP-01. Mediante ese mecanismo garantizamos que nosotros somos los dueños del dominio colocando un archivo concreto en una ruta concreta que se nos pide.

Usa kubectl apply para añadir este fichero en el clúster. Luego puedes verificar que el issuer está instalado con kubect get issuers.

Crear el certificado

Ahora vamos a definir el objeto certificado. Para ello tenemos un pre-requisito importante: debes localizar la IP pública de tu cluster en Azure y asignarle un DNS.

Bien, ahora usa el siguiente YAML como plantilla para tu certificado:

apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: my-cert
  namespace: default
spec:
  secretName: my-cert-tls
  issuerRef:
    name: letsencrypt-staging
  commonName: example.com
  dnsNames:
  - example.com
  acme:
    config:
    - http01:
        ingress: my-ingress
      domains:
      - example.com

Aquí definimos un certificado para el DNS example.com. Por supuesto debes usar el que hayas asignado a la IP pública de Azure (a no ser que uses dominios propios el DNS será del estilo mycluster.eastus.cloudapp.azure.com).

Otras entradas a tener en cuenta son:

  • secretName: Nombre del secreto en k8s que contendrá el certificado descargado. Deberemos usarlo en el recurso ingress
  • issuerRef: El nombre del recurso issuer que emite el certificado
  • http01: La configuración de http01. Eso requiere algunas palabras más 🙂

Si recuerdas en HTTP-01 validamos que somos los propietarios del dominio porque se nos pedirá (como parte del protocolo ACME) que coloquemos un fichero en una determinada ruta. Bien, la idea es que de forma automática cert-manager creará un servicio que servirá este fichero y usará un recurso ingress para exponer este servicio en la ruta que le pida Let’s Encrypt. Todo eso ocurre automáticamente, pero debemos indicarle que recurso ingress usar. Lo habitual es tener un solo recurso ingress y ese es el que usarías, pero en el caso (tampoco tan raro) de que tengas más de uno, debes usar uno que esté asignado al controlador NGINX cuya IP pública estás validando (en el caso, este sí bastante raro, de que tengas más de un controlador ingress instalado).

En este YAML de ejemplo definimos que para el dominio (example.com) use el recurso ingress llamado my-ingress (podríamos usar recursos ingress distintos por dominios distintos).

Como antes usa kubectl apply para crear el certificado. Ahora usando kubectl get certificates deberías verlo. Si todo ha ido bien, ya tienes tu certificado instalado en el clúster. De hecho si usas kubectl get secrets deberías ver un secreto llamado my-cert-tls (o el nombre usado en secretName del YAML del objeto certificado) de tipo  kubernetes.io/tls:

NAME                  TYPE                                  DATA      AGE
letsencrypt-staging   Opaque                                1         2h
my-cert-tls           kubernetes.io/tls                     2         1h

(Puedes ver otros secretos adicionales, pero esos dos deberías verlos)

Asociar el recurso ingress con el certificado

Ahora solo falta asociar el recurso ingress con el certificado.  Para ello debes usar una entrada tls para que ingress sepa cual es el secreto que contiene el certificado:

spec:
  rules:
  - host: YOUR_DNS_NAME
    http:
      paths:
      - path: /foo
        backend:
          serviceName: foosvc
          servicePort: 80                    
  tls:
  - secretName: my-cert-tls
    hosts: 
      - YOUR_DNS_NAME

Observa la sección tls donde se indica el secreto que contiene el certificado (y la lista de hosts asociados a este certificado).

Ahora solo te queda redesplegar el recurso ingress con kubectl apply y ¡listos! No tienes que hacer nada más: ya tienes un certificado de Let’s Encrypt en tu clúster.

NOTA IMPORTANTE: Si has usado el servidor de staging de Let’s Encrypt el certificado NO ES VÁLIDO. Pero sabrás que has hecho todos los pasos bien porque la entidad es Fake LE Intermediate X1. Si te aparece esta entidad, es que todo lo has hecho bien. Para obtener un certificado de producción realiza los siguientes pasos:

  1. Actualiza el issuer para usar el servidor de producción de Let’s Encrypt (https://acme-v01.api.letsencrypt.org/directory) y redespliegalo con kubectl apply
  2. Elimina el secreto que contiene el certificado (el my-cert-tls)
  3. Elimina el objeto certificado (p. ej. si el YAML se llamaba certificate.yaml puedes usar kubectl delete -f certificate.yaml)
  4. Vuelve a crear el certificado (kubectl apply -f certificate.yaml)

Al cabo de unos segundos te debe volver a aparecer el secreto my-cert-tls pero ahora contiene un certificado firmado y ya deberías poder acceder vía https a tu clúster (usando el DNS asignado).

¡Espero que te haya resultado intersante! Eso sí, una cosilla: cert-manager está en “alfa” por lo que puede haber breaking changes. Funcionar funciona, pero los YAML que definen los objetos issuercertificate pueden no funcionar bien en futuras versiones. Si buscas algo más estable puedes usar kube-lego, pero está deprecado (cert-manager es su sucesor) y no soporta versiones de Kubernetes más allá de 1.8

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *