Encriptemos certificados SSL con cert-manager en Kubernetes



En este artículo hablaré sobre cómo automatizar el pedido y la renovación de certificados de Let's Encrypt (y no solo) para Ingress en Kubernetes utilizando el complemento cert-manager. Pero comenzaré con una breve introducción a la esencia del problema.

Un poco de programa educativo


El protocolo HTTP, desarrollado a principios de los años 90 del siglo pasado, entró en nuestra vida cotidiana con tanta fuerza que es difícil imaginar al menos un día sin usarlo. Sin embargo, por sí solo, ni siquiera proporciona un nivel mínimo de seguridad al intercambiar información entre un usuario y un servidor web. HTTPS ("S" - seguro) viene al rescate: utilizando el paquete de los datos transmitidos en SSL / TLS, este protocolo ha demostrado su eficacia en la protección de la información contra la intercepción y es promovido activamente por la industria.

Por ejemplo, Google se ha adherido a 2014Posición "HTTPS en todas partes" e incluso reduce la prioridad de los sitios sin ella en los resultados de búsqueda. Esta "propaganda" tampoco pasa por alto a los consumidores comunes: los navegadores modernos advierten a sus usuarios sobre la presencia y la corrección de los certificados SSL para los sitios visitados.





El costo de un certificado para un sitio personal comienza desde decenas de dólares. No siempre comprarlo está justificado y es apropiado. Afortunadamente, desde finales de 2015, hay disponible una alternativa gratuita en forma de certificados Let's Encrypt (LE). Este proyecto sin fines de lucro fue creado por entusiastas de Mozilla para cubrir la mayoría de los sitios web con encriptación.

La autoridad de certificación emite certificados validados por el dominio(el más simple entre los disponibles en el mercado) con un período de validez de 90 días, y durante muchos años ha sido posible emitir el llamado certificado comodín para varios subdominios.

Para obtener el certificado, el sitio utiliza los algoritmos descritos en el protocolo del Entorno de administración de certificados automatizado (ACME), creado específicamente para Let's Encrypt. Al usarlo, la confirmación de la propiedad del dominio se lleva a cabo mediante solicitudes mediante la colocación de un código HTTP específico (llamado HTTP-01 ) o la instalación de registros DNS (DNS-01) ; a continuación se brindará más información sobre ellos.

Gerente de certificacion


Cert-manager es un proyecto especial para Kubernetes, que es un conjunto de CustomResourceDefinitions (de ahí la limitación de la versión mínima admitida de K8s - v1.12) para la configuración de CA (autoridades de certificación) y el pedido directo de certificados. Instalar CRD en un clúster es trivial y se reduce a usar un archivo YAML:

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

( También existe la posibilidad de instalar usando Helm.)

Para iniciar el procedimiento de pedido, los recursos de las autoridades de certificación (CA) deben declararse en el clúster: Issuero ClusterIssuer, que se utilizan para firmar el CSR (solicitudes de emisión de certificados). La diferencia entre el primer recurso y el segundo está en el alcance:

  • Issuer se puede usar dentro del mismo espacio de nombres,
  • ClusterIssuer es un objeto de clúster global.

Practica con cert-manager


No 1. Certificado autofirmado


Comencemos con el caso más simple: pedir un certificado autofirmado. Esta opción es bastante común, por ejemplo, para entornos de prueba dinámicos para desarrolladores o en el caso de utilizar un equilibrador externo que finalice el tráfico SSL.

El recurso Issuerse verá así:

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

Y para emitir un certificado, es necesario describir el recurso Certificate, dónde se indica cómo emitirlo (consulte la sección a issuerRefcontinuación) y dónde se encuentra la clave privada (campo secretName). Después de eso, Ingress tendrá que referirse a esta clave (ver sección 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

Unos segundos después de agregar estos recursos al clúster, se emitirá el certificado. Puede ver el informe correspondiente en la salida del comando:

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 observa el recurso secreto en sí, entonces contiene:

  • clave privada tls.key,
  • certificado raíz ca.crt,
  • Nuestro certificado autofirmado tls.crt.

El contenido de estos archivos se puede ver usando la utilidad openssl, por ejemplo, así:

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

Vale la pena señalar que, en el caso general, los Issuerclientes conectados no confiarán en el certificado emitido con este . La razón es simple: no tiene una CA (consulte la nota en el sitio web del proyecto ) . Para evitar esto, debe especificar en la Certificateruta al archivo secreto donde está contenido ca.crt. Puede ser una organización de CA corporativa: para firmar los certificados emitidos para Ingress con una clave que ya se utiliza para las necesidades de otros servicios de servidor / sistemas de información.

No 2. Cifremos el certificado con validación HTTP


Para emitir certificados LE, como se mencionó anteriormente, hay dos tipos de confirmación de propiedad de dominio disponibles : HTTP-01 y DNS-01.

El primer enfoque (HTTP-01) es lanzar un pequeño servidor web con una implementación separada, que enviará a Internet a través del enlace <YOUR_DOMAIN > /. Conocidos / acme-challenge / <TOKEN> algunos datos solicitados al servidor de certificación. Por lo tanto, este método implica la disponibilidad de Ingress desde el exterior en el puerto 80 y la resolución del registro DNS del dominio en direcciones IP públicas.

La segunda opción para verificar el certificado emitido (DNS-01) proviene dede tener una API para el servidor DNS que aloja los registros de dominio. El emisor, utilizando los tokens indicados, crea registros TXT en el dominio, que el servidor ACME luego recibe durante la confirmación. Entre los proveedores de DNS oficialmente compatibles están CloudFlare, AWS Route53, Google CloudDNS y otros, incluida su propia implementación ( acme-dns ).

Nota : Let's Encrypt tiene límites bastante estrictos en las solicitudes a los servidores ACME. Para no entrar en una prohibición larga, se recomienda utilizar el tipo de certificado de ensayo de letencrypt para la depuración (la diferencia es solo en el servidor ACME).

Entonces, describimos los recursos:

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

Tenga en cuenta que la dirección del servidor provisional se especifica como servery acme(c Issuer) . Reemplazarlo con una batalla será posible más tarde. Aplicando esta configuración, rastreamos toda la ruta del pedido:



  1. La creación Certificategeneró un nuevo recurso CertificateRequest:

    kubectl -n app describe certificate le-crt
    ...
    Created new CertificateRequest resource "le-crt-1127528680"
  2. En su descripción, una marca en la creación Order:

    kubectl -n app describe certificaterequests le-crt-1127528680
    Created Order resource app/le-crt-1127528680-1805948596
  3. Se Orderdescribe con qué parámetros de los pases de prueba y cuál es su estado actual es. Dicha verificación es realizada por el recurso Challenge:

    kubectl -n app describe order le-crt-1127528680-1805948596
    Created Challenge resource "le-crt-1127528680-1805948596-1231544594" for domain "yet-another.website"
  4. Finalmente, los detalles de este recurso contienen información sobre el estado del escaneo en sí:

    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 se han cumplido todas las condiciones (es decir, se puede acceder al dominio desde el exterior, no hay prohibición de la LE ...): en menos de un minuto, se emitirá el certificado. Si tiene éxito, describe certificate le-crtaparecerá un registro en la salida Certificate issued successfully.

Ahora puede cambiar de forma segura la dirección del servidor para combatir ( https://acme-v02.api.letsencrypt.org/directory) y volver a ordenar los certificados reales ya firmados Fake LE Intermediate X1, pero no Let's Encrypt Authority X3.

Para hacer esto, primero debe eliminar el recurso Certificate: de lo contrario, no se activarán los procedimientos de pedido, porque el certificado ya existe y es relevante. Eliminar un secreto llevará a su devolución inmediata con un mensaje a describe certificate:

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

Queda por aplicar el manifiesto de "combate" para Issuerel ya descrito anteriormente Certificate(no ha cambiado):

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

Después de recibir el mensaje Certificate issued successfully, describeverifíquelo:

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

Numero 3. Comodín LE con validación de DNS


Complicaremos la tarea escribiendo un certificado de inmediato en todos los subdominios del sitio y utilizando esta vez la verificación de DNS (a través de CloudFlare).

Primero, obtenga el token en el panel de control de CloudFlare para trabajar a través de la API:

  1. Perfil → Tokens API → Crear token.
  2. Establezca los permisos de la siguiente manera:
    • Permisos:
      • Zone - DNS - Editar
      • Zona - Zona - Leer
    • Recursos de zona:
      • Incluir: todas las zonas
  3. Copie el token recibido después de guardar (por ejemplo :) y_JNkgQwkroIsflbbYqYmBooyspN6BskXZpsiH4M.

Cree un secreto en el que se almacenará este token y consúltelo en el emisor:

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"

(¡No olvide usar la puesta en escena si está experimentando!)

Pasemos por el proceso de confirmar la propiedad del dominio:

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

Aparecerá un registro TXT en el panel:



... y después de un tiempo el estado cambiará a:

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

Asegúrese de que el certificado sea válido para cualquier subdominio:

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 validación por DNS, por regla general, no ocurre rápidamente, ya que la mayoría de los proveedores de DNS tienen un período de actualización de datos que muestra cuánto tiempo transcurre desde el momento en que el registro DNS se cambia a la actualización real de todos los servidores DNS del proveedor. Sin embargo, el estándar ACME también proporciona una combinación de dos opciones de verificación, que pueden usarse para acelerar la recepción de un certificado para el dominio principal. En este caso, la descripción Issuerserá la siguiente:

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 aplica esta configuración, se crearán dos recursos 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"

Número 4. Uso de anotaciones especiales de entrada


Además de la ruta directa para crear certificados en cert-manager, es posible usar un componente llamado ingress-shim y no crear recursos explícitamente Certificate. La idea es que con la ayuda de anotaciones especiales de Ingress, el certificado se ordenará automáticamente utilizando el indicado en ellos Issuer. El resultado es aproximadamente el siguiente recurso de Ingress:

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

Para un funcionamiento correcto, solo la presencia del Emisor es suficiente aquí, es decir, crear una entidad menos.

Además, hay una anotación obsoleta kube-lego - kubernetes.io/tls-acme: "true", - que requiere un trabajo Issuerpredeterminado al instalar cert-manager usando los parámetros de Helm (o en las opciones de inicio del contenedor del administrador).

Nosotros en la compañía no utilizamos estas opciones y no podemos aconsejarlas debido a la opacidad de los enfoques utilizados para pedir certificados SSL (y al mismo tiempo a los diversos problemas que surgen ), pero aún así decidimos mencionar en el artículo para obtener una imagen más completa.

En lugar de una conclusión


Mediante simples manipulaciones con CRD, aprendimos cómo escribir certificados SSL renovables, autofirmados y gratuitos del proyecto Let's Encrypt para sitios de dominio lanzados como parte de Ingresss en clústeres de Kubernetes.

El artículo proporciona ejemplos para resolver los problemas más comunes en nuestra práctica. Sin embargo, las funciones de cert-manager no se limitan a las características descritas anteriormente. En el sitio web de la utilidad, puede encontrar ejemplos de cómo trabajar con otros servicios, por ejemplo, un paquete con Vault o el uso de centros emisores externos (emisores).

PD


Lea también en nuestro blog:


All Articles