Joao Brietzke Blog

← voltar

Gateway Load Balancer: colocando um firewall no caminho do tráfego

Os load balancers que a gente conhece são destinos. O cliente resolve um nome no DNS, recebe um IP, abre uma conexão com o load balancer, e o load balancer abre outra conexão com algum servidor atrás dele. Ele é o fim de uma conexão e o começo de outra.

O Gateway Load Balancer não é nada disso. Nenhuma aplicação aponta para ele. Ele não aparece em DNS nenhum. Você nunca abre uma conexão com ele. Ele é um desvio no caminho, declarado numa tabela de rotas, e o tráfego que passa por ele sequer fica sabendo que passou.

Ele existe para resolver um problema bem específico: colocar um firewall de terceiros no meio do caminho do seu tráfego, sem que esse firewall vire um ponto único de falha e sem mentir sobre quem é a origem e o destino de cada pacote.

Se a mecânica geral de load balancer ainda não está fresca, ela está em Load balancer: o que é e como a lógica dele funciona por dentro. Este post assume aquele.


Antes de tudo: o que é um appliance virtual

Não dá para entender o GWLB sem entender o que ele carrega nas costas.

Por trinta anos, firewall foi uma caixa física. Você comprava um Palo Alto, um FortiGate, um Check Point, parafusava no rack e passava cabos de rede por ele. Todo o tráfego atravessava aquela caixa, e ela bloqueava o que não devia passar. O nome genérico dessa categoria é middlebox: um equipamento que fica no meio do caminho fazendo alguma coisa com os pacotes que passam.

Na nuvem não existe rack. Não há onde plugar cabo.

Então os fabricantes fizeram o óbvio: pegaram o mesmo sistema operacional que rodava dentro da caixa e empacotaram como uma imagem de máquina virtual.

Caixa física Appliance virtual
Palo Alto PA-series VM-Series (AMI)
FortiGate FortiGate-VM
Check Point CloudGuard Network
F5 BIG-IP BIG-IP VE

Isso é um appliance virtual: uma caixa de segurança física, transformada numa instância EC2.

E repare que é um produto pronto, não software que você instala. Você não sobe um Ubuntu vazio e configura um firewall nele. Você sobe a AMI do fabricante e ela inicia já sendo um firewall, com o console próprio dele, a mesma sintaxe de regras da caixa física, e normalmente sem nem te dar um shell de propósito geral. Atualizar é trocar a imagem, não rodar apt upgrade.

O que isso resolve, e por que empresas grandes insistem nesse caminho em vez de usar só o que a AWS oferece:

  • Profundidade. Security group é filtro de camada 4 com estado. AWS Network Firewall roda regras Suricata. Nenhum dos dois faz identificação de milhares de aplicações, decriptação de TLS com a CA da empresa, sandbox de arquivo ou DLP. Se o requisito de segurança cita uma capacidade que só o fabricante tem, o appliance é como ela entra na AWS.
  • Continuidade, que é o motivo real na maioria dos casos. Um banco tem quinze anos de regras escritas naquele fabricante, um time certificado nele, um processo de auditoria construído em cima dos logs dele e um documento de conformidade que cita o produto pelo nome. Reescrever tudo isso em termos nativos da AWS é um projeto de anos com risco real. Rodar o mesmo firewall que já se conhece, agora como EC2, custa infinitamente menos atrito.

E o que isso custa. Aqui está a parte que importa para o resto do post: um appliance virtual é uma instância EC2. Tudo o que era bom na caixa física, o hardware dedicado, a redundância de fábrica, o fato de ela ficar inerte no rack, sumiu. O que sobrou é uma VM que pode morrer, que tem teto de throughput ditado pelo tipo de instância, e que, no momento em que você a coloca no caminho do tráfego, vira um gargalo do qual tudo depende.

O problema: como se insere isso no caminho

Antes do GWLB, em 2020, existiam duas saídas, e as duas eram ruins.

Rota apontando para uma ENI. A tabela de rotas diz 0.0.0.0/0 -> eni-abc123. Funciona, e é uma instância só. Não escala, e se ela morrer o tráfego cai num buraco negro até alguma Lambda reescrever a tabela de rotas. Failover medido em dezenas de segundos. O motivo dessa fragilidade é o mesmo discutido em ENI: uma ENI é um objeto preso a uma AZ e a uma instância por vez.

