Cloudflare utiliza con regularidad Kubernetes para las cargas de trabajo de ingeniería. Lo usa para activar el backend de nuestras API y para gestionar el procesamiento por lotes, como la agregación de análisis y la detección de bots, así como para las herramientas de ingeniería, como nuestros canales CI/CD. Sin embargo, entre los equilibradores de carga, los servidores de la API, etcd, los ingresos y los pods, la superficie que expone Kubernetes puede ser bastante grande.
En esta publicación, comentaremos cómo nuestro equipo de ingeniería prueba primero Cloudflare Zero Trust a nivel interno para proteger Kubernetes, y activa kubectl sin proxies.
Nuestro enfoque general para la seguridad de Kubernetes
Como parte de nuestras medidas de seguridad, limitamos considerablemente lo que puede acceder a nuestros clústeres a través de la red. Cuando se expone un servicio de red, añadimos protecciones adicionales, como exigir la autenticación de Cloudflare Access o TLS mutuo (o ambos) para acceder a los recursos de ingreso.
Estas restricciones de red incluyen el acceso al servidor de la API del clúster. Sin este acceso, los ingenieros de Cloudflare no podrían utilizar herramientas como kubectl para examinar los recursos de su equipo. Aunque creemos que las Implementaciones continuas y GitOps son buenas prácticas, permitir que los desarrolladores usen la API de Kubernetes ayuda en la solución de problemas y a aumentar la velocidad de los desarrolladores. No tener acceso hubiera sido un problema.
Usamos Cloudflare Zero Trust para satisfacer nuestros requisitos de seguridad, y queríamos contar cómo lo estamos utilizando, y el proceso que nos ha llevado hasta aquí.
Antes de Zero Trust
En el entorno anterior a Zero Trust, los ingenieros podían acceder a la API de Kubernetes conectándose a un dispositivo VPN. Aunque es algo habitual en el sector, y permite el acceso a la API, también dejaba a los ingenieros como clientes en la red interna: tenían mucho más acceso a la red del necesario.
No nos gustaba esta situación, pero ese fue el statu quo durante varios años. A principios de 2020 retiramos nuestra VPN, y entonces el equipo de Kubernetes tuvo que encontrar otra solución.
Kubernetes con Cloudflare Tunnels
En ese momento trabajamos estrechamente con el equipo que desarrolla Cloudflare Tunnels para que fuera posible gestionar las conexiones kubectl utilizando Access y los túneles cloudflared.
Aunque esto funcionaba para nuestros usuarios de ingeniería, era un obstáculo importante para la incorporación de nuevos empleados. Cada clúster de Kubernetes necesitaba su propia conexión de túnel desde el dispositivo del ingeniero, lo que hacía que el cambio entre clústeres fuera incómodo. Aunque kubectl permitía la conexión mediante proxies SOCKS, esta compatibilidad no era válida para todas las herramientas del ecosistema Kubernetes.
Seguimos usando esta solución a nivel interno mientras buscábamos una solución mejor.
Kubernetes con Zero Trust
Desde el lanzamiento de Cloudflare One, hemos estado probando primero a nivel interno el agente Zero Trust en distintas configuraciones. Al principio, lo habíamos utilizado para implementar un DNS seguro con 1.1.1.1. Con el paso del tiempo, empezamos a utilizarlo para probar primero a nivel interno funciones adicionales de Zero Trust.
Ahora estamos aprovechando el enrutamiento de la red privada en Cloudflare Zero Trust para permitir que los ingenieros accedan a las API de Kubernetes sin que haya que configurar túneles cloudflared, o configurar kubectl y otras herramientas del ecosistema Kubernetes para usar túneles. Esto no es algo específico de Cloudflare, ¡puedes hacerlo hoy mismo para tu equipo!
Configurar Zero Trust
Para habilitar la infraestructura como código, utilizamos una herramienta de gestión de la configuración para nuestra configuración Zero Trust, que hemos adaptado a continuación. Sin embargo, es posible conseguir la misma configuración mediante el panel de control de Cloudflare Zero Trust.
Lo primero que tenemos que hacer es crear un nuevo túnel. Se usará este túnel para conectar la red perimetral de Cloudflare a la API de Kubernetes. Ejecutamos los puntos de conexión del túnel en Kubernetes, utilizando la configuración que mostramos más adelante en esta publicación.
El secreto "tunnel_secret" debe ser un número aleatorio de 32 bytes, que debes guardar de forma temporal, ya que lo volveremos a usar más tarde para la configuración de Kubernetes.
resource "cloudflare_argo_tunnel" "k8s_zero_trust_tunnel" {
account_id = var.account_id
name = "k8s_zero_trust_tunnel"
secret = var.tunnel_secret
}
Después de haber creado el túnel, tenemos que crear las rutas para que la red de Cloudflare sepa qué tráfico debe enviar por el túnel.
Ofrecemos compatibilidad con el acceso a la API de Kubernetes mediante direcciones IPv4 e IPv6, así que configuramos rutas para ambas. Si te conectas a tu servidor de la API mediante un nombre de host, estas direcciones IP deben coincidir con lo que se devuelve a través de una búsqueda de DNS.
resource "cloudflare_tunnel_route" "k8s_zero_trust_tunnel_ipv4" {
account_id = var.account_id
tunnel_id = cloudflare_argo_tunnel.k8s_zero_trust_tunnel.id
network = "198.51.100.101/32"
comment = "Kubernetes API Server (IPv4)"
}
resource "cloudflare_tunnel_route" "k8s_zero_trust_tunnel_ipv6" {
account_id = var.account_id
tunnel_id = cloudflare_argo_tunnel.k8s_zero_trust_tunnel.id
network = "2001:DB8::101/128"
comment = "Kubernetes API Server (IPv6)"
}
A continuación, configuraremos los parámetros de Cloudflare Gateway para que sea compatible con los servidores y clientes de la API.
Como utilizamos TLS mutuo entre los clientes y el servidor de la API, y no todo el tráfico entre kubectl y los servidores de la API es HTTP, hemos desactivado la inspección de HTTP para estas conexiones.
resource "cloudflare_teams_list" "k8s_apiserver_ips" {
account_id = var.account_id
name = "Kubernetes API IPs"
type = "IP"
items = ["198.51.100.101/32", "2001:DB8::101/128"]
}
resource "cloudflare_teams_rule" "k8s_apiserver_zero_trust_http" {
account_id = var.account_id
name = "Don't inspect Kubernetes API"
description = "Allow connections from kubectl to API"
precedence = 10000
action = "off"
enabled = true
filters = ["http"]
traffic = format("any(http.conn.dst_ip[*] in $%s)", replace(cloudflare_teams_list.k8s_apiserver_ips.id, "-", ""))
}
Para personalizar aún más tu seguridad, puedes emparejar estas reglas con otras de Zero Trust, como confirmación de dispositivos, duración de la sesión y políticas de acceso de usuarios y grupos.
Implementar túneles
Una vez que hayas creado y configurado tus túneles, puedes implementar sus puntos de conexión en tu red. Hemos optado por implementar los túneles como pods, ya que esto nos permite utilizar las estrategias de implementación de Kubernetes para desplegar las actualizaciones y gestionar los fallos de nodos.
Esto crea un ConfigMap de Kubernetes con una configuración básica, que permite el enrutamiento WARP para el ID del túnel especificado. Puedes obtener este ID de túnel desde tu sistema de gestión de configuración, desde el panel de control de Cloudflare Zero Trust, o ejecutando el siguiente comando desde otro dispositivo conectado a la misma cuenta.
apiVersion: v1
kind: ConfigMap
metadata:
name: tunnel-zt
namespace: example
labels:
tunnel: api-zt
data:
config.yaml: |
tunnel: 8e343b13-a087-48ea-825f-9783931ff2a5
credentials-file: /opt/zt/creds/creds.json
metrics: 0.0.0.0:8081
warp-routing:
enabled: true
cloudflared tunnel list
A continuación, tendremos que crear un secreto para nuestras credenciales del túnel. Aunque deberías utilizar un sistema de gestión de secretos, para simplificar crearemos uno directamente aquí.
Esto crea un nuevo secreto "tunnel-creds" en el espacio de nombres "example" con el archivo de credenciales que espera el túnel.
jq -cn --arg accountTag $CF_ACCOUNT_TAG \
--arg tunnelID $CF_TUNNEL_ID \
--arg tunnelName $CF_TUNNEL_NAME \
--arg tunnelSecret $CF_TUNNEL_SECRET \
'{AccountTag: $accountTag, TunnelID: $tunnelID, TunnelName: $tunnelName, TunnelSecret: $tunnelSecret}' | \
kubectl create secret generic -n example tunnel-creds --from-file=creds.json=/dev/stdin
Ahora podemos implementar los túneles. Implementamos varias réplicas para garantizar que algunas estén siempre disponibles, incluso mientras se vacían los nodos.
Uso de Kubectl con Cloudflare Zero Trust
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
tunnel: api-zt
name: tunnel-api-zt
namespace: example
spec:
replicas: 3
selector:
matchLabels:
tunnel: api-zt
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
template:
metadata:
labels:
tunnel: api-zt
spec:
containers:
- args:
- tunnel
- --config
- /opt/zt/config/config.yaml
- run
env:
- name: GOMAXPROCS
value: "2"
- name: TZ
value: UTC
image: cloudflare/cloudflared:2022.5.3
livenessProbe:
failureThreshold: 1
httpGet:
path: /ready
port: 8081
initialDelaySeconds: 10
periodSeconds: 10
name: tunnel
ports:
- containerPort: 8081
name: http-metrics
resources:
limits:
cpu: "1"
memory: 100Mi
volumeMounts:
- mountPath: /opt/zt/config
name: config
readOnly: true
- mountPath: /opt/zt/creds
name: creds
readOnly: true
volumes:
- secret:
name: tunnel-creds
name: creds
- configMap:
name: tunnel-api-zt
name: config
Después de implementar el agente Cloudflare Zero Trust, los miembros de tu equipo podrán acceder a la API de Kubernetes sin tener que configurar ningún túnel SOCKS especial.
¿Y ahora qué?
kubectl version --short
Client Version: v1.24.1
Server Version: v1.24.1
Si lo pruebas, envíanos tus comentarios. Seguimos mejorando Zero Trust para los flujos de trabajo que no usen HTTP.