Blindar um servidor e colocar um WAF na frente da sua aplicação

Desde a primeira implantação, o erpc worker só permite SSH a partir do seu endereço e descarta todas as outras conexões de entrada. Uma tabela [waf] coloca um proxy de filtragem na frente da sua API.
Esta página descreve o ERPC CLI v0.1.0-rc.4, uma versão de pré-lançamento.

O firewall

Uma implantação configura o firewall do servidor antes de a aplicação iniciar pela primeira vez:
  • SSH somente a partir de firewall.ssh_allow e do endereço de onde vem a implantação;
  • todas as outras conexões de entrada descartadas, exceto run.port a partir de firewall.app_allow; para uma aplicação atrás de um WAF, a porta do WAF no lugar dela, e somente onde o WAF no servidor já executa as regras que [waf] define agora (abaixo).
Cada alteração é executada sob um temporizador de rollback de 120 segundos e só é mantida depois que uma nova conexão consegue entrar, de modo que uma alteração errada não pode deixar você trancado do lado de fora.
Mas a allowlist continua valendo depois que o endereço a partir do qual ela foi criada deixa de ser o seu: se o endereço da sua casa mudar, o caminho de volta é o suporte da ERPC, para um servidor ERPC, ou o console do provedor, para um servidor próprio. Mantenha em firewall.ssh_allow um segundo endereço a partir do qual você consegue acessar o servidor (um escritório, uma saída de VPN) antes de confiar em um endereço doméstico.
Se o seu endereço mudar, adicione todos os endereços fixos que seus administradores usam:
bash
erpc worker firewall allow --to ssh 203.0.113.10 198.51.100.7
ComandoO que faz
erpc worker firewall listMostra quem pode acessar o servidor: o erpc.toml ao lado da tabela do servidor
erpc worker firewall allow --to ssh|app <addresses>Permite a entrada dos endereços e aplica a alteração. O SSH é permitido a partir de endereços individuais; uma rede só é aceita com --to app
erpc worker firewall deny --to ssh|app <addresses>Deixa de permitir a entrada deles
erpc worker firewall syncFaz o firewall do servidor voltar a ser o que o erpc.toml diz

Colocar um WAF na frente da sua aplicação

Adicione uma tabela ao erpc.toml:
toml
[waf]
port = 8080
Em seguida, o erpc worker deploy executa um proxy reverso HTTP no servidor, em uma unidade systemd própria (erpc-waf-<app>-<env>): um usuário dinâmico sem privilégios, sem chave, sem segredo e sem nada do worker, limitado a 1 GiB de memória. Ele escuta em waf.port, lê cada requisição antes da aplicação, recusa o que as regras dele indicam e encaminha o restante para a aplicação em 127.0.0.1:<run.port>.
  • A aplicação fica no loopback. A porta dela nunca é aberta para fora. O erpc worker api e o health check acessam a aplicação pelo loopback, sem passar pelo WAF.
  • Quem pode se conectar continua definido pela lista do firewall, e a porta do WAF é o único lugar em que essa lista pode dizer todos:
    sh
    erpc worker firewall allow --to app 0.0.0.0/0     # and ::/0
    Sem ninguém na lista, o WAF escuta e ninguém entra, e a implantação avisa isso.
  • A porta é aberta depois que o WAF passa a escutar nela, nunca antes. Uma implantação cujo WAF não sobe falha e diz o motivo, com a aplicação ainda em execução.
  • Quando você altera [waf], a porta não é aberta para ninguém novo até que uma implantação tenha colocado as novas regras em vigor.
  • Retirar [waf] do erpc.toml e implantar remove o WAF do servidor.

Configurações do WAF

Somente port é obrigatória. Todas as chaves, com seus valores padrão:
toml
[waf]
port = 8080          # the public port, 1024 or above; the app keeps [run] port
methods = ["GET", "POST", "PUT", "DELETE"]
allow_paths = []     # every path
deny_paths = []
max_body_bytes = 1048576
max_header_bytes = 16384
rate = { per_second = 50, burst = 100 }
max_connections_per_address = 32
ban = { after = 100, window_secs = 60, for_secs = 600 }
block_probes = true
header_timeout_secs = 10
body_timeout_secs = 30
upstream_timeout_secs = 30
new_connections = { per_second = 50, burst = 100 }
ChaveO que fazO que é recusado recebe
methodsOs métodos encaminhados, em maiúsculas (no máximo 16; nunca CONNECT)405, com Allow
allow_pathsOs caminhos encaminhados; sem nenhum na lista, são todos os caminhos. Um padrão é um único caminho, ou tudo sob um prefixo quando termina em *: /api/* é /api e tudo abaixo dele. É comparado com o caminho depois da decodificação percentual, diferenciando maiúsculas de minúsculas404
deny_pathsCaminhos recusados, escritos da mesma forma e verificados primeiro403
max_body_bytesO maior corpo de requisição, até 64 MiB413
max_header_bytesO maior tamanho da linha de requisição e dos cabeçalhos juntos, de 1024 a 65536431
rateRequisições por segundo de um endereço, e quantas de uma vez antes que a taxa se aplique429, com Retry-After
max_connections_per_addressConexões que um endereço mantém ao mesmo tempoA conexão é fechada, sem resposta
banDepois de after requisições bloqueadas dentro de window_secs, o endereço é recusado por for_secs (até 30 dias)As conexões dele são fechadas, sem resposta
block_probesRecusa o que scanners pedem (/.env, /.git, /wp-login.php e similares)403
header_timeout_secsQuanto tempo um cliente tem para enviar o cabeçalho de uma requisição, de 1 a 120. Também é quanto tempo se espera por um cliente que parou no meio de um corpo ou parou de receber a resposta408 para o cabeçalho; 400 para um corpo que parou; a conexão fechada para uma resposta não recebida
body_timeout_secsQuanto tempo o corpo de uma requisição pode levar no total, de 1 a 600, sem contar o tempo em que o WAF espera pela aplicação408
upstream_timeout_secsQuanto tempo a aplicação tem para aceitar a conexão e começar a responder, de 1 a 600504; 502 quando a aplicação não está escutando
new_connectionsO limite do próprio firewall para novas conexões TCP por segundo de um endereço à porta do WAF, descartadas pelo nftables antes que o proxy as veja. Enquanto o conjunto de endereços do nftables estiver cheio, um endereço que não está nele não é contado, então esse limite não se aplica a ele até que o conjunto tenha espaço de novo; os limites do próprio proxy (rate, max_connections_per_address, ban) continuam valendoO pacote é descartado
rate, max_connections_per_address, ban e new_connections são desativados com false (ban = false), e uma chave omitida fica com o valor padrão. O erpc worker check diz o que a tabela [waf] vai fazer, e o que há de errado com ela, antes de qualquer implantação.

O que o WAF faz sem nenhuma configuração

  • Uma requisição que não pode ser lida como uma única requisição HTTP/1 sem ambiguidade recebe 400, assim como um caminho que não está na forma normal.
  • Uma requisição para mudar de protocolo (Upgrade) recebe 501.
  • Toda recusa conta para o banimento, e cada recusa encerra a conexão dela.
  • Um endereço é um endereço IPv4 ou um /64 IPv6.
  • O WAF não encaminha ao proxy os cabeçalhos da conexão do cliente (Connection e os cabeçalhos que ele nomeia, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, TE, Trailer), Expect, cabeçalhos cujo nome tem um sublinhado, nem os Forwarded, X-Real-IP e X-Forwarded-Proto enviados pelo próprio cliente. Ele define X-Real-IP como o endereço de onde veio a conexão e adiciona esse endereço ao final de X-Forwarded-For; somente a última entrada de X-Forwarded-For vem do proxy.

Ver o que o WAF faz

ComandoMostra
erpc worker statusA porta do WAF, para onde ele encaminha, desde quando, e as contagens desde então
erpc worker firewall listQuem pode acessar a porta do WAF, e o que foi bloqueado, por regra
erpc worker logs --wafCada requisição bloqueada: a regra, o status, o endereço do cliente, o método e os primeiros 200 bytes do destino
Uma requisição encaminhada não deixa linha de log, e nenhum cabeçalho ou corpo é registrado em log em momento algum; a query de uma requisição bloqueada é registrada, até onde os 200 bytes alcançam. O que um cliente envia não consegue escrever no seu terminal: o WAF grava o journal dele como ASCII imprimível.

O que o WAF não faz

  • Sem TLS. Ele usa HTTP/1.1 simples na porta dele.
  • Sem HTTP/2: ele responde 400 a esse protocolo.
  • Sem WebSocket, nem qualquer outro Upgrade: 501.
  • Não olha dentro dos corpos. Um corpo é medido, nunca lido. A lista de sondagens é curta e é um filtro de ruído, não uma garantia: a aplicação continua verificando a própria entrada.
  • Sem autenticação. Quem pode chamar a aplicação é decisão da aplicação, e da lista do firewall.
  • Um endereço é um cliente. Clientes atrás de um mesmo NAT, proxy ou CDN compartilham uma taxa e um banimento; um cliente com muitos endereços tem tantas taxas quanto endereços.
  • Banimentos e contagens são esquecidos quando o WAF reinicia.
  • Ele lembra no máximo 8192 endereços e mantém no máximo 1024 conexões ao mesmo tempo.
  • Ele escuta somente em IPv4, então uma entrada IPv6 em app_allow ainda não alcança nada.
  • Um caminho é comparado como os bytes em que ele é decodificado. Uma aplicação que lê caminhos de forma mais permissiva (ignorando maiúsculas e minúsculas, parâmetros ;, decodificando duas vezes) pode ser alcançada por um nome que deny_paths não cobre. allow_paths não tem essa brecha: o que não está na lista não é encaminhado.
  • Um leitor lento é atendido até o fim. Um cliente que continua recebendo uma resposta, por mais devagar que seja, é atendido até o fim.
  • Um flood pode interromper um upload grande. O WAF roda em uma única thread, então um flood pode interromper um upload grande com 408. Uma aplicação que recebe corpos grandes aumenta body_timeout_secs junto com max_body_bytes.
O guia do worker traz todas as regras, inclusive quando uma alteração em app_allow fica retida até a próxima implantação.