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ísticaNetworkPolicy (nativa)CiliumNetworkPolicy
APInetworking.k8s.io/v1cilium.io/v2
CNIQualquer que implementeApenas Cilium
CamadaL3/L4L3/L4 + L7
Seleção por labelsSimSim
Seleção por namespaceSimSim
Filtragem por CIDRSimSim
Filtragem por FQDNNãoSim (toFQDNs)
Filtragem HTTP (path, method)NãoSim
Filtragem DNSNãoSim
Filtragem TLS/SNINãoSim
Identity-basedNãoSim
Portabilidade entre CNIsSimNão

Quando usar qual

CenárioEscolha
Cluster sem Cilium como CNINetworkPolicy
Precisa filtrar por domínio (FQDN)CiliumNetworkPolicy
Precisa filtrar por HTTP path/methodCiliumNetworkPolicy
Quer portabilidade entre diferentes CNIsNetworkPolicy
Precisa de filtragem TLS/SNICiliumNetworkPolicy
Regras simples de L3/L4 são suficientesNetworkPolicy
Precisa controlar quais domínios um pod pode resolver via DNSCiliumNetworkPolicy

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.


Referências