System Design: exposição de serviços

Adriano Croco
6 min readMay 23, 2023

Olá!

Este texto faz parte de uma série sobre System Design. Você pode encontrar os desafios anteriores nos links abaixo.

Desafio 0: separando banco de dados
Desafio 1: integração de arquivos
Desafio 2: processamento idempotente de arquivos
Desafio 3: síncrono para assíncrono
Desafio 4: métricas em tempo real
Desafio 5: integração com eventos via webhooks

Desafio 6: expor serviços internos para parceiros

Você possui uma API interna, mas o negócio decidiu que irá expô-la para outras empresas (algumas dezenas). Como você exporia um serviço que está numa rede privada para essas empresas parceiras? considere aspectos de infraestrutura de rede (disponibilizar em uma rede pública e controlar via firewall/ACL ou via conexão privada, por exemplo), problemas como autenticação/autorização/tokens de acesso e limites de requisições (throttling/rate limit).

Representando o escopo geral do problema em uma linguagem visual, temos a situação abaixo, na qual múltiplas empresas externas tentam acessar uma API que está hospedada em um contexto interno. O desafio é definir qual componente que existirá entre elas para que esse acesso seja feito de forma robusta, segura e performática.

Visão macro do problema

Solução usando WAF

O maior dos problemas nesse tipo de integração é garantir que os acessos externos sejam feitos de forma adequada por qual sistema que deveria ter acesso de fato. Uma das ferramentas mais simples de se fazer isso é adicionando um WAF (Web Application Firewall) entre as empresas envolvidas. Dessa forma, é possível adicionar regras de IP que se baseiam no conceito de Zero-Trust para controlar os acessos a rede interna. Nesse exemplo, uma forma de se fazer isso é bloquear todos os IPs externos de acessarem a API por padrão, somente liberando os IPs das máquinas que de fato irão acessar os nossos sistemas. Em segurança da informação não há presunção de inocência, portanto, nenhuma máquina externa é confiável até que se prove o contrário. Com essa alteração, temos:

Solução usando WAF

O grande problema desse tipo de solução é que não há nenhum controle mais granular de acesso a API. Portanto, qualquer sistema pode demandar demais o componente e acabar usando nossos sistemas de forma abusiva, causando indisponibilidade (mesmo sendo não intencional, é uma situação que pode ser considerada um ataque DDoS).

No caso de atacantes externos (ou seja, máquinas fora do range de IPs liberados), espera-se que o WAF consiga lidar com a carga de uma forma adequada, bloqueando esses acessos indevidos. Porém, como não podemos confiar em nenhum sistema devido ao Zero Trust, o ideal seria usar serviços de proteção contra DDoS como Cloudflare ou Akamai. Esse tipo de serviço detecta o alto fluxo de requisições e aplica redirecionamentos eficientes das requisições, protegendo o contratante de negações de serviço devido ao alto volume de ataques. Tudo tem um limite, no entanto. Para algumas botnets compostas de milhões de computadores (alguns exemplos encontram-se aqui), eu tenho minhas dúvidas se esses serviços seriam 100% confiáveis. Obviamente que se trata de casos extremos, mas, o ideal é sempre arquitetar sistemas pensando nos corner cases, se possível. Com essas proteções em ação, temos:

Solução somente considerando adição de ferramentas na camada de infraestrutura

Isso é suficientemente bom para resolver os principais problemas relacionados a acesso não autorizado, considerando somente ações na camada de infraestrutura. No entanto, alguns problemas ainda permanecem, tais como: controle de acessos mais granulares, com limitação de acessos por token temporal e monetização da API.

API Gateway

Imagine agora que precisamos controlar quantas requisições cada empresa externa pode fazer (também chamado de throttling), além de cobrar por cada requisição recebida pela API (billing) e controlar o acesso de forma individualizada, a ponto de caso algum desses clientes não pague a conta do que utilizou, o acesso seja bloqueado instantaneamente e de forma automática.

Além desses problemas, caso a API que será exposta não suporte um volume muito grande de chamadas simultâneas (comum em sistemas mais antigos), é necessário aplicar mecanismos de controle baseados em tempo, como rate limit. É possível utilizar uma Camada Anti-Corrupção (Anti-Corruption Layer, um padrão popularizado pelo Domain Driven Design) como uma camada sistêmica que serve de proxy entre os serviços externos e a nossa API que implemente esses tipos de restrições via código, mas, eu acredito que seja mais elegante seguir por outro caminho.

O tipo de ferramenta que suporta todas as essas features nativamente é chamada de API Gateway. Que nada mais é que um software que tem como principal função servir como uma camada de indireção entre um sistema e uma ou mais APIs. Dessa forma, nessa camada é possível aplicar monetização, validação de acessos, rate limit e throttling com pouco esforço. Grosseiramente, essas ferramentas podem ser classificadas em microgateways (software mais enxuto, enriquecido via plugins) e enterprise gateways (softwares complexos, que suportam inúmeras features no próprio gateway).

Como exemplos dessas ferramentas, consigo citar o Kraken e o Kong. Porém, existem ferramentas nativas que fazem isso em praticamente todos os provedores de nuvem, como na GCP, AWS e Azure. Lembrando que a quantidade de features disponibilizadas por cada provider pode variar um pouco e isso deve ser levado em consideração ao desenhar uma solução.

Com esse tipo de software rodando, é possível expor sua API como um produto, com todos os controles e mecanismos de segurança adequados. O principal ponto de atenção é não adicionar recursos demais ao gateway, pois ele irá ser demandado em cada requisição para cada serviço. Portanto, caso ele leve, por exemplo, 100ms para executar a lógica adicional, será uma adição de 100ms em todas as requisições que estiverem sendo gerenciadas por ele. Depende do nível de criticidade da API, isso pode ser um baita problema.

Naturalmente, não é necessário usar um único gateway para todas as APIs, uma estratégia comum é usar um gateway por domínio sistêmico (exemplo: um Kong para as APIs do financeiro, um Kraken para o marketing e assim por diante). Dessa forma, é possível atingir um bom equilíbrio entre isolamento e performance. Caso isso não seja feito, sistemas críticos irão competir por recursos com outros sistemas desnecessariamente.

Com a escolha do API Gateway feita, temos o seguinte desenho de solução até o momento:

Solução contemplando o uso de API gateway

Só falta dois pequenos detalhes: como controlar quais usuários possuem acesso a qual recurso e como adicionar tokens que permitem acesso temporário e que expirem após um determinado período de inatividade, por exemplo?

Isso pode ser atingido usando um auth service desenvolvido internamente (também é possível utilizar serviços prontos dos providers também, como o AWS Cognito, Azure Active Directory ou o Google Identity Platform), que implementam essa relação entre usuários e permissões, além da geração de tokens que podem ser integrados ao funcionamento do gateway. Lembrando que esse tipo de serviço nessa arquitetura se torna um ponto único de falha (afinal, se o auth service ficar indisponível, todas as APIs que estão sendo gerenciadas por aquela instância do gateway e que precisam desse mecanismo irão falhar como consequência).

A recomendação em caso de serviços caseiros é: escreva um software enxuto, pois ele será muito utilizado. Uma boa regra geral aqui é: pense nessas requests como um funil, portanto, os componentes de infraestrutura precisam suportar as requests na seguinte proporção, aproximadamente:

Esse funil representa uma ordem de grandeza dos padrões das requisições

Então, se sua API já tem um alto volume, antecipe que qualquer componente de infraestrutura que venha antes no fluxo de requisições precisará estar provisionado para o volume da API + uma quantidade extra de requests para operar corretamente.

Com esses mecanismos funcionando, a solução completa ficaria como a da imagem abaixo:

Solução considerando todos os problemas do desafio

Com todos esses componentes em operação, obtém-se as seguintes features: isolamentos dos acessos por empresa, suporte a múltiplos usuários simultâneos, controle temporal via token, cobrança por requests, bloqueio de IPs indesejados, proteção contra DDoS, limites anti-abuso e baixa latência. Com isso, concluímos o desafio atendendo todas os requisitos.

Caso você encontre algum erro ou informação incompleta neste artigo, por favor, me mande uma mensagem que eu ajustarei o texto.

Obrigado por ler até aqui!

Até!

Você gostou do conteúdo e gostaria de fazer mentoria comigo? Clique aqui e descubra como.

--

--