Envoy Gateway TLS y Seguridad: Configura HTTPS y Deja de Exponer tu API sin Protección

6 min read

Hacer funcionar HTTP es solo el calentamiento. A menos que tu servicio viva en la Edad de Piedra, producción casi con certeza necesita HTTPS. Este post cubre los fundamentos de TLS para Envoy Gateway para que tus APIs dejen de correr en texto claro.

Cómo Funciona TLS en Gateway

El enfoque más común: terminar TLS en el listener del Gateway.

Esto significa:

  • El cliente se conecta vía HTTPS
  • El Gateway gestiona el handshake y el certificado
  • El tráfico se reenvía a tu Service sin cifrar internamente

Un listener HTTPS básico luce así:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
spec:
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            group: ""
            name: example-cert

Dos cosas importan aquí:

  • protocol: HTTPS
  • tls.mode: Terminate

Esto significa: TLS termina aquí, y el Gateway se encarga del certificado.

HTTPS + HTTPRoute vs TLSRoute: Cuál es la Diferencia

Esto es importante, porque mucha gente ve TLSRoute y enseguida pregunta:

"¿No es suficiente con un listener HTTPS? ¿Para qué necesitamos un TLSRoute separado?"

La diferencia está en si el Gateway termina TLS o no:

Escenario Combinación Habitual Descripción
El Gateway termina TLS y luego aplica reglas HTTP listener HTTPS + HTTPRoute El escenario más común para sitios web y APIs
El Gateway no descifra — enruta tráfico cifrado basándose solo en SNI listener TLS + TLSRoute Escenario de TLS passthrough

En otras palabras:

  • Si quieres inspeccionar path, header, method en el Gateway, primero necesitas terminar TLS y luego usar HTTPRoute
  • Si quieres preservar el cifrado de extremo a extremo y no descifrar en el Gateway, TLSRoute es el camino correcto

Ningún enfoque es "más avanzado" — lo importante es saber qué quieres: "terminar TLS y hacer routing L7," o "mantener el cifrado intacto y hacer routing basado en SNI."

Preparar el Secret del Certificado

Para entornos de prueba, un certificado autofirmado funciona bien. Los ejemplos oficiales usan exactamente esto:

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
  -subj '/O=example Inc./CN=example.com' \
  -keyout example.com.key \
  -out example.com.crt
 
openssl req -out www.example.com.csr -newkey rsa:2048 -nodes \
  -keyout www.example.com.key \
  -subj "/CN=www.example.com/O=example organization"
 
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key \
  -set_serial 0 -in www.example.com.csr -out www.example.com.crt

Guarda el certificado y la clave como un Secret de Kubernetes:

kubectl create secret tls example-cert \
  --key=www.example.com.key \
  --cert=www.example.com.crt

El Gateway puede entonces referenciar este Secret mediante certificateRefs.

⚠️ Los certificados autofirmados son solo para pruebas. En producción, usa cert-manager o tu flujo de gestión de certificados existente. No lleves la configuración del demo directamente a producción — es como sobrevivir a base de fideos instantáneos: está bien de vez en cuando, pero no es sostenible a largo plazo.

Agregar un Listener HTTPS a un Gateway Existente

Si ya tienes un Gateway eg del post anterior, puedes parchearlo directamente:

kubectl patch gateway eg --type=json --patch '
  - op: add
    path: /spec/listeners/-
    value:
      name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
        - kind: Secret
          group: ""
          name: example-cert
'

Verifica el estado:

kubectl get gateway/eg -o yaml

Si el listener se ve saludable, prueba HTTPS:

curl -v -HHost:www.example.com \
  --resolve "www.example.com:443:${GATEWAY_HOST}" \
  --cacert example.com.crt \
  https://www.example.com/get

Este comando es excelente para validación local, especialmente cuando el DNS todavía no está configurado.

Un Gateway, Múltiples Dominios HTTPS

Si tienes múltiples dominios, agrega múltiples listeners HTTPS al mismo Gateway. Por ejemplo, para agregar foo.example.com:

listeners:
  - name: https-main
    protocol: HTTPS
    port: 443
    hostname: www.example.com
    tls:
      mode: Terminate
      certificateRefs:
        - name: example-cert
  - name: https-foo
    protocol: HTTPS
    port: 443
    hostname: foo.example.com
    tls:
      mode: Terminate
      certificateRefs:
        - name: foo-cert

No olvides actualizar el HTTPRoute correspondiente con el hostname correcto — de lo contrario, el listener puede estar escuchando, pero la ruta no sabe a dónde enviar el tráfico.

Modelo Mental para TLSRoute

Para TLS passthrough, piensa en TLSRoute así:

💡 El apiVersion para TLSRoute puede variar dependiendo de qué versión de los CRDs de Gateway API tengas instalada. El ejemplo a continuación es solo para ilustración conceptual — verifica la versión instalada en tu clúster antes de aplicarlo.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg-tls
spec:
  gatewayClassName: eg
  listeners:
    - name: tls
      protocol: TLS
      port: 443
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
  name: passthrough-route
spec:
  parentRefs:
    - name: eg-tls
  hostnames:
    - "db.example.com"
  rules:
    - backendRefs:
        - name: db-service
          port: 5432

Casos de uso comunes para este patrón:

  • Routing basado únicamente en el hostname SNI
  • Preservar el cifrado hasta el backend
  • Sin análisis de path o headers HTTP en el Gateway

Así que la mayor diferencia entre TLSRoute y HTTPRoute no es solo el nombre — operan en capas de tráfico completamente distintas.

Referencias de Certificados Entre Namespaces

La documentación oficial destaca una capacidad útil: Un Gateway puede referenciar Secrets de certificados de otros namespaces — pero requiere un ReferenceGrant.

Por ejemplo, si el equipo de plataforma almacena los certificados de forma centralizada en envoy-gateway-system, pero el Gateway de la aplicación está en default, no puedes simplemente referenciar entre namespaces sin autorización explícita.

Ejemplo:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-default-to-read-cert
  namespace: envoy-gateway-system
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: Gateway
      namespace: default
  to:
    - group: ""
      kind: Secret

Esto significa:

  • Un Gateway en el namespace default
  • Está autorizado a referenciar recursos Secret en envoy-gateway-system

Sin un ReferenceGrant, esta referencia entre namespaces se trata como inválida. Es un mecanismo de protección — no Gateway API siendo innecesariamente estricta.

Resumen en Una Línea

TLS en Envoy Gateway no es tan misterioso como suena: prepara el Secret del certificado, configura el listener HTTPS, confirma que tu ruta y hostname coincidan — y básicamente eso es todo.

💡 El error más común de los principiantes no es que TLS sea difícil — es que tres cosas no están alineadas: hostname, certificateRefs y los hostnames de HTTPRoute. Si cualquiera de ellas está mal, el tráfico empieza a comportarse de forma extraña.

Siguiente Paso

Ahora que has cubierto instalación, conceptos, routing y TLS, el siguiente post consolida todo esto en un conjunto de prácticas que tienen menos probabilidad de explotar en producción: 👉 Buenas Prácticas