Entendendo Kubernetes Ingress Controllers e as vulnerabilidades recentemente anunciadas do Nginx

Recentemente, uma série de vulnerabilidades foi anunciada para o Nginx Ingress Controller, algo bem preocupante, levando em consideração que este é o Ingress Controller "padrão" para a maioria das pessoas que estão começando com Kubernetes. Uma delas, em particular, recebeu um score de 9.8, pois não exige basicamente nenhum privilégio especial, bastando o usuário ter conectividade com o componente vulnerável. Fui torpedeado com várias dúvidas a respeito essa semana no trabalho e fora dele a respeito, e acredito que vale a pena compartilhar algumas delas por aqui. O que é Ingress? Ingress é um tipo de recurso do Kubernetes que permite descrever como sua aplicação pode ser acessada usando definições de protocolo de alto nível ("camada 7"). Diferentemente dos Services do tipo LoadBalancer, que configuram um balanceador de carga camada 4 para encaminhar todo tráfego recebido para um conjunto de Pods normalmente associados a um único Deployment, o Ingress permite que várias aplicações diferentes sejam servidas a partir de um mesmo domínio ou balanceador: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: subs-devsres-com spec: rules: - host: "subs.devsres.com" http: paths: - pathType: Prefix path: "/admin" backend: service: name: admin-module port: number: 8080 - pathType: Prefix path: "/assets" backend: service: name: assets port: number: 8080 - pathType: Prefix path: "/esskay" backend: service: name: uwu port: number: 8080 As requisições ao domínio subs.devsres.com serão encaminhadas para os módulos da minha aplicação admin, assets ou esskay, dependendo do path acessado na URL. Eu descrevo neste post como usar Ingress pode ser o que viabiliza um projeto por minimizar o número de balanceadores instanciados em uma nuvem pública. O que é um Ingress Controller? Controllers são a base do funcionamento do Kubernetes, e correspondem a programas que executam de maneira contínua para garantir que o que você descreve em seus arquivos yaml sejam criados e continuem existindo. Controllers instanciam seus Pods ao criar um Deployment, e garantem que iniciarão novamente caso a aplicação dê um crash, ou sejam recriados caso alguma máquina do cluster Kubernetes falhe. Um Ingress Controller é um programa que lê os objetos Ingress do cluster e viabiliza sua implementação em algum lugar. A descrição acima ("em algum lugar") é bastante genérica porque, diferentemente da grande maioria dos outros recursos nativos, como Pods, Services ou Deployments, clusters Kubernetes não vêm com Controllers para o objeto Ingress na sua instalação padrão. E isso é algo mais comum do que parece: Services do tipo LoadBalancer também podem não funcionar, dependendo de onde seu cluster é criado; NetworkPolicies dependem do plugin CNI escolhido e podem não funcionar; PersistentVolumes também precisam de configuração de componentes externos... Isso contribui com a percebida complexidade de uso do Kubernetes, que nem mesmo soluções gerenciadas de nuvem conseguem apaziguar. A pior parte: muitos dos componentes sem Controllers nativos não são "opcionais" para a maioria dos usos da tecnologia! O que é o Ingress Nginx Controller? O projeto Kubernetes mantém três subprojetos de Ingress sob sua guarda; um deles é o Ingress Nginx Controller, e os demais são específicos para clouds públicas (AWS e GCP). Os dois Ingress Controllers de nuvens públicas operam como o esperado, traduzindo o que é descrito nos yamls de Ingress para configurações das ofertas de balanceadores de carga. Já o Ingress Nginx Controller executa um Nginx como proxy reverso e gera configurações para este Nginx a partir dos seus objetos Ingress. Embora a documentação liste uns trinta controllers diferentes, é bastante comum pensar que o Ingress Nginx Controller é a principal oferta disponível para Kubernetes fora das principais nuvens, e sua ampla adoção reflete este pensamento. Ingress NGINX Controller ou NGINX Ingress Controller? Parece piada, mas não é. Mas o Ingress NGINX Controller não tem nada em comum com o NGINX Ingress Controller além do NGINX no nome. O primeiro, que é o assunto em ênfase deste post, é um subprojeto mantido pelo próprio Kubernetes. O segundo é mantido pelos próprios mantenedores do NGINX, cuja empresa foi comprada pela F5. São softwares diferentes, mantidos por pessoas diferentes, com parametrizações e funcionalidades diferentes e - o mais importante - incompatíveis entre si. Não é tão raro assim ver usuários instalando a segunda solução pensando ser a primeira, e se perguntando porque as configurações aplicadas não estão funcionando! E aqui vem um dos aspectos mais importantes do entendimento de Ingress no Kubernetes: por ser um objeto exageradamente simples, e

Mar 31, 2025 - 09:56
 0
Entendendo Kubernetes Ingress Controllers e as vulnerabilidades recentemente anunciadas do Nginx

Recentemente, uma série de vulnerabilidades foi anunciada para o Nginx Ingress Controller, algo bem preocupante, levando em consideração que este é o Ingress Controller "padrão" para a maioria das pessoas que estão começando com Kubernetes. Uma delas, em particular, recebeu um score de 9.8, pois não exige basicamente nenhum privilégio especial, bastando o usuário ter conectividade com o componente vulnerável.

Fui torpedeado com várias dúvidas a respeito essa semana no trabalho e fora dele a respeito, e acredito que vale a pena compartilhar algumas delas por aqui.

O que é Ingress?

Ingress é um tipo de recurso do Kubernetes que permite descrever como sua aplicação pode ser acessada usando definições de protocolo de alto nível ("camada 7").

Diferentemente dos Services do tipo LoadBalancer, que configuram um balanceador de carga camada 4 para encaminhar todo tráfego recebido para um conjunto de Pods normalmente associados a um único Deployment, o Ingress permite que várias aplicações diferentes sejam servidas a partir de um mesmo domínio ou balanceador:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: subs-devsres-com
spec:
  rules:
  - host: "subs.devsres.com"
    http:
      paths:
      - pathType: Prefix
        path: "/admin"
        backend:
          service:
            name: admin-module
            port:
              number: 8080
      - pathType: Prefix
        path: "/assets"
        backend:
          service:
            name: assets
            port:
              number: 8080
      - pathType: Prefix
        path: "/esskay"
        backend:
          service:
            name: uwu
            port:
              number: 8080

As requisições ao domínio subs.devsres.com serão encaminhadas para os módulos da minha aplicação admin, assets ou esskay, dependendo do path acessado na URL.

Eu descrevo neste post como usar Ingress pode ser o que viabiliza um projeto por minimizar o número de balanceadores instanciados em uma nuvem pública.

O que é um Ingress Controller?

Controllers são a base do funcionamento do Kubernetes, e correspondem a programas que executam de maneira contínua para garantir que o que você descreve em seus arquivos yaml sejam criados e continuem existindo.

Controllers instanciam seus Pods ao criar um Deployment, e garantem que iniciarão novamente caso a aplicação dê um crash, ou sejam recriados caso alguma máquina do cluster Kubernetes falhe.

Um Ingress Controller é um programa que lê os objetos Ingress do cluster e viabiliza sua implementação em algum lugar.

A descrição acima ("em algum lugar") é bastante genérica porque, diferentemente da grande maioria dos outros recursos nativos, como Pods, Services ou Deployments, clusters Kubernetes não vêm com Controllers para o objeto Ingress na sua instalação padrão. E isso é algo mais comum do que parece: Services do tipo LoadBalancer também podem não funcionar, dependendo de onde seu cluster é criado; NetworkPolicies dependem do plugin CNI escolhido e podem não funcionar; PersistentVolumes também precisam de configuração de componentes externos... Isso contribui com a percebida complexidade de uso do Kubernetes, que nem mesmo soluções gerenciadas de nuvem conseguem apaziguar.

A pior parte: muitos dos componentes sem Controllers nativos não são "opcionais" para a maioria dos usos da tecnologia!

O que é o Ingress Nginx Controller?

O projeto Kubernetes mantém três subprojetos de Ingress sob sua guarda; um deles é o Ingress Nginx Controller, e os demais são específicos para clouds públicas (AWS e GCP).

Os dois Ingress Controllers de nuvens públicas operam como o esperado, traduzindo o que é descrito nos yamls de Ingress para configurações das ofertas de balanceadores de carga.

Já o Ingress Nginx Controller executa um Nginx como proxy reverso e gera configurações para este Nginx a partir dos seus objetos Ingress.

Embora a documentação liste uns trinta controllers diferentes, é bastante comum pensar que o Ingress Nginx Controller é a principal oferta disponível para Kubernetes fora das principais nuvens, e sua ampla adoção reflete este pensamento.

Ingress NGINX Controller ou NGINX Ingress Controller?

Parece piada, mas não é. Mas o Ingress NGINX Controller não tem nada em comum com o NGINX Ingress Controller além do NGINX no nome.

O primeiro, que é o assunto em ênfase deste post, é um subprojeto mantido pelo próprio Kubernetes. O segundo é mantido pelos próprios mantenedores do NGINX, cuja empresa foi comprada pela F5.

São softwares diferentes, mantidos por pessoas diferentes, com parametrizações e funcionalidades diferentes e - o mais importante - incompatíveis entre si. Não é tão raro assim ver usuários instalando a segunda solução pensando ser a primeira, e se perguntando porque as configurações aplicadas não estão funcionando!

E aqui vem um dos aspectos mais importantes do entendimento de Ingress no Kubernetes: por ser um objeto exageradamente simples, e balanceamento de carga em camada 7 ser algo absurdamente complexo, existe um volume absurdo de configurações que dependem de implementação, e são ativados de maneiras diferentes. Ingress Controllers, via de regra, não são facilmente intercambiáveis, especialmente se você fizer uso de funcionalidades avançadas. E certas configurações, mesmo que disponíveis em outros Controllers (como o encaminhamento de certificados TLS para a aplicação), podem exigir reescrita do código da sua aplicação porque são implementadas de maneira diferentes por cada um.

Quais os problemas do Ingress NGINX Controller?

Componentes de software de infraestrutura geralmente precisam executar com privilégios especiais em clusters Kubernetes.

E aqui está um dos principais problemas do Ingress NGINX Controller:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: ingress-nginx
rules:
- apiGroups:
  - ""
  resources:
  - configmaps
  - endpoints
  - nodes
  - pods
  - secrets
  - namespaces
  verbs:
  - list
  - watch
...

A instalação padrão, que imagino ser usada por 99,99% dos usuários, dá permissão para acessar todos os Secrets de um cluster. Isso geralmente é necessário porque uma das funcionalidades do objeto Ingress costuma ser ler certificados TLS a partir de Secrets:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: devsres-tls
spec:
  tls:
  - hosts:
      - subs.devsres.com
    secretName: subs-devsres-com
  rules:
  - host: subs.devsres.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: devsres
            port:
              number: 8080

Um atacante capaz de comprometer o Ingress Controller daria acesso não só a todos os certificados (e chaves privadas destes), como outras credenciais importantes normalmente salvas em Secrets (de bancos de dados, serviços, chaves de API).

Além disso, se você criar um token de ServiceAccount persistente, aos moldes do que era comum no Kubernetes até a versão 1.22, o atacante também poderia acessá-los e potencialmente escalar privilégios.

Como todo componente de infraestrutura, entender sobre a segurança do Ingress Controller é essencial, em especial pelo fato de ser este geralmente o único componente exposto na Internet.

"Validating Webhook Configuration"

Existe um objeto nativo do Kubernetes chamado validatingwebhookconfiguration que entrega para os usuários uma funcionalidade interessante: a de inspecionar as operações em recursos na API.

O Ingress NGINX Controller faz uso desse recurso para monitorar a criação e atualização dos objetos Ingress:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: ingress-nginx-admission
...
webhooks:
- admissionReviewVersions:
  - v1
  clientConfig:
    service:
      name: ingress-nginx-controller-admission
      namespace: ingress-nginx
      path: /networking/v1/ingresses
      port: 443
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: validate.nginx.ingress.kubernetes.io
  namespaceSelector: {}
  objectSelector: {}
  rules:
  - apiGroups:
    - networking.k8s.io
    apiVersions:
    - v1
    operations:
    - CREATE
    - UPDATE
    resources:
    - ingresses
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10

Para que ele faz isso? Para evitar conflito entre configurações.

Como o objeto Ingress define um conjunto bastante simplório de configurações e sua implementação fica ao encargo de outro software, é possível que as configurações definidas sejam incompatíveis entre si.

Inclusive, uma infelicidade do Ingress é que uma configuração em uma Namespace pode impactar negativamente aplicações em outra Namespace - é o único caso em todo o Kubernetes em que consigo imaginar algo desse tipo de ser possível!

Por exemplo, caso definamos o seguinte objeto Ingress para receber as conexões feitas ao balanceador sem escopo definido:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: aplicacao1
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 8080

Este segundo objeto define uma configuração que conflita diretamente com a primeira: elas não podem existir no mesmo cluster!

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: desastre
  namespace: aplicacao2
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: desastre
            port:
              number: 8080

O resultado final de conflitos de configurações deste tipo é imprevisível: pode ser que a segunda substitua a primeira, ou que o Controller identifique o conflito e escolha o mais antigo, ou o pior dos casos, que costuma ser a geração de uma configuração inválida que faz com que o Nginx não consiga mais atualizar suas configurações, deixando o Ingress "estagnado" e inconsistente com o ambiente, incapaz de executar as reconciliações necessárias para um ambiente super dinâmico.

Para evitar isso, o Ingress NGINX Controller implementa essa validação antes da criação ou modificação de objetos, fazendo com que o Kubernetes encaminhe o objeto para um programa (que, no caso, é o próprio ingress-nginx-controller):

$ kubectl create -f ingress2.yaml
Error from server (AlreadyExists): error when creating "ingress2.yaml": ingresses.networking.k8s.io "ingress1" already exists

Problemas do Webhook

O principal problema é o fato do Controller estar acessível pela rede:

# Dentro do container, você pode ver o processo que escuta na porta 8443:
$ netstat -nptlwev | fgrep 8443
tcp6       0      0 :::8443                 :::*                    LISTEN      101        174698     7/nginx-ingress-controller

Um atacante que consiga comprometer um Pod qualquer em execução (ou criar um, ainda que sem qualquer privilégio especial) terá acesso livre ao Webhook:

$ kubectl run shellhacker  --image=shellhacker
pod/shellhacker created

$ kubectl exec -it shellhacker -- /bin/bash
root@shellhacker:/# curl -kv https://ingress-nginx-controller-admission.ingress-nginx:443
*   Trying 10.100.66.184:443...
* Connected to ingress-nginx-controller-admission.ingress-nginx (10.100.66.184) port 443 (#0)
> Host: ingress-nginx-controller-admission.ingress-nginx
> User-Agent: curl/7.88.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 400 Bad Request
< Date: Mon, 31 Mar 2025 07:35:47 GMT
< Content-Length: 0
<
* Connection #0 to host ingress-nginx-controller-admission.ingress-nginx left intact

Felizmente, a maior parte das instalações padrão não expõe o acesso ao Controller diretamente na Internet, apenas internamente ao cluster:

$ kubectl -n ingress-nginx get services
NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.100.246.156   a26b9332b9a154edc9b6d741dc759fdd-1876731234.us-west-2.elb.amazonaws.com   80:32691/TCP,443:31242/TCP   85m
ingress-nginx-controller-admission   ClusterIP      10.100.66.184                                                                        443/TCP                      85m

Mas pode acontecer que usuários mais desavisados acabem por expô-lo também! Mais uma coisa para prestar atenção nas suas instalações!

Vulnerabilidade CVE 2025-1974 e demais

O uso do Ingress NGINX Controller na forma de webhook para este recurso de validação é justamente o assunto da vulnerabilidade mais grave - a CVE 2025-1974.

As outras vulnerabilidades dependem da criação de objetos Ingresses no cluster formatadas de maneira maliciosa; essa operação é considerada privilegiada e a maior parte dos workloads em execução em um cluster não tem permissão para fazê-lo, tornando-as perigosas mas mais difíceis de explorar.

A CVE 2025-1974 é muito mais grave que as outras porque você não precisa da permissão para criar Ingresses; basta ter acesso de rede ao Controller com acesso ao Webhook para enviar um objeto Ingress devidamente formatado que pode, entre outras coisas, gerar execução de código malicioso.

Como remediar?

As correções já saíram e os ambientes já podem ser atualizados.

Caso a atualização não seja possível, o importante é ao menos bloquear os acessos ao Controller pela rede de Pods. A maneira mais comum de fazer isso é simplesmente removendo o ValidatingWebhookconfiguration ingress-nginx-admission, mas também pode ser feito por meio do uso de NetworkPolicies.

O uso de NetworkPolicies, costumeiramente ignorado, é uma excelente solução para mitigar este tipo de risco. Nenhum software, por mais maduro que seja, está livre de falhas desta natureza. O importante, neste caso, é sempre se antecipar e impedir ser pego de surpresa por algo desta gravidade.