Application Gateway com WAF v2 e Terraform: Bloqueando Ataques de Verdade

Ter um Application Gateway na frente da sua aplicação já ajuda com balanceamento de carga e TLS termination, mas não protege contra SQL injection, XSS ou os outros ataques mais comuns na web. Pra isso existe o Application Gateway com WAF v2 e Terraform: a mesma infraestrutura de load balancer de camada 7, só que com uma Web Application Firewall Policy na frente, usando as regras gerenciadas da OWASP.

Neste artigo, segundo da série artigos-network (o primeiro foi o Azure Virtual Network Manager com Terraform), a gente cria um Application Gateway WAF_v2 com Terraform na frente de um backend real e testa de verdade — não só confirma que o `terraform apply` funcionou, mas manda uma tentativa de SQL injection de propósito e confirma que o WAF bloqueia.

Segurança: os comandos deste guia de Application Gateway com WAF v2 e Terraform utilizam variáveis de ambiente para credenciais. Nunca insira IDs de subscription, senhas ou chaves diretamente nos comandos — use TF_VAR_*, um arquivo .tfvars fora do Git, ou o Azure Key Vault.

Sumário

Pré-requisitos

Antes de criar o Application Gateway com WAF v2 e Terraform, você vai precisar do Azure CLI autenticado (az login), do Terraform >= 1.5 e de permissão de Network Contributor na subscription. O provider usado é o azurerm ~> 4.0 (mais o provider random, só pra gerar um sufixo único no nome do Storage Account).

az account show --query "{name:name, id:id}" -o table

Backend de teste: Storage Static Website

Pra manter o custo do teste em praticamente zero, o backend do Application Gateway com WAF v2 aqui não é uma VM nem um App Service — é um Storage Account com Static Website habilitado, servindo uma página HTML simples. O Storage Account em si vem do módulo reutilizável terraform-storage-modules; o Static Website e o blob do `index.html` continuam como recursos diretos (feature de nicho, não generalizada no módulo compartilhado):

module "storage_backend" {
  source = "../../terraform-storage-modules"

  name                = "stappgwbackend${random_string.storage_suffix.result}"
  resource_group_name = module.rg.name
  location            = var.location

  tags = var.tags
}

resource "azurerm_storage_account_static_website" "backend" {
  storage_account_id = module.storage_backend.storage_account_id
  index_document     = "index.html"
}

resource "azurerm_storage_blob" "index" {
  name                   = "index.html"
  storage_account_name   = module.storage_backend.storage_account_name
  storage_container_name = "$web"
  type                   = "Block"
  content_type           = "text/html"
  source_content         = "<html><body><h1>Backend protegido pelo Application Gateway + WAF</h1></body></html>"

  depends_on = [azurerm_storage_account_static_website.backend]
}

Rede: VNet, subnet e Public IP

O Application Gateway com WAF v2 precisa de uma subnet dedicada (não pode compartilhar subnet com outros recursos) e de um IP público Standard — aqui vindos de terraform-virtual-network-modules e terraform-public-ip-modules:

module "vnet" {
  source = "../../terraform-virtual-network-modules"

  name                = "vnet-appgw-blog-castilho"
  resource_group_name = module.rg.name
  location            = var.location
  address_space       = [var.vnet_address_space]

  subnets = [
    { key = "appgw", name = "snet-appgw", address_prefixes = [var.appgw_subnet_prefix] },
  ]

  tags = var.tags
}

module "pip_appgw" {
  source = "../../terraform-public-ip-modules"

  name                = "pip-appgw-blog-castilho"
  resource_group_name = module.rg.name
  location            = var.location

  tags = var.tags
}

WAF Policy com regras OWASP

No Application Gateway com WAF v2, a WAF Policy é um recurso separado — ela existe de forma independente e depois é associada a ele. Isso permite reaproveitar a mesma policy em vários Application Gateways. Aqui, ativamos o conjunto de regras gerenciadas da OWASP em modo Prevention (bloqueia de verdade, ao contrário do modo Detection, que só loga) — encapsulado no novo módulo terraform-appgw-waf-modules, mostrado por completo na próxima seção junto com o Application Gateway.

Criando o Application Gateway com WAF v2

Com o backend e a rede prontos, o módulo novo terraform-appgw-waf-modules junta a WAF Policy (OWASP 3.2, Prevention) e o Application Gateway em si numa chamada só — SKU WAF_v2, autoscale de 0 a 2 instâncias (mínimo custo pra um teste), backend_http_settings já com protocol = "Https"/porta 443 (ver o troubleshooting logo abaixo pra entender por quê) e pick_host_name_from_backend_address = true:

module "appgw_waf" {
  source = "../../terraform-appgw-waf-modules"

  name                 = "agw-blog-castilho"
  waf_policy_name      = "wafpolicy-blog-castilho"
  resource_group_name  = module.rg.name
  location             = var.location
  gateway_subnet_id    = module.vnet.subnets["appgw"].id
  public_ip_address_id = module.pip_appgw.id

  backend_fqdns = [
    replace(replace(module.storage_backend.primary_web_endpoint, "https://", ""), "/", ""),
  ]

  tags = var.tags
}

Internamente, o módulo cria a azurerm_web_application_firewall_policy (regras gerenciadas OWASP 3.2, modo Prevention) e o azurerm_application_gateway (SKU WAF_v2, autoscale 0-2, listener HTTP na porta 80, backend HTTPS na 443) já conectados via firewall_policy_id.

terraform plan e apply

terraform init -backend-config=backend.hcl
terraform plan -out=tfplan.out
terraform apply tfplan.out

Esse ambiente do Application Gateway com WAF v2 e Terraform cria 10 recursos: Resource Group, Storage Account + static website + blob, VNet + subnet, Public IP, WAF Policy e o Application Gateway. Atenção ao custo: diferente do Network Manager do artigo anterior, o Application Gateway v2 cobra por hora de gateway mais unidades de capacidade consumidas — não é gratuito, então vale destruir (terraform destroy) assim que terminar de testar, se for só um ambiente de estudo.

Resource group com o Application Gateway WAF v2 e demais recursos criados no Azure
Resource group depois do apply: 5 recursos incluindo o Application Gateway e a WAF Policy

Troubleshooting: o backend só fala HTTPS

A primeira versão desse Terraform usava protocol = "Http" e port = 80 no backend_http_settings, copiando o padrão mais comum de tutorial de Application Gateway. O apply passou sem erro, mas o teste de requisição real falhava — porque o Storage Static Website só responde em HTTPS por padrão (o argumento https_traffic_only_enabled do azurerm_storage_account é true por padrão no provider). O Application Gateway tentava falar HTTP com um backend que só aceita HTTPS na porta 443, e a conexão nunca se completava.

A correção foi trocar protocol = "Https" e port = 443, mantendo pick_host_name_from_backend_address = true pra garantir que o hostname enviado bate com o certificado público do domínio *.web.core.windows.net — sem precisar configurar nenhum certificado customizado no Application Gateway.

Testando o Application Gateway com WAF v2 de verdade

Depois do apply, o output devolve o IP público do Application Gateway com WAF v2. Uma requisição normal, com o cabeçalho Host apontando pro backend, confirma que a cadeia inteira funciona:

curl -s -o /dev/null -w "HTTP %{http_code}\n" "http://20.120.11.185/" \
  -H "Host: stappgwbackendvg1klbm8.z13.web.core.windows.net"
# HTTP 200

Agora o teste que importa de verdade: uma tentativa de SQL injection no mesmo endpoint, do jeito mais clássico possível (1' OR '1'='1):

curl -s -o /dev/null -w "HTTP %{http_code}\n" --get "http://20.120.11.185/" \
  -H "Host: stappgwbackendvg1klbm8.z13.web.core.windows.net" \
  --data-urlencode "id=1' OR '1'='1"
# HTTP 403

403 Forbidden — no Application Gateway com WAF v2, a regra gerenciada OWASP identificou o padrão de SQL injection e bloqueou a requisição antes dela chegar ao Storage. É essa diferença de comportamento (200 na requisição normal, 403 na maliciosa) que prova que a WAF Policy está de fato inspecionando o tráfego, e não só configurada “no papel”.

WAF Policy do Application Gateway com WAF v2 mostrando Policy state Enabled e Policy mode Prevention
WAF Policy confirmada no portal: Enabled, modo Prevention

Conclusão

Um Application Gateway com WAF v2 e Terraform configurado corretamente é a diferença entre “tem load balancer” e “tem load balancer que também bloqueia ataque de verdade” — e a única forma de confiar nisso é testando com uma requisição maliciosa real, não só confiando que o apply passou. O bug do protocolo HTTP/HTTPS neste artigo também mostra algo comum em Application Gateway: o erro não aparece no terraform apply, só na hora de testar de verdade — reforça por que vale sempre validar com uma requisição real antes de considerar o ambiente pronto.

Próximo artigo da série artigos-network: Azure NAT Gateway.

Referências oficiais

Interessado em saber mais sobre esse assunto?

Deixe uma resposta

Descubra mais sobre Blog do Castilho - Tecnologia | FinOps | DevOps | Cloud

Assine agora mesmo para continuar lendo e ter acesso ao arquivo completo.

Continuar lendo