Fazer o appliance atuar como proxy ou NAT. Ele termina a conexão e abre outra. Funciona, mas agora todo pacote que o appliance e o destino enxergam tem o IP de origem errado. Suas regras de segurança, seus logs e a lógica de IP do cliente na aplicação passam todos a mentir. E IP de origem é exatamente o insumo do qual uma política de firewall depende.

O GWLB é a terceira saída: escala horizontal, com health check, e transparente. O appliance vê o IP real do cliente e o IP real do destino. O destino vê o IP real do cliente. Como se não houvesse nada no caminho.

A lógica: por que encapsular é obrigatório

Essa é a parte que vale internalizar, porque todo o resto decorre dela.

Um pacote que chega no GWLB é endereçado a outra pessoa. O cliente 10.0.1.5 está falando com 54.x.x.x. O IP do appliance não aparece em lugar nenhum desse pacote.

Então o GWLB tem uma contradição para resolver: precisa entregar o pacote a um appliance sem alterar o pacote, porque alterar o pacote destrói justamente a razão de ele existir.

A única saída é um túnel. O GWLB embrulha o pacote original, intacto, dentro de um pacote novo endereçado ao appliance:

externo:  srcIP=GWLB   dstIP=appliance   UDP dport=6081
  ├── cabeçalho GENEVE (VNI + opções TLV)
  └── pacote original: srcIP=10.0.1.5  dstIP=54.x.x.x  TCP 443 ...

O protocolo é o GENEVE (RFC 8926), sobre UDP na porta 6081. Duas propriedades fizeram dele a escolha certa:

  • Ele é agnóstico ao conteúdo. O GWLB não precisa de um listener por porta como o ALB e o NLB. TCP, UDP, ICMP, ESP, qualquer coisa sobre IP é encapsulada do mesmo jeito. É por isso que a configuração de listener do GWLB é essencialmente inexistente.
  • Ele tem opções TLV extensíveis. A AWS enfia metadado ali dentro: o ID do endpoint, o ID do attachment e um cookie de fluxo. É assim que o appliance consegue distinguir tráfego vindo de VPCs diferentes e aplicar política diferente para cada um, sem que nada disso apareça no pacote do cliente.

O preço do embrulho é 68 bytes de overhead, e isso vira problema operacional de verdade mais adiante.

As duas peças

O GWLB é composto de dois objetos que vivem em lugares diferentes:

  • O GWLB propriamente dito, na VPC de segurança, junto com os appliances no target group.
  • O GWLB Endpoint (GWLBe), na VPC consumidora, que é o objeto que você coloca na tabela de rotas.

Os dois são ligados por um VPC Endpoint Service, a mesma máquina do PrivateLink usada com NLB. Essa escolha não é detalhe de implementação, é o que define o modelo de propriedade: o time de segurança é dono da VPC de appliances e publica um serviço; os times de aplicação apenas criam um endpoint e adicionam uma linha na tabela de rotas. Nenhum dos dois lados precisa acesso à conta do outro.

O caminho de um pacote

  VPC consumidora                     │  VPC de segurança
                                      │
  cliente 10.0.1.5                    │
      │                               │
      │ tabela de rotas: 0.0.0.0/0 -> vpce-gwlb
      ▼                               │
  ┌────────┐   GENEVE / UDP 6081      │   ┌────────┐    ┌──────────────┐
  │ GWLBe  │ ════════════════════════════▶│  GWLB  │───▶│ appliance A  │
  └────────┘                          │   └────────┘ │  ├──────────────┤
      ▲   ▲  volta pelo mesmo túnel   │      ▲       └─▶│ appliance B  │
      │   ╚════════════════════════════════════════════ └──────────────┘
      ▼                               │     (desencapsula, inspeciona, reencapsula)
  IGW / destino original              │

Passo a passo:

  1. Uma entrada de tabela de rotas manda o pacote para o GWLBe em vez do próximo salto natural. Pode ser a tabela de rotas da subnet, a edge association do Internet Gateway ou a do Virtual Private Gateway.
  2. O GWLBe encaminha para o GWLB pela conexão de PrivateLink.
  3. O GWLB escolhe um alvo por hash de fluxo e encapsula o pacote original em GENEVE.
  4. O appliance recebe na UDP 6081, desencapsula, inspeciona o pacote real e aplica a política.
  5. Se permitiu, ele reencapsula e devolve pelo mesmo túnel. Se negou, simplesmente descarta, e não há nada para devolver.
  6. O GWLB desencapsula e entrega o pacote de volta ao GWLBe, que o solta para continuar em direção ao destino original.

O passo 5 é onde o GWLB se separa de todos os outros load balancers. O alvo aqui não gera uma resposta. Ele devolve o mesmo pacote que recebeu. O appliance é um passa-tudo, e isso precisa ter sido construído de propósito: ele tem que falar GENEVE e tem que devolver pelo túnel. É por isso que se usa AMI de fabricante e não qualquer EC2.

E é por isso também que o modelo mental correto é desvio, não encaminhamento. Depois da inspeção, o pacote volta para o mesmo endpoint de onde saiu e retoma a rota em que já estava. O GWLB não entrega nada às suas instâncias de aplicação. Ele não sabe que elas existem. Quem entrega é o ALB, ou a tabela de rotas, ou o Internet Gateway, e nada disso passa pelo GWLB.

ALB e NLB respondem "qual dos meus servidores atende essa requisição?". GWLB responde "qual dos meus inspetores olha esse pacote antes dele seguir para onde já ia?".

Aderência de fluxo, o que faz tudo funcionar ou quebrar

Um firewall com estado é inútil se o SYN cai no appliance A e o ACK cai no appliance B. O segundo enxerga um ACK de uma conexão que ele nunca viu nascer, e descarta. Então o GWLB gruda cada fluxo num alvo:

Modo Hash sobre Quando
5-tuple (padrão) IP origem, IP destino, porta origem, porta destino, protocolo TCP e UDP. Obrigatório com appliance mode no TGW.
3-tuple IP origem, IP destino, protocolo Tráfego que não é TCP nem UDP, ou configurado explicitamente
2-tuple IP origem, IP destino Configurado explicitamente, aderência mais grossa

É o mesmo hash de camada 4 que sustenta o NLB. A diferença é que o GWLB garante também que as duas direções do fluxo caiam no mesmo appliance, e é essa garantia que permite ao firewall manter estado.

Roteamento assimétrico e o appliance mode

O GWLB só consegue garantir simetria dentro do escopo dele. Quando entra Transit Gateway na história, o TGW escolhe a AZ de cada direção de forma independente, e você ganha o clássico:

ida:    VPC A ─▶ TGW ─▶ inspeção AZ-a ─▶ VPC B      (firewall a vê o SYN)
volta:  VPC B ─▶ TGW ─▶ inspeção AZ-b ─▶ VPC A      (firewall b vê o ACK e descarta)

A correção é ligar o appliance mode no attachment do TGW. Com ele ativo, o TGW calcula o hash do fluxo uma vez e mantém as duas direções na mesma ENI da VPC de inspeção pelo tempo de vida do fluxo. Sem appliance mode, a alternativa histórica era fazer SNAT no appliance para forçar o retorno, o que reintroduz exatamente a mentira de IP de origem que o GWLB tinha eliminado.

Ligue o appliance mode. É de longe a causa mais comum de incidente com GWLB.

Ligado a isso: o GWLBe é um objeto zonal, e cross-zone load balancing vem desligado por padrão. Mais barato e com menos latência, porque o tráfego não atravessa AZ, mas significa que os appliances de uma AZ só atendem o endpoint daquela AZ. Crie um GWLBe por AZ.

Os padrões que você vai encontrar

1. Inspeção distribuída. Um GWLBe em cada VPC, appliances centralizados. Na entrada, a edge association do IGW manda o tráfego para o GWLBe antes dele chegar no ALB. Na saída, a tabela de rotas da subnet manda 0.0.0.0/0 para o GWLBe antes do NAT Gateway. Não precisa de Transit Gateway, tem a menor latência, e custa mais endpoints.

2. Inspeção centralizada com Transit Gateway. Uma VPC de inspeção com o GWLB e o auto scaling group de appliances. As tabelas de rotas do TGW forçam o tráfego entre VPCs e para on-premises a passar por ela. É a arquitetura de referência do whitepaper de multi-VPC da AWS e onde empresas grandes acabam parando. Exige appliance mode.

