Istio, SDS y cert-manager

samcre

Sam

Posted on June 8, 2019

Istio, SDS y cert-manager

Si has llegado hasta aquí, ya sabes qué es Kubernetes. Incluso puede que hayas levantado tu propio clúster en local, usando Minikube. O hasta en una de los cientos de nubes públicas y privadas que ofrecen un servicio de Kubernetes auto gestionado. ¡Será por opciones!

En esta serie de posts vamos a ver cómo instalar/actualizar Istio para hacerlo funcionar conjuntamente con cert-manager, que nos proporcionará certificados gratuitos mediante Let's Encrypt, y ExternalDNS, que actualizará nuestros DNS cada vez que añadamos una entrada a un Gateway de Istio.

¿Qué es SDS? ¿y mTLS?

SDS son las siglas en inglés de Secret Discovery Service. Antes de Istio 1.1, las claves privadas y certificados generados por Istio eran manejados por Citadel, y montados como volúmenes en los pods sidecar de los pods. Esto tenía ciertas desventajas:

  • Al renovar un certificado, Istio reinicia en caliente el sidecar de Envoy, provocando pérdidas de rendimiento.
  • Las claves privadas se guardaban en secretos de Kubernetes, lo cual tiene ciertos riesgos.

Resumiendo, SDS soluciona estos problemas generando una clave privada y CSR en Citadel (que funciona como DaemonSet, es decir, en todos los nodos de Kubernetes) mediante token JWT enviado por el sidecar de Envoy. Citadel comprueba el token y, si todo es correcto, genera y envía el certificado y la clave privada al sidecar.

Podéis encontrar más detalles de cómo funciona en el blog de Istio, y en la documentación de Envoy.

Por otra parte, mTLS (mutual TLS), configura todos los pods del clúster para que hablen entre ellos usando TLS, por lo que todas las comunicaciones entre microservicios estarán cifradas y los componentes de Istio se encargarán de gestionar los certificados.

Instalando Istio con mTLS y SDS

En este artículo, partimos de un clúster vacío. Si ya tenemos un clúster funcionando sin Istio, tenemos que tener en cuenta que, para que Istio inyecte el pod sidecar en un namespace, tendremos que etiquetar ese namespace con la siguiente label:

kubectl label namespace production istio-injection=enabled
Enter fullscreen mode Exit fullscreen mode

Una vez aplicado, tendremos que levantar nuevos pods para que Istio inyecte el sidecar, y eliminar los antiguos.


Para instalar Istio, usaremos Helm. Nos descargamos la última versión de Istio, y la descomprimimos.

Instalamos primeros los CRDs de Istio en el clúster. Puedes omitir este paso si ya lo tenías instalado:

helm install --name istio-init --namespace istio-system istio-1.1.7/install/kubernetes/helm/istio-init
Enter fullscreen mode Exit fullscreen mode

Crearemos un fichero de values parecido a values-istio-sds-auth.yaml que viene en el tar, con ciertas modificaciones que explico comentadas en el fichero:

$ cat values-istio-sds-auth.yaml
global:
  controlPlaneSecurityEnabled: false

  mtls:
    enabled: true

  sds:
    enabled: true
    udsPath: "unix:/var/run/sds/uds_path"
    useNormalJwt: true

nodeagent:
  enabled: true
  image: node-agent-k8s
  env:
    CA_PROVIDER: "Citadel"
    CA_ADDR: "istio-citadel:8060"
    VALID_TOKEN: true
# Fin del fichero original

# Valores añadidos manualmente
# En la sección anterior, el parámetro global.sds.enabled=true activa SDS únicamente en los pods sidecar de Istio.
# En esta sección, configuramos el ingressgateway para que reciba los certificados con el mismo método.
gateways:
  istio-ingressgateway:
    enabled: true
    sds:
      enabled: true

# Esta sección inferior soluciona los livenessProbe y readinessProbe de Kubernetes configurados mediante httpGet,
# que fallan por el sidecar de Istio.
sidecarInjectorWebhook:
  enabled: true
  rewriteAppHTTPProbe: true
Enter fullscreen mode Exit fullscreen mode

Instalamos con el comando:

helm install \
  --name istio \
  --namespace istio-system \
  istio-1.1.7/install/kubernetes/helm/istio \
  --values values.yaml # values.yaml es nuestro fichero modificado
Enter fullscreen mode Exit fullscreen mode

Tras unos minutos de tensión, deberías tener Istio funcionando en tu clúster:

$ kubectl get pods -n istio-system
NAME                                       READY   STATUS    RESTARTS   AGE
istio-citadel-7f447d4d4b-f8lzt             1/1     Running   0          1m
istio-galley-84749d54b7-7sngg              1/1     Running   0          1m
istio-ingressgateway-57c97bb7f6-8294g      2/2     Running   0          1m
istio-nodeagent-nrl7v                      1/1     Running   0          1m
istio-nodeagent-vkchb                      1/1     Running   0          1m
istio-pilot-76899788b6-xcw7t               2/2     Running   0          1m
istio-policy-578bcb878f-ddwh7              2/2     Running   2          1m
istio-sidecar-injector-6895997989-5qsnn    1/1     Running   0          1m
istio-telemetry-5448cbd995-jcpdg           2/2     Running   2          1m
Enter fullscreen mode Exit fullscreen mode

¡Enhorabuena! Ya tienes Istio funcionando. Tómate un café, te lo has ganado.


Instalando cert-manager

Debido a un bug con cert-manager y la última versión de Helm, vamos a instalar el primero templatizando el chart de cert-manager.

missing "caBundle" in ValidatingWebhookConfiguration when installing with helm #1702

Describe the bug: When installing using helm as per documentation helm install --name cert-manager --namespace cert-manager --version v0.7.2 jetstack/cert-manager (step-by-step following the documentation on a clean new cluster running v1.12.7 in cluster (AKS) and helm v2.14.0)

The result is: Error: validation failed: error validating "": error validating data: [ValidationError(ValidatingWebhookConfiguration.webhooks[0].clientConfig): missing required field "caBundle" in io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig, ValidationError(ValidatingWebhookConfiguration.webhooks[1].clientConfig): missing required field "caBundle" in io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig, ValidationError(ValidatingWebhookConfiguration.webhooks[2].clientConfig): missing required field "caBundle" in io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig]

Expected behaviour: Cert-manager is installed

Steps to reproduce the bug: Steps as per https://docs.cert-manager.io/en/latest/getting-started/install.html#installing-with-helm

Anything else we need to know?:

Environment details::

  • Kubernetes version (e.g. v1.12.7):
  • Cloud-provider/provisioner (e.g. GKE, kops AWS, etc): AKS
  • cert-manager version (e.g. v0.4.0): 0.7.2
  • Install method (e.g. helm or static manifests): helm

/kind bug

Descargamos el chart de cert-manager y descomprimimos:

helm fetch --untar stable/cert-manager
Enter fullscreen mode Exit fullscreen mode

Como no tenemos que modificar ningún parámetro del values.yaml para instalarlo en el clúster, templatizamos y aplicamos:

helm template . --name cert-manager --namespace istio-system > cert-manager.yaml
kubectl apply --file cert-manager.yaml
Enter fullscreen mode Exit fullscreen mode

IMPORTANTE: Como veis, instalamos cert-manager en el namespace de Istio, istio-system. Esto es un requisito, ya que para que funcione SDS, los secretos que contienen los certificados deben encontrarse en el mismo namespace en el que está Istio. Los Gateways y VirtualServices de Istio podrán estar en su namespace correspondiente.

NOTA: en el propio chart de Istio podemos indicar una opción para instalar cert-manager. En este artículo lo instalamos de manera independiente para poder manejarlo independientemente de la instalación de Istio.

Una vez esté instalado cert-manager en el namespace de Istio, tendremos que configurar un emisor de certificados. Vamos a configurarlo para que use Let's Encrypt, y confirme los certificados resolviendo un DNS01 Challenge, que nos permitirá generar certificados de tipo wildcard, es decir, *.example.com.

En este caso, usamos AWS Route 53 como DNS, por lo que el clúster o pods necesitarán unos permisos específicos de IAM para trabajar con este servicio de AWS.

Generamos el fichero clusterissuer.yaml con este contenido:

# clusterissuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt
  labels:
    app: certmanager
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <EMAIL> # Sustituir por un correo válido
    privateKeySecretRef:
      name: letsencrypt # Secreto donde cert-manager guardará datos
    # Configuramos el proveedor para Route 53. En este caso no se indican credenciales de AWS,
    # dado que los pods y/o nodos de k8s tienen asignado un rol de IAM con permisos.
    dns01:
      providers:
      - name: route53
        route53:
          region: eu-west-1
#          # Descomenta esto, y modifica acorde,
#          # para usar unas credenciales de IAM que otorguen los permisos
#          accessKeyID: AKIAIOSFODNN7EXAMPLE
#          secretAccessKeySecretRef:
#            name: prod-route53-credentials-secret
#            key: secret-access-key
Enter fullscreen mode Exit fullscreen mode

Y aplicamos:

kubectl apply -f clusterissuer.yaml
Enter fullscreen mode Exit fullscreen mode

Tendremos disponible, a nivel de clúster, la creación de certificados usando los certificados de Let's Encrypt. Pero primero tenemos que definir un certificado, para que cert-manager se encargue de solicitarlo automáticamente:

# wildcard.example.com.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: wildcard.example.com
  namespace: istio-system # Recordad: es necesario que los certificados estén en el namespace de Istio
spec:
  secretName: wildcard.example.com
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt
  commonName: "*.example.com"
  dnsNames:
  - example.com
  acme:
    config:
    - dns01:
        provider: route53
      domains:
        - '*.example.com'
        - example.com
Enter fullscreen mode Exit fullscreen mode
kubectl apply -f wildcard.example.com.yaml
Enter fullscreen mode Exit fullscreen mode

Si todo está correctamente configurado, en unos minutos tendremos disponible el certificado:

$ kubectl describe certificates wildcard.example.com
[...]
Status:
  Conditions:
    Last Transition Time:  2019-05-30T09:27:56Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2019-08-28T08:27:54Z
[...]
Enter fullscreen mode Exit fullscreen mode

¡Excelente! Ya tenemos el certificado. Pero, ¿cómo hacemos para que Istio envie el tráfico a nuestros pods, y se encargue de configurar el certificado? Para ello, hacemos uso de los Gateways y VirtualServices de Istio.

Configurando Gateways y VirtualServices

Esta es fácil. Simplemente tendremos que generar un Gateway de Istio donde configuramos el DNS y el certificado, y un VirtualService que envíe el tráfico según el host al servicio correspondiente de Kubernetes. Para este ejemplo, usaremos el servicio nginx:

# route.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: example-gateway
  namespace: production # Podemos crear los Gateways en el namespace que queramos
spec:
  selector:
    istio: ingressgateway # Gateway de Istio por donde entra el tráfico al service mesh
  servers:
  # Configuramos el puerto 80 para que Istio haga la redirección a HTTPS
  - port:
      number: 80
      name: http-redirect
      protocol: HTTP
    hosts:
    - "nginx.example.com"
    tls:
      httpsRedirect: true
  - port:
      number: 443
      name: nginx-https
      protocol: HTTPS
    tls:
      # En credentialName, indicamos el nombre del secreto configurado
      # en el Certificate anteriormente creado
      credentialName: wildcard.example.com
      mode: SIMPLE
      # En las dos siguientes claves, indicamos a Istio que use el sistema SDS
      # para obtener el certificado
      privateKey: sds
      serverCertificate: sds
    hosts:
      - "nginx.example.com"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: nginx-route
  namespace: production
spec:
  gateways:
    - example-gateway # OJO, hace referencia al gateway creado más arriba
  hosts:
    - nginx.example.com
  http:
    - match:
      - uri:
          prefix: "/"
      route:
      - destination:
          host: nginx.production.svc.cluster.local
          port:
            number: 443
Enter fullscreen mode Exit fullscreen mode

Aplicamos este fichero con kubectl apply -f routes.yaml, et voilà! Istio inyectará automáticamente el certificado configurado (en este caso, el wildcard) cuando entremos al DNS del servicio, nginx.example.com.

¿Pero no íbamos a configurar ExternalDNS para que generara las entradas automáticamente en Route 53? De momento, hazlo a mano, y en el próximo post vemos cómo integrar ExternalDNS con Istio, y Route 53.

Prometo que será corto.


Fuentes y más información

💖 💪 🙅 🚩
samcre
Sam

Posted on June 8, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

The K8 Gateway API 🚀
kubernetes The K8 Gateway API 🚀

November 3, 2022

Istio IP based Whitelisting
kubernetes Istio IP based Whitelisting

October 22, 2022

ExternalDNS, Istio y AWS Route 53
kubernetes ExternalDNS, Istio y AWS Route 53

June 12, 2019

Istio, SDS y cert-manager
kubernetes Istio, SDS y cert-manager

June 8, 2019