Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Autenticação entre microsserviços: é tão difícil assim?

By Joshua FoxJun 28, 202210 min read

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

Mostramos vários métodos para fazer autenticação entre microsserviços, do mais simples — porém menos seguro e sustentável — até as arquiteturas recomendadas.

authentication-microservices

Uma arquitetura de microsserviços é, no fundo, serviços chamando uns aos outros. Mas como manter isso seguro, garantindo que só os seus serviços consigam chamar cada um deles? Como autenticar?

Em aplicações de navegador, isso é bem conhecido: o usuário digita as credenciais, o navegador envia para o serviço, e o serviço devolve um token de sessão que autentica o usuário por algum tempo. Já em um microsserviço, não tem ninguém ali para digitar uma senha ou um código de autenticação multifator.

Aqui vou descrever como fazer isso, com foco em servidores rodando em serviços do Google Cloud Platform, como Cloud Run ou Google Kubernetes Engine (GKE). Os serviços clientes podem estar no GCP, on-premises ou na AWS.

O que me motivou a escrever este artigo é que API Gateway e Cloud Endpoints são tecnologias em rápida evolução, com recursos sólidos de autenticação, mas também com limitações entre si e em relação a outros serviços concorrentes. Há muitas formas de fazer autenticação entre microsserviços. Vou começar pela mais simples — porém menos segura e sustentável — e avançar até as arquiteturas recomendadas.

Para deixar tudo mais claro, vou focar em microsserviços nos quais você controla os dois lados, mas os mesmos princípios valem quando o serviço cliente é externo à sua organização.

O básico: cabeçalhos, chaves e proxies

Embora existam formas mais simples e mais complexas de fazer isso, a autenticação entre serviços exige um design muito cuidadoso. O esquema básico é o seguinte.

  • O cliente usa uma chave secreta para assinar um token
  • O formato padrão para transmiti-lo é o JSON Web Token
  • O token vai em um cabeçalho HTTP Authorization, assim:
Authorization: Bearer <JWT>

onde é o token codificado em base64.

  • O servidor valida esse token consultando um serviço. Como alternativa, um proxy reverso pode receber a requisição e, antes de repassá-la ao servidor real, validar o token consultando um serviço.
  • No GCP, esse serviço é fornecido pela própria plataforma.

Este artigo vai mostrar várias formas de fazer isso, partindo do simples e inseguro até soluções mais completas.

Simples demais: "API Keys" autogerenciadas

Uma solução básica, muito usada por quem não conhece todo o leque de tecnologias disponíveis, é parecida com o que os usuários humanos fazem ao se autenticar com usuário e senha: armazenar uma string secreta no serviço cliente, uma "API Key", que serve como credencial, e validá-la no lado do servidor.

APIKEY=Microservice1:78eb9a45897f

Limitações

Não é seguro.

Vazamento de chaves

Chaves vazam de mais formas do que você imagina.

Para evitar isso, você não deve guardar segredos no Git ou em outro sistema de controle de código-fonte — é fácil demais expor sem querer; em vez disso, use um serviço de gerenciamento de segredos, como Google Cloud Secret Manager ou Hashicorp Vault. Mesmo assim, o problema continua: o serviço cliente vai precisar guardar credenciais para acessar o gerenciador de segredos.

Gerenciamento de chaves

Você vai precisar criar um banco de dados no servidor para armazenar essas chaves e uma camada para validar se a chave recebida está correta. Também não vai querer que chaves vazem dessa camada, então o cliente não deve enviar a API key em si, e sim um hash, que o servidor compara com um hash armazenado da chave. Manter tudo isso é caro. E também é inseguro, já que você não tem como dedicar a expertise e o esforço necessários para fechar todas as brechas possíveis. Sempre que possível, sistemas de segurança devem ficar a cargo de especialistas.

Rotação

Como vazamentos são inevitáveis, a boa prática é rotacionar a chave com frequência: criar uma nova e invalidar a antiga depois de um certo período. Isso exige automatizar um mecanismo no lado do cliente que solicita uma nova chave (autenticando essa requisição com a chave antiga!). E, no lado do servidor, é preciso um mecanismo para gerar novas chaves sob demanda e invalidar a antiga em uma data específica posterior.

E esse tipo de coisa é sempre mais complicado do que parece à primeira vista: por exemplo, é provável que você queira impor um número máximo de versões válidas de uma chave a qualquer momento, já que ter duas versões válidas faz parte da rotação, mas cem versões é vazamento esperando para acontecer.

Chaves de service account do provedor de nuvem

Por que implementar você mesmo o mecanismo de hashing, validação, rotação e expiração? Uma opção melhor é criar uma service account e baixar um arquivo de chave do Google Cloud Identity and Access Management (IAM). Você pode criar uma chave na página de Service Account.

authentication-between-microservices

authentication-and-authorization-in-microservices

Baixe o JSON e não esqueça de definir a data de expiração — um novo recurso do Google Cloud. O JSON tem este formato. (Pode ficar tranquilo, eu redigi bem o texto 😁, e além disso já desativei a chave!)

{
"type": "service_account",
"project_id": "myproject",
"private_key_id": "ded9d97108b…..5cfd179e95e0e1",
"private_key": " — — -BEGIN PRIVATE KEY — — -\nMIIEvKIBADABNBg….QDA6woGjE4Q — — -END PRIVATE KEY — — -\n",
"client_email": "[email protected]",
"client_id": "106482...4210366919",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/kubeflowpipeline%40joshua-playground.iam.gserviceaccount.com"
}

Limitação

Parece bom, né? Mas (como você já deve estar imaginando), isso também não é tão seguro e prático quanto a gente gostaria.

Assim como as API keys caseiras, o arquivo de chave da Service Account pode vazar, e por isso você precisa rotacioná-lo. O Google ajuda a garantir que as chaves expirem e oferece APIs para você obter uma nova chave, mas ainda assim é você quem precisa usar esses recursos.

Mais adiante, vamos explicar como evitar de vez ter um arquivo de chave, embutindo a Service Account direto nas suas aplicações cliente. Mas antes, vamos ver o que fazer com a Service Account, seja via arquivo de chave, seja na versão embutida.

Autenticação no código da aplicação

Para se autenticar, o serviço cliente usa sua Service Account.

Isso é feito com OpenID Connect (OIDC), que transmite JSON Web Tokens (JWT) assinados. Esses tokens são válidos por pouco tempo — horas, não semanas — minimizando o risco em caso de vazamento.

Você pode fazer isso no nível do seu próprio código, com bibliotecas no lado do cliente e do servidor.

  • Primeiro, o cliente cria e usa uma biblioteca de software e a chave da Service Account para assinar um token JWT de requisição de acesso.
  • Em seguida, ele usa esse token para solicitar outro — um access token — ao servidor de autenticação do Google. O servidor de autenticação do Google verifica que a Service Account de fato assinou aquele JWT de requisição de acesso e devolve o access token comprovando isso.
  • O cliente usa esse access token para chamar o seu microsserviço.
  • Seu microsserviço usa uma biblioteca de software para conferir se o access token foi de fato validado e assinado pelo serviço do Google.

É parecido com este fluxo, só que o serviço invocado é o seu próprio microsserviço, e não uma API do Google.

authorization-in-microservices

Limitação

Acoplamento com o seu servidor

Essa solução envolve código dentro do seu servidor. Como você pode ter vários microsserviços com as mesmas necessidades, isso significa manter e garantir a segurança dessa camada de autenticação em várias bases de código.

Mais adiante, vamos mostrar como evitar inserir esse código na sua aplicação. Mas antes, vamos ver como deixar de usar arquivos de chave de uma vez por todas.

Service Accounts embutidas: GCP

Se você está implantando o serviço cliente no GCP, não use uma chave de Service Account. Em vez disso, suba o serviço já com a Service Account associada (embutida).

Por exemplo, com uma instância do Google Compute Engine (GCE), você pode usar

gcloud compute instances create [INSTANCE_NAME] --service-account [SERVICE_ACCOUNT_EMAIL] ...

para especificar a Service Account; e o mesmo vale para Cloud Run e outros serviços do GCP a partir dos quais seu microsserviço cliente pode invocar outros microsserviços.

Agora não há por que se preocupar com vazamento de arquivos de chave, porque eles simplesmente não existem. Em vez disso, o metadata server gera um token de instância assinado, comprovando a identidade da service account. (E a requisição ao metadata server nunca sai da instância física onde a VM está rodando.)

No Kubernetes

O Kubernetes tem seu próprio sistema de service accounts, que faz parte de um sistema de autenticação específico do Kubernetes. Isso é separado da camada de IAM do GCP, então, se o seu serviço cliente está no Google Kubernetes Engine, use a Workload Identity para atribuir uma Service Account do GCP IAM à sua camada Kubernetes. A Workload Identity intercepta de forma transparente todas as chamadas do GKE para APIs do GCP, atuando como proxy, e as enriquece com o access token.

Se o seu cliente está no Elastic Kubernetes Service da AWS, você também pode atribuir uma role do IAM para participar dos fluxos do GCP, conforme descrito na próxima seção.

Roles embutidas: AWS e Workload Identity Federation

Se o serviço cliente está na AWS, você não pode iniciá-lo com uma Service Account do GCP, mas pode iniciá-lo com a equivalente da AWS, a Role. Você sobe sua Lambda com uma execution role, ou sua instância EC2 com uma role (encapsulada em um "Instance Profile".)

O GCP não pode confiar diretamente nessa role, então você usa a Workload Identity Federation (WIF) para fazer a ponte entre AWS e GCP ( artigo).

Aqui, o fluxo é o seguinte:

  • Primeiro, o serviço cliente na AWS usa sua role para assinar um token (token 1).
  • Ele usa o token 1 para solicitar outro (token 2), assinado pelo AWS IAM.
  • Ele usa o token 2 para pedir à WIF do GCP que assine um access token (token 3). A WIF do Google foi pré-configurada para confiar naquela role da AWS e, agora que a AWS comprovou que a requisição vem da role, a WIF assina e devolve o access token (token 3).
  • O serviço cliente passa então a usar o token 3 da mesma forma que um serviço cliente baseado no GCP usaria um access token; daqui em diante, o fluxo é igual.

microservices-authentication-and-authorization

Parece complicado, mas evita o envio dessas strings secretas — e altamente passíveis de vazamento — por toda a internet, neste caso para outra nuvem.

Autenticando do Google para um workload na AWS

Este artigo é majoritariamente sobre serviços rodando no Google, mas o gtoken merece uma menção rápida aqui. Ele faz o inverso da Workload Identity Federation: autentica um workload do GKE para consultar APIs da AWS, atribuindo uma identidade temporária da AWS à invocação.

Proxy de autenticação: API Gateway

Ainda assim, como mencionado antes, ficamos limitados pelo fato de o seu próprio código de aplicação executar a etapa final — a validação de que a assinatura realmente vem do principal autorizado pelo Google. Sempre que possível, é melhor usar sistemas comprovados, transformados em produto e criados por especialistas em segurança.

Por isso, vale a pena dar uma olhada no API Gateway, uma camada de autenticação serviço a serviço robusta e configurável, que você não precisa manter.

Ele funciona como um proxy. Expõe um endereço público, ficando entre o cliente e o seu serviço serverless no Cloud Run, Cloud Functions e App Engine. Ele cuida de receber o token e invocar os serviços do Google para autenticar a requisição, antes de repassá-la para o seu backend serverless. Para proteger o link entre o API Gateway e o backend, o Google insere um cabeçalho especial que ele mesmo controla e que nenhum atacante consegue adicionar.

Limitação

Por outro lado, o API Gateway não funciona com GKE, já que é fortemente integrado às interfaces expostas pelos serviços serverless gerenciados pelo Google.

Proxy de autenticação: Cloud Endpoint

Então, como autenticar para o GKE, mantendo a segurança do link entre a camada de autenticação e o seu serviço de backend? Para isso, você usa o Extensible Service Proxy com Google Cloud Endpoints: um serviço um pouco mais antigo, que serviu de base para o API Gateway e foi estendido por ele.

O ESP (atualmente na v2) é um container que expõe um endereço público e autentica as requisições. Para usá-lo no GKE, implante-o como um pod no seu cluster. (A propósito, embora a documentação diga que apenas os clusters mais novos VPC-native/IP alias são suportados, ele também funciona com os antigos clusters baseados em routes.)

O link entre o ESPv2 e seus serviços Kubernetes dentro do cluster também precisa ser protegido. Você pode fazer isso na camada de rede do cluster, não expondo nenhum endereço público além do ESP, ou pode usar soluções mais sofisticadas, como mTLS ou Istio Security.

Para uma segurança ainda maior, implante o ESPv2 como sidecar, para que o proxy e a sua aplicação (Kubernetes Deployment) convivam no espaço seguro de "localhost" de um pod. (Embora esse não seja o modo de implantação principal do ESPv2, ele é suportado nesta receita YAML compartilhada na conta oficial do Google Cloud no GitHub.)

Para fechar: proteja seus microsserviços!

Você não pode deixar qualquer um chamar suas APIs. Isso costumava ser resolvido com fronteiras de rede ou, na nuvem, com VPCs. Mas as arquiteturas modernas suportam integração entre contas de nuvem, entre provedores de nuvem e com sistemas fora da nuvem. E mesmo dentro da VPC, você quer outra camada de segurança voltada exatamente para o link específico entre cliente e servidor: cada endpoint precisa confiar no outro.

Para isso, o desafio é:

  • Autenticar sem deixar arquivos sensíveis circulando por aí, sujeitos a vazamento.
  • Delegar a autenticação a serviços confiáveis; não acoplar a autenticação ao código no nível da aplicação.

Neste artigo, descrevi algumas formas de fazer isso, ganhando aos poucos em segurança e facilidade de manutenção, mas exigindo conhecimento em mais tecnologias. Aprendê-las vale muito o investimento — sai bem mais barato do que ser vítima de um ataque!