3. Saída centralizada. A VPC de inspeção tem o GWLB e o NAT Gateway. Todo tráfego de saída de todas as contas é inspecionado e sai por um conjunto único e auditado de IPs. Popular em setor regulado, porque "toda saída passa por um firewall e sai por estes IPs" é um controle que se demonstra para auditoria em uma frase.

4. Uso que não é segurança. O GWLB é uma primitiva genérica de "desviar pacotes para uma frota". Também se usa para captura de pacotes e analytics de rede, IDS com Suricata ou Zeek, e appliances próprios escritos em casa, desde que falem GENEVE.

As pegadinhas

  • MTU. O GENEVE soma 68 bytes. Atravessando um IGW o teto é 1500, então um pacote interno de 1500 fragmenta ou é descartado. Pelo TGW o teto é 8500. Na prática isso aparece como "requisição pequena funciona, upload grande trava", que é o sintoma clássico de PMTUD quebrado.
  • O health check não prova o caminho de dados. O GWLB sonda o appliance por HTTP, HTTPS ou TCP numa porta própria, não pelo túnel GENEVE. Um alvo saudável não significa que a inspeção está funcionando.
  • Custo em camadas. Hora de GWLB, mais GLCU (Gateway Load Balancer Capacity Units), mais hora de GWLBe por endpoint e por AZ, mais processamento de dados dos dois lados. Arquiteturas distribuídas multiplicam endpoints rápido.
  • Depurar fica mais difícil. O desvio é transparente, então um pacote descartado parece falha silenciosa de rede para o time de aplicação. O log do appliance vira a fonte da verdade, e ele fica em outra conta e em outro console.
  • Dois planos de controle. AWS de um lado, console do fabricante do outro. Isso é custo operacional permanente, não custo de setup.

Onde ele se encaixa na família

ALB NLB GWLB
Camada 7 4 3 + 4
Como o tráfego chega DNS DNS Tabela de rotas
É um destino? Sim Sim Não, é um desvio
O que balanceia Requisições Conexões Fluxos de pacotes
Tipo de alvo Servidores da aplicação Servidores da aplicação Appliances de inspeção
Preserva IP de origem No X-Forwarded-For Sim Sim, integralmente
Configura listener por porta Sim Sim Não, aceita todo IP

O modelo mental

O GWLB é um desvio no roteamento com balanceamento embutido. Ele existe porque inspecionar tráfego exige duas coisas que são contraditórias entre si: o pacote precisa chegar num appliance cujo IP não está no pacote, e o pacote não pode ser alterado. Encapsular é a única forma de satisfazer as duas ao mesmo tempo, e todo o resto do produto, o GENEVE, o hairpin, a aderência de 5-tuple, o appliance mode, é consequência direta dessa escolha.

Dito de outro jeito, dividindo responsabilidade:

appliance virtual  =  a função (firewall, IDS, DPI)
GWLB               =  como colocar N deles no caminho, com segurança

Os fabricantes resolveram "como eu rodo meu firewall na nuvem". Faltava resolver "como eu coloco dez deles no caminho, escalo, monitoro saúde e faço failover em segundos, sem NAT e sem perder os IPs originais". Essa metade era a que faltava.

Vale notar que a Azure lançou o próprio Gateway Load Balancer com desenho praticamente idêntico, trocando GENEVE por VXLAN e a tabela de rotas por encadeamento a um frontend IP. Quando duas nuvens chegam independentemente na mesma primitiva, é sinal forte de que a abstração está certa.

Como isso se conecta com o resto

Este post fecha um arco. Em Load balancer a pergunta era "para qual servidor mando essa requisição". Aqui a pergunta mudou de natureza: não é mais sobre entregar, é sobre interceptar antes de entregar, e por isso o objeto deixou de ser um destino e virou uma entrada de rota.

A tabela de rotas que faz o desvio é a mesma discutida em CIDR e subnets na VPC, e a razão de o failover por ENI não bastar, que é o buraco original que o GWLB preenche, está em ENI. A separação entre o que o GWLB enxerga (IP e porta) e o que ele não enxerga (o conteúdo) é a mesma divisão de camadas de Modelo OSI e TCP/IP, com um detalhe curioso: o GWLB não enxerga o conteúdo, mas entrega o conteúdo inteiro para quem enxerga.

Referências