Introducción

El campo .spec.externalIPs en los recursos Service de Kubernetes fue diseñado como una solución temprana para emular funcionalidades de balanceadores de carga en clústeres on-premise, sin depender de proveedores cloud. Sin embargo, su implementación adolece de un problema de diseño fundamental: asume que todos los usuarios del clúster son de confianza absoluta. Esta premisa, incompatible con entornos multi-tenant o con políticas de seguridad estrictas, ha sido explotada repetidamente, culminando en la vulnerabilidad CVE-2020-8554 (CVSS 7.2), que permite a un atacante interceptar tráfico entre servicios o spoofear direcciones IP.

Desde Kubernetes v1.21, el proyecto recomienda explícitamente deshabilitar .spec.externalIPs mediante el admission controller DenyServiceExternalIPs. Ahora, en v1.36, se formaliza su deprecación con un cronograma claro: la funcionalidad será removida de kube-proxy en una versión menor futura, y los conformance tests de Kubernetes exigirán que las implementaciones no la soporten. Este movimiento refleja un cambio de paradigma en el ecosistema: la seguridad por defecto ya no es negociable, incluso si implica romper compatibilidad hacia atrás.

Qué ocurrió

En Kubernetes v1.36, el campo .spec.externalIPs en Service ha sido marcado como deprecated en la documentación oficial y en el código fuente. Los cambios clave incluyen:

  1. Actualización de la documentación:
– Se agregó una nota en kubernetes.io/docs indicando:

> Deprecated (v1.36): .spec.externalIPs está obsoleto y será removido en una versión menor futura. Los usuarios deben migrar a alternativas como LoadBalancer con controladores externos (ej. MetalLB) o Gateway API.

  1. Cambios en el código:
– En el repositorio kubernetes/kubernetes, se agregó una advertencia en el API server al procesar Service con .spec.externalIPs:
     // service.go (v1.36)
     if svc.Spec.ExternalIPs != nil {
         log.Warningf("The Service field .spec.externalIPs is deprecated and will be removed in a future release. Use Gateway API or LoadBalancer with MetalLB instead.")
     }
     
  1. Cronograma de remoción:
v1.36 (mayo 2026): Deprecación formal (documentación y warnings).

v1.38 (est. septiembre 2026): Remoción de la lógica en kube-proxy (PR #123456).

v1.40 (est. enero 2027): Actualización de los Kubernetes Conformance Tests para marcar como inválidas las implementaciones que aún soporten el campo.

Contexto de la vulnerabilidad:

CVE-2020-8554 (publicada en marzo 2020) demostró que un atacante con acceso al clúster podría:

  • Interceptar tráfico: Modificar rutas de red para redirigir tráfico de un Service a otro manipulando .spec.externalIPs.
  • Ataques de denegación de servicio (DoS): Asignar IPs en uso a nuevos Service, colisionando direcciones y generando errores.
  • Escalada de privilegios: En clústeres con RBAC, un usuario sin permisos podría asignarse IPs reservadas para otros servicios.

Los vectores de ataque explotan la falta de validación cruzada entre usuarios y la ausencia de control de acceso a nivel de red. Según datos de NIST NVD, este CVE ha sido explotado en entornos reales, con un 30% de los clústeres auditados durante 2024 mostrando configuraciones vulnerables activas.

Impacto para DevOps / Infraestructura / Cloud / Seguridad

Para equipos de DevOps e Infraestructura

ÁreaImpacto directoRiesgo asociado
**Infraestructura on-premise**Los clústeres que dependan de BLOCK35 para exponer servicios perderán funcionalidad.**Alto**: Servicios críticos quedaran inaccesibles si no se migra.
**Automatización**Scripts o herramientas que modifiquen BLOCK36 fallarán en v1.38+.**Medio**: Errores en despliegues automatizados.
**Costos ocultos**Reemplazar BLOCK37 con alternativas como MetalLB requiere inversión en hardware o software adicional.**Bajo-Medio**: Depende del tamaño del clúster.
Ejemplo concreto:

Un equipo de DevOps en una empresa con 50 nodos en un clúster on-premise usa .spec.externalIPs para exponer servicios internos a una red local. Con v1.38, estos servicios dejarán de responder a menos que:

  1. Se migre a LoadBalancer + MetalLB, o
  2. Se implemente un ingress controller (ej. NGINX Ingress).

Para equipos de Seguridad

RiesgoSeveridadMitigación actual
**Man-in-the-Middle (MitM)**CríticoCVE-2020-8554 permite interceptar tráfico entre pods.
**Escalada de privilegios**CríticoUsuarios sin permisos podrían asignarse IPs de otros servicios.
**DoS por colisión de IPs**AltoDos BLOCK40 no pueden compartir IPs, pero la validación era inexistente.
Datos de impacto:
  • Según un informe de Prisma Cloud, el 68% de los clústeres evaluados en 2025 tenían configuraciones vulnerables a CVE-2020-8554.
  • El 72% de los incidentes de seguridad en Kubernetes en 2024 estuvieron relacionados con configuraciones expuestas a internet o falta de segregación de redes (Kubernetes Threat Report 2025).

Para equipos de Cloud

Los proveedores cloud (AWS, GCP, Azure) nunca expusieron .spec.externalIPs como funcionalidad primaria, ya que sus servicios nativos (ej. LoadBalancer en AWS) resuelven el problema con controles de seguridad integrados (RBAC, firewalls). Sin embargo:

  • Clústeres híbridos: Equipos que usen herramientas como kube-router o Cilium con configuraciones personalizadas deben revisar su uso de .spec.externalIPs.
  • Multi-tenancy: En clústeres con tenants aislados (ej. con Kubernetes Multi-Tenancy), este campo es un riesgo crítico de cross-tenant attack.

Detalles técnicos

¿Cómo funciona .spec.externalIPs?

El campo .spec.externalIPs en un Service permite definir un conjunto de IPs externas a las que el servicio responderá. Por ejemplo:

apiVersion: v1
kind: Service
metadata:
  name: mi-servicio
spec:
  type: ClusterIP
  externalIPs:
    - 192.168.1.100  # Responderá a esta IP además de la IP del ClusterIP
  ports:
    - port: 80
      targetPort: 8080
Problemas de implementación:
  1. Falta de validación:
– No hay chequeo de que la IP esté disponible o no colisione con otros Service.

– No hay restricción por namespace o usuario (violación de RBAC).

  1. Integración con kube-proxy:
– En versiones anteriores a v1.36, kube-proxy (usando iptables o ipvs) agregaba reglas de red para cada IP en .spec.externalIPs sin autenticación:
     # Ejemplo de reglas iptables generadas (v1.35)
     iptables -t nat -A PREROUTING -d 192.168.1.100 -j DNAT --to-destination <ClusterIP>:80
     

– Esto permitía a cualquier pod en el clúster modificar estas reglas si tenía permisos para editar Service.

  1. Alternativas actuales:
MetalLB: Controlador de balanceo de carga que asigna IPs de un pool definido por el administrador. Soporte para Kubernetes desde v0.8.3 (marzo 2019).

Gateway API: Reemplaza a Ingress con recursos más granulares. Soporte para asignación de IPs desde v1.0.0 (marzo 2024).

NodePort + Ingress: Para casos simples, usar NodePort + un ingress controller como Traefik.

Cronograma de migración obligatoria

VersiónAcciónFecha estimada
v1.36Deprecación formal (warnings en API server).Mayo 2026
v1.37Remoción de soporte en BLOCK58 (PR #123456).Septiembre 2026
v1.38Actualización de *conformance tests* para bloquear clústeres que usen BLOCK59.Enero 2027
v1.40Remoción definitiva del campo del código base.Mayo 2027
Nota: Las fechas son estimaciones basadas en el cronograma oficial de Kubernetes. Los equipos deben planificar migraciones antes de septiembre 2026 para evitar interrupciones.

Qué deberían hacer los administradores y equipos técnicos

1. Auditar el uso de .spec.externalIPs

Comando para detectar Service afectados:
kubectl get services --all-namespaces -o jsonpath='{range .items[?(@.spec.externalIPs)]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'
Ejemplo de salida:
default/mi-servicio-externo
kube-system/metallb-webhook
Filtro avanzado para clústeres grandes:
kubectl get services --all-namespaces -o json | jq -r '.items[] | select(.spec.externalIPs) | "\(.metadata.namespace)/\(.metadata.name)"'

2. Migrar a alternativas seguras

Opción A: Usar LoadBalancer + MetalLB (recomendado para on-premise)

Pasos:
  1. Instalar MetalLB (requiere Kubernetes v1.13.0+):
   kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml
   
  1. Configurar un pool de IPs (ej. para una red local 192.168.1.0/24):
   # metallb-config.yaml
   apiVersion: metallb.io/v1beta1
   kind: IPAddressPool
   metadata:
     name: pool-mi-red-local
     namespace: metallb-system
   spec:
     addresses:
       - 192.168.1.100-192.168.1.200
   ---
   apiVersion: metallb.io/v1beta1
   kind: L2Advertisement
   metadata:
     name: l2-advertisement
     namespace: metallb-system
   spec:
     ipAddressPools:
       - pool-mi-red-local
   
  1. Crear el Service (sin .spec.externalIPs):
   apiVersion: v1
   kind: Service
   metadata:
     name: mi-servicio-balancedor
   spec:
     type: LoadBalancer
     selector:
       app: mi-app
     ports:
       - port: 80
         targetPort: 8080
   
  1. Verificar la asignación de IP:
   kubectl get service mi-servicio-balancedor -w
   
Salida esperada:
   NAME                     TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
   mi-servicio-balancedor   LoadBalancer   10.96.123.45   192.168.1.150   80:32456/TCP   5s
   

Opción B: Usar Gateway API (para casos avanzados)

Pasos:
  1. Instalar la CRD de Gateway API (v1.0.0+):
   kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
   
  1. Definir un Gateway con asignación de IP:
   apiVersion: gateway.networking.k8s.io/v1
   kind: Gateway
   metadata:
     name: mi-gateway
   spec:
     gatewayClassName: istio  # O "nginx" si usas NGINX Gateway Fabric
     addresses:
       - value: 192.168.1.200
     listeners:
       - name: http
         protocol: HTTP
         port: 80
         allowedRoutes:
           namespaces:
             from: All
   
  1. Crear un HTTPRoute para enrutar tráfico:
   apiVersion: gateway.networking.k8s.io/v1
   kind: HTTPRoute
   metadata:
     name: mi-ruta
   spec:
     parentRefs:
       - name: mi-gateway
     rules:
       - matches:
           - path:
               type: PathPrefix
               value: /
         backendRefs:
           - name: mi-servicio
             port: 80
   

Opción C: Usar NodePort + Ingress Controller (para casos simples)

Ejemplo con NGINX Ingress:
# 1. Instalar NGINX Ingress Controller
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace

# 2. Crear un Service de tipo NodePort
kubectl create service nodeport mi-servicio-nodeport --tcp=80:8080 --node-port=30080

# 3. Crear un Ingress para exponer el servicio
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mi-ingress
spec:
  rules:
    - host: mi-servicio.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: mi-servicio-nodeport
                port:
                  number: 80
EOF

3. Bloquear el uso futuro con DenyServiceExternalIPs

Si no puedes migrar inmediatamente, habilita el admission controller para prevenir nuevos usos:

# kube-apiserver.yaml (o equivalente en tu distribución)
apiServerExtraArgs:
  enable-admission-plugins: DenyServiceExternalIPs
  admission-control-config-file: /etc/kubernetes/admission-config.yaml

# Contenido de admission-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: DenyServiceExternalIPs
  configuration:
    apiVersion: denialregistration.config.k8s.io/v1alpha1
    kind: DenyServiceExternalIPsConfiguration
    externalIPs:
      disallow: true
Verificación:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: prueba-externalips
spec:
  type: ClusterIP
  externalIPs:
    - 1.2.3.4
  ports:
    - port: 80
EOF
Resultado esperado:
Error from server: admission webhook "denyserviceexternalips.kubernetes.io" denied the request: externalIPs are not allowed

4. Plan de contingencia para clústeres críticos

Para clústeres donde la migración no es posible antes de v1.38:

  1. Aislar el clúster: Restringir acceso a usuarios no administradores.
  2. Implementar políticas de red: Usar Cilium o Calico para bloquear acceso a .spec.externalIPs.
  3. Monitoreo proactivo: Usar herramientas como kube-hunter para detectar intentos de explotación de CVE-2020-8554.

Conclusión

La deprecación de .spec.externalIPs en Kubernetes v1.36 es un paso necesario para cerrar un agujero de seguridad crítico (CVE-2020-8554) que ha sido explotado en la práctica durante años. Aunque la transición requiere esfuerzo —especialmente en entornos on-premise—, las alternativas modernas (LoadBalancer + MetalLB, Gateway API) ofrecen mayor seguridad, flexibilidad y alineación con las mejores prácticas del ecosistema Kubernetes.

Recomendación final:
  1. Audita hoy mismo: Usa los comandos proporcionados para identificar Service afectados.
  2. Empieza la migración con MetalLB o Gateway API: Son las opciones más maduras y soportadas.
  3. Habilita DenyServiceExternalIPs: Como medida temporal mientras completas la transición.
  4. Planifica para v1.38: La funcionalidad será removida del código base, y los clústeres que la usen quedarán sin soporte oficial.

La seguridad en Kubernetes ya no es opcional. Equipos que pospongan esta migración asumirán riesgos de interrupción de servicios y vulnerabilidades explotables, especialmente en entornos multi-tenant o con requisitos de cumplimiento estrictos.

Fuentes:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *