Skip to content

Criar política pra banir requisições abusivas #426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
2 tasks done
berinhard opened this issue Sep 16, 2020 · 15 comments
Closed
2 tasks done

Criar política pra banir requisições abusivas #426

berinhard opened this issue Sep 16, 2020 · 15 comments

Comments

@berinhard
Copy link
Contributor

berinhard commented Sep 16, 2020

Atualmente tanto throttling do DRF quanto o uso do django-ratelimit são amigáveis e permite que a pessoa faça requisições após ter expirado o tempo do bloqueio. Precisamos criar uma política para banir requisições recorrentemente abusivas e evitar assim sobrecarga dos servidores.

  • Adicionar controle de X vezes em Y minutos que uma pessoa pode ser bloqueada (429);
  • Criar middleware que fica na frente de todos os outros e checa se o request (IP ou user agent) está banido;
@lguima
Copy link

lguima commented Sep 16, 2020

não tenho experiência, mas em um curso de segurança de VPS, foi citado o Fail2ban, não sei se é aplicável pra esse cenário

@francilioaraujo
Copy link

Se as requisições estiverem sendo feitas em grande volume de um só ip o fail2ban pode resolver.
Talvez uma solução mais robusta seja utilizar um Proxy reverso a frente da API e realizar o limiting por ele. Esta abordagem também pode dar uma melhor visibilidade do que está ocorrendo no ataque e dá mais possibilidades de bloqueio

@francilioaraujo
Copy link

O dokku usa o nginx como proxy reverso. Neste artigo há algumas configurações de segurança para o mesmo https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-http/

@rudaporto
Copy link

Uma opção em geral pra melhorar a performance é usar um proxy reverso com cache.

Como os dados não mudam o tempo todo e podem expirar a cada 5 ou 10 minutos deve ajudar bem.

Não sei se vocês tem um servico de CDN mas eles também podem fazer isso automaticamente.

Outra opção é adicionar o cache na camada do DRF mas nesse caso requer mudanças na aplicação.

@rudaporto
Copy link

Sobre cache no DRF achei essa documentação:

https://www.django-rest-framework.org/api-guide/caching/

@carloseahenriques
Copy link

Atualmente tanto throttling do DRF quanto o uso do django-ratelimit são amigáveis e permite que a pessoa faça requisições após ter expirado o tempo do bloqueio. Precisamos criar uma política para banir requisições recorrentemente abusivas e evitar assim sobrecarga dos servidores.

* [ ]  Adicionar controle de X vezes em Y minutos que uma pessoa pode ser bloqueada (429);

* [ ]  Criar middleware que fica na frente de todos os outros e checa se o request (IP ou user agent) está banido;

Da uma lida. Vai ajudae muito. https://www.linkedin.com/pulse/quando-pr%C3%B3ximo-finja-estar-longe-arte-da-guerra-sun-tze-henriques/

@turicas
Copy link
Owner

turicas commented Sep 17, 2020

Pessoal, obrigado pelas sugestões. Gostaria de salientar que as requisições que estão impactando negativamente os servidores são requisições não cacheáveis, pois utilizam a API ou a interface Web para fazer buscas (full-text search) na tabela govbr/auxilio_emergencial.

Pela quantidade de requisições, a pessoa que está fazendo isso terminaria muito antes suas buscas se simplesmente baixasse o dataset completo e fizesse as buscas localmente - provavelmente é alguém que não sabe que é possível baixar o dataset completo, ou é preguiçoso e prefere fazer de maneira não otimizada pela API, ou não se importa com o impacto que as requisições podem causar no servidor. Porém o site (usado principalmente por não programadores) não pode ficar lento ou inutilizável porque alguém fez um programa nada otimizado para consultar nossa API e não se preocupou com os impactos disso; quem deve sofrer sanções nesse caso é o usuário abusivo, não o usuário normal.

Como essa pessoa não está usando a API segundo nossas recomendações, nós não sabemos quem é para alertar e esse comportamento já aconteceu outras vezes (ver #316), decidimos restringir o acesso à API apenas para requisições autenticadas - dessa forma conseguiremos contato com quem eventualmente abusar (para que seja orientado a usar de maneira a não prejudicar outros usuários) e o controle de limitação de requisições poderá ser por usuário/token de autenticação. Será importante termos algum contato dos usuários da API e traçar métricas de uso por usuário para que possamos melhorar o serviço no futuro e definir estratégias melhores de infraestrutura (separação em mais servidores, por exemplo).

Dito isso, seguem algumas considerações sobre nossa infra atual:

  • Estamos usando a CloudFlare como CDN, que já bloqueia algumas requisições e cacheia parte do tráfego que pode ser cacheado (porém ainda temos um problema relacionado a isso: algumas requisições que deveriam ser cacheadas pela CloudFlare não estão sendo, provavelmente por falta de ajuste fino nos cabeçalhos HTTP - ver Corrigir cabeçalhos HTTP para que cache da CloudFlare funcione #341);
  • Para evitar Web scraping indesejado na interface Web (existe API e download do dataset completo, não é necessário fazer Web scraping na interface Web da plataforma), bloqueamos requisições sem User-Agent e com User-Agents relacionados a Web scraping;
  • Existem vários níveis de cache no projeto Django da plataforma:
    • Cache do próprio Django, para views acessadas por usuários não logados;
    • django-cachalot para cache de consultas SQL;
  • Sobre rate limiting:
    • O throttling do DRF está habilitado;
    • Usamos o django-ratelimit para limitar o número de requisições por minuto nas páginas de listagem de dados (a chave de sessão para a contagem das requisições é o IP real do usuário e o User-Agent).
  • fail2ban não deve resolver porque as requisições chegam até nossos servidores com IPs da CloudFlare (que são os IPs que ficam nos logs).

@GabrielLasso
Copy link

GabrielLasso commented Sep 17, 2020

Pelo que eu entendi, a ideia é continuar permitindo as pessoas a usar a API, porem de forma mais controlada, certo?

Uma possível solução que eu tenho em mente é obrigar o uso de uma chave de autenticação para usar a API, e para criar a chave pode ter um processo simples de preencher um formulário com por exemplo CPF/CNPJ, nome, etc, e um captcha. Aí vcs limitam a uma chave por CPF (pode deixar a pessoa criar uma nova e apagar a antiga se ele tiver perdido/vazado), talvez exigindo uma senha para isso.

Isso possibilita vcs monitorarem melhor quem está usando a API e ela continua pública, só com um passo a mais para cadastro. E se alguma chave começar a fazer ataques, é só banir ela. Se a pessoa continuar criando chaves, daria para banir o CPF ou entrar em contato com a pessoa para explicar como é o melhor jeito de consumir a API sem prejudicar o servidor.

Eu nunca consumi a API de vocês, só baixei os dados em csv uma vez pelo site, mas espero que essas ideias ajudem

EDIT: opsss, agora que eu li a sua resposta que você falou que já restringiu só para requests autenticados, foi praticamente o que eu falei

@turicas
Copy link
Owner

turicas commented Sep 17, 2020

EDIT: opsss, agora que eu li a sua resposta que você falou que já restringiu só para requests autenticados, foi praticamente o que eu falei

@GabrielLasso ainda não restringimos, mas decidimos restringir. Porém não pretendemos pedir o CPF.

@rudaporto
Copy link

Achei muito boa a decisão de pedir a identificação pra usar a interface e as APIs de busca.

Não sei se faz sentido mas uma idéia seria pedir a autenticação e como alternativa sugerir o download do dataset inteiro o qual neste caso não precisaria de identificação.

@turicas
Copy link
Owner

turicas commented Sep 18, 2020

Não sei se faz sentido mas uma idéia seria pedir a autenticação e como alternativa sugerir o download do dataset inteiro o qual neste caso não precisaria de identificação.

@rudaporto é exatamente isso que propomos. ;) O download dos dados completos existe hoje e não necessita de autenticação (e continuará assim).

@DaniloNC
Copy link

DaniloNC commented Sep 21, 2020

Edit.: Parece que o problema é com a API e portanto a implementação do captcha não seria possível.
De qualquer maneira, é possível configurar o rate-limit no cloudflare antes dele chegar no servidor web/backend

@turicas
Copy link
Owner

turicas commented Sep 22, 2020

Edit.: Parece que o problema é com a API e portanto a implementação do captcha não seria possível.
De qualquer maneira, é possível configurar o rate-limit no cloudflare antes dele chegar no servidor web/backend

Nós consideramos utilizar o rate limit do CloudFlare, mas pela quantidade de requisições que recebemos parecia sair um pouco caro, porém ainda não descartei totalmente, mas precisamos avaliar se resolveria completamente (muitas requisições vêm de IPs completamente diferentes - não vem somente de um IP -, o que dificulta o rate limit).

@turicas
Copy link
Owner

turicas commented Sep 22, 2020

Pra ficar mais claro a questão dos valores, seguem algumas estimativas:

Nota: considerei o dólar a R$6 para já contabilizar o spread bancário de 4%, IOF de 6,38%, possíveis oscilações e para facilitar as contas.

  • O preço do rate limit da CloudFlare é de 0,05 USD para cada 10.000 requisições.
  • As primeiras 10.000 requisições são grátis, mas vou desconsiderar isso, dado que daria um desconto de apenas 0,05 USD (R$ 0,30) e esse valor é desprezível perto do total que temos hoje
  • Apenas as requisições bloqueadas por regras no firewall não contam, as outras contam todas (sejam elas bloqueadas pelo rate limit ou não);
  • Seriam contabilizadas todas as requisições para o domínio brasil.io (site, API, chat e data.brasil.io); como o ataque está sendo feito para a interface Web também, não daria para criar uma regra de rate limit somente para a API (não resolveria).
  • Para cada 1 milhão de requisições o valor é de R$ 30 mensais: (1_000_000 * (0.05 / 10_000)) * 6 = 30
  • Usando dados dos 30 últimos dias teríamos um custo extra de R$ 720 mensais (24 milhões de requisições), mas essa é uma estimativa muito otimista, dado que os ataques aumentaram ao longo dos últimos dias: caso se mantenha a quantidade de requisições no pico que tivemos o valor extra seria de R$ 3.600 mensais. O projeto hoje recebe pouco mais de R$ 7.500 mensais das doações, que são suficientes para bancar a infraestrutura mas não as horas trabalho dos envolvidos; um gasto pesado desses com infraestrutura só tornaria o projeto mais insustentável financeiramente.

@turicas
Copy link
Owner

turicas commented Oct 31, 2020

Como já implementamos a autenticação na API e o bloqueio por quantidade de requisições, vou fechar essa issue. Relacionado a esse assunto, acabei de publicar no blog o artigo Nossa API será obrigatoriamente autenticada.

@turicas turicas closed this as completed Oct 31, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants