Skip to content
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

Sistema de Eventos + Firewall #444

Merged
merged 5 commits into from
Jun 13, 2022
Merged

Sistema de Eventos + Firewall #444

merged 5 commits into from
Jun 13, 2022

Conversation

filipedeschamps
Copy link
Owner

@filipedeschamps filipedeschamps commented Jun 8, 2022

Dando os primeiros passos na issue #351, este PR implementa um novo model event que registra eventos de negócio, junto com o ip originador e alguns metadados.

Por hora há dois tipos de eventos: create:user e create:content.

A interface para criar um evento é a seguinte:

await event.create({
  type: 'create:content',
  originatorId: request.context.user.id,
  originatorIp: request.context.clientIp,
  metadata: {
    id: secureOutputValues.id,
  },
});

Resulta em:

{
  id: 'd423993c-bb8d-412c-b3d0-a0a34dd0e9f1',
  type: 'create:content',
  originator_id: 'aa25f04e-747f-45cb-bcf2-04a06a5aa88b',
  originator_ip: '127.0.0.1',
  metadata: {
    id: '1b8f6ef8-04e4-484f-9056-ad9a8c085521'
  },
  created_at: 2022-06-08T22:27:13.428Z
}

Todos os campos são validados usando o validator e a estrutura a ser validada do metadata dependerá do type fornecido. Por hora, para ambos eu coloquei o id do objeto gerado. Quando existir outros eventos como os de transferência de tabcoins, o metadata poderá conter coisas como amount, from, to e coisas assim. E agora revisando, talvez o campo originator_id fique melhor como originator_user_id.

De qualquer forma, com esses dados (principalmente o ip), conseguimos dar início a issue #338 sobre comportamentos abusivos (mesmo ip criando várias contas, ou várias publicações), e logo depois também a issue #352 sobre as tabcoins.

Em paralelo, refatorei o controller.js e os controllers para ter um método de nome mais genérico e que sirva para injetar na request outras coisas além do requestId. Então agora é injetado no request.context o clientIp.

@vercel
Copy link

vercel bot commented Jun 8, 2022

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated
tabnews ✅ Ready (Inspect) Visit Preview Jun 13, 2022 at 4:44PM (UTC)

@filipedeschamps
Copy link
Owner Author

Os testes estão quebrando no npm ci, tem alguma confusão nas dependências. Em localhost isso não está acontecendo, e nada foi mexido no package-lock.json, muito massa:

image

Queria conseguir reproduzir localmente. Bom, depois eu vou tentar atualizar todas as dependências 🤝 👍

@aprendendofelipe
Copy link
Collaborator

Era alguma instabilidade no ci, agora foi

@filipedeschamps
Copy link
Owner Author

filipedeschamps commented Jun 10, 2022

Estou mesclando nesse PR a issue #338 e propondo a implementação de um novo model chamado firewall. Estou fazendo isso, pois o sistema de eventos (events) e o firewall são colados um no outro. Eu poderia fazer em PRs separados, mas os dois precisam dançar juntos (o firewall usa os dados do events), então para ter uma visão macro se tudo faz sentido, estou juntando tudo aqui.

Bom, o objetivo do firewall é proteger a aplicação de certos comportamentos abusivos relacionados a regras de negócio. Então ele não deve ser usado para segurar um DDoS, isso deveria ser feito numa camada antes e com soluções como o Cloudflare ou Middleware usando o Upstash.

Primeira regra

A primeira regra está relacionada a um IP spammear a criação de contas. Mas o importante é notar que uma regra é disparada por um sinal e se isso acontecer, os efeitos colaterais são rodados:

  // Signals:
  //   - IP tried to create more than 5 users in the last 1 hour.
  // Side effects:
  //   - Blocks all users created by that IP in the last 6 hours.
  //   - Throws a TooManyRequestsError.

Esses valores vão precisar de lapidação, mas por enquanto se um IP tentar criar mais de 5 contas dentro da mesma hora, a request é bloqueada e todos os usuários criados dentro das últimas 6 horas por aquele ip são bloqueados.

Isso vai ser bastante útil quando alguém tentar fazer um ataque contra os conteúdos (seja de sacanagem, seja para gerar tabcoins) e ter como efeito colateral todos esses conteúdos removidos (ou colocados como draft por enquanto) e as tabcoins debitadas.

Problema

O sistema parece estar funcionando, tanto que os testes automatizados não passam mais na parte de criar usuários. O firewall identifica como um ataque. Não sei o que fazer. Eu poderia identificar se estamos em ambiente de desenvolvimento, mas daí não daria para escrever um teste de integração (que no caso tem um no último commit que testa que a sexta request é bloqueada, e os 5 usuários que foram criados foram devidamente bloqueados).

Alguém tem alguma sugestão?

throw new TooManyRequestsError({
message: 'Você está tentando criar muitos usuários.',
action:
'Todos os usuários criados recentemente foram bloqueados. Contate o suporte caso acredite que isso é um erro.',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eu não daria tanta informação sobre a regra infringida, principalmente por esses motivos:

  1. Caso seja alguém tentando burlar o sistema, ele receberá muitas dicas de como obter sucesso.
  2. Caso seja um grupo de pessoas com o mesmo IP público que, por qualquer motivo, tenham se cadastrado ao mesmo tempo, por exemplo ao assistirem a live de algum famoso por aí, pode ser considerado uma quebra de privacidade se alguém desse grupo de pessoas receber essa mensagem.

@aprendendofelipe
Copy link
Collaborator

Alguém tem alguma sugestão?

  1. O limite por IP poderia ser fornecido pelo banco de dados (com a vantagem de permitir mudar facilmente em determinadas situações que estimulariam um grande número de novos cadastros).
  2. Deixar um limite bem grande durante os testes e só setar um valor pequeno na hora de testar o firewall.

@filipedeschamps
Copy link
Owner Author

  • O limite por IP poderia ser fornecido pelo banco de dados (com a vantagem de permitir mudar facilmente em determinadas situações que estimulariam um grande número de novos cadastros).
  • Deixar um limite bem grande durante os testes e só setar um valor pequeno na hora de testar o firewall.

Perfeito! 😍 Vou juntar essa sugestão com aquela feita no código e tentar esconder ao máximo esses valores e regras.

Caso seja um grupo de pessoas com o mesmo IP público que, por qualquer motivo, tenham se cadastrado ao mesmo tempo, por exemplo ao assistirem a live de algum famoso por aí, pode ser considerado uma quebra de privacidade se alguém desse grupo de pessoas receber essa mensagem.

De fato todo mundo seria bloqueado de forma errada. Mas fiquei curioso: como que isso é considerada uma quebra de privacidade?

@aprendendofelipe
Copy link
Collaborator

aprendendofelipe commented Jun 10, 2022

como que isso é considerada uma quebra de privacidade?

Imagina uma empresa pequena com um chefe e 5 funcionários. 🤔

O chefe trancado na sua sala nem imagina que os funcionários na sala ao lado também estão matando serviço para assistir o novo vídeo em que Filipe Deschamps fala do TabNews. 🤯

Ele resolve se cadastrar e recebe como retorno a informação de que está tentando criar muitos usuários. 🤨

Automaticamente descobre que não é o único fã do Filipe matando serviço naquele momento 😡

@filipedeschamps
Copy link
Owner Author

filipedeschamps commented Jun 10, 2022

Automaticamente descobre que não é o único fã do Filipe matando serviço naquele momento 😡

ahahah sensacional! De fato, mas isso se aplica a qualquer rate limiting de ip, por exemplo no escritório do Pagar.me um dia do nada minhas buscas no Google foram bloqueadas. O Google retornava uma mensagem de erro por abuso... na verdade a empresa inteira ficou travada de usar o Google. O que tinha acontecido é que um dos desenvolvedores tinha feito um script que marretou o buscador usando a interface dele pra tentar burlar o rate limiting da API 😂

Por isso que precisaremos ir calibrando os valores, principalmente quando tivermos rate limiting na camada da rede.

De qualquer forma @aprendendofelipe refiz toda a implementação do firewall e agora estou usando as functions / stored procedures do Postgres e a lógica ficou o seguinte 92dadbd:

  1. A stored procedure se encarrega de validar a regra, automaticamente aplicar os efeitos colaterais e retornar se passou ou não na validação (true se passou, ou false se não passou).
  2. Se passou, o Next.js continua com a request e, se não passou, retorna um 429 - TooManyRequestsError.
  3. O "template" dessas procedures estão em /infra/stored-procedures. Coloco "template" entre aspas, pois o que pode ser de fato criado no banco de dados de produção pode ser outra coisa, com outros valores e outros efeitos colaterais. Tanto que o que está no source code são valores para os testes automatizados.
  4. Os testes que quiserem sofrer a validação do firewall devem chamar await orchestrator.createFirewallTestFunctions(); no beforeAll().
  5. Isso significa que os testes que não chamarem não vão criar as stored procedures no banco de dados e o firewall vai assumir por padrão que o teste passou.
  6. Fiz isso em cima da sua ideia @aprendendofelipe e também para podermos descasar o deploy do código do deploy das stored procedures. Então no caso atual se não encontrou no banco de dados a função firewall_create_user(clientIp inet), tudo bem, a request passa... mas se encontrou, ela passa pelo check da regra.

O que acha?

@tcarreira
Copy link

Deixando uma nota rápida

O IP é considerando um dado privado ao abrigo da LGPD. Guardar ele no banco não é aconselhado.
É no entanto possível implementar o mesmo comportamento guardando um parcial de uma hash unidirecional do IP.

ver K-anonymity

@filipedeschamps
Copy link
Owner Author

@tcarreira que informação valiosíssima! Principalmente porque queremos que a lista de eventos seja pública para as pessoas conseguirem analisar os dados (no caso do fluxo das tabcoins, tentar identificar algum abuso) 🤝

@aprendendofelipe
Copy link
Collaborator

O que acha?

@filipedeschamps, acho incrível que você consegue sempre ir muito além do que lhe é proposto. Com stored procedure ficou perfeito! 😍

no banco de dados de produção pode ser outra coisa

É isso! Assim a pessoa pode saber que caiu na regra, mas não vai ter como saber exatamente qual é a regra.

Obs. Acho que a função blockAccess ali em models/user ficou atoa, ou eu perdi algo?

guardando um parcial de uma hash unidirecional do IP.

@tcarreira boa!!!

@aprendendofelipe
Copy link
Collaborator

queremos que a lista de eventos seja pública para as pessoas conseguirem analisar os dados 🤝

Pensando em LGPD, vai ser preciso mascarar o id dos usuários na lista de eventos, pois, da maneira como está, mesmo que o IP fique mascarado, vai ser possível criar um vínculo entre usuários que acessam do mesmo IP.

Ou seja, vai se tornar pública a informação de que Fulano e Beltrano utilizam o mesmo IP público, mesmo sem divulgar qual é esse IP.

Mascarando o id, então só vai ser possível saber que existem N diferentes usuários no mesmo IP, mas sem identificar quais são os usuários.

@filipedeschamps
Copy link
Owner Author

Obs. Acho que a função blockAccess ali em models/user ficou atoa, ou eu perdi algo?

@aprendendofelipe muito bem observado! Era da estratégia anterior, vou remover 🤝

Mascarando o id, então só vai ser possível saber que existem N diferentes usuários no mesmo IP, mas sem identificar quais são os usuários.

Ahhhh vocês são demaaaaais 😍 a gente pode fazer o id ser filtrado na saída, mas o IP seria legal gravar no banco já mascarado.

Alguém conhece algum módulo que implemente o K-anonymity? Não to encontrando nada :( ou qualquer outra alternativa segura.

@aprendendofelipe
Copy link
Collaborator

aprendendofelipe commented Jun 10, 2022

Será que a estratégia vai ter que ser destruir o dado na raiz, como o Google Analytics faz?

Precisamos tratar algumas coisas diferentes: como salvar os eventos no banco de maneira que sejam úteis (o requisito para o firewall, para gerar a página de eventos e para fornecimento de informações para autoridades podem ser diferentes) e como divulgar os dados de maneira anonimizada na página de eventos.

Firewall

Para o firewall, não é muito interessante usar a estratégia de juntar vários endereços em um único grupo, que é o que o Google Analytics faz. Pois isso obrigaria a adotar limites menos restritos para eventos repetidos com o mesmo grupo de IPs. E como vimos que não adianta muito usar um hash, talvez seja o caso de salvar o endereço explicitamente mesmo e excluí-lo após o período em que ele não é mais necessário para o firewall.

Informações para Autoridades

Para fornecer infos para as autoridades, vale quase o mesmo que para o firewall, só que com um prazo bem maior de período de armazenagem dos endereços.

Página pública com os eventos

Só pode conter dados anonimizados. Para montar uma página com informações anonimizadas de início pode ser utilizada a estratégia de K-anonymity manipulando os dados no momento da geração da página estática (ou na API) enquanto não se tornar um caso para Big Data em que os dados devem ser armazenados já tratados.

Só que aqui a preocupação não deve ser só com o IP, mas sim em como apresentar os dados respeitando as leis de proteção de dados. No caso do IP, aí sim entraria a estratégia de unificar grupos de endereços, mas precisamos pensar mais sobre como anonimizar os outros dados que serão disponibilizados nessa página.

Resumindo

Não vejo problema em armazenar os dados da maneira que foi implementada. Existindo justificativa para isso (e existem), deixando transparente e pedindo autorização dos usuários, acho que estará tudo certo com a LGPD. Futuramente pode-se pensar em eliminar os dados antigos que não serão mais necessários.

Agora sobre a divulgação dos dados, acho que é preciso pensar muito mais como vai ser...

[Edit] Quando falei sobre não ver problema sobre a maneira que foi implementado, ainda estava sendo armazenado o endereço explicitamente. E reforço que não vejo problema, pois é justificável. O que não pode é divulgar assim.

@filipedeschamps
Copy link
Owner Author

Sensacional sua resposta @aprendendofelipe ! Super completa 🤝

Do meu lado, após ler os últimos links e vários outros que não mandei aqui, fazer o hash de um IP é algo que não protege muita coisa... na verdade quase nada, dado que é uma informação com poucos bits. Então tornar ela pública beira o impossível.

Em paralelo, eu fiz o push com um commit que zera o último bloco do ip, o snippet é esse:

    const ipParts = ip.split('.');
    ipParts[3] = '0';
    const anonymizedIp = ipParts.join('.');

    return anonymizedIp;

Fico me perguntando se teremos problemas reais (ou recorrentes) ao agrupar os ips e ajustarmos as regras do firewall, e se algum dia tivermos um falso positivo, não seria interessante implementar um comando de desfazer o que o firewall fez?

@joaogelado
Copy link

joaogelado commented Jun 10, 2022

Mesmo mascarando o id do usuário, ainda será possível identificá-lo pelo tipo de evento, pelo horário e/ou pelos metadados.

como divulgar os dados de maneira anonimizada na página de eventos.

Poderíamos implementar que apenas usuários com X feature poderiam ver os dados exatos, como a hora. O resto dos usuários poderiam ver a hora, mas em um range de horário. Ex.:

um usuário publicou/editou um post/comentário entre 15:00 e 16:00

Até poderiam ter diferentes opções de privacidade, como se o usuário quer mostrar seu nome para todos ou não, e só moderadores ou pessoas com a feature X poderiam ver quem realmente fez aquilo e "bypassar" as opções de privacidade do usuário.

@tcarreira
Copy link

tcarreira commented Jun 10, 2022

Então, eu não sei de nenhum módulo que faça isso. Mas eu faria desta forma (pseudo código)

salt_seed = ENV("ANONYMITY_HASH")              // "abcdefghij#.>$%!"
ip_number = ip2number(IP)                      // "192.168.0.45" -> 19368585
idx = ip_number % len(salt_seed)               // 9
double_seed = salt_seed + salt_seed            // "abcdefghij#.>$%!abcdefghij#.>$%!"
salt = double_seed[idx] + double_seed[idx+5] + double_seed[idx+7]  // "j%a"
hashed_ip = sha256(salt+IP)[0:16]              // sha256("j%a192.168.0.45")[0:16]  -> "a472a6b79636f01f"

(onde ip2number() transforma os octectos do IP em um número inteiro único (eg 192*10^4 + 168*10^3 +... que dê uma distribuição uniforme, e que não gere números seguidos para IPs seguidos!)

A mim, parece-me uma solução bastante segura e irreversível.

Pontos de segurança com este algoritmo:

  • não usa todo o salt, que poderia ser usado para fazer engenharia reversa do salt por brute force
  • com brute force, vc apenas consegue 3 caracteres não seguidos da seed
  • mesmo que vc tenha acesso a todo um range de IPs, não é possível encontrar o salt_seed (se ele for grande o suficiente)
    • apenas se o ip2number() não gerar números seguidos para IPs seguidos
  • se guardarmos um subrange suficientemente pequeno do hash, garantindo que haja alguma sobreposição de inputs, temos uma anonimização mais eficaz

Fraquezas:

  • Revelando o salt_seed (alguma falha de segurança), vc pode gerar facilmente uma rainbow table
  • um admin do sistema, consegue facilmente construir uma rainbow table, mesmo tendo uma sobreposição de hash, como os IPs estão associados a uma geolocalização, será fácil saber o IP de origem a partir de um hash

Acho que as fraquezas não são preocupantes. Qualquer admin de sistema pode eventualmente ter acesso à RAM e encontrar qualquer informação privada :)

@aprendendofelipe
Copy link
Collaborator

Em paralelo, eu fiz o push com um commit que zera o último bloco do ip

O único porém é se tiver que fornecer o IP de algum evento para alguma autoridade. Deixaria em uma lista de tarefas a consulta com algum especialista sobre quando o TabNews se enquadraria nisso. Sei que tem questões bem recentes, que envolvem inclusive o período eleitoral que está chegando, mas não estou por dentro.

Fico me perguntando se teremos problemas reais (ou recorrentes) ao agrupar os ips e ajustarmos as regras do firewall,

Com o monitoramento, o tempo dirá! 🤓

e se algum dia tivermos um falso positivo, não seria interessante implementar um comando de desfazer o que o firewall fez?

Com certeza! Isso precisa ser implementado antes do lançamento do TabNews. E isso levanta uma questão. Está sendo gerado um evento que registre o bloqueio pelo firewall? Me parece que não, pois não vai chegar no postHandler. Tem como consultar os usuários bloqueados, mas não os dados do evento que bloqueou.

É preciso registrar de maneira que fique fácil a reversão. E alertas devem chegar bem rápido para quem tiver esse poder de desfazer. Está usando o Logflare, né?

@filipedeschamps
Copy link
Owner Author

@tcarreira show!! Confesso que não entendi muito bem a implementação, mas de tudo que li sobre hash (dado que o range de IP válidos é pequeno), todos são reversíveis. A única forma de deixar anônimo pelo que entendi é o salt variar a cada evento, mas isso invalida o uso dele para o firewall. Outra forma seria o bcrypt, daí você usa um tamanho de salt que inviabiliza a operação pelo poder de computação que vai tomar, mas que também vai penalizar o registro do evento.


O único porém é se tiver que fornecer o IP de algum evento para alguma autoridade. Deixaria em uma lista de tarefas a consulta com algum especialista sobre quando o TabNews se enquadraria nisso. Sei que tem questões bem recentes, que envolvem inclusive o período eleitoral que está chegando, mas não estou por dentro.

@aprendendofelipe justo 🤝

Está sendo gerado um evento que registre o bloqueio pelo firewall?

Não está sendo gerado, vou ver uma forma de retornar da função os usuários penalizados pelo efeito colateral e daí colocar todos eles num único payload de evento do firewall.

Está usando o Logflare, né?

Hoje estou usando apenas o Axiom: https://www.tabnews.com.br/filipedeschamps/axiom-melhor-servico-de-logs-que-encontrei-ate-agora-para-vercel

E vou cadastrar para ele me alertar de qualquer 429 👍

Poderíamos implementar que apenas usuários com X feature poderiam ver os dados exatos, como a hora. O resto dos usuários poderiam ver a hora, mas em um range de horário. Ex.:

Ideia interessante @joaogelado 🤝 poderíamos talvez ter pessoas de BI com uma feature em que consigam ver os dados de forma mais detalhada 👍


Sugiro então darmos sequência da seguinte forma:

  1. Registrar o ip, mas sem o último bloco.
  2. Colocar alerta para entender se a falta do último bloco vai ativar muitos falsos positivos.
  3. Não expor esses dados antes de conversar muito muito muito bem como fazer isso.

E turma, que conversa de altíssimo nível!!! Vocês são sen-sa-ci-o-nais!!!!!

@tcarreira
Copy link

O único porém é se tiver que fornecer o IP de algum evento para alguma autoridade

Não sou especialista legal, mas não acho que seja obrigatório guardar o IP do que quer que seja (até porque isso é um dado privado). Não estaremos confundindo o cenário em que vc tem que fornecer esses dados porque vc os tem guardados?

eg: "a autoridade obriga vc a devolver todas as armas que vc tem". Vc não entrega nada porque não tem.

🤷

estaremos complicando demasiado este assunto?
certamente

@tcarreira
Copy link

A única forma de deixar anônimo pelo que entendi é o salt variar a cada evento, mas isso invalida o uso dele para o firewall.

Comentei um pouco o código no meu comentário para se entender melhor, mas a parte complicada do meio serve para dar um salt diferente a cada IP.
(eu fiz aquilo mais por desafio acadêmico do que vontade de implementar no tabnews. A verdade é que eu não sei fazer um ip2number() que não possa ser explorado no caso de vc possuir um range de IPs /24)

Mas acho que vale as perguntas:

  • precisamos mesmo do IP?
  • será que firewall por IP é um método eficaz?
  • existem outras alternativas?
  • coisas como captcha ou outra IA podem ajudar melhor?
  • será que estamos tentando otimizar um problema do futuro que talvez nem exista?

@rodrigoKulb
Copy link
Contributor

rodrigoKulb commented Jun 11, 2022

Com uma rápida pesquisada na LGPD, acredito que temos uma alternativa bem válida para criar um firewall dentro das normas.

DADOS ANONIMIZADOS

Se a lista de IPs não tiver nenhuma relação com os usuários, podemos manter isso em banco de dados trabalhar esses dados de forma anonimizadas por exemplo:
IP vs tentativa de criar Postagem
IP vs tentativa de login
IP vs tentativa de criar conta

Resumindo uma forma de trabalhar o firewall dentro das normas da LGPD.

Fonte: https://www.serpro.gov.br/lgpd/menu/protecao-de-dados/dados-anonimizados-lgpd

Edit: O @aprendendofelipe já tinha comentando sobre isso, não existe problema em ter essa lista de IPs se não temos vinculo direto ao usuário.

@filipedeschamps
Copy link
Owner Author

O TabNews seria 1/10 do que é sem a participação de vocês!!! Olha quanta informação de valor concreto estamos colocando nesse PR!!! Muito muito bom e isso está lapidando a feature de uma forma que eu não imaginava 😍😍😍

  • precisamos mesmo do IP?
  • será que firewall por IP é um método eficaz?
  • existem outras alternativas?
  • coisas como captcha ou outra IA podem ajudar melhor?
  • será que estamos tentando otimizar um problema do futuro que talvez nem exista?

@tcarreira ótimas perguntas!!! Começando pela última pergunta e vou dar primeiro minha resposta de médio prazo, depois de curto prazo: hoje o TabNews já foi atacado 3 vezes (sendo que a última foi por falha numa integração que alguém estava fazendo), mas nenhum desses ataques foi de conteúdo, por enquanto. Reforço o por enquanto, pois hoje não existe incentivo que existirá com as TabCoins e TabCash. Fazer o spam de conteúdo vai ser literalmente minerar valor (espaço nos anúncios). Então minha sugestão é colocarmos em prática todo tipo de abordagem e ir desenvolvendo uma maestria nelas, nem que essa maestria seja jogar a abordagem fora.

Essa foi a visão de médio prazo, mas a de curto prazo é essa: hoje, ao criar uma conta nós enviamos um email, e ao responder um comentário, também enviamos um email. Então por exemplo se alguém atacar a criação de contas, nós enviaremos um mar de emails e nosso domínio poderá ser barrado pelos provedores de email (ainda pior se alguém usar emails que não existem). Da mesma forma, se alguém criar um monte de respostas, iremos enviar um mar de emails similares. Então esse é um tipo de situação que acho válido especular, pois é um tipo de problema que nós não gostaríamos de ter que resolver. Ter o domínio marcado como spam será desastroso para o projeto, mesmo que isso seja consertado, pois durante o tempo que estivermos marcados, ninguém vai conseguir ativar a conta. Isso é tão sensível que isso já aconteceu, mas não por nossa culpa, foi na época que usamos o Sendgrid e eles estavam com um IP barrado pela Microsoft. IP muda, domínio não. Então novamente, na minha visão vale a pena especular, nem que seja um pouco.

Sobre CAPTCHA, eu gosto bastante, mas levanto para discussão dois pontos:

  1. Para não mostrar o CAPTCHA logo de cara, é preciso anotar a sessão/ip em algum lugar, correto? Para só mostrar o desafio na segunda, terceira vez que alguém tente fazer aquela ação, por exemplo. Ou o serviço já abstrai isso?
  2. Existem bots com 91% de precisão e serviços que fazem o fallback com humanos, então é burlável.

Mas mesmo assim continuo achando válido, pois vira uma briga de saldo, onde se todas as dificuldades técnicas (mesmo sendo burláveis), causam um saldo negativo (você gasta mais do que ganha), não há incentivo para o ataque. Então acho que vai se tornar uma situação da briga eterna entre gato e rato, mas defendo que já é bom entrarmos nesse jogo agora.

Se a lista de IPs não tiver nenhuma relação com os usuários, podemos manter isso em banco de dados trabalhar esses dados de forma anonimizadas

Show @rodrigoKulb nosso problema é que temos 100% de vínculo com o usuário, inclusive "os usuários" por conta do efeito colateral de deletar todas as contas criadas por aquele IP. Ou todos os conteúdos de todas as contas que foram marcadas como abuso.

Mas com a idéia de não anotar o IP exato (que é mascarando o último valor como o Google Analytics faz), não estamos anotando a informação do usuário individual.


E novamente, olha o nível de responsabilidade que estamos tendo com o TabNews!!! Por favor, nunca podemos deixar de ter esse carinho com o projeto 🤝

@filipedeschamps
Copy link
Owner Author

filipedeschamps commented Jun 12, 2022

Agora pelo commit 8d091b2 o firewall, ao verificar que a regra create:user não passou, irá aplicar os efeitos colaterais e irá criar um outro evento chamado firewall:blocked_users com os ids dos usuários afetados, por exemplo:

{
  type: 'firewall:blocked_users',
  originatorUserId: undefined,
  originatorIp: '127.0.0.0',
  metadata: {
    users: [
      'b809e72f-e6b5-4724-817c-2e0802750002',
      '0d999f2e-9406-45df-b26a-d0536ba03d97'
    ]
  }
}

Então mais para frente poderemos fazer aquele comando de desfazer o efeito colateral.

Isso está coberto com testes, inclusive verificando se criou o evento adicional do firewall 🤝 Se vai funcionar em produção, só a vida real irá nos dizer 😂

[edit]

To pensando aqui numa segunda versão do evento de bloqueio:

{
    type: 'firewall:users_blocked',
    originatorUserId: context.user.id,
    originatorIp: context.clientIp,
    metadata: {
      from_rule: 'create:user',
      users: usersAffected,
    },
  }

@filipedeschamps
Copy link
Owner Author

O commit b135bf0 implementa as regras contra abuso em create:content:text_root and create:content:text_child sendo que o efeito colateral das duas é atualizar o status dos conteúdos do período identificado como ataque para draft.

Achei mais responsável deixar como draft do que deleted por enquanto. E estou pensando talvez mover o efeito colateral do banco para a aplicação, pois daí podemos mudar as coisas respeitando as regras de negócio, por exemplo, ao invés de só atualizar o status para deleted lá na procedure, a gente pode atualizar pelo model para ele também preencher o deleted_at.

E o fluxo para tudo isso até certo ponto está simples:

  1. Certas ações criam um event no banco de dados e que marca um acontecimento no TabNews. Cada event possui um type, por exemplo create:user que marca o tipo do evento.
  2. Esses eventos podem ser consumidos por qualquer outra parte do sistema, como no caso atual o firewall.
  3. O firewall expõe um middleware, onde no meio dos controllers você pode pedir para verificar uma regra dele.
  4. Essa regra no final das contas é uma stored procedure que faz alguma verificação e retorna true se passou na regra ou false se não passou.
  5. Se passou na regra, isso significa que não foi identificado nenhum problema e o fluxo da request continua.
  6. Se não passou, é aplicado os efeitos colaterais (cada regra tem o seu) e é retornado para request um 429 - TooManyRequestsError.

E no futuro não somente o firewall deveria usar os eventos, como também qualquer parte do sistema que precisar, por exemplo, remover a gambiarra que fiz para montar a página de status. Assim podemos criar qualquer tipo de evento e expor o histograma diário de forma mais fácil e padronizada.

@filipedeschamps
Copy link
Owner Author

Turma, vou começar o deploy em homologação (o app já está deployado pela Vercel, falta rodar as migrations e criar as stored procedures) 👍

@filipedeschamps
Copy link
Owner Author

Pessoal, fiz os testes das 3 regras em ambiente de homologação (spam de users, spam de conteúdos root e spam de conteúdos child) e tudo se comportou como esperado, incluindo aplicando os efeitos colaterais e o fluxo da aplicação continuando normalmente enquanto eu não tinha criado as funções no banco de homologação 🤝

Vou passar para produção 👍

@filipedeschamps
Copy link
Owner Author

Merged!! E rodei as migrations , criei as funções em Produção e coloquei alerta para ser disparado para qualquer request que receber 429. Vou estar acompanhando de perto 🤝 Let's goooo!!!

Isso levou a Milestone 4 para 90% concluída 🎉

image

@filipedeschamps
Copy link
Owner Author

Turma, vocês foram citados no post comemorativo: https://www.tabnews.com.br/filipedeschamps/novas-melhorias-husky-sistema-de-eventos-firewall-e-melhorias-no-seo 🎉

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

Successfully merging this pull request may close these issues.

None yet

5 participants