Controle o tráfego do Kubernetes com políticas de rede

KubernetesBeginner
Pratique Agora

Introdução

Os namespaces organizam os recursos, mas não bloqueiam automaticamente o tráfego de rede. As NetworkPolicies definem quais Pods selecionados podem enviar ou receber conexões, desde que o plugin de rede do cluster aplique essas regras. A rede Calico preparada para este laboratório faz isso.

Você comprovará que o tráfego inicialmente está aberto, aplicará uma negação padrão e permitirá então apenas um cliente identificado por rótulo, vindo de um namespace aprovado.

O laboratório segue um fluxo seguro para trabalhar com políticas: estabelecer uma linha de base funcional, bloquear o tráfego, adicionar a menor exceção necessária e testar tanto um caso permitido quanto um bloqueado. Testar apenas o cliente permitido não revelaria uma política ampla demais.

Implante o servidor e os clientes

Nesta etapa, crie namespaces separados para a aplicação e os clientes, um servidor NGINX e dois clientes de longa duração.

Os comandos usam intencionalmente formas imperativas conhecidas, para que o experimento de rede não fique oculto em YAML não relacionado. Um cliente recebe o rótulo role=approved; o outro recebe role=untrusted. O namespace dos clientes recebe access=approved. Mais adiante, os seletores das políticas usarão esses dois níveis de rótulos.

cd /home/labex/project/network-policy
kubectl create namespace practice-apps
kubectl create namespace practice-clients
kubectl label namespace practice-clients access=approved

Crie e exponha o servidor. kubectl expose cria um Service ClusterIP cujo seletor corresponde ao rótulo de Pod app=protected-web gerado pelo Deployment:

kubectl create deployment protected-web -n practice-apps --image=nginx:1.27-alpine --replicas=1
kubectl expose deployment protected-web -n practice-apps --port=80 --target-port=80

Crie dois Pods clientes de diagnóstico. Os loops de espera mantêm os clientes disponíveis para execuções repetidas de testes com kubectl exec:

kubectl run cria diretamente cada Pod independente. --labels associa a identidade que será selecionada pela política, --restart=Never impede que o kubectl crie um controlador de workload, e -- separa as opções do kubectl do comando sh -c executado dentro do contêiner. No comando de espera, --all significa que todos os Pods em practice-clients precisam ficar prontos.

kubectl run approved-client -n practice-clients --image=busybox:1.36 --image-pull-policy=IfNotPresent --labels=role=approved --restart=Never -- sh -c 'while true; do sleep 30; done'
kubectl run untrusted-client -n practice-clients --image=busybox:1.36 --image-pull-policy=IfNotPresent --labels=role=untrusted --restart=Never -- sh -c 'while true; do sleep 30; done'
kubectl wait --for=condition=Ready pod -n practice-clients --all --timeout=120s
kubectl rollout status deployment/protected-web -n practice-apps --timeout=60s

A simples separação em namespaces ainda permite que ambos os clientes alcancem o Service.

Os comandos de verificação de prontidão devem terminar exibindo evidências dos dois Pods clientes e do Deployment do servidor:

pod/approved-client condition met
pod/untrusted-client condition met
deployment "protected-web" successfully rolled out

Neste ponto, os rótulos descrevem os clientes, mas ainda não os restringem. Um rótulo só se transforma em regra de tráfego quando é usado por um seletor de NetworkPolicy.

Comprove que o tráfego está inicialmente aberto

Nesta etapa, faça uma requisição ao Service entre namespaces a partir dos dois clientes. O nome totalmente qualificado inclui o nome do Service e o namespace.

Dentro do mesmo namespace, protected-web seria suficiente. A partir de practice-clients, protected-web.practice-apps informa ao DNS do cluster tanto o nome do Service quanto o namespace ao qual ele pertence. Teste os dois clientes antes de aplicar a política, para que qualquer falha posterior possa ser atribuída à política, e não ao DNS ou à configuração do Service.

Assim como no laboratório de verificação de integridade, wget -qO- exibe o corpo da resposta sem mostrar o progresso. --timeout=3 evita que uma requisição bloqueada fique pendurada indefinidamente, e o pipe para grep exibe apenas o texto reconhecível do NGINX.

cd /home/labex/project/network-policy
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl exec untrusted-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl get networkpolicies -n practice-apps

Sem uma NetworkPolicy que selecione os Pods, a entrada é permitida. Isso corrige o equívoco comum de que os namespaces, por si só, funcionam como limites de segurança de rede.

Os dois comandos devem encontrar Welcome to nginx, e kubectl get networkpolicies deve informar que não há recursos. Essa é a linha de base conhecida e funcional que você modificará intencionalmente.

<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
No resources found in practice-apps namespace.

A repetição do texto da página comprova que as identidades dos dois clientes conseguem alcançar o mesmo Service antes do início do isolamento.

Aplique a negação padrão de entrada

Nesta etapa, selecione todos os Pods em practice-apps e não permita nenhuma origem de entrada.

Uma NetworkPolicy só passa a restringir o tráfego dos Pods selecionados por ela e na direção indicada em policyTypes. Aqui, podSelector: {} seleciona todos os Pods em practice-apps, e a ausência de qualquer regra de permissão de ingress cria uma negação padrão de entrada.

cd /home/labex/project/network-policy
cat <<'EOF' > default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny-ingress, namespace: practice-apps}
spec:
  podSelector: {}
  policyTypes: [Ingress]
EOF
kubectl apply -f default-deny.yaml
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps || echo 'blocked as expected'

Um podSelector vazio seleciona todos os Pods no namespace da política. O tempo limite esperado comprova que a entrada foi negada, e não que o Service desapareceu.

|| echo ... trata o código de saída diferente de zero esperado do wget, permitindo que o terminal continue. Neste experimento controlado, a falha é uma evidência. O Pod do servidor continua pronto; apenas a admissão de rede foi alterada.

networkpolicy.networking.k8s.io/default-deny-ingress created
wget: download timed out
command terminated with exit code 1
blocked as expected

O tempo limite é esperado porque um pacote bloqueado não recebe resposta da aplicação. A última linha confirma que o shell converteu deliberadamente essa falha esperada em um resultado legível.

Permita um cliente aprovado

Nesta etapa, adicione uma política de permissão que exija simultaneamente um namespace aprovado e um rótulo de Pod aprovado.

As NetworkPolicies são aditivas. A política de negação padrão continua ativa, enquanto esta segunda política acrescenta uma única origem permitida. O Pod do servidor selecionado poderá aceitar conexões TCP na porta 80 somente quando o namespace de origem e o Pod de origem corresponderem aos critérios definidos.

cd /home/labex/project/network-policy
cat <<'EOF' > allow-approved.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: allow-approved-client, namespace: practice-apps}
spec:
  podSelector: {matchLabels: {app: protected-web}}
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector: {matchLabels: {access: approved}}
          podSelector: {matchLabels: {role: approved}}
      ports:
        - {protocol: TCP, port: 80}
EOF
kubectl apply -f allow-approved.yaml
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl exec untrusted-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps || echo 'untrusted client blocked'

Como os dois seletores estão na mesma entrada de from, ambos precisam corresponder. Entradas separadas representariam uma condição lógica OU.

A requisição do cliente aprovado deve funcionar novamente, enquanto a requisição do cliente não confiável deve continuar expirando por tempo limite. Esse resultado combinado comprova que a política é seletiva, em vez de simplesmente reabrir toda a entrada.

networkpolicy.networking.k8s.io/allow-approved-client created
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
wget: download timed out
command terminated with exit code 1
untrusted client blocked

Leia o resultado como um par: o conteúdo da página para approved-client, seguido pelo tempo limite para untrusted-client. Ver apenas a requisição bem-sucedida não provaria que a regra é suficientemente restritiva.

Resumo

Você comprovou que os namespaces não isolam o tráfego por padrão, aplicou a negação padrão de entrada e permitiu a passagem de um cliente usando critérios combinados de namespace, Pod, protocolo e porta.