Por que Network Policies importam
No Kubernetes, por padrão, todos os pods podem se comunicar livremente entre si, independentemente do namespace. Essa permissividade facilita o desenvolvimento, mas em produção é um problema sério. Um pod comprometido pode escanear a rede interna, acessar bancos de dados, serviços de cache e qualquer outro workload sem restrição.
O modelo Zero Trust parte do princípio de que nenhum tráfego deve ser confiável por padrão. Cada comunicação precisa ser explicitamente autorizada. Network Policies são o mecanismo do Kubernetes para implementar esse princípio na camada de rede, criando segmentação entre workloads.
Na prática, isso significa:
- Pods só acessam o que precisam (princípio do menor privilégio)
- Um serviço comprometido não consegue se mover lateralmente pelo cluster
- Tráfego de saída é controlado, impedindo exfiltração de dados para destinos não autorizados
Para que as Network Policies funcionem, o cluster precisa de um CNI (Container Network Interface) que as implemente. Nem todo CNI suporta Network Policies. Entre os que suportam estão Calico, Cilium e Weave Net.
NetworkPolicy nativa do Kubernetes
A NetworkPolicy nativa usa a API networking.k8s.io/v1 e funciona com qualquer CNI que a implemente. Ela opera nas camadas L3/L4 do modelo OSI, ou seja, filtra tráfego por IP, porta e protocolo (TCP/UDP).
O que ela suporta
- Seleção de pods por labels (
podSelector) - Filtragem por namespace (
namespaceSelector) - Filtragem por blocos de IP (CIDR)
- Controle de ingress (tráfego de entrada) e egress (tráfego de saída)
- Portas e protocolos específicos
O que ela não suporta
- Filtragem por domínio (FQDN)
- Inspeção de camada 7 (HTTP path, method, headers)
- Regras baseadas em identidade do serviço
- Filtragem TLS/SNI
Exemplo: default deny para todo o namespace
O primeiro passo em qualquer estratégia de Network Policy é bloquear todo o tráfego por padrão. Depois, você libera apenas o necessário.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: producao
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Com essa policy aplicada, nenhum pod no namespace producao aceita tráfego de entrada nem consegue enviar tráfego de saída. A partir daqui, você cria policies específicas para cada fluxo permitido.
Exemplo: permitir ingress de um pod específico
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: producao
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- podSelector:
matchLabels:
app: api-server
ports:
- port: 5432
protocol: TCP
Apenas pods com o label app: api-server podem acessar a porta 5432 dos pods com label app: postgres. Todo o restante do tráfego de ingress é bloqueado.
Limitações
A grande limitação da NetworkPolicy nativa é que ela opera apenas em L3/L4. Você consegue dizer “pod A pode acessar pod B na porta 8080”, mas não consegue dizer “pod A pode fazer GET em /api/health mas não POST em /api/admin”. Além disso, não é possível criar regras baseadas em domínio (FQDN), o que dificulta o controle de acesso a serviços externos cujos IPs mudam com frequência.
CiliumNetworkPolicy
A CiliumNetworkPolicy usa a API cilium.io/v2 e exige que o Cilium seja o CNI do cluster. Ela oferece tudo que a NetworkPolicy nativa faz, mais capacidades significativas nas camadas superiores.
Capacidades extras
- Filtragem L7: inspeção de requisições HTTP por path, method e headers
- Regras por FQDN (
toFQDNs): permite ou bloqueia acesso a domínios externos - DNS-aware: resolve nomes e aplica regras dinamicamente conforme os IPs mudam
- Filtragem TLS/SNI: controle baseado no Server Name Indication
- Identity-based: regras baseadas na identidade criptográfica dos workloads, não apenas em labels
Exemplo: filtragem L7 por HTTP path e method
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-health-check-only
namespace: producao
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"
Nesse exemplo, o serviço de monitoramento só pode fazer GET em /health e /metrics na porta 8080 do api-server. Qualquer outra requisição HTTP é bloqueada, mesmo vindo do mesmo pod.
Exemplo: egress restrito por FQDN
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-external-apis
namespace: producao
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"
O payment-service só pode fazer requisições externas para api.stripe.com e api.pagar.me na porta 443. A segunda regra libera consultas DNS ao kube-dns, mas apenas para os domínios permitidos. Sem essa regra de DNS, o pod não consegue resolver os nomes e a regra de FQDN não funciona.
Tabela comparativa
| Característica | NetworkPolicy (nativa) | CiliumNetworkPolicy |
|---|---|---|
| API | networking.k8s.io/v1 | cilium.io/v2 |
| CNI | Qualquer que implemente | Apenas Cilium |
| Camada | L3/L4 | L3/L4 + L7 |
| Seleção por labels | Sim | Sim |
| Seleção por namespace | Sim | Sim |
| Filtragem por CIDR | Sim | Sim |
| Filtragem por FQDN | Não | Sim (toFQDNs) |
| Filtragem HTTP (path, method) | Não | Sim |
| Filtragem DNS | Não | Sim |
| Filtragem TLS/SNI | Não | Sim |
| Identity-based | Não | Sim |
| Portabilidade entre CNIs | Sim | Não |
Quando usar qual
| Cenário | Escolha |
|---|---|
| Cluster sem Cilium como CNI | NetworkPolicy |
| Precisa filtrar por domínio (FQDN) | CiliumNetworkPolicy |
| Precisa filtrar por HTTP path/method | CiliumNetworkPolicy |
| Quer portabilidade entre diferentes CNIs | NetworkPolicy |
| Precisa de filtragem TLS/SNI | CiliumNetworkPolicy |
| Regras simples de L3/L4 são suficientes | NetworkPolicy |
| Precisa controlar quais domínios um pod pode resolver via DNS | CiliumNetworkPolicy |
Se o cluster já usa Cilium, aproveite as CiliumNetworkPolicies para cenários que exigem mais granularidade. Para regras básicas de isolamento entre pods e namespaces, a NetworkPolicy nativa é suficiente e mais portável.
As duas abordagens não são mutuamente exclusivas. É possível usar NetworkPolicy nativa para regras L3/L4 básicas e CiliumNetworkPolicy para regras L7 e FQDN no mesmo cluster com Cilium.
Exemplo prático: liberando acesso a APIs externas por domínio
Um cenário comum é quando um serviço precisa acessar APIs externas como gateways de pagamento, serviços de monitoramento ou APIs de terceiros. Com a NetworkPolicy nativa, você precisaria definir blocos CIDR para cada IP desses serviços. O problema é que esses IPs mudam com frequência (CDNs, load balancers, failovers), e manter CIDRs fixos se torna inviável.
Com a CiliumNetworkPolicy e toFQDNs, você define o domínio diretamente. O Cilium intercepta as resoluções DNS e atualiza as regras de firewall automaticamente conforme os IPs mudam.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: notification-service-egress
namespace: producao
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"
O notification-service só pode enviar requisições para SendGrid, Firebase Cloud Messaging e Twilio. Qualquer tentativa de acessar outro domínio ou IP é bloqueada. Se um atacante comprometer esse pod, ele não consegue exfiltrar dados para servidores externos.
Comunicação pod-a-pod via Service DNS
Para comunicação interna entre serviços no cluster, tanto a NetworkPolicy nativa quanto a CiliumNetworkPolicy permitem selecionar pods por labels. No caso do Cilium, a diretiva toEndpoints funciona de forma similar ao podSelector, mas integrada ao modelo de identidade do Cilium.
Os pods se comunicam usando o DNS interno do Kubernetes:
- Mesmo namespace:
http://nome-do-service:porta - Namespace diferente:
http://nome-do-service.namespace.svc.cluster.local:porta
Exemplo: api-server acessa postgres e redis no mesmo namespace
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-server-egress
namespace: producao
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
O api-server só pode se comunicar com postgres na porta 5432 e redis na porta 6379. A regra de DNS permite que ele resolva os nomes dos services internos. A Network Policy controla se a comunicação é permitida. O DNS do Service resolve para onde o tráfego vai. São camadas complementares.