Introducción
En abril de 2025, un ingeniero de Reddit reportó en r/devops un hallazgo que encendió alertas en la comunidad: un endpoint interno de su cluster de Kubernetes estaba accesible públicamente sin autenticación. El problema no era un 0-day ni un fallo de software, sino un error de configuración en un Ingress Controller mal aplicado durante la migración de servicios a Kubernetes. Lo más preocupante es que el endpoint en cuestión controlaba el balanceo de carga de tráfico crítico entre regiones, lo que expuso potencialmente metadatos de pods, logs de operación e incluso rutas internas de la infraestructura de Reddit.
Este caso no es aislado. Según datos de OVHcloud, el 68% de los incidentes de seguridad en entornos cloud en 2024 fueron causados por configuraciones incorrectas en servicios expuestos a internet, y Kubernetes lidera el ranking de servicios mal configurados. La pregunta clave no es si esto puede pasar en tu infraestructura, sino cuándo y cómo lo detectarás antes de que alguien más lo haga.
Qué ocurrió
En Reddit, el problema comenzó durante la migración de servicios legacy a Kubernetes. Según el reporte en Reddit, el equipo usó un Ingress Controller basado en NGINX para enrutar tráfico hacia pods internos. El error ocurrió en la definición de la regla de Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: internal-traffic-router
spec:
rules:
- host: internal-reddit.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: internal-service
port:
number: 8080El problema no estaba en la definición del Ingress en sí, sino en dos detalles críticos:
- Falta de
ingressClassName: El Ingress Controller no estaba explícitamente asociado al Ingress, lo que en algunos clusters permite que el controlador por defecto (a menudo mal configurado) asuma el control. Esto es común en clusters donde se usan múltiples controladores o versiones antiguas de Kubernetes. - Host sin wildcard: El host
internal-reddit.example.comno estaba restringido a un namespace específico ni a un dominio interno controlado por DNS interno (como*.internal). Esto permitió que, en ciertos escenarios de resolución DNS, el endpoint fuera accesible desde fuera del cluster.
El error persistió porque:
- No hubo una revisión automática de configuraciones (policy-as-code) que bloqueara reglas sin
ingressClassName. - El equipo no monitoreaba tráfico anómalo en el Ingress Controller: no había alertas para IPs externas accediendo a hosts con nombres internos.
Impacto para DevOps / Infraestructura / Cloud / Seguridad
El impacto de este tipo de errores va más allá de una exposición momentánea:
Para equipos de DevOps e Infraestructura:- Violación del principio de menor privilegio: Cualquier endpoint interno accesible desde internet rompe la segmentación de red que Kubernetes intenta imponer por defecto.
- Fuga de metadatos: En Kubernetes, los endpoints internos suelen exponer información sensible como nombres de pods, IPs internas, versiones de servicios e incluso variables de entorno de otros equipos.
- Riesgo de escalada: Un atacante podría usar la información expuesta para pivotear hacia otros servicios internos, especialmente si no hay network policies que limiten el tráfico entre namespaces.
- CVE aplicables: No hay un CVE específico para este error, pero se alinea con prácticas inseguras descritas en CWE-284 (Improper Access Control), donde el acceso no está restringido por diseño.
- Compliance: Configuraciones como esta violan controles de frameworks como NIST 800-53 (AC-3, Access Enforcement) y CIS Benchmarks para Kubernetes (controles 5.1.1 a 5.1.5).
- Exposición a ataques: Según SUSE Security, el 42% de los clusters Kubernetes auditados en 2024 tenían endpoints internos accesibles desde internet debido a errores similares.
- Costos ocultos: Servicios como AWS ALB o GCP Ingress que exponen endpoints internos pueden generar costos adicionales por tráfico no planificado.
- Detección tardía: Sin monitoreo adecuado en el Ingress Controller, la exposición puede durar semanas o meses, como en el caso de Reddit.
Detalles técnicos
El error en Reddit se relaciona con cómo Kubernetes y los Ingress Controllers manejan el enrutamiento. Estos son los componentes clave involucrados:
- Kubernetes Ingress API (versión 1.19+):
networking.k8s.io/v1 es estable desde Kubernetes 1.19, pero su configuración depende de los Ingress Controllers.– La falta de ingressClassName en el Ingress permite que el controlador por defecto (a menudo NGINX o Traefik) asuma la regla, incluso si no está configurado para ese propósito.
- Ingress Controllers comunes:
ingressClassName y nginx.ingress.kubernetes.io/whitelist-source-range.– Traefik: Versiones 2.10.x tienen un bug conocido (CVE-2024-2912) donde reglas de Ingress sin clases pueden exponer servicios internos si el Middleware no está correctamente aplicado.
– Istio: Versiones 1.20.x a 1.21.2 permiten configuraciones de Gateway sin restricciones de namespace si no se usa VirtualService con hosts específicos.
- Configuraciones críticas omitidas:
ingressClassName: Obligatorio desde Kubernetes 1.20 para asociar el Ingress a un controlador específico.– nginx.ingress.kubernetes.io/whitelist-source-range: Restringe el acceso a IPs específicas.
– spec.tls: Si el Ingress usa HTTPS, debe incluir certificados válidos para el host interno. En el caso de Reddit, no había TLS, lo que exponía tráfico en claro.
- Herramientas de detección:
ingressClassName en controles como 5.1.1 (Kubernetes CIS Benchmark).– OPA/Gatekeeper: Políticas como la siguiente bloquean Ingress sin clases:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredIngressClasses
metadata:
name: require-ingress-class
spec:
match:
kinds:
- apiGroups: ["networking.k8s.io"]
kinds: ["Ingress"]
parameters:
allowedClasses:
- "nginx"
- "traefik"Qué deberían hacer los administradores y equipos técnicos
Para evitar que un error similar afecte tu infraestructura, seguí estos pasos concretos:
1. Auditar la configuración actual de Ingress
Ejecutá estos comandos para listar todos los Ingress sin ingressClassName o con hosts internos mal definidos:
# Listar Ingress sin ingressClassName
kubectl get ingress --all-namespaces -o json |
jq -r '.items[] | select(.spec.ingressClassName == null) | "\(.metadata.namespace)/\(.metadata.name)"'
# Listar Ingress con hosts que contengan "internal", "private", "admin", etc.
kubectl get ingress --all-namespaces -o json |
jq -r '.items[] | select(.spec.rules[].host | test("internal|private|admin")) | "\(.metadata.namespace)/\(.metadata.name)"'2. Aplicar políticas de policy-as-code
Usá Gatekeeper o Kyverno para bloquear Ingress sin ingressClassName y con hosts internos mal definidos:
# Ejemplo con Gatekeeper: bloquear Ingress con hosts internos sin TLS
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockInternalIngress
metadata:
name: block-internal-ingress
spec:
match:
kinds:
- apiGroups: ["networking.k8s.io"]
kinds: ["Ingress"]
parameters:
internalHosts: ["*.internal", "*.private", "localhost"]
requireTLS: true3. Configurar el Ingress Controller correctamente
Para NGINX Ingress Controller (versión 1.10.1+), aplicá estas reglas:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: secure-internal-service
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- internal-service.internal
secretName: internal-tls-secret
rules:
- host: internal-service.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: internal-service
port:
number: 80804. Monitorear tráfico anómalo en Ingress Controllers
Configurá Prometheus + Grafana para alertar sobre:
- Tráfico hacia hosts internos desde IPs externas.
- Solicitudes a endpoints sin autenticación (
401o403inesperados).
Ejemplo de alerta en Prometheus:
- alert: IngressInternalHostFromExternal
expr: sum by (ingress, namespace) (
rate(nginx_ingress_controller_requests{host=~".*internal.*"}[5m])
) > 0
for: 1h
labels:
severity: warning
annotations:
summary: "Ingress con host interno accedido desde internet"
description: "El Ingress {{ $labels.ingress }} en el namespace {{ $labels.namespace }} está recibiendo tráfico desde IPs externas"5. Segmentar tráfico con Network Policies
Aplicá Network Policies para restringir el tráfico entre namespaces:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-except-internal
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
access: "internal"
ports:
- protocol: TCP
port: 80806. Capacitar a los equipos en seguridad de Kubernetes
- Revisión de PRs: Exigí que toda regla de Ingress sea revisada por un segundo ingeniero, especialmente si incluye hosts internos.
- Simulacros: Usá herramientas como kube-hunter para simular ataques a tu cluster.
Conclusión
El caso de Reddit no es un fallo de software, sino de diseño y operaciones. La exposición de endpoints internos en Kubernetes es un error común que puede evitarse con:
- Configuraciones explícitas: Siempre definí
ingressClassNamey restringí hosts internos con DNS controlado. - Automatización: Usá policy-as-code para bloquear configuraciones inseguras antes de que lleguen a producción.
- Monitoreo activo: Alertá sobre tráfico anómalo en Ingress Controllers y endpoints internos.
- Segmentación: Aplicá Network Policies para limitar el tráfico entre namespaces.
La infraestructura moderna exige que los equipos de DevOps y Seguridad trabajen con la misma rigurosidad que los desarrolladores aplican a su código. Un endpoint mal configurado no es un «error de novato»: es un riesgo que puede costar datos y reputación. La lección de Reddit es clara: la seguridad en Kubernetes no se improvisa, se diseña.
