Por qué importan las Network Policies

En Kubernetes, por defecto, todos los pods pueden comunicarse libremente entre sí, independientemente del namespace. Esta permisividad es conveniente durante el desarrollo, pero en producción es un problema serio. Un pod comprometido puede escanear la red interna, acceder a bases de datos, servicios de cache y cualquier otro workload sin restricción.

El modelo Zero Trust parte del principio de que ningún tráfico debe ser confiable por defecto. Cada comunicación necesita ser explícitamente autorizada. Las Network Policies son el mecanismo de Kubernetes para implementar este principio en la capa de red, creando segmentación entre workloads.

En la práctica, esto significa:

  • Los pods solo acceden a lo que necesitan (principio de menor privilegio)
  • Un servicio comprometido no puede moverse lateralmente por el cluster
  • El tráfico de salida está controlado, impidiendo la exfiltración de datos hacia destinos no autorizados

Para que las Network Policies funcionen, el cluster necesita un CNI (Container Network Interface) que las implemente. No todo CNI soporta Network Policies. Entre los que las soportan están Calico, Cilium y Weave Net.


NetworkPolicy nativa de Kubernetes

La NetworkPolicy nativa usa la API networking.k8s.io/v1 y funciona con cualquier CNI que la implemente. Opera en las capas L3/L4 del modelo OSI, es decir, filtra tráfico por IP, puerto y protocolo (TCP/UDP).

Qué soporta

  • Selección de pods por labels (podSelector)
  • Filtrado por namespace (namespaceSelector)
  • Filtrado por bloques de IP (CIDR)
  • Control de ingress (tráfico de entrada) y egress (tráfico de salida)
  • Puertos y protocolos específicos

Qué no soporta

  • Filtrado por dominio (FQDN)
  • Inspección de capa 7 (HTTP path, method, headers)
  • Reglas basadas en identidad del servicio
  • Filtrado TLS/SNI

Ejemplo: default deny para todo el namespace

El primer paso en cualquier estrategia de Network Policy es bloquear todo el tráfico por defecto. Después, se permite solo lo necesario.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: produccion
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Con esta policy aplicada, ningún pod en el namespace produccion acepta tráfico de entrada ni puede enviar tráfico de salida. A partir de aquí, se crean policies específicas para cada flujo permitido.

Ejemplo: permitir ingress de un pod específico

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
  namespace: produccion
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: api-server
      ports:
        - port: 5432
          protocol: TCP

Solo los pods con el label app: api-server pueden acceder al puerto 5432 de los pods con label app: postgres. Todo el resto del tráfico de ingress está bloqueado.

Limitaciones

La gran limitación de la NetworkPolicy nativa es que opera solo en L3/L4. Se puede decir “pod A puede acceder a pod B en el puerto 8080”, pero no se puede decir “pod A puede hacer GET en /api/health pero no POST en /api/admin”. Además, no es posible crear reglas basadas en dominio (FQDN), lo que dificulta el control de acceso a servicios externos cuyos IPs cambian con frecuencia.


CiliumNetworkPolicy

La CiliumNetworkPolicy usa la API cilium.io/v2 y exige que Cilium sea el CNI del cluster. Ofrece todo lo que la NetworkPolicy nativa hace, más capacidades significativas en las capas superiores.

Capacidades adicionales

  • Filtrado L7: inspección de solicitudes HTTP por path, method y headers
  • Reglas por FQDN (toFQDNs): permite o bloquea acceso a dominios externos
  • DNS-aware: resuelve nombres y aplica reglas dinámicamente conforme los IPs cambian
  • Filtrado TLS/SNI: control basado en Server Name Indication
  • Identity-based: reglas basadas en la identidad criptográfica de los workloads, no solo en labels

Ejemplo: filtrado L7 por HTTP path y method

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-health-check-only
  namespace: produccion
spec:
  endpointSelector:
    matchLabels:
      app: api-server
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: monitoring
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
          rules:
            http:
              - method: GET
                path: "/health"
              - method: GET
                path: "/metrics"

En este ejemplo, el servicio de monitoreo solo puede hacer solicitudes GET a /health y /metrics en el puerto 8080 del api-server. Cualquier otra solicitud HTTP está bloqueada, incluso viniendo del mismo pod.

Ejemplo: egress restringido por FQDN

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-external-apis
  namespace: produccion
spec:
  endpointSelector:
    matchLabels:
      app: payment-service
  egress:
    - toFQDNs:
        - matchName: "api.stripe.com"
        - matchName: "api.pagar.me"
      toPorts:
        - ports:
            - port: "443"
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
          rules:
            dns:
              - matchPattern: "*.stripe.com"
              - matchPattern: "*.pagar.me"

El payment-service solo puede hacer solicitudes externas a api.stripe.com y api.pagar.me en el puerto 443. La segunda regla permite consultas DNS al kube-dns, pero solo para los dominios permitidos. Sin esta regla de DNS, el pod no puede resolver los nombres y la regla de FQDN no funciona.


Tabla comparativa

CaracterísticaNetworkPolicy (nativa)CiliumNetworkPolicy
APInetworking.k8s.io/v1cilium.io/v2
CNICualquiera que la implementeSolo Cilium
CapaL3/L4L3/L4 + L7
Selección por labels
Selección por namespace
Filtrado por CIDR
Filtrado por FQDNNoSí (toFQDNs)
Filtrado HTTP (path, method)No
Filtrado DNSNo
Filtrado TLS/SNINo
Identity-basedNo
Portabilidad entre CNIsNo

Cuándo usar cuál

EscenarioElección
Cluster sin Cilium como CNINetworkPolicy
Necesita filtrar por dominio (FQDN)CiliumNetworkPolicy
Necesita filtrar por HTTP path/methodCiliumNetworkPolicy
Quiere portabilidad entre diferentes CNIsNetworkPolicy
Necesita filtrado TLS/SNICiliumNetworkPolicy
Reglas simples de L3/L4 son suficientesNetworkPolicy
Necesita controlar qué dominios un pod puede resolver vía DNSCiliumNetworkPolicy

Si el cluster ya usa Cilium, aproveche las CiliumNetworkPolicies para escenarios que exigen más granularidad. Para reglas básicas de aislamiento entre pods y namespaces, la NetworkPolicy nativa es suficiente y más portable.

Los dos enfoques no son mutuamente exclusivos. Es posible usar NetworkPolicy nativa para reglas L3/L4 básicas y CiliumNetworkPolicy para reglas L7 y FQDN en el mismo cluster con Cilium.


Ejemplo práctico: permitir acceso a APIs externas por dominio

Un escenario común es cuando un servicio necesita acceder a APIs externas como gateways de pago, servicios de monitoreo o APIs de terceros. Con la NetworkPolicy nativa, sería necesario definir bloques CIDR para cada IP de estos servicios. El problema es que estos IPs cambian con frecuencia (CDNs, load balancers, failovers), y mantener CIDRs fijos se vuelve impracticable.

Con la CiliumNetworkPolicy y toFQDNs, se define el dominio directamente. Cilium intercepta las resoluciones DNS y actualiza las reglas de firewall automáticamente conforme los IPs cambian.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: notification-service-egress
  namespace: produccion
spec:
  endpointSelector:
    matchLabels:
      app: notification-service
  egress:
    - toFQDNs:
        - matchName: "api.sendgrid.com"
        - matchName: "fcm.googleapis.com"
        - matchName: "api.twilio.com"
      toPorts:
        - ports:
            - port: "443"
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
          rules:
            dns:
              - matchPattern: "*.sendgrid.com"
              - matchPattern: "*.googleapis.com"
              - matchPattern: "*.twilio.com"

El notification-service solo puede enviar solicitudes a SendGrid, Firebase Cloud Messaging y Twilio. Cualquier intento de acceder a otro dominio o IP está bloqueado. Si un atacante compromete este pod, no puede exfiltrar datos hacia servidores externos.


Comunicación pod-a-pod vía Service DNS

Para la comunicación interna entre servicios en el cluster, tanto la NetworkPolicy nativa como la CiliumNetworkPolicy permiten seleccionar pods por labels. En el caso de Cilium, la directiva toEndpoints funciona de forma similar al podSelector, pero integrada al modelo de identidad de Cilium.

Los pods se comunican usando el DNS interno de Kubernetes:

  • Mismo namespace: http://nombre-del-service:puerto
  • Namespace diferente: http://nombre-del-service.namespace.svc.cluster.local:puerto

Ejemplo: api-server accede a postgres y redis en el mismo namespace

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: api-server-egress
  namespace: produccion
spec:
  endpointSelector:
    matchLabels:
      app: api-server
  egress:
    - toEndpoints:
        - matchLabels:
            app: postgres
      toPorts:
        - ports:
            - port: "5432"
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            app: redis
      toPorts:
        - ports:
            - port: "6379"
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP

El api-server solo puede comunicarse con postgres en el puerto 5432 y redis en el puerto 6379. La regla de DNS permite que resuelva los nombres de los services internos. La Network Policy controla si la comunicación está permitida. El DNS del Service resuelve hacia dónde va el tráfico. Son capas complementarias.


Referencias