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ística | NetworkPolicy (nativa) | CiliumNetworkPolicy |
|---|---|---|
| API | networking.k8s.io/v1 | cilium.io/v2 |
| CNI | Cualquiera que la implemente | Solo Cilium |
| Capa | L3/L4 | L3/L4 + L7 |
| Selección por labels | Sí | Sí |
| Selección por namespace | Sí | Sí |
| Filtrado por CIDR | Sí | Sí |
| Filtrado por FQDN | No | Sí (toFQDNs) |
| Filtrado HTTP (path, method) | No | Sí |
| Filtrado DNS | No | Sí |
| Filtrado TLS/SNI | No | Sí |
| Identity-based | No | Sí |
| Portabilidad entre CNIs | Sí | No |
Cuándo usar cuál
| Escenario | Elección |
|---|---|
| Cluster sin Cilium como CNI | NetworkPolicy |
| Necesita filtrar por dominio (FQDN) | CiliumNetworkPolicy |
| Necesita filtrar por HTTP path/method | CiliumNetworkPolicy |
| Quiere portabilidad entre diferentes CNIs | NetworkPolicy |
| Necesita filtrado TLS/SNI | CiliumNetworkPolicy |
| Reglas simples de L3/L4 son suficientes | NetworkPolicy |
| Necesita controlar qué dominios un pod puede resolver vía DNS | CiliumNetworkPolicy |
